@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 +904 -526
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -6
- package/dist/index.d.ts +30 -6
- package/dist/index.js +892 -516
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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/
|
|
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
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
|
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: "
|
|
1131
|
-
lead: "
|
|
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 ===
|
|
1135
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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,
|
|
1151
|
-
report?.issued === null ? /* @__PURE__ */ (0,
|
|
1152
|
-
minted ? /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1255
|
-
/* @__PURE__ */ (0,
|
|
1256
|
-
/* @__PURE__ */ (0,
|
|
1257
|
-
/* @__PURE__ */ (0,
|
|
1258
|
-
/* @__PURE__ */ (0,
|
|
1259
|
-
/* @__PURE__ */ (0,
|
|
1260
|
-
/* @__PURE__ */ (0,
|
|
1261
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1264
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1500
|
+
t2.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
1267
1501
|
" ",
|
|
1268
|
-
/* @__PURE__ */ (0,
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "revoked" })
|
|
1269
1503
|
] }) : null
|
|
1270
1504
|
] }),
|
|
1271
|
-
/* @__PURE__ */ (0,
|
|
1272
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1278
|
-
/* @__PURE__ */ (0,
|
|
1279
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
1298
|
-
report && report.legacy.length === 0 ? /* @__PURE__ */ (0,
|
|
1299
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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
|
|
1309
|
-
var
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
const
|
|
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,
|
|
1321
|
-
const history = (0,
|
|
1322
|
-
const yesNo = (v) => v ? /* @__PURE__ */ (0,
|
|
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,
|
|
1328
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
1357
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1361
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
1377
|
-
!selected ? /* @__PURE__ */ (0,
|
|
1378
|
-
list.error ? /* @__PURE__ */ (0,
|
|
1379
|
-
list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0,
|
|
1380
|
-
/* @__PURE__ */ (0,
|
|
1381
|
-
/* @__PURE__ */ (0,
|
|
1382
|
-
/* @__PURE__ */ (0,
|
|
1383
|
-
/* @__PURE__ */ (0,
|
|
1384
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1387
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1397
|
-
/* @__PURE__ */ (0,
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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,
|
|
1406
|
-
list.hasMore && !list.isLoading ? /* @__PURE__ */ (0,
|
|
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,
|
|
1417
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1421
|
-
user.deletedAt ? /* @__PURE__ */ (0,
|
|
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,
|
|
1424
|
-
/* @__PURE__ */ (0,
|
|
1425
|
-
/* @__PURE__ */ (0,
|
|
1426
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1429
|
-
/* @__PURE__ */ (0,
|
|
1430
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1437
|
-
/* @__PURE__ */ (0,
|
|
1438
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1441
|
-
/* @__PURE__ */ (0,
|
|
1442
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1448
|
-
/* @__PURE__ */ (0,
|
|
1449
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1452
|
-
/* @__PURE__ */ (0,
|
|
1453
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1457
|
-
user.suspended ? /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
1488
|
-
|
|
1489
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
1958
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
|
|
1496
1959
|
" ",
|
|
1497
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1503
|
-
history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0,
|
|
1504
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
|
|
1507
1970
|
" ",
|
|
1508
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1511
|
-
e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
1533
|
-
var
|
|
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,
|
|
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,
|
|
1538
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
2006
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
|
|
1544
2007
|
}
|
|
1545
2008
|
),
|
|
1546
|
-
error ? /* @__PURE__ */ (0,
|
|
1547
|
-
isLoading && !catalog ? /* @__PURE__ */ (0,
|
|
1548
|
-
catalog ? /* @__PURE__ */ (0,
|
|
1549
|
-
/* @__PURE__ */ (0,
|
|
1550
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1565
|
-
catalog.stored.length === 0 ? /* @__PURE__ */ (0,
|
|
1566
|
-
/* @__PURE__ */ (0,
|
|
1567
|
-
/* @__PURE__ */ (0,
|
|
1568
|
-
/* @__PURE__ */ (0,
|
|
1569
|
-
/* @__PURE__ */ (0,
|
|
1570
|
-
/* @__PURE__ */ (0,
|
|
1571
|
-
/* @__PURE__ */ (0,
|
|
1572
|
-
/* @__PURE__ */ (0,
|
|
1573
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1576
|
-
/* @__PURE__ */ (0,
|
|
1577
|
-
/* @__PURE__ */ (0,
|
|
1578
|
-
p.description ? /* @__PURE__ */ (0,
|
|
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,
|
|
1581
|
-
/* @__PURE__ */ (0,
|
|
1582
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1588
|
-
/* @__PURE__ */ (0,
|
|
1589
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
1610
|
-
var
|
|
1611
|
-
var
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
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: "
|
|
1632
|
-
lead: "Who is
|
|
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,
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
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
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
/* @__PURE__ */ (0,
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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: () =>
|
|
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,
|
|
1725
|
-
/* @__PURE__ */ (0,
|
|
1726
|
-
"
|
|
1727
|
-
|
|
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,
|
|
1730
|
-
/* @__PURE__ */ (0,
|
|
1731
|
-
|
|
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,
|
|
2233
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
|
|
1740
2234
|
] }) : null
|
|
1741
2235
|
] }),
|
|
1742
|
-
/* @__PURE__ */ (0,
|
|
1743
|
-
|
|
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
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
|
1912
|
-
var
|
|
1913
|
-
var
|
|
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,
|
|
1916
|
-
const [filter, setFilter] = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
1934
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
1958
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
1975
|
-
events.length === 0 && !isLoading ? /* @__PURE__ */ (0,
|
|
1976
|
-
/* @__PURE__ */ (0,
|
|
1977
|
-
/* @__PURE__ */ (0,
|
|
1978
|
-
/* @__PURE__ */ (0,
|
|
1979
|
-
/* @__PURE__ */ (0,
|
|
1980
|
-
/* @__PURE__ */ (0,
|
|
1981
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1984
|
-
/* @__PURE__ */ (0,
|
|
1985
|
-
/* @__PURE__ */ (0,
|
|
1986
|
-
/* @__PURE__ */ (0,
|
|
1987
|
-
/* @__PURE__ */ (0,
|
|
1988
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
1992
|
-
hasMore && !isLoading ? /* @__PURE__ */ (0,
|
|
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
|
|
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,
|
|
2026
|
-
(0,
|
|
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: "
|
|
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: "
|
|
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,
|
|
2081
|
-
const [own, setOwn] = (0,
|
|
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,
|
|
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,
|
|
2452
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
|
|
2100
2453
|
break;
|
|
2101
2454
|
case "modules":
|
|
2102
|
-
body = /* @__PURE__ */ (0,
|
|
2455
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
|
|
2103
2456
|
break;
|
|
2104
2457
|
case "doctor":
|
|
2105
|
-
body = /* @__PURE__ */ (0,
|
|
2458
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
|
|
2106
2459
|
break;
|
|
2107
2460
|
case "environment":
|
|
2108
|
-
body = /* @__PURE__ */ (0,
|
|
2461
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
|
|
2109
2462
|
break;
|
|
2110
2463
|
case "routes":
|
|
2111
|
-
body = /* @__PURE__ */ (0,
|
|
2464
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
|
|
2112
2465
|
break;
|
|
2113
2466
|
case "tokens":
|
|
2114
|
-
body = /* @__PURE__ */ (0,
|
|
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,
|
|
2473
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
|
|
2118
2474
|
break;
|
|
2119
2475
|
case "log":
|
|
2120
|
-
body = /* @__PURE__ */ (0,
|
|
2476
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
|
|
2121
2477
|
break;
|
|
2122
2478
|
case "catalog":
|
|
2123
|
-
body = billingClient ? /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2136
|
-
/* @__PURE__ */ (0,
|
|
2137
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
2164
|
-
/* @__PURE__ */ (0,
|
|
2165
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
2187
|
-
/* @__PURE__ */ (0,
|
|
2188
|
-
/* @__PURE__ */ (0,
|
|
2189
|
-
/* @__PURE__ */ (0,
|
|
2190
|
-
envLabel ? /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
2589
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
|
|
2215
2590
|
}
|
|
2216
2591
|
) : null
|
|
2217
2592
|
] });
|
|
2218
|
-
const nav = /* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
2232
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2262
|
-
narrow ? /* @__PURE__ */ (0,
|
|
2263
|
-
/* @__PURE__ */ (0,
|
|
2264
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
2648
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })
|
|
2273
2649
|
}
|
|
2274
2650
|
),
|
|
2275
|
-
/* @__PURE__ */ (0,
|
|
2276
|
-
envLabel ? /* @__PURE__ */ (0,
|
|
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,
|
|
2654
|
+
drawer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.drawer, children: [
|
|
2279
2655
|
nav,
|
|
2280
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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
|