@fonderie/react-admin-screens 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -29,7 +29,9 @@ __export(index_exports, {
29
29
  EnvironmentScreen: () => EnvironmentScreen,
30
30
  MigrationsScreen: () => MigrationsScreen,
31
31
  ModulesScreen: () => ModulesScreen,
32
+ OperatorsScreen: () => OperatorsScreen,
32
33
  RoutesScreen: () => RoutesScreen,
34
+ SubscriberBilling: () => SubscriberBilling,
33
35
  SubscriberScreen: () => SubscriberScreen,
34
36
  TokensScreen: () => TokensScreen,
35
37
  UsersScreen: () => UsersScreen
@@ -39,7 +41,7 @@ module.exports = __toCommonJS(index_exports);
39
41
  // src/screens/AdminShell.tsx
40
42
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
41
43
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
42
- var import_react6 = require("react");
44
+ var import_react9 = require("react");
43
45
 
44
46
  // src/styles.ts
45
47
  var t = {
@@ -1111,33 +1113,265 @@ function MigrationsScreen({ client }) {
1111
1113
  ] });
1112
1114
  }
1113
1115
 
1114
- // src/screens/TokensScreen.tsx
1116
+ // src/screens/OperatorsScreen.tsx
1115
1117
  var import_react_admin8 = require("@fonderie/react-admin");
1116
1118
  var import_react2 = require("react");
1117
1119
  var import_jsx_runtime9 = require("react/jsx-runtime");
1118
- var ALL = ["read", "write", "secrets"];
1119
- function TokensScreen({ client }) {
1120
- const { report, isLoading, error, issue, revoke } = (0, import_react_admin8.useAdminTokens)(client);
1121
- const [name, setName] = (0, import_react2.useState)("");
1122
- const [scopes, setScopes] = (0, import_react2.useState)(["read"]);
1123
- const [days, setDays] = (0, import_react2.useState)("");
1120
+ var LEVELS = [
1121
+ { label: "Read only", scopes: ["read"], hint: "look around, change nothing" },
1122
+ { label: "Editor", scopes: ["read", "write"], hint: "change config, templates, users" },
1123
+ {
1124
+ label: "Owner",
1125
+ scopes: ["read", "write", "secrets"],
1126
+ hint: "secrets, tokens and operators too"
1127
+ }
1128
+ ];
1129
+ var levelOf = (scopes) => scopes.includes("secrets") ? "Owner" : scopes.includes("write") ? "Editor" : "Read only";
1130
+ function Minted({ link, onClose }) {
1131
+ const [copied, setCopied] = (0, import_react2.useState)(false);
1132
+ const url = `${window.location.origin}${link.url}`;
1133
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1134
+ "div",
1135
+ {
1136
+ style: {
1137
+ ...styles.notice,
1138
+ background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
1139
+ borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1140
+ },
1141
+ children: [
1142
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("strong", { children: [
1143
+ "Send this link to ",
1144
+ link.email,
1145
+ ". It is shown once."
1146
+ ] }),
1147
+ " It works once and expires",
1148
+ " ",
1149
+ new Date(link.expiresAt).toLocaleString(),
1150
+ ".",
1151
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 8, alignItems: "center" }, children: [
1152
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { style: { ...styles.code, flex: 1, padding: "7px 10px", wordBreak: "break-all" }, children: url }),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1154
+ "button",
1155
+ {
1156
+ type: "button",
1157
+ style: styles.button,
1158
+ onClick: () => {
1159
+ void navigator.clipboard?.writeText(url);
1160
+ setCopied(true);
1161
+ },
1162
+ children: copied ? "Copied" : "Copy"
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", style: styles.buttonGhost, onClick: onClose, "aria-label": "Dismiss", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "close", size: 14 }) })
1166
+ ] })
1167
+ ]
1168
+ }
1169
+ );
1170
+ }
1171
+ function OperatorsScreen({ client, me }) {
1172
+ const { report, isLoading, error, invite, recover, update, revokeLink } = (0, import_react_admin8.useAdminOperators)(client);
1173
+ const [email, setEmail] = (0, import_react2.useState)("");
1174
+ const [level, setLevel] = (0, import_react2.useState)("Editor");
1124
1175
  const [minted, setMinted] = (0, import_react2.useState)(null);
1125
- const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
1176
+ const scopesFor = (label) => LEVELS.find((l) => l.label === label)?.scopes ?? ["read"];
1177
+ const status = (o) => o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "neutral", children: "disabled" }) : o.locked ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: "locked" }) : !o.enrolled ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: "setting up" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "ok", children: "active" });
1126
1178
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: styles.container, children: [
1127
1179
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1128
1180
  PageHeader,
1129
1181
  {
1130
- title: "Access",
1131
- lead: "Who can open this surface: the root token's strength, scoped tokens you have issued, and any legacy per-module tokens."
1182
+ title: "Operators",
1183
+ lead: "The people who can sign in to this console. Each one uses a password and an authenticator app; there is no sign-up."
1132
1184
  }
1133
1185
  ),
1134
- error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
1135
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Root token" }),
1136
- report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { ...styles.card, display: "flex", gap: 12, alignItems: "flex-start" }, children: [
1137
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "lock", size: 18, style: { marginTop: 2 } }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1139
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: report.admin.ok ? "ok" : "bad", children: report.admin.ok ? "strong" : "weak" }),
1140
- report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1186
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.error, role: "alert", children: error.status === 403 && error.reason === "FORBIDDEN" ? "Managing operators needs the Owner level." : error.explanation }) : null,
1187
+ minted ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Minted, { link: minted, onClose: () => setMinted(null) }) : null,
1188
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1189
+ "form",
1190
+ {
1191
+ style: {
1192
+ ...styles.card,
1193
+ display: "flex",
1194
+ gap: 8,
1195
+ alignItems: "center",
1196
+ flexWrap: "wrap",
1197
+ marginBottom: 20
1198
+ },
1199
+ onSubmit: (e) => {
1200
+ e.preventDefault();
1201
+ void invite({ email: email.trim(), scopes: scopesFor(level) }).then((l) => {
1202
+ setMinted(l);
1203
+ setEmail("");
1204
+ }).catch(() => {
1205
+ });
1206
+ },
1207
+ children: [
1208
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "users", size: 16 }),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: "Invite" }),
1210
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1211
+ "input",
1212
+ {
1213
+ type: "email",
1214
+ required: true,
1215
+ value: email,
1216
+ onChange: (e) => setEmail(e.target.value),
1217
+ placeholder: "teammate@company.com",
1218
+ style: { ...styles.input, flex: 1, minWidth: 220 },
1219
+ "aria-label": "Email to invite"
1220
+ }
1221
+ ),
1222
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1223
+ "select",
1224
+ {
1225
+ value: level,
1226
+ onChange: (e) => setLevel(e.target.value),
1227
+ style: styles.input,
1228
+ "aria-label": "Access level",
1229
+ children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("option", { value: l.label, children: [
1230
+ l.label,
1231
+ " \u2014 ",
1232
+ l.hint
1233
+ ] }, l.label))
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: !email.trim(), children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "plus", size: 14 }),
1238
+ "Create invite link"
1239
+ ] })
1240
+ ]
1241
+ }
1242
+ ),
1243
+ isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : report && report.operators.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Empty, { icon: "users", title: "No operators yet" }) : report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { style: styles.table, children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
1245
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Operator" }),
1246
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Access" }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Status" }),
1248
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Last sign-in" }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th })
1250
+ ] }) }),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: report.operators.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontWeight: 600 }, children: o.name || o.email }),
1254
+ o.name ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: styles.muted, children: o.email }) : null
1255
+ ] }),
1256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1257
+ "select",
1258
+ {
1259
+ value: levelOf(o.scopes),
1260
+ onChange: (e) => void update(o.id, { scopes: scopesFor(e.target.value) }).catch(() => {
1261
+ }),
1262
+ style: { ...styles.input, height: 28, fontSize: 12.5 },
1263
+ disabled: o.email === me,
1264
+ "aria-label": `Access level for ${o.email}`,
1265
+ children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: l.label, children: l.label }, l.label))
1266
+ }
1267
+ ) }),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
1269
+ status(o),
1270
+ o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { ...styles.muted, marginTop: 4 }, children: [
1271
+ o.backupCodesLeft,
1272
+ " backup code(s) left"
1273
+ ] }) : null
1274
+ ] }),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: o.lastLoginAt ? new Date(o.lastLoginAt).toLocaleString() : "never" }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, textAlign: "right", whiteSpace: "nowrap" }, children: o.email === me ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.muted, children: "you" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1277
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1278
+ "button",
1279
+ {
1280
+ type: "button",
1281
+ style: { ...styles.button, height: 28, marginRight: 6 },
1282
+ onClick: () => {
1283
+ if (window.confirm(
1284
+ `Create a recovery link for ${o.email}? It signs them out everywhere; the link sets a new password and a new authenticator.`
1285
+ ))
1286
+ void recover(o.id).then(setMinted).catch(() => {
1287
+ });
1288
+ },
1289
+ children: "Recovery link"
1290
+ }
1291
+ ),
1292
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1293
+ "button",
1294
+ {
1295
+ type: "button",
1296
+ style: {
1297
+ ...o.disabledAt ? styles.button : styles.buttonDanger,
1298
+ height: 28
1299
+ },
1300
+ onClick: () => {
1301
+ if (o.disabledAt || window.confirm(`Disable ${o.email}? They are signed out immediately.`))
1302
+ void update(o.id, { disabled: !o.disabledAt }).catch(() => {
1303
+ });
1304
+ },
1305
+ children: o.disabledAt ? "Enable" : "Disable"
1306
+ }
1307
+ )
1308
+ ] }) })
1309
+ ] }, o.id)) })
1310
+ ] }) : null,
1311
+ report && report.links.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1312
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Pending links" }),
1313
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: styles.list, children: report.links.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1314
+ "li",
1315
+ {
1316
+ style: { ...styles.row, display: "flex", gap: 12, alignItems: "center" },
1317
+ children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: l.kind === "invite" ? "info" : "warn", dot: false, children: l.kind }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { flex: 1 }, children: [
1320
+ l.email,
1321
+ l.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: styles.muted, children: [
1322
+ " \xB7 ",
1323
+ levelOf(l.scopes)
1324
+ ] }) : null
1325
+ ] }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: styles.muted, children: [
1327
+ "expires ",
1328
+ new Date(l.expiresAt).toLocaleString()
1329
+ ] }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1331
+ "button",
1332
+ {
1333
+ type: "button",
1334
+ style: { ...styles.buttonDanger, height: 28 },
1335
+ onClick: () => void revokeLink(l.id).catch(() => {
1336
+ }),
1337
+ children: "Revoke"
1338
+ }
1339
+ )
1340
+ ]
1341
+ },
1342
+ l.id
1343
+ )) })
1344
+ ] }) : null
1345
+ ] });
1346
+ }
1347
+
1348
+ // src/screens/TokensScreen.tsx
1349
+ var import_react_admin9 = require("@fonderie/react-admin");
1350
+ var import_react3 = require("react");
1351
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1352
+ var ALL = ["read", "write", "secrets"];
1353
+ function TokensScreen({ client }) {
1354
+ const { report, isLoading, error, issue, revoke } = (0, import_react_admin9.useAdminTokens)(client);
1355
+ const [name, setName] = (0, import_react3.useState)("");
1356
+ const [scopes, setScopes] = (0, import_react3.useState)(["read"]);
1357
+ const [days, setDays] = (0, import_react3.useState)("");
1358
+ const [minted, setMinted] = (0, import_react3.useState)(null);
1359
+ const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
1360
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
1361
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1362
+ PageHeader,
1363
+ {
1364
+ title: "Tokens",
1365
+ lead: "Credentials for machines: the root token's strength, scoped tokens for the CLI and CI, and any legacy per-module tokens. People sign in as operators."
1366
+ }
1367
+ ),
1368
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
1369
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Root token" }),
1370
+ report ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { ...styles.card, display: "flex", gap: 12, alignItems: "flex-start" }, children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "lock", size: 18, style: { marginTop: 2 } }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
1373
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: report.admin.ok ? "ok" : "bad", children: report.admin.ok ? "strong" : "weak" }),
1374
+ report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1141
1375
  "div",
1142
1376
  {
1143
1377
  style: { marginTop: 6, color: "var(--fonderie-danger,#e00)", fontSize: 13 },
@@ -1147,9 +1381,9 @@ function TokensScreen({ client }) {
1147
1381
  ))
1148
1382
  ] })
1149
1383
  ] }) : null,
1150
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Issued tokens" }),
1151
- report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1152
- minted ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1384
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Issued tokens" }),
1385
+ report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1386
+ minted ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1153
1387
  "div",
1154
1388
  {
1155
1389
  style: {
@@ -1158,11 +1392,11 @@ function TokensScreen({ client }) {
1158
1392
  borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1159
1393
  },
1160
1394
  children: [
1161
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: "Copy this now \u2014 it is never shown again." }),
1395
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "Copy this now \u2014 it is never shown again." }),
1162
1396
  " (",
1163
1397
  minted.name,
1164
1398
  ")",
1165
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1399
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1166
1400
  "div",
1167
1401
  {
1168
1402
  style: {
@@ -1178,7 +1412,7 @@ function TokensScreen({ client }) {
1178
1412
  ]
1179
1413
  }
1180
1414
  ) : null,
1181
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1415
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1182
1416
  "form",
1183
1417
  {
1184
1418
  style: styles.toolbar,
@@ -1197,7 +1431,7 @@ function TokensScreen({ client }) {
1197
1431
  });
1198
1432
  },
1199
1433
  children: [
1200
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1434
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1201
1435
  "input",
1202
1436
  {
1203
1437
  value: name,
@@ -1207,7 +1441,7 @@ function TokensScreen({ client }) {
1207
1441
  "aria-label": "Name"
1208
1442
  }
1209
1443
  ),
1210
- ALL.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1444
+ ALL.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1211
1445
  "label",
1212
1446
  {
1213
1447
  style: {
@@ -1219,14 +1453,14 @@ function TokensScreen({ client }) {
1219
1453
  fontSize: 13
1220
1454
  },
1221
1455
  children: [
1222
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
1223
1457
  " ",
1224
1458
  s
1225
1459
  ]
1226
1460
  },
1227
1461
  s
1228
1462
  )),
1229
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1463
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1230
1464
  "input",
1231
1465
  {
1232
1466
  value: days,
@@ -1236,14 +1470,14 @@ function TokensScreen({ client }) {
1236
1470
  "aria-label": "Days"
1237
1471
  }
1238
1472
  ),
1239
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1473
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1240
1474
  "button",
1241
1475
  {
1242
1476
  type: "submit",
1243
1477
  style: styles.buttonPrimary,
1244
1478
  disabled: !name.trim() || scopes.length === 0 || isLoading,
1245
1479
  children: [
1246
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "plus", size: 14 }),
1480
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "plus", size: 14 }),
1247
1481
  "Issue token"
1248
1482
  ]
1249
1483
  }
@@ -1251,32 +1485,32 @@ function TokensScreen({ client }) {
1251
1485
  ]
1252
1486
  }
1253
1487
  ),
1254
- report?.issued && report.issued.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { style: styles.table, children: [
1255
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
1256
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Name" }),
1257
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Scopes" }),
1258
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Created" }),
1259
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Expires" }),
1260
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Last used" }),
1261
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th })
1488
+ report?.issued && report.issued.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("table", { style: styles.table, children: [
1489
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Name" }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Scopes" }),
1492
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Created" }),
1493
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Expires" }),
1494
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Last used" }),
1495
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th })
1262
1496
  ] }) }),
1263
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: report.issued.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
1264
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: report.issued.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
1265
1499
  t2.name,
1266
- t2.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1500
+ t2.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1267
1501
  " ",
1268
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "neutral", children: "revoked" })
1502
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "revoked" })
1269
1503
  ] }) : null
1270
1504
  ] }),
1271
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.scopes.join(", ") }),
1272
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: { ...styles.td, ...styles.muted }, children: [
1505
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.scopes.join(", ") }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: { ...styles.td, ...styles.muted }, children: [
1273
1507
  new Date(t2.createdAt).toLocaleDateString(),
1274
1508
  " \xB7 ",
1275
1509
  t2.createdBy
1276
1510
  ] }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.expiresAt ? new Date(t2.expiresAt).toLocaleDateString() : "never" }),
1278
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.lastUsedAt ? new Date(t2.lastUsedAt).toLocaleString() : "never" }),
1279
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: t2.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1511
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.expiresAt ? new Date(t2.expiresAt).toLocaleDateString() : "never" }),
1512
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.lastUsedAt ? new Date(t2.lastUsedAt).toLocaleString() : "never" }),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: t2.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1280
1514
  "button",
1281
1515
  {
1282
1516
  type: "button",
@@ -1292,47 +1526,254 @@ function TokensScreen({ client }) {
1292
1526
  }
1293
1527
  ) })
1294
1528
  ] }, t2.id)) })
1295
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Empty, { icon: "tokens", title: "No scoped tokens yet", children: "Issue a read-only token for a dashboard or a teammate instead of sharing the root." })
1529
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Empty, { icon: "tokens", title: "No scoped tokens yet", children: "Issue a read-only token for a dashboard or a teammate instead of sharing the root." })
1296
1530
  ] }),
1297
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
1298
- report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { style: styles.row, children: [
1299
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.mono, children: l.module }),
1531
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
1532
+ report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { style: styles.row, children: [
1533
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.mono, children: l.module }),
1300
1534
  " ",
1301
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
1535
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
1302
1536
  ] }, l.module)) })
1303
1537
  ] });
1304
1538
  }
1305
1539
 
1306
1540
  // src/screens/UsersScreen.tsx
1307
1541
  var import_client = require("@fonderie/client");
1308
- var import_react_admin9 = require("@fonderie/react-admin");
1309
- var import_react3 = require("react");
1310
- var import_jsx_runtime10 = require("react/jsx-runtime");
1311
- function UsersScreen({ client, pageSize = 50 }) {
1312
- const [input, setInput] = (0, import_react3.useState)("");
1313
- const [selected, setSelected] = (0, import_react3.useState)(null);
1314
- const list = (0, import_react_admin9.useAdminUsers)(client, { limit: pageSize });
1315
- const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin9.useAdminUser)(
1542
+ var import_react_admin11 = require("@fonderie/react-admin");
1543
+ var import_react6 = require("react");
1544
+
1545
+ // src/billing.ts
1546
+ var import_react4 = require("react");
1547
+ function periodEnd(s) {
1548
+ if (!s.currentPeriodEnd) return null;
1549
+ const date = new Date(s.currentPeriodEnd).toLocaleDateString();
1550
+ if (s.status === "canceled")
1551
+ return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
1552
+ if (s.cancelAtPeriodEnd) return { label: "Ends", date };
1553
+ return { label: "Renews", date };
1554
+ }
1555
+ var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
1556
+ function useSubscriptionIndex(client) {
1557
+ const [index, setIndex] = (0, import_react4.useState)(null);
1558
+ (0, import_react4.useEffect)(() => {
1559
+ if (!client) return;
1560
+ let live = true;
1561
+ (async () => {
1562
+ const map = /* @__PURE__ */ new Map();
1563
+ let cursor;
1564
+ for (let page = 0; page < 20; page++) {
1565
+ const { result } = await client.listSubscriptions({
1566
+ limit: 100,
1567
+ ...cursor ? { cursor } : {}
1568
+ });
1569
+ for (const s of result.subscriptions) {
1570
+ const key = `${s.subscriberType}/${s.subscriberId}`;
1571
+ if (!map.has(key)) map.set(key, s);
1572
+ }
1573
+ if (!result.nextCursor) break;
1574
+ cursor = result.nextCursor;
1575
+ }
1576
+ if (live) setIndex(map);
1577
+ })().catch(() => {
1578
+ if (live) setIndex(/* @__PURE__ */ new Map());
1579
+ });
1580
+ return () => {
1581
+ live = false;
1582
+ };
1583
+ }, [client]);
1584
+ return index;
1585
+ }
1586
+
1587
+ // src/screens/SubscriberBilling.tsx
1588
+ var import_react_admin10 = require("@fonderie/react-admin");
1589
+ var import_react5 = require("react");
1590
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1591
+ function SubscriberBilling({ client, subscriber }) {
1592
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
1593
+ const [amount, setAmount] = (0, import_react5.useState)("");
1594
+ const [note, setNote] = (0, import_react5.useState)("");
1595
+ const [granted, setGranted] = (0, import_react5.useState)(null);
1596
+ if (isLoading && !wallet && !subscription) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading billing\u2026" });
1597
+ const end = subscription ? periodEnd(subscription) : null;
1598
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1599
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1600
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.grid, marginBottom: 16 }, children: [
1601
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1602
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Plan" }),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1604
+ "div",
1605
+ {
1606
+ style: {
1607
+ display: "flex",
1608
+ alignItems: "center",
1609
+ gap: 8,
1610
+ marginTop: 6,
1611
+ flexWrap: "wrap"
1612
+ },
1613
+ children: [
1614
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 18 }, children: subscription ? subscription.plan : "Free" }),
1615
+ subscription ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1617
+ "\xB7 ",
1618
+ subscription.interval
1619
+ ] }),
1620
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: statusTone(subscription.status), children: subscription.status })
1621
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: "no subscription" })
1622
+ ]
1623
+ }
1624
+ ),
1625
+ end ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1626
+ end.label,
1627
+ " ",
1628
+ end.date
1629
+ ] }) : null,
1630
+ subscription?.providerSubscriptionId ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" }, children: subscription.providerSubscriptionId }) : null
1631
+ ] }),
1632
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1633
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Credits" }),
1634
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statValue, children: [
1636
+ wallet.balance,
1637
+ " ",
1638
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, fontWeight: 500 }, children: wallet.currency })
1639
+ ] }),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1641
+ wallet.purchased ?? "0",
1642
+ " permanent \xB7 ",
1643
+ wallet.granted ?? "0",
1644
+ " plan allowance",
1645
+ wallet.grantedExpiresAt ? ` (expires ${new Date(wallet.grantedExpiresAt).toLocaleDateString()})` : ""
1646
+ ] })
1647
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statHint, children: "No wallet \u2014 billing has no wallet configuration." })
1648
+ ] })
1649
+ ] }),
1650
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1651
+ "form",
1652
+ {
1653
+ style: {
1654
+ ...styles.card,
1655
+ display: "flex",
1656
+ gap: 8,
1657
+ alignItems: "center",
1658
+ flexWrap: "wrap",
1659
+ marginBottom: 16
1660
+ },
1661
+ onSubmit: (e) => {
1662
+ e.preventDefault();
1663
+ const key = `admin-grant-${subscriber.type}-${subscriber.id}-${Date.now()}`;
1664
+ void grant({
1665
+ amount: amount.trim(),
1666
+ ...note ? { description: note } : {},
1667
+ idempotencyKey: key
1668
+ }).then(() => {
1669
+ setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
1670
+ setAmount("");
1671
+ setNote("");
1672
+ }).catch(() => {
1673
+ });
1674
+ },
1675
+ children: [
1676
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: "Grant credits" }),
1677
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1678
+ "input",
1679
+ {
1680
+ value: amount,
1681
+ onChange: (e) => setAmount(e.target.value),
1682
+ placeholder: `amount (${wallet.currency})`,
1683
+ inputMode: "numeric",
1684
+ style: { ...styles.input, width: 150 },
1685
+ "aria-label": "Amount"
1686
+ }
1687
+ ),
1688
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1689
+ "input",
1690
+ {
1691
+ value: note,
1692
+ onChange: (e) => setNote(e.target.value),
1693
+ placeholder: "reason (optional)",
1694
+ style: { ...styles.input, flex: 1, minWidth: 180 },
1695
+ "aria-label": "Reason"
1696
+ }
1697
+ ),
1698
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1699
+ "button",
1700
+ {
1701
+ type: "submit",
1702
+ style: styles.buttonPrimary,
1703
+ disabled: !/^\d+$/.test(amount.trim()),
1704
+ children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "plus", size: 14 }),
1706
+ "Grant"
1707
+ ]
1708
+ }
1709
+ ),
1710
+ granted ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.ok, children: granted }) : null
1711
+ ]
1712
+ }
1713
+ ) : null,
1714
+ wallet ? ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "subscriber", title: "No credit movements yet" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1716
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1717
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
1718
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Kind" }),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Amount" }),
1720
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Note" })
1721
+ ] }) }),
1722
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1723
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(t2.createdAt).toLocaleString() }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: t2.type }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1726
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1727
+ ] }, t2.id)) })
1728
+ ] }),
1729
+ hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1730
+ "button",
1731
+ {
1732
+ type: "button",
1733
+ style: { ...styles.button, marginTop: 8 },
1734
+ onClick: () => void loadMoreLedger(),
1735
+ children: "Load more"
1736
+ }
1737
+ ) : null
1738
+ ] }) : null
1739
+ ] });
1740
+ }
1741
+
1742
+ // src/screens/UsersScreen.tsx
1743
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1744
+ function UsersScreen({
1745
+ client,
1746
+ pageSize = 50,
1747
+ billingClient,
1748
+ openUserId
1749
+ }) {
1750
+ const [input, setInput] = (0, import_react6.useState)("");
1751
+ const [selected, setSelected] = (0, import_react6.useState)(
1752
+ openUserId ? { id: openUserId } : null
1753
+ );
1754
+ const plans = useSubscriptionIndex(billingClient);
1755
+ const list = (0, import_react_admin11.useAdminUsers)(client, { limit: pageSize });
1756
+ const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin11.useAdminUser)(
1316
1757
  client,
1317
1758
  selected ?? {}
1318
1759
  );
1319
1760
  const userId = user?.id ?? null;
1320
- const sessions = (0, import_react_admin9.useAdminUserSessions)(client, userId);
1321
- const history = (0, import_react_admin9.useAdminLoginHistory)(client, userId, { limit: 20 });
1322
- const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "no" });
1761
+ const sessions = (0, import_react_admin11.useAdminUserSessions)(client, userId);
1762
+ const history = (0, import_react_admin11.useAdminLoginHistory)(client, userId, { limit: 20 });
1763
+ const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "no" });
1323
1764
  const clear = () => {
1324
1765
  setSelected(null);
1325
1766
  setInput("");
1326
1767
  };
1327
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
1328
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1768
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1329
1770
  PageHeader,
1330
1771
  {
1331
1772
  title: "Users",
1332
1773
  lead: "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1333
1774
  }
1334
1775
  ),
1335
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1776
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1336
1777
  "form",
1337
1778
  {
1338
1779
  style: styles.toolbar,
@@ -1342,7 +1783,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1342
1783
  setSelected(email ? { email } : null);
1343
1784
  },
1344
1785
  children: [
1345
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1786
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1346
1787
  "input",
1347
1788
  {
1348
1789
  type: "email",
@@ -1353,14 +1794,14 @@ function UsersScreen({ client, pageSize = 50 }) {
1353
1794
  "aria-label": "Email"
1354
1795
  }
1355
1796
  ),
1356
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1357
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "search", size: 14 }),
1797
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "search", size: 14 }),
1358
1799
  "Look up"
1359
1800
  ] }),
1360
- selected ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1361
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "back", size: 14 }),
1801
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1802
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "back", size: 14 }),
1362
1803
  "All users"
1363
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1804
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1364
1805
  "button",
1365
1806
  {
1366
1807
  type: "button",
@@ -1373,18 +1814,19 @@ function UsersScreen({ client, pageSize = 50 }) {
1373
1814
  ]
1374
1815
  }
1375
1816
  ),
1376
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1377
- !selected ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1378
- list.error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1379
- list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("table", { style: styles.table, children: [
1380
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1381
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Email" }),
1382
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Name" }),
1383
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Created" }),
1384
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Status" })
1817
+ error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1818
+ !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1819
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1820
+ list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1822
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Email" }),
1823
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Name" }),
1824
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }) : null,
1825
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Created" }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Status" })
1385
1827
  ] }) }),
1386
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1387
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1828
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1829
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1388
1830
  "button",
1389
1831
  {
1390
1832
  type: "button",
@@ -1393,17 +1835,28 @@ function UsersScreen({ client, pageSize = 50 }) {
1393
1835
  children: u.email
1394
1836
  }
1395
1837
  ) }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: "\u2014" }) }),
1397
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1398
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
1399
- u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1400
- u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1401
- !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "ok", children: "active" }) : null
1838
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2014" }) }),
1839
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: (() => {
1840
+ const sub = plans?.get(`user/${u.id}`);
1841
+ if (!plans) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2026" });
1842
+ if (!sub || sub.status === "canceled")
1843
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "free" });
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: sub.plan }),
1846
+ " ",
1847
+ sub.status !== "active" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: statusTone(sub.status), children: sub.status }) : null
1848
+ ] });
1849
+ })() }) : null,
1850
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1852
+ u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1853
+ u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1854
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "active" }) : null
1402
1855
  ] })
1403
1856
  ] }, u.id)) })
1404
1857
  ] }),
1405
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1406
- list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1858
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1859
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1407
1860
  "button",
1408
1861
  {
1409
1862
  type: "button",
@@ -1413,48 +1866,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1413
1866
  }
1414
1867
  ) : null
1415
1868
  ] }) : null,
1416
- user ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1417
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("h2", { style: styles.subtitle, children: [
1869
+ user ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1870
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("h2", { style: styles.subtitle, children: [
1418
1871
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1419
1872
  " ",
1420
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1421
- user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
1873
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1874
+ user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
1422
1875
  ] }),
1423
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tbody", { children: [
1424
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1425
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "id" }),
1426
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1876
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1877
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1878
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "id" }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1427
1880
  ] }),
1428
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1429
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "email" }),
1430
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
1881
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1882
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "email" }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1431
1884
  user.email,
1432
1885
  " \xB7 verified ",
1433
1886
  yesNo(user.isEmailVerified)
1434
1887
  ] })
1435
1888
  ] }),
1436
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1437
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "MFA" }),
1438
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1889
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1890
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "MFA" }),
1891
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1439
1892
  ] }),
1440
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1441
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "provider" }),
1442
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
1893
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1894
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "provider" }),
1895
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1443
1896
  user.provider || "password",
1444
1897
  user.hasPassword ? "" : " \xB7 no password set"
1445
1898
  ] })
1446
1899
  ] }),
1447
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1448
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "last login" }),
1449
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: "never" }) })
1900
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1901
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "last login" }),
1902
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "never" }) })
1450
1903
  ] }),
1451
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1452
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "created" }),
1453
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1904
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "created" }),
1906
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1454
1907
  ] })
1455
1908
  ] }) }),
1456
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1457
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1909
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1910
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1458
1911
  "button",
1459
1912
  {
1460
1913
  type: "button",
@@ -1463,7 +1916,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1463
1916
  disabled: isLoading,
1464
1917
  children: "Unsuspend"
1465
1918
  }
1466
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1919
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1467
1920
  "button",
1468
1921
  {
1469
1922
  type: "button",
@@ -1473,7 +1926,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1473
1926
  children: "Suspend"
1474
1927
  }
1475
1928
  ),
1476
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1477
1930
  "button",
1478
1931
  {
1479
1932
  type: "button",
@@ -1484,38 +1937,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1484
1937
  }
1485
1938
  )
1486
1939
  ] }),
1487
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
1488
- sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { style: styles.row, children: [
1489
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1940
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
1942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1943
+ SubscriberBilling,
1944
+ {
1945
+ client: billingClient,
1946
+ subscriber: { type: "user", id: user.id }
1947
+ }
1948
+ )
1949
+ ] }) : null,
1950
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
1951
+ sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1490
1953
  " ",
1491
- (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: styles.muted, children: [
1954
+ (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1492
1955
  (0, import_client.describeLocation)(s.location),
1493
1956
  " "
1494
1957
  ] }) : null,
1495
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1496
1959
  " ",
1497
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: styles.muted, children: [
1960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1498
1961
  "since ",
1499
1962
  new Date(s.createdAt).toLocaleString()
1500
1963
  ] })
1501
1964
  ] }, s.id)) }),
1502
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1503
- history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { style: styles.row, children: [
1504
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1965
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1966
+ history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
1967
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1505
1968
  " ",
1506
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: e.method }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
1507
1970
  " ",
1508
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1509
1972
  " ",
1510
- (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
1511
- e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1973
+ (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
1974
+ e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1512
1975
  " ",
1513
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1976
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1514
1977
  ] }) : null,
1515
1978
  " ",
1516
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1979
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1517
1980
  ] }, e.id)) }),
1518
- history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1981
+ history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1519
1982
  "button",
1520
1983
  {
1521
1984
  type: "button",
@@ -1529,25 +1992,25 @@ function UsersScreen({ client, pageSize = 50 }) {
1529
1992
  }
1530
1993
 
1531
1994
  // src/screens/CatalogScreen.tsx
1532
- var import_react_admin10 = require("@fonderie/react-admin");
1533
- var import_jsx_runtime11 = require("react/jsx-runtime");
1995
+ var import_react_admin12 = require("@fonderie/react-admin");
1996
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1534
1997
  function CatalogScreen({ client }) {
1535
- const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin10.useAdminCatalog)(client);
1998
+ const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin12.useAdminCatalog)(client);
1536
1999
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
1537
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.container, children: [
1538
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1539
2002
  PageHeader,
1540
2003
  {
1541
2004
  title: "Catalog",
1542
2005
  lead: "What you sell: plans as configured in code, and as stored in the database, side by side.",
1543
- actions: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
2006
+ actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1544
2007
  }
1545
2008
  ),
1546
- error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1547
- isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1548
- catalog ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1549
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
1550
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2009
+ error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2010
+ isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2011
+ catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2012
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1551
2014
  "pre",
1552
2015
  {
1553
2016
  style: {
@@ -1561,32 +2024,32 @@ function CatalogScreen({ client }) {
1561
2024
  children: JSON.stringify(catalog.configured, null, 2)
1562
2025
  }
1563
2026
  ),
1564
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
1565
- catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "catalog", title: "No stored plans", children: "Plans are configured in code; nothing has been written to the database." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1566
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1567
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Plan" }),
1568
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Tier" }),
1569
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Seats" }),
1570
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Monthly" }),
1571
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Yearly" }),
1572
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Trial" }),
1573
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th })
2027
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
2028
+ catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "catalog", title: "No stored plans", children: "Plans are configured in code; nothing has been written to the database." }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
2029
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2030
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
2031
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Tier" }),
2032
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Seats" }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Monthly" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Yearly" }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Trial" }),
2036
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th })
1574
2037
  ] }) }),
1575
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1576
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: styles.td, children: [
1577
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: p.name }),
1578
- p.description ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.muted, children: p.description }) : null
2038
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2039
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
2040
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: p.name }),
2041
+ p.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: styles.muted, children: p.description }) : null
1579
2042
  ] }),
1580
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: p.tier }),
1581
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
1582
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: styles.td, children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.tier }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
2045
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1583
2046
  money(p.pricing?.monthly),
1584
2047
  " ",
1585
2048
  p.pricing?.currency ?? ""
1586
2049
  ] }),
1587
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
1588
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
1589
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2050
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2051
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
2052
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1590
2053
  "button",
1591
2054
  {
1592
2055
  type: "button",
@@ -1606,114 +2069,148 @@ function CatalogScreen({ client }) {
1606
2069
  }
1607
2070
 
1608
2071
  // src/screens/SubscriberScreen.tsx
1609
- var import_react_admin11 = require("@fonderie/react-admin");
1610
- var import_react4 = require("react");
1611
- var import_jsx_runtime12 = require("react/jsx-runtime");
1612
- function SubscriberScreen({ client, pageSize = 50 }) {
1613
- const [type, setType] = (0, import_react4.useState)("user");
1614
- const [idInput, setIdInput] = (0, import_react4.useState)("");
1615
- const [subscriber, setSubscriber] = (0, import_react4.useState)(null);
1616
- const list = (0, import_react_admin11.useAdminSubscribers)(client, { limit: pageSize });
1617
- const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin11.useAdminSubscriber)(client, subscriber, { limit: 20 });
1618
- const [amount, setAmount] = (0, import_react4.useState)("");
1619
- const [note, setNote] = (0, import_react4.useState)("");
1620
- const [granted, setGranted] = (0, import_react4.useState)(null);
1621
- const open = (t2, id) => {
1622
- setGranted(null);
1623
- setType(t2);
1624
- setIdInput(id);
1625
- setSubscriber({ type: t2, id });
1626
- };
1627
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1628
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2072
+ var import_react_admin13 = require("@fonderie/react-admin");
2073
+ var import_react7 = require("react");
2074
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2075
+ var FILTERS = [
2076
+ { label: "All", match: () => true },
2077
+ { label: "Active", match: (s) => s === "active" },
2078
+ { label: "Trialing", match: (s) => s === "trialing" },
2079
+ { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2080
+ { label: "Canceled", match: (s) => s === "canceled" }
2081
+ ];
2082
+ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2083
+ const list = (0, import_react_admin13.useAdminSubscribers)(client, { limit: pageSize });
2084
+ const [filter, setFilter] = (0, import_react7.useState)("All");
2085
+ const [open, setOpen] = (0, import_react7.useState)(null);
2086
+ const [type, setType] = (0, import_react7.useState)("workspace");
2087
+ const [idInput, setIdInput] = (0, import_react7.useState)("");
2088
+ if (open) {
2089
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2090
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2091
+ PageHeader,
2092
+ {
2093
+ title: `${open.type}/${open.id}`,
2094
+ lead: "Plan, credits and what moved.",
2095
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2096
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2097
+ "All subscriptions"
2098
+ ] })
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open })
2102
+ ] });
2103
+ }
2104
+ const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2105
+ const rows = list.subscriptions.filter((s) => match(s.status));
2106
+ const openRow = (t2, id) => t2 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t2, id });
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1629
2109
  PageHeader,
1630
2110
  {
1631
- title: "Subscribers",
1632
- lead: "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
2111
+ title: "Subscriptions",
2112
+ lead: "Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
2113
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefreshButton, { onClick: () => void list.refresh(), busy: list.isLoading })
1633
2114
  }
1634
2115
  ),
1635
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1636
- "form",
1637
- {
1638
- style: styles.toolbar,
1639
- onSubmit: (e) => {
1640
- e.preventDefault();
1641
- setGranted(null);
1642
- setSubscriber(idInput.trim() ? { type, id: idInput.trim() } : null);
1643
- },
1644
- children: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1646
- "select",
1647
- {
1648
- value: type,
1649
- onChange: (e) => setType(e.target.value),
1650
- style: styles.input,
1651
- "aria-label": "Type",
1652
- children: [
1653
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "user", children: "user" }),
1654
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "workspace", children: "workspace" })
1655
- ]
1656
- }
1657
- ),
1658
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1659
- "input",
1660
- {
1661
- value: idInput,
1662
- onChange: (e) => setIdInput(e.target.value),
1663
- placeholder: "subscriber id",
1664
- style: { ...styles.input, minWidth: 280 },
1665
- "aria-label": "Subscriber id"
1666
- }
1667
- ),
1668
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1669
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "search", size: 14 }),
1670
- "Look up"
1671
- ] }),
1672
- subscriber ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1673
- "button",
1674
- {
1675
- type: "button",
1676
- style: styles.button,
1677
- onClick: () => {
1678
- setSubscriber(null);
1679
- setIdInput("");
1680
- setGranted(null);
2116
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
2117
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2118
+ "fieldset",
2119
+ {
2120
+ style: {
2121
+ display: "flex",
2122
+ gap: 6,
2123
+ flexWrap: "wrap",
2124
+ border: "none",
2125
+ margin: 0,
2126
+ padding: 0,
2127
+ minWidth: 0
2128
+ },
2129
+ "aria-label": "Filter by status",
2130
+ children: FILTERS.map((f) => {
2131
+ const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2132
+ const on = filter === f.label;
2133
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2134
+ "button",
2135
+ {
2136
+ type: "button",
2137
+ "aria-pressed": on,
2138
+ onClick: () => setFilter(f.label),
2139
+ style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2140
+ children: [
2141
+ f.label,
2142
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.7 }, children: n })
2143
+ ]
1681
2144
  },
1682
- children: [
1683
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "back", size: 14 }),
1684
- "All subscribers"
1685
- ]
1686
- }
1687
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1688
- "button",
1689
- {
1690
- type: "button",
1691
- style: styles.button,
1692
- onClick: () => void list.refresh(),
1693
- disabled: list.isLoading,
1694
- children: "Refresh"
1695
- }
1696
- )
1697
- ]
2145
+ f.label
2146
+ );
2147
+ })
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2151
+ "form",
2152
+ {
2153
+ style: { display: "flex", gap: 6 },
2154
+ onSubmit: (e) => {
2155
+ e.preventDefault();
2156
+ if (idInput.trim()) openRow(type, idInput.trim());
2157
+ },
2158
+ children: [
2159
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2160
+ "select",
2161
+ {
2162
+ value: type,
2163
+ onChange: (e) => setType(e.target.value),
2164
+ style: styles.input,
2165
+ "aria-label": "Type",
2166
+ children: [
2167
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: "workspace" }),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "user", children: "user" })
2169
+ ]
2170
+ }
2171
+ ),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2173
+ "input",
2174
+ {
2175
+ value: idInput,
2176
+ onChange: (e) => setIdInput(e.target.value),
2177
+ placeholder: "subscriber id",
2178
+ style: { ...styles.input, width: 220 },
2179
+ "aria-label": "Subscriber id"
2180
+ }
2181
+ ),
2182
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2183
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2184
+ "Open"
2185
+ ] })
2186
+ ]
2187
+ }
2188
+ )
2189
+ ] }),
2190
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2191
+ rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2192
+ Empty,
2193
+ {
2194
+ icon: "subscriber",
2195
+ title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2196
+ children: "Subscriptions appear here once someone checks out."
1698
2197
  }
1699
- ),
1700
- error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1701
- !subscriber ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1702
- list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1703
- list.subscriptions.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "subscriber", title: "No subscribers yet", children: "Subscriptions appear here once someone checks out." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1704
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1705
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Subscriber" }),
1706
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }),
1707
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Status" }),
1708
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Renews" })
1709
- ] }) }),
1710
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.subscriptions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1711
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2198
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2199
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2200
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Subscriber" }),
2201
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Plan" }),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Status" }),
2203
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Renews / ends" })
2204
+ ] }) }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: rows.map((s) => {
2206
+ const end = periodEnd(s);
2207
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2208
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1712
2209
  "button",
1713
2210
  {
1714
2211
  type: "button",
1715
2212
  style: { ...styles.link, ...styles.mono },
1716
- onClick: () => open(s.subscriberType, s.subscriberId),
2213
+ onClick: () => openRow(s.subscriberType, s.subscriberId),
1717
2214
  children: [
1718
2215
  s.subscriberType,
1719
2216
  "/",
@@ -1721,206 +2218,56 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1721
2218
  ]
1722
2219
  }
1723
2220
  ) }),
1724
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1725
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: s.plan }),
1726
- " \xB7 ",
1727
- s.interval
2221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2222
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("strong", { children: s.plan }),
2223
+ " ",
2224
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: styles.muted, children: [
2225
+ "\xB7 ",
2226
+ s.interval
2227
+ ] })
1728
2228
  ] }),
1729
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1730
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1731
- Pill,
1732
- {
1733
- tone: s.status === "active" ? "ok" : s.status === "trialing" ? "info" : s.status === "past_due" ? "warn" : "neutral",
1734
- children: s.status
1735
- }
1736
- ),
1737
- s.cancelAtPeriodEnd ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2229
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2230
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: statusTone(s.status), children: s.status }),
2231
+ s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1738
2232
  " ",
1739
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
2233
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
1740
2234
  ] }) : null
1741
2235
  ] }),
1742
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2014" }) })
1743
- ] }, s.id)) })
1744
- ] }),
1745
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1746
- list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1747
- "button",
1748
- {
1749
- type: "button",
1750
- style: { ...styles.button, marginTop: 8 },
1751
- onClick: () => void list.loadMore(),
1752
- children: "Load more"
1753
- }
1754
- ) : null
1755
- ] }) : null,
1756
- subscriber && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1757
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Subscription" }),
1758
- subscription ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1759
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1760
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "plan" }),
1761
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1762
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: subscription.plan }),
1763
- " \xB7 ",
1764
- subscription.interval
1765
- ] })
1766
- ] }),
1767
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1768
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "status" }),
1769
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1770
- subscription.status,
1771
- subscription.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""
1772
- ] })
1773
- ] }),
1774
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1775
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "period" }),
1776
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1777
- subscription.currentPeriodStart ? new Date(subscription.currentPeriodStart).toLocaleDateString() : "\u2014",
2236
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, whiteSpace: "nowrap" }, children: end ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2237
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: end.label }),
1778
2238
  " ",
1779
- "\u2192",
1780
- " ",
1781
- subscription.currentPeriodEnd ? new Date(subscription.currentPeriodEnd).toLocaleDateString() : "\u2014"
1782
- ] })
1783
- ] }),
1784
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1785
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "provider" }),
1786
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: subscription.providerSubscriptionId ?? "\u2014" })
1787
- ] })
1788
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "No subscription." }),
1789
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Wallet" }),
1790
- wallet ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1791
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1792
- "p",
1793
- {
1794
- style: {
1795
- ...styles.card,
1796
- display: "flex",
1797
- alignItems: "baseline",
1798
- gap: 8,
1799
- flexWrap: "wrap"
1800
- },
1801
- children: [
1802
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1803
- "strong",
1804
- {
1805
- style: {
1806
- fontSize: 22,
1807
- letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
1808
- },
1809
- children: wallet.balance
1810
- }
1811
- ),
1812
- " ",
1813
- wallet.currency,
1814
- " ",
1815
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1816
- "(granted ",
1817
- wallet.granted ?? "0",
1818
- " \xB7 purchased ",
1819
- wallet.purchased ?? "0",
1820
- ") \xB7 minor units, precision ",
1821
- wallet.precision
1822
- ] })
1823
- ]
1824
- }
1825
- ),
1826
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1827
- "form",
1828
- {
1829
- style: styles.toolbar,
1830
- onSubmit: (e) => {
1831
- e.preventDefault();
1832
- const key = `admin-grant-${subscriber.type}-${subscriber.id}-${Date.now()}`;
1833
- void grant({
1834
- amount: amount.trim(),
1835
- ...note ? { description: note } : {},
1836
- idempotencyKey: key
1837
- }).then(() => {
1838
- setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
1839
- setAmount("");
1840
- setNote("");
1841
- }).catch(() => {
1842
- });
1843
- },
1844
- children: [
1845
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1846
- "input",
1847
- {
1848
- value: amount,
1849
- onChange: (e) => setAmount(e.target.value),
1850
- placeholder: "amount (minor units)",
1851
- style: styles.input,
1852
- "aria-label": "Amount"
1853
- }
1854
- ),
1855
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1856
- "input",
1857
- {
1858
- value: note,
1859
- onChange: (e) => setNote(e.target.value),
1860
- placeholder: "reason (optional)",
1861
- style: { ...styles.input, minWidth: 220 },
1862
- "aria-label": "Reason"
1863
- }
1864
- ),
1865
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1866
- "button",
1867
- {
1868
- type: "submit",
1869
- style: styles.buttonPrimary,
1870
- disabled: !/^\d+$/.test(amount.trim()),
1871
- children: [
1872
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "plus", size: 14 }),
1873
- "Grant"
1874
- ]
1875
- }
1876
- ),
1877
- granted ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.ok, children: granted }) : null
1878
- ]
1879
- }
1880
- ),
1881
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Ledger" }),
1882
- ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "subscriber", title: "Nothing moved yet" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1883
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1884
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "When" }),
1885
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Kind" }),
1886
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Amount" }),
1887
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Note" })
1888
- ] }) }),
1889
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1890
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(t2.createdAt).toLocaleString() }),
1891
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2.type }),
1892
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1893
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1894
- ] }, t2.id)) })
1895
- ] }),
1896
- hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1897
- "button",
1898
- {
1899
- type: "button",
1900
- style: { ...styles.button, marginTop: 8 },
1901
- onClick: () => void loadMoreLedger(),
1902
- children: "Load more"
1903
- }
1904
- ) : null
1905
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "No wallet \u2014 billing has no wallet configuration." })
1906
- ] }) : null
2239
+ end.date
2240
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: "\u2014" }) })
2241
+ ] }, s.id);
2242
+ }) })
2243
+ ] }),
2244
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2245
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2246
+ "button",
2247
+ {
2248
+ type: "button",
2249
+ style: { ...styles.button, marginTop: 8 },
2250
+ onClick: () => void list.loadMore(),
2251
+ children: "Load more"
2252
+ }
2253
+ ) : null
1907
2254
  ] });
1908
2255
  }
1909
2256
 
1910
2257
  // src/screens/AuditScreen.tsx
1911
- var import_react_admin12 = require("@fonderie/react-admin");
1912
- var import_react5 = require("react");
1913
- var import_jsx_runtime13 = require("react/jsx-runtime");
2258
+ var import_react_admin14 = require("@fonderie/react-admin");
2259
+ var import_react8 = require("react");
2260
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1914
2261
  function AuditScreen({ client, pageSize = 50 }) {
1915
- const [draft, setDraft] = (0, import_react5.useState)({ workspaceId: "", type: "", actorId: "" });
1916
- const [filter, setFilter] = (0, import_react5.useState)(
2262
+ const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "" });
2263
+ const [filter, setFilter] = (0, import_react8.useState)(
1917
2264
  {}
1918
2265
  );
1919
- const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin12.useAdminAudit)(client, {
2266
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin14.useAdminAudit)(client, {
1920
2267
  ...filter,
1921
2268
  limit: pageSize
1922
2269
  });
1923
- const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2270
+ const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1924
2271
  "input",
1925
2272
  {
1926
2273
  value: draft[key],
@@ -1930,15 +2277,15 @@ function AuditScreen({ client, pageSize = 50 }) {
1930
2277
  "aria-label": placeholder
1931
2278
  }
1932
2279
  );
1933
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
1934
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2280
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.container, children: [
2281
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1935
2282
  PageHeader,
1936
2283
  {
1937
2284
  title: "Audit",
1938
2285
  lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
1939
2286
  }
1940
2287
  ),
1941
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2288
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1942
2289
  "form",
1943
2290
  {
1944
2291
  style: styles.toolbar,
@@ -1954,11 +2301,11 @@ function AuditScreen({ client, pageSize = 50 }) {
1954
2301
  field("workspaceId", "workspace id (all if empty)"),
1955
2302
  field("type", "event type"),
1956
2303
  field("actorId", "actor id"),
1957
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1958
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { name: "search", size: 14 }),
2304
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2305
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "search", size: 14 }),
1959
2306
  "Filter"
1960
2307
  ] }),
1961
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2308
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1962
2309
  "button",
1963
2310
  {
1964
2311
  type: "button",
@@ -1971,25 +2318,25 @@ function AuditScreen({ client, pageSize = 50 }) {
1971
2318
  ]
1972
2319
  }
1973
2320
  ),
1974
- error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1975
- events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
1976
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
1977
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "When" }),
1978
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Type" }),
1979
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Workspace" }),
1980
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Actor" }),
1981
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Request" })
2321
+ error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2322
+ events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("table", { style: styles.table, children: [
2323
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2324
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "When" }),
2325
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Type" }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Workspace" }),
2327
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Actor" }),
2328
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Request" })
1982
2329
  ] }) }),
1983
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
1984
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
1985
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
1986
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
1988
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2330
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2333
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2334
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2335
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
1989
2336
  ] }, e.id)) })
1990
2337
  ] }),
1991
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1992
- hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2338
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2339
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1993
2340
  "button",
1994
2341
  {
1995
2342
  type: "button",
@@ -2002,7 +2349,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2002
2349
  }
2003
2350
 
2004
2351
  // src/screens/AdminShell.tsx
2005
- var import_jsx_runtime14 = require("react/jsx-runtime");
2352
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2006
2353
  var ICON = {
2007
2354
  attention: "attention",
2008
2355
  modules: "modules",
@@ -2017,13 +2364,14 @@ var ICON = {
2017
2364
  audit: "audit",
2018
2365
  log: "log",
2019
2366
  tokens: "tokens",
2367
+ operators: "users",
2020
2368
  migrations: "migrations"
2021
2369
  };
2022
2370
  var NARROW = "(max-width: 820px)";
2023
2371
  function useNarrow() {
2024
2372
  const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
2025
- const [narrow, setNarrow] = (0, import_react6.useState)(() => query?.matches ?? false);
2026
- (0, import_react6.useEffect)(() => {
2373
+ const [narrow, setNarrow] = (0, import_react9.useState)(() => query?.matches ?? false);
2374
+ (0, import_react9.useEffect)(() => {
2027
2375
  if (!query) return;
2028
2376
  const on = (e) => setNarrow(e.matches);
2029
2377
  query.addEventListener("change", on);
@@ -2047,7 +2395,7 @@ var NAV = [
2047
2395
  group: "Money",
2048
2396
  items: [
2049
2397
  { page: "catalog", label: "Catalog", needs: "billing" },
2050
- { page: "subscriber", label: "Subscriber", needs: "billing" }
2398
+ { page: "subscriber", label: "Subscriptions", needs: "billing" }
2051
2399
  ]
2052
2400
  },
2053
2401
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
@@ -2057,7 +2405,8 @@ var NAV = [
2057
2405
  items: [
2058
2406
  { page: "audit", label: "Audit", needs: "audit" },
2059
2407
  { page: "log", label: "Admin log" },
2060
- { page: "tokens", label: "Access" },
2408
+ { page: "operators", label: "Operators", needs: "operators" },
2409
+ { page: "tokens", label: "Tokens" },
2061
2410
  { page: "migrations", label: "Migrations" }
2062
2411
  ]
2063
2412
  }
@@ -2074,70 +2423,96 @@ function AdminShell({
2074
2423
  onNavigate,
2075
2424
  appName = "Admin",
2076
2425
  envLabel,
2077
- footer
2426
+ footer,
2427
+ operators = false,
2428
+ currentOperator
2078
2429
  }) {
2079
2430
  const narrow = useNarrow();
2080
- const [drawer, setDrawer] = (0, import_react6.useState)(false);
2081
- const [own, setOwn] = (0, import_react6.useState)("attention");
2431
+ const [drawer, setDrawer] = (0, import_react9.useState)(false);
2432
+ const [own, setOwn] = (0, import_react9.useState)("attention");
2433
+ const [openUser, setOpenUser] = (0, import_react9.useState)(void 0);
2082
2434
  const current = page ?? own;
2083
2435
  const go = (p) => {
2084
2436
  if (onNavigate) onNavigate(p);
2085
2437
  if (page === void 0) setOwn(p);
2086
2438
  setDrawer(false);
2087
2439
  };
2088
- const [editing, setEditing] = (0, import_react6.useState)(null);
2440
+ const [editing, setEditing] = (0, import_react9.useState)(null);
2089
2441
  const has = {
2090
2442
  config: Boolean(configClient),
2091
2443
  courier: Boolean(courierClient),
2092
2444
  auth: Boolean(authClient),
2093
2445
  billing: Boolean(billingClient),
2094
- audit: Boolean(auditClient)
2446
+ audit: Boolean(auditClient),
2447
+ operators
2095
2448
  };
2096
2449
  let body;
2097
2450
  switch (current) {
2098
2451
  case "attention":
2099
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AttentionScreen, { client });
2452
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
2100
2453
  break;
2101
2454
  case "modules":
2102
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ModulesScreen, { client });
2455
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
2103
2456
  break;
2104
2457
  case "doctor":
2105
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DoctorScreen, { client });
2458
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
2106
2459
  break;
2107
2460
  case "environment":
2108
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EnvironmentScreen, { client });
2461
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
2109
2462
  break;
2110
2463
  case "routes":
2111
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RoutesScreen, { client });
2464
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
2112
2465
  break;
2113
2466
  case "tokens":
2114
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TokensScreen, { client });
2467
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client });
2468
+ break;
2469
+ case "operators":
2470
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator });
2115
2471
  break;
2116
2472
  case "migrations":
2117
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MigrationsScreen, { client });
2473
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
2118
2474
  break;
2119
2475
  case "log":
2120
- body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AdminLogScreen, { client });
2476
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
2121
2477
  break;
2122
2478
  case "catalog":
2123
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
2479
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
2124
2480
  break;
2125
2481
  case "subscriber":
2126
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2482
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2483
+ SubscriberScreen,
2484
+ {
2485
+ client: billingClient,
2486
+ ...authClient ? {
2487
+ onOpenUser: (id) => {
2488
+ setOpenUser(id);
2489
+ go("users");
2490
+ }
2491
+ } : {}
2492
+ }
2493
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2127
2494
  break;
2128
2495
  case "audit":
2129
- body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
2496
+ body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
2130
2497
  break;
2131
2498
  case "users":
2132
- body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2499
+ body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2500
+ UsersScreen,
2501
+ {
2502
+ client: authClient,
2503
+ billingClient,
2504
+ openUserId: openUser
2505
+ },
2506
+ openUser ?? "list"
2507
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2133
2508
  break;
2134
2509
  case "settings":
2135
- body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2136
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2137
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2510
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2511
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2138
2513
  "Config & secrets"
2139
2514
  ] }) }),
2140
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2515
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2141
2516
  import_react_config_admin_screens.ConfigEditorScreen,
2142
2517
  {
2143
2518
  client: configClient,
@@ -2147,7 +2522,7 @@ function AdminShell({
2147
2522
  onSaved: () => setEditing(null)
2148
2523
  }
2149
2524
  )
2150
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2525
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2151
2526
  import_react_config_admin_screens.ConfigListScreen,
2152
2527
  {
2153
2528
  client: configClient,
@@ -2160,12 +2535,12 @@ function AdminShell({
2160
2535
  );
2161
2536
  break;
2162
2537
  case "templates":
2163
- body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2164
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2165
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2538
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2540
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2166
2541
  "Templates"
2167
2542
  ] }) }),
2168
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2543
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2169
2544
  import_react_courier_admin_screens.TemplateEditorScreen,
2170
2545
  {
2171
2546
  client: courierClient,
@@ -2173,7 +2548,7 @@ function AdminShell({
2173
2548
  onSaved: () => setEditing(null)
2174
2549
  }
2175
2550
  )
2176
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2551
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2177
2552
  import_react_courier_admin_screens.TemplateListScreen,
2178
2553
  {
2179
2554
  client: courierClient,
@@ -2183,11 +2558,11 @@ function AdminShell({
2183
2558
  break;
2184
2559
  }
2185
2560
  const production = envLabel ? /^prod/i.test(envLabel) : false;
2186
- const brand = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.navBrand, children: [
2187
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.navMark, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "mark", size: 15 }) }),
2188
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2189
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.navBrandTitle, children: appName }),
2190
- envLabel ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2561
+ const brand = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.navBrand, children: [
2562
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navMark, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "mark", size: 15 }) }),
2563
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2564
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navBrandTitle, children: appName }),
2565
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2191
2566
  "span",
2192
2567
  {
2193
2568
  style: {
@@ -2204,18 +2579,18 @@ function AdminShell({
2204
2579
  }
2205
2580
  ) : null
2206
2581
  ] }),
2207
- narrow ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2582
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2208
2583
  "button",
2209
2584
  {
2210
2585
  type: "button",
2211
2586
  style: styles.buttonGhost,
2212
2587
  "aria-label": "Close menu",
2213
2588
  onClick: () => setDrawer(false),
2214
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "close" })
2589
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
2215
2590
  }
2216
2591
  ) : null
2217
2592
  ] });
2218
- const nav = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2593
+ const nav = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2219
2594
  "nav",
2220
2595
  {
2221
2596
  style: {
@@ -2225,14 +2600,14 @@ function AdminShell({
2225
2600
  "aria-label": "Admin",
2226
2601
  children: [
2227
2602
  brand,
2228
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2603
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2229
2604
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2230
2605
  if (items.length === 0) return null;
2231
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
2232
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.navGroup, children: g.group }),
2606
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
2607
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: g.group }),
2233
2608
  items.map((i) => {
2234
2609
  const active = current === i.page;
2235
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2610
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2236
2611
  "button",
2237
2612
  {
2238
2613
  type: "button",
@@ -2241,10 +2616,11 @@ function AdminShell({
2241
2616
  "aria-current": active ? "page" : void 0,
2242
2617
  onClick: () => {
2243
2618
  setEditing(null);
2619
+ setOpenUser(void 0);
2244
2620
  go(i.page);
2245
2621
  },
2246
2622
  children: [
2247
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2623
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2248
2624
  i.label
2249
2625
  ]
2250
2626
  },
@@ -2253,15 +2629,15 @@ function AdminShell({
2253
2629
  })
2254
2630
  ] }, g.group);
2255
2631
  }) }),
2256
- footer ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.navFooter, children: footer }) : null
2632
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navFooter, children: footer }) : null
2257
2633
  ]
2258
2634
  }
2259
2635
  );
2260
2636
  const title = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label ?? "";
2261
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2262
- narrow ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2263
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.topbar, children: [
2264
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2637
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2638
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.topbar, children: [
2640
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2265
2641
  "button",
2266
2642
  {
2267
2643
  type: "button",
@@ -2269,18 +2645,18 @@ function AdminShell({
2269
2645
  "aria-label": "Open menu",
2270
2646
  "aria-expanded": drawer,
2271
2647
  onClick: () => setDrawer(true),
2272
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "menu" })
2648
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })
2273
2649
  }
2274
2650
  ),
2275
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2276
- envLabel ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2651
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2652
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2277
2653
  ] }),
2278
- drawer ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.drawer, children: [
2654
+ drawer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.drawer, children: [
2279
2655
  nav,
2280
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2656
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2281
2657
  ] }) : null
2282
2658
  ] }) : nav,
2283
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2659
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2284
2660
  ] });
2285
2661
  }
2286
2662
  // Annotate the CommonJS export names for ESM import in node:
@@ -2294,7 +2670,9 @@ function AdminShell({
2294
2670
  EnvironmentScreen,
2295
2671
  MigrationsScreen,
2296
2672
  ModulesScreen,
2673
+ OperatorsScreen,
2297
2674
  RoutesScreen,
2675
+ SubscriberBilling,
2298
2676
  SubscriberScreen,
2299
2677
  TokensScreen,
2300
2678
  UsersScreen