@dreamtree-org/landing-kit 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/platform.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Card: () => Card,
34
34
  CopyButton: () => CopyButton,
35
+ DomainsTab: () => DomainsTab,
35
36
  Facts: () => Facts,
36
37
  HealthDot: () => HealthDot,
37
38
  InspectResult: () => InspectResult,
@@ -40,19 +41,26 @@ __export(index_exports, {
40
41
  PLATFORM_API_BASE: () => PLATFORM_API_BASE,
41
42
  PLATFORM_FEATURES: () => PLATFORM_FEATURES,
42
43
  PLATFORM_LOGIN_PATH: () => PLATFORM_LOGIN_PATH,
44
+ PlansView: () => PlansView,
43
45
  PlatformApiError: () => PlatformApiError,
44
46
  PlatformConsole: () => PlatformConsole,
45
47
  PlatformLogin: () => PlatformLogin,
46
48
  PlatformShell: () => PlatformShell,
47
49
  SHIPPED_FEATURES: () => SHIPPED_FEATURES,
48
50
  STEP_UP_REQUIRED: () => STEP_UP_REQUIRED,
51
+ SettingsTab: () => SettingsTab,
49
52
  StatusPill: () => StatusPill,
50
53
  StepUpProvider: () => StepUpProvider,
54
+ SubscriptionTab: () => SubscriptionTab,
55
+ SupportAccessView: () => SupportAccessView,
56
+ SupportGrants: () => SupportGrants,
57
+ SupportTab: () => SupportTab,
51
58
  createPlatformClient: () => createPlatformClient,
52
59
  createStepUp: () => createStepUp,
53
60
  describeAction: () => describeAction,
54
61
  enabledFeatures: () => enabledFeatures,
55
62
  formatDate: () => formatDate,
63
+ formatPrice: () => formatPrice,
56
64
  isStepUpRequired: () => isStepUpRequired,
57
65
  matchConsoleRoute: () => matchConsoleRoute,
58
66
  readPlatformCsrf: () => readPlatformCsrf,
@@ -67,7 +75,7 @@ module.exports = __toCommonJS(index_exports);
67
75
  var PLATFORM_API_BASE = "/api/platform";
68
76
  var PLATFORM_LOGIN_PATH = "/platform/login";
69
77
  var STEP_UP_REQUIRED = "STEP_UP_REQUIRED";
70
- var PLATFORM_FEATURES = ["overview", "companies", "jobs", "plans", "audit", "support", "settings", "admins"];
78
+ var PLATFORM_FEATURES = ["overview", "companies", "jobs", "plans", "audit", "support", "settings", "domains", "admins"];
71
79
  var SHIPPED_FEATURES = ["overview", "companies", "jobs", "admins"];
72
80
  var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
73
81
  var CSRF_HEADER = "x-csrf-token";
@@ -99,7 +107,7 @@ function readPlatformCsrf(cookieString, name) {
99
107
  return null;
100
108
  }
101
109
  function joinUrl(basePath, path, query) {
102
- const base2 = basePath.replace(/\/+$/, "");
110
+ const base3 = basePath.replace(/\/+$/, "");
103
111
  const rel = path ? `/${String(path).replace(/^\/+/, "")}` : "";
104
112
  const params = new URLSearchParams();
105
113
  for (const [k, v] of Object.entries(query != null ? query : {})) {
@@ -107,7 +115,7 @@ function joinUrl(basePath, path, query) {
107
115
  params.append(k, String(v));
108
116
  }
109
117
  const qs = params.toString();
110
- return `${base2}${rel}${qs ? `?${qs}` : ""}`;
118
+ return `${base3}${rel}${qs ? `?${qs}` : ""}`;
111
119
  }
112
120
  function createPlatformClient(opts = {}) {
113
121
  var _a, _b, _c, _d, _e;
@@ -210,8 +218,8 @@ function timeAgo(value, now = Date.now()) {
210
218
  }
211
219
 
212
220
  // src/platform/PlatformConsole.jsx
213
- var import_react13 = __toESM(require("react"), 1);
214
- var import_twreact_ui12 = require("@dreamtree-org/twreact-ui");
221
+ var import_react16 = __toESM(require("react"), 1);
222
+ var import_twreact_ui15 = require("@dreamtree-org/twreact-ui");
215
223
 
216
224
  // src/platform/context.jsx
217
225
  var import_react = __toESM(require("react"), 1);
@@ -463,6 +471,23 @@ var IconArchive = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_
463
471
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8M10 12h4" })
464
472
  ] }));
465
473
  var IconDownload = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" }));
474
+ var IconTag = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4Z" }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "7.5", cy: "7.5", r: "1.5" })
477
+ ] }));
478
+ var IconLifebuoy = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
479
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "4" }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m4.93 4.93 4.24 4.24M14.83 14.83l4.24 4.24M14.83 9.17l4.24-4.24M4.93 19.07l4.24-4.24" })
482
+ ] }));
483
+ var IconGlobe = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
484
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })
486
+ ] }));
487
+ var IconSettings = make(/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
488
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "3" })
490
+ ] }));
466
491
 
467
492
  // src/platform/PlatformShell.jsx
468
493
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -536,7 +561,13 @@ var STATUS_MAP = {
536
561
  running: { label: "Running", tone: "info" },
537
562
  pending: { label: "Pending", tone: "neutral" },
538
563
  queued: { label: "Queued", tone: "neutral" },
539
- cancelled: { label: "Cancelled", tone: "muted" }
564
+ cancelled: { label: "Cancelled", tone: "muted" },
565
+ trial: { label: "Trial", tone: "info" },
566
+ past_due: { label: "Past due", tone: "warning" },
567
+ expired: { label: "Expired", tone: "muted" },
568
+ requested: { label: "Awaiting approval", tone: "warning" },
569
+ revoked: { label: "Revoked", tone: "muted" },
570
+ verified: { label: "Verified", tone: "success" }
540
571
  };
541
572
  function StatusPill({ status }) {
542
573
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_twreact_ui3.StatusPill, { status: String(status != null ? status : ""), map: STATUS_MAP });
@@ -693,8 +724,31 @@ var ACTION_LABEL = {
693
724
  "platform.admin.roles": "changed platform admin roles",
694
725
  "platform.admin.disable": "disabled platform admin",
695
726
  "platform.admin.enable": "enabled platform admin",
696
- "audit.export": "exported the audit log"
727
+ "audit.export": "exported the audit log",
728
+ "plan.create": "created plan",
729
+ "plan.update": "updated plan",
730
+ "subscription.update": "changed subscription",
731
+ "settings.update": "changed settings",
732
+ "support.grant.request": "requested support access",
733
+ "support.grant.approve": "approved support access",
734
+ "support.grant.revoke": "revoked support access",
735
+ "domain.add": "added domain",
736
+ "domain.verify": "verified domain",
737
+ "domain.primary": "set primary domain",
738
+ "domain.remove": "removed domain"
697
739
  };
740
+ function formatPrice(priceMinor, currency = "INR", interval) {
741
+ var _a;
742
+ const amount = Number(priceMinor != null ? priceMinor : 0) / 100;
743
+ let text;
744
+ try {
745
+ text = new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
746
+ } catch {
747
+ text = `${currency} ${amount.toFixed(2)}`;
748
+ }
749
+ const per = (_a = { month: " / month", year: " / year", "one-time": " once" }[interval]) != null ? _a : "";
750
+ return `${text}${per}`;
751
+ }
698
752
  function describeAction(action) {
699
753
  var _a;
700
754
  return (_a = ACTION_LABEL[action]) != null ? _a : String(action != null ? action : "").replace(/[._]/g, " ");
@@ -1171,19 +1225,503 @@ function NewCompanyView() {
1171
1225
  }
1172
1226
 
1173
1227
  // src/platform/views/CompanyDetail.jsx
1228
+ var import_react11 = __toESM(require("react"), 1);
1229
+ var import_twreact_ui10 = require("@dreamtree-org/twreact-ui");
1230
+
1231
+ // src/platform/views/CompanyFeatures.jsx
1232
+ var import_react10 = __toESM(require("react"), 1);
1233
+ var import_twreact_ui9 = require("@dreamtree-org/twreact-ui");
1234
+
1235
+ // src/platform/views/SupportAccess.jsx
1174
1236
  var import_react9 = __toESM(require("react"), 1);
1175
1237
  var import_twreact_ui8 = require("@dreamtree-org/twreact-ui");
1176
1238
  var import_jsx_runtime10 = require("react/jsx-runtime");
1239
+ var FILTERS2 = [
1240
+ { value: "", label: "All" },
1241
+ { value: "requested", label: "Awaiting approval" },
1242
+ { value: "active", label: "Active" },
1243
+ { value: "expired", label: "Expired" },
1244
+ { value: "revoked", label: "Revoked" }
1245
+ ];
1246
+ var DURATIONS = [
1247
+ { value: "30", label: "30 minutes" },
1248
+ { value: "60", label: "1 hour" },
1249
+ { value: "120", label: "2 hours" },
1250
+ { value: "240", label: "4 hours" },
1251
+ { value: "480", label: "8 hours" }
1252
+ ];
1253
+ function until(value) {
1254
+ const ms = new Date(value).getTime() - Date.now();
1255
+ if (!Number.isFinite(ms) || ms <= 0) return "now";
1256
+ const m = Math.round(ms / 6e4);
1257
+ return m < 60 ? `in ${m} min` : `in ${Math.floor(m / 60)} h ${m % 60} min`;
1258
+ }
1259
+ function SupportGrants({ companyId, companyName }) {
1260
+ var _a, _b;
1261
+ const { client, can, me } = usePlatform();
1262
+ const sensitive = useSensitive();
1263
+ const [status, setStatus] = (0, import_react9.useState)(companyId ? "" : "requested");
1264
+ const { data, error, loading, reload } = useLoad("support-access", { company: companyId || void 0, status: status || void 0 });
1265
+ const [flash, setFlash] = (0, import_react9.useState)(null);
1266
+ const [requesting, setRequesting] = (0, import_react9.useState)(false);
1267
+ const canGrant = can("support.grant");
1268
+ const myId = (_a = me == null ? void 0 : me.admin) == null ? void 0 : _a.id;
1269
+ const grants = (_b = data == null ? void 0 : data.grants) != null ? _b : [];
1270
+ const act = async (g, action) => {
1271
+ var _a2;
1272
+ setFlash(null);
1273
+ try {
1274
+ await sensitive(() => client.post(`support-access/${encodeURIComponent(g.id)}`, { action }));
1275
+ setFlash({ kind: "success", msg: action === "approve" ? `Approved access to ${(_a2 = g.companyName) != null ? _a2 : "the company"}` : "Access revoked" });
1276
+ await reload();
1277
+ } catch (e) {
1278
+ const msg = errorText(e);
1279
+ if (msg) setFlash({ kind: "error", msg });
1280
+ }
1281
+ };
1282
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1283
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
1284
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.SegmentedControl, { "aria-label": "Filter by status", options: FILTERS2, value: status, onChange: (v) => setStatus(String(v)) }) }),
1285
+ can("support.request") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: companyId ? "outline" : "primary", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconPlus, {}), onClick: () => setRequesting(true), children: "Request access" })
1286
+ ] }),
1287
+ flash && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
1288
+ error && !data ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.ErrorState, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.LoadingState, { variant: "table", rows: 3 }) : grants.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1289
+ import_twreact_ui8.EmptyState,
1290
+ {
1291
+ compact: true,
1292
+ icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconLifebuoy, { size: 24 }),
1293
+ title: status ? "No grants with this status" : "No support access yet",
1294
+ description: "Support access lets a platform admin work in one company for a limited time, after another admin approves it."
1295
+ }
1296
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: grants.map((g) => {
1297
+ var _a2, _b2;
1298
+ const mine = myId && g.requestedById === myId;
1299
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "flex flex-wrap items-start gap-3 px-4 py-3", children: [
1300
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "min-w-0 flex-1", children: [
1301
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "text-sm font-medium text-foreground", children: [
1302
+ g.requestedBy,
1303
+ !companyId && g.companyName ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-muted", children: [
1304
+ " \u2192 ",
1305
+ g.companyName
1306
+ ] }) : null
1307
+ ] }),
1308
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-0.5 break-words text-sm text-foreground", children: g.reason }),
1309
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mt-0.5 text-xs text-muted", children: [
1310
+ g.status === "requested" && `${(_a2 = g.minutes) != null ? _a2 : "\u2014"} min once approved \xB7 requested ${timeAgo(g.createdAt)}`,
1311
+ g.status === "active" && `Approved by ${(_b2 = g.approvedBy) != null ? _b2 : "\u2014"} \xB7 ends ${until(g.expiresAt)}`,
1312
+ g.status === "expired" && `Ended ${formatDate(g.expiresAt)}`,
1313
+ g.status === "revoked" && `Revoked ${formatDate(g.revokedAt)}`
1314
+ ] })
1315
+ ] }),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: g.status }),
1317
+ canGrant && (g.status === "requested" || g.status === "active") && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1", children: [
1318
+ g.status === "requested" && !mine && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", onClick: () => act(g, "approve"), children: "Approve" }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", variant: "ghost", onClick: () => act(g, "revoke"), children: g.status === "requested" ? "Decline" : "Revoke" })
1320
+ ] })
1321
+ ] }, g.id);
1322
+ }) }),
1323
+ requesting && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1324
+ RequestDialog,
1325
+ {
1326
+ companyId,
1327
+ companyName,
1328
+ onClose: () => setRequesting(false),
1329
+ onDone: async (g) => {
1330
+ var _a2;
1331
+ setRequesting(false);
1332
+ setFlash({ kind: "success", msg: `Requested access to ${(_a2 = g.companyName) != null ? _a2 : "the company"}. Another admin has to approve it.` });
1333
+ await reload();
1334
+ }
1335
+ }
1336
+ )
1337
+ ] });
1338
+ }
1339
+ function RequestDialog({ companyId, companyName, onClose, onDone }) {
1340
+ var _a, _b;
1341
+ const { client } = usePlatform();
1342
+ const companies = useLoad(companyId ? null : "companies");
1343
+ const [company, setCompany] = (0, import_react9.useState)(companyId != null ? companyId : "");
1344
+ const [reason, setReason] = (0, import_react9.useState)("");
1345
+ const [minutes, setMinutes] = (0, import_react9.useState)("60");
1346
+ const [busy, setBusy] = (0, import_react9.useState)(false);
1347
+ const [err, setErr] = (0, import_react9.useState)(null);
1348
+ const options = ((_b = (_a = companies.data) == null ? void 0 : _a.companies) != null ? _b : []).map((c) => {
1349
+ var _a2;
1350
+ return { value: c.id, label: (_a2 = c.displayName) != null ? _a2 : c.slug };
1351
+ });
1352
+ const submit = async () => {
1353
+ setBusy(true);
1354
+ setErr(null);
1355
+ try {
1356
+ const r = await client.post("support-access", { companyId: company, reason: reason.trim(), minutes: Number(minutes) });
1357
+ await onDone(r.grant);
1358
+ } catch (e) {
1359
+ setErr(errorText(e));
1360
+ } finally {
1361
+ setBusy(false);
1362
+ }
1363
+ };
1364
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1365
+ import_twreact_ui8.Dialog,
1366
+ {
1367
+ isOpen: true,
1368
+ onClose,
1369
+ title: "Request support access",
1370
+ size: "sm",
1371
+ footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1372
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: onClose, children: "Cancel" }),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { loading: busy, disabled: !company || reason.trim().length < 8, onClick: submit, children: "Request" })
1374
+ ] }),
1375
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1376
+ err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Alert, { variant: "error", children: err }),
1377
+ companyId ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Company", value: companyName != null ? companyName : companyId, disabled: true }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Select, { label: "Company", searchable: true, options, loading: companies.loading, value: company, onChange: (v) => setCompany(String(v)), placeholder: "Pick a company" }),
1378
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Textarea, { label: "Reason", rows: 3, value: reason, onChange: (e) => setReason(e.target.value), helperText: "At least 8 characters, e.g. the ticket number. The company's audit trail shows it." }),
1379
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Select, { label: "For", options: DURATIONS, value: minutes, onChange: (v) => setMinutes(String(v)), helperText: "Counted from approval" })
1380
+ ] })
1381
+ }
1382
+ );
1383
+ }
1384
+ function SupportAccessView() {
1385
+ const { Link } = usePlatform();
1386
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
1387
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.PageHeader, { title: "Support access", subtitle: "Time-boxed access to one company, approved by a second platform admin", linkAs: Link }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SupportGrants, {})
1389
+ ] });
1390
+ }
1391
+
1392
+ // src/platform/views/CompanyFeatures.jsx
1393
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1394
+ var SUB_STATUSES = [
1395
+ { value: "active", label: "Active" },
1396
+ { value: "trial", label: "Trial" },
1397
+ { value: "past_due", label: "Past due" },
1398
+ { value: "cancelled", label: "Cancelled" },
1399
+ { value: "expired", label: "Expired" }
1400
+ ];
1401
+ var day = (v) => v ? String(v).slice(0, 10) : "";
1402
+ function featureTabs({ features, can }) {
1403
+ return [
1404
+ features.includes("plans") && can("plans.read") && { id: "subscription", label: "Subscription", Component: SubscriptionTab },
1405
+ features.includes("domains") && can("domains.read") && { id: "domains", label: "Domains", Component: DomainsTab },
1406
+ features.includes("settings") && can("settings.read") && { id: "settings", label: "Settings", Component: SettingsTab },
1407
+ features.includes("support") && can("support.request") && { id: "support", label: "Support", Component: SupportTab }
1408
+ ].filter(Boolean);
1409
+ }
1410
+ function SubscriptionTab({ company, setFlash }) {
1411
+ var _a, _b, _c, _d, _e, _f;
1412
+ const { client, can } = usePlatform();
1413
+ const sensitive = useSensitive();
1414
+ const id = encodeURIComponent(company.id);
1415
+ const { data, error, loading, reload } = useLoad(`companies/${id}/subscription`);
1416
+ const plans = useLoad(can("plans.write") ? "plans" : null);
1417
+ const [editing, setEditing] = (0, import_react10.useState)(null);
1418
+ const [busy, setBusy] = (0, import_react10.useState)(false);
1419
+ const current = data == null ? void 0 : data.subscription;
1420
+ const history = (_a = data == null ? void 0 : data.history) != null ? _a : [];
1421
+ const planOptions = ((_c = (_b = plans.data) == null ? void 0 : _b.plans) != null ? _c : []).filter((p) => p.status === "active" || p.id === (current == null ? void 0 : current.planId)).map((p) => ({ value: p.id, label: `${p.name} \xB7 ${formatPrice(p.priceMinor, p.currency, p.interval)}` }));
1422
+ const save = async () => {
1423
+ setBusy(true);
1424
+ try {
1425
+ await sensitive(
1426
+ () => client.put(`companies/${id}/subscription`, {
1427
+ planId: editing.planId,
1428
+ status: editing.status,
1429
+ startsAt: editing.startsAt || void 0,
1430
+ endsAt: editing.endsAt || null,
1431
+ seats: editing.seats === "" ? null : Number(editing.seats),
1432
+ notes: editing.notes || null
1433
+ })
1434
+ );
1435
+ setEditing(null);
1436
+ setFlash({ kind: "success", msg: "Subscription updated" });
1437
+ await reload();
1438
+ } catch (e) {
1439
+ const msg = errorText(e);
1440
+ if (msg) setFlash({ kind: "error", msg });
1441
+ } finally {
1442
+ setBusy(false);
1443
+ }
1444
+ };
1445
+ if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.ErrorState, { description: error.message, onRetry: reload });
1446
+ if (loading && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.LoadingState, { variant: "detail" });
1447
+ const open = () => {
1448
+ var _a2, _b2;
1449
+ return setEditing({
1450
+ planId: (_a2 = current == null ? void 0 : current.planId) != null ? _a2 : "",
1451
+ status: (_b2 = current == null ? void 0 : current.status) != null ? _b2 : "active",
1452
+ startsAt: "",
1453
+ endsAt: day(current == null ? void 0 : current.endsAt),
1454
+ seats: (current == null ? void 0 : current.seats) == null ? "" : String(current.seats),
1455
+ notes: ""
1456
+ });
1457
+ };
1458
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1460
+ Card,
1461
+ {
1462
+ title: "Current plan",
1463
+ actions: can("plans.write") && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "outline", onClick: open, children: current ? "Change" : "Assign a plan" }),
1464
+ children: current ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1465
+ Facts,
1466
+ {
1467
+ rows: [
1468
+ ["Plan", (_e = (_d = current.planName) != null ? _d : current.planCode) != null ? _e : current.planId],
1469
+ ["Status", /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { status: current.status }, "s")],
1470
+ ["Started", formatDate(current.startsAt)],
1471
+ ["Ends", current.endsAt ? formatDate(current.endsAt) : "No end date"],
1472
+ ["Seats", (_f = current.seats) != null ? _f : "Plan default"],
1473
+ ...current.notes ? [["Notes", current.notes]] : []
1474
+ ]
1475
+ }
1476
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.EmptyState, { compact: true, icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconTag, { size: 24 }), title: "No plan yet", description: "This company is not on a plan." })
1477
+ }
1478
+ ),
1479
+ history.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Card, { title: "History", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "divide-y divide-line", children: history.map((s) => {
1480
+ var _a2, _b2;
1481
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "flex flex-wrap items-center gap-3 py-2 text-sm", children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "min-w-0 flex-1 text-foreground", children: (_b2 = (_a2 = s.planName) != null ? _a2 : s.planCode) != null ? _b2 : s.planId }),
1483
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { status: s.status }),
1484
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "text-xs text-muted", children: [
1485
+ "from ",
1486
+ formatDate(s.startsAt)
1487
+ ] })
1488
+ ] }, s.id);
1489
+ }) }) }),
1490
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1491
+ import_twreact_ui9.Dialog,
1492
+ {
1493
+ isOpen: editing !== null,
1494
+ onClose: () => setEditing(null),
1495
+ title: current ? "Change subscription" : "Assign a plan",
1496
+ size: "sm",
1497
+ footer: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-end gap-2", children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
1499
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { loading: busy, disabled: !(editing == null ? void 0 : editing.planId), onClick: save, children: "Save" })
1500
+ ] }),
1501
+ children: editing && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1502
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Select, { label: "Plan", options: planOptions, loading: plans.loading, value: editing.planId, onChange: (v) => setEditing((s) => ({ ...s, planId: String(v) })), placeholder: "Pick a plan" }),
1503
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Select, { label: "Status", options: SUB_STATUSES, value: editing.status, onChange: (v) => setEditing((s) => ({ ...s, status: String(v) })) }),
1504
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1505
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Input, { label: "Starts", type: "date", value: editing.startsAt, onChange: (e) => setEditing((s) => ({ ...s, startsAt: e.target.value })), helperText: "Empty: today" }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Input, { label: "Ends", type: "date", value: editing.endsAt, onChange: (e) => setEditing((s) => ({ ...s, endsAt: e.target.value })), helperText: "Empty: no end date" })
1507
+ ] }),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Input, { label: "Seats", type: "number", min: "1", value: editing.seats, onChange: (e) => setEditing((s) => ({ ...s, seats: e.target.value })), helperText: "Empty: the plan's default" }),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Textarea, { label: "Notes", rows: 2, value: editing.notes, onChange: (e) => setEditing((s) => ({ ...s, notes: e.target.value })) })
1510
+ ] })
1511
+ }
1512
+ )
1513
+ ] });
1514
+ }
1515
+ function DomainsTab({ company, setFlash }) {
1516
+ var _a;
1517
+ const { client, can } = usePlatform();
1518
+ const sensitive = useSensitive();
1519
+ const id = encodeURIComponent(company.id);
1520
+ const { data, error, loading, reload } = useLoad(`companies/${id}/domains`);
1521
+ const [host, setHost] = (0, import_react10.useState)("");
1522
+ const [adding, setAdding] = (0, import_react10.useState)(false);
1523
+ const [busy, setBusy] = (0, import_react10.useState)(null);
1524
+ const [removing, setRemoving] = (0, import_react10.useState)(null);
1525
+ const writable = can("domains.write");
1526
+ const domains = (_a = data == null ? void 0 : data.domains) != null ? _a : [];
1527
+ const act = async (d, action) => {
1528
+ setBusy(`${d.id}:${action}`);
1529
+ setFlash(null);
1530
+ try {
1531
+ const call = () => client.post(`companies/${id}/domains/${encodeURIComponent(d.id)}`, { action });
1532
+ const r = await (action === "remove" ? sensitive(call) : call());
1533
+ if (action === "verify") {
1534
+ setFlash(r.verified ? { kind: "success", msg: `${d.host} is verified` } : { kind: "warning", msg: `The TXT record for ${d.host} was not found yet. DNS changes can take a while to appear.` });
1535
+ } else setFlash({ kind: "success", msg: action === "remove" ? `${d.host} removed` : `${d.host} is now the primary domain` });
1536
+ await reload();
1537
+ } catch (e) {
1538
+ const msg = errorText(e);
1539
+ if (msg) setFlash({ kind: "error", msg });
1540
+ } finally {
1541
+ setBusy(null);
1542
+ }
1543
+ };
1544
+ const add = async () => {
1545
+ setBusy("add");
1546
+ setFlash(null);
1547
+ try {
1548
+ await client.post(`companies/${id}/domains`, { host: host.trim() });
1549
+ setHost("");
1550
+ setAdding(false);
1551
+ setFlash({ kind: "success", msg: "Domain added. Publish the TXT record below, then verify." });
1552
+ await reload();
1553
+ } catch (e) {
1554
+ const msg = errorText(e);
1555
+ if (msg) setFlash({ kind: "error", msg });
1556
+ } finally {
1557
+ setBusy(null);
1558
+ }
1559
+ };
1560
+ if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.ErrorState, { description: error.message, onRetry: reload });
1561
+ if (loading && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.LoadingState, { variant: "table", rows: 2 });
1562
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1563
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm text-muted", children: "Custom host names for this company. A domain routes only after its TXT record is verified." }),
1565
+ writable && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconPlus, {}), onClick: () => setAdding(true), children: "Add domain" })
1566
+ ] }),
1567
+ domains.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.EmptyState, { compact: true, icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconGlobe, { size: 24 }), title: "No custom domains", description: "The company signs in on the platform's own address." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "space-y-3", children: domains.map((d) => {
1568
+ var _a2;
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "rounded-xl border border-line bg-surface p-4", children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
1571
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "w-full break-all font-mono text-sm text-foreground sm:w-auto sm:min-w-0 sm:flex-1", children: d.host }),
1572
+ d.primary && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Badge, { variant: "primary", size: "sm", children: "Primary" }),
1573
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { status: (_a2 = d.status) != null ? _a2 : d.verifiedAt ? "verified" : "pending" }),
1574
+ writable && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-1", children: [
1575
+ !d.verifiedAt && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", loading: busy === `${d.id}:verify`, onClick: () => act(d, "verify"), children: "Verify" }),
1576
+ d.verifiedAt && !d.primary && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", loading: busy === `${d.id}:make-primary`, onClick: () => act(d, "make-primary"), children: "Make primary" }),
1577
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", onClick: () => setRemoving(d), children: "Remove" })
1578
+ ] })
1579
+ ] }),
1580
+ d.verifiedAt && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "mt-1 text-xs text-muted", children: [
1581
+ "Verified ",
1582
+ formatDate(d.verifiedAt)
1583
+ ] }),
1584
+ !d.verifiedAt && d.verification && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-3 space-y-2 rounded-lg bg-background p-3", children: [
1585
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted", children: "Add this TXT record at the domain's DNS provider:" }),
1586
+ [
1587
+ ["Name", d.verification.name],
1588
+ ["Value", d.verification.value]
1589
+ ].map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1590
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-xs text-muted", children: k }),
1591
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "flex min-w-0 items-center gap-2", children: [
1592
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "break-all font-mono text-xs text-foreground", children: v }),
1593
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CopyButton, { value: v })
1594
+ ] })
1595
+ ] }, k))
1596
+ ] })
1597
+ ] }, d.id);
1598
+ }) }),
1599
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1600
+ import_twreact_ui9.Dialog,
1601
+ {
1602
+ isOpen: adding,
1603
+ onClose: () => setAdding(false),
1604
+ title: "Add domain",
1605
+ size: "sm",
1606
+ footer: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-end gap-2", children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
1608
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { loading: busy === "add", disabled: !host.trim().includes("."), onClick: add, children: "Add domain" })
1609
+ ] }),
1610
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Input, { label: "Host name", autoFocus: true, placeholder: "app.example.com", value: host, onChange: (e) => setHost(e.target.value), helperText: "The company points this host at the platform and proves it with a TXT record." })
1611
+ }
1612
+ ),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1614
+ import_twreact_ui9.ConfirmDialog,
1615
+ {
1616
+ isOpen: removing !== null,
1617
+ title: `Remove ${removing == null ? void 0 : removing.host}?`,
1618
+ description: "The company can no longer sign in on this host.",
1619
+ confirmLabel: "Remove",
1620
+ destructive: true,
1621
+ onCancel: () => setRemoving(null),
1622
+ onConfirm: () => {
1623
+ const d = removing;
1624
+ setRemoving(null);
1625
+ void act(d, "remove");
1626
+ }
1627
+ }
1628
+ )
1629
+ ] });
1630
+ }
1631
+ function SettingsTab({ company, setFlash }) {
1632
+ var _a;
1633
+ const { can } = usePlatform();
1634
+ const { data, error, loading, reload } = useLoad("settings", { company: company.id });
1635
+ if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.ErrorState, { description: error.message, onRetry: reload });
1636
+ if (loading && !data) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.LoadingState, { variant: "detail" });
1637
+ const families = (_a = data == null ? void 0 : data.families) != null ? _a : [];
1638
+ if (!families.length) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.EmptyState, { compact: true, icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconSettings, { size: 24 }), title: "No settings", description: "This product defines no company settings." });
1639
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-4", children: families.map((f) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SettingsFamily, { family: f, companyId: company.id, version: data.version, writable: can("settings.write"), setFlash, onSaved: reload }, f.family)) });
1640
+ }
1641
+ var initialValues = (family) => Object.fromEntries(family.fields.map((f) => {
1642
+ var _a;
1643
+ return [f.key, f.type === "secret" ? "" : (_a = f.value) != null ? _a : f.type === "boolean" ? false : ""];
1644
+ }));
1645
+ function SettingsFamily({ family, companyId, version, writable, setFlash, onSaved }) {
1646
+ const { client } = usePlatform();
1647
+ const sensitive = useSensitive();
1648
+ const [values, setValues] = (0, import_react10.useState)(() => initialValues(family));
1649
+ const [busy, setBusy] = (0, import_react10.useState)(false);
1650
+ (0, import_react10.useEffect)(() => setValues(initialValues(family)), [family]);
1651
+ const changed = family.fields.filter((f) => {
1652
+ var _a;
1653
+ return f.type === "secret" ? values[f.key] !== "" : JSON.stringify(values[f.key]) !== JSON.stringify((_a = f.value) != null ? _a : f.type === "boolean" ? false : "");
1654
+ });
1655
+ const save = async () => {
1656
+ setBusy(true);
1657
+ setFlash(null);
1658
+ try {
1659
+ const body = Object.fromEntries(changed.map((f) => [f.key, f.type === "number" ? values[f.key] === "" ? null : Number(values[f.key]) : values[f.key]]));
1660
+ await sensitive(() => client.put(`settings/${encodeURIComponent(family.family)}`, { companyId, version, values: body }));
1661
+ setFlash({ kind: "success", msg: `${family.label} saved` });
1662
+ await onSaved();
1663
+ } catch (e) {
1664
+ const msg = errorText(e);
1665
+ if (msg) setFlash({ kind: "error", msg });
1666
+ } finally {
1667
+ setBusy(false);
1668
+ }
1669
+ };
1670
+ const set = (key) => (v) => setValues((s) => ({ ...s, [key]: v }));
1671
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1672
+ Card,
1673
+ {
1674
+ title: family.label,
1675
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", loading: busy, disabled: !changed.length, onClick: save, children: "Save" }),
1676
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "grid gap-4 sm:grid-cols-2", children: family.fields.map((f) => {
1677
+ var _a, _b, _c;
1678
+ const help = [f.help, f.source === "default" ? "Using the default" : null].filter(Boolean).join(" \xB7 ") || void 0;
1679
+ if (f.type === "boolean") {
1680
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-line px-3 py-2", children: [
1681
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0", children: [
1682
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "text-sm text-foreground", children: f.label }),
1683
+ help && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "text-xs text-muted", children: help })
1684
+ ] }),
1685
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Switch, { checked: Boolean(values[f.key]), disabled: !writable, onChange: set(f.key), ariaLabel: f.label })
1686
+ ] }, f.key);
1687
+ }
1688
+ if (f.type === "select") {
1689
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Select, { label: f.label, options: (_a = f.options) != null ? _a : [], value: (_b = values[f.key]) != null ? _b : "", disabled: !writable, onChange: (v) => set(f.key)(String(v)), helperText: help }, f.key);
1690
+ }
1691
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1692
+ import_twreact_ui9.Input,
1693
+ {
1694
+ label: f.label,
1695
+ type: f.type === "secret" ? "password" : f.type === "number" ? "number" : f.type === "email" ? "email" : f.type === "url" ? "url" : "text",
1696
+ autoComplete: f.type === "secret" ? "new-password" : void 0,
1697
+ placeholder: f.type === "secret" ? f.set ? "Configured \xB7 leave blank to keep" : "Not set" : void 0,
1698
+ value: (_c = values[f.key]) != null ? _c : "",
1699
+ disabled: !writable,
1700
+ onChange: (e) => set(f.key)(e.target.value),
1701
+ helperText: help
1702
+ },
1703
+ f.key
1704
+ );
1705
+ }) })
1706
+ }
1707
+ );
1708
+ }
1709
+ function SupportTab({ company }) {
1710
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SupportGrants, { companyId: company.id, companyName: company.displayName });
1711
+ }
1712
+
1713
+ // src/platform/views/CompanyDetail.jsx
1714
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1177
1715
  var SETUP_STATES = ["draft", "provisioning", "provisioning_failed"];
1178
1716
  function CompanyDetailView({ companyId }) {
1179
1717
  var _a;
1180
- const { client, Link, href, can, companyTabs, companyLoginUrl, companyLoginPath } = usePlatform();
1718
+ const { client, Link, href, can, companyTabs, companyLoginUrl, companyLoginPath, features = [] } = usePlatform();
1181
1719
  const sensitive = useSensitive();
1182
1720
  const { data, error, reload } = useLoad(`companies/${encodeURIComponent(companyId)}`);
1183
- const [tab, setTab] = (0, import_react9.useState)(0);
1184
- const [flash, setFlash] = (0, import_react9.useState)(null);
1185
- const [secret, setSecret] = (0, import_react9.useState)(null);
1186
- const run = (0, import_react9.useCallback)(
1721
+ const [tab, setTab] = (0, import_react11.useState)(0);
1722
+ const [flash, setFlash] = (0, import_react11.useState)(null);
1723
+ const [secret, setSecret] = (0, import_react11.useState)(null);
1724
+ const run = (0, import_react11.useCallback)(
1187
1725
  async (fn, ok, { stepUp = false } = {}) => {
1188
1726
  setFlash(null);
1189
1727
  try {
@@ -1199,69 +1737,70 @@ function CompanyDetailView({ companyId }) {
1199
1737
  },
1200
1738
  [reload, sensitive]
1201
1739
  );
1202
- if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.ErrorState, { description: error.message, onRetry: reload }) });
1203
- if (!data) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.LoadingState, { variant: "detail" }) });
1740
+ if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.ErrorState, { description: error.message, onRetry: reload }) });
1741
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.LoadingState, { variant: "detail" }) });
1204
1742
  const company = data.company;
1205
1743
  const database = (_a = company.database) != null ? _a : { configured: false };
1206
1744
  const ctx = { company, users: data.users, run, setFlash, setSecret, client, can, reload };
1207
1745
  const tabs = [
1208
- { id: "overview", label: "Overview", render: () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OverviewTab, { ...ctx }) },
1209
- { id: "branches", label: "Branches", render: () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BranchesTab, { ...ctx }) },
1210
- { id: "database", label: "Database", render: () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DatabaseTab, { ...ctx }) },
1211
- { id: "admins", label: "Admins", render: () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AdminsTab, { ...ctx }) },
1746
+ { id: "overview", label: "Overview", render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(OverviewTab, { ...ctx }) },
1747
+ { id: "branches", label: "Branches", render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BranchesTab, { ...ctx }) },
1748
+ { id: "database", label: "Database", render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DatabaseTab, { ...ctx }) },
1749
+ { id: "admins", label: "Admins", render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AdminsTab, { ...ctx }) },
1750
+ ...featureTabs({ features, can }).map(({ id, label, Component }) => ({ id, label, render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Component, { ...ctx }) })),
1212
1751
  ...(companyTabs != null ? companyTabs : []).map((t) => ({ id: t.id, label: t.label, render: () => t.render({ company, reload, client, sensitive }) })),
1213
- { id: "activity", label: "Activity", render: () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ActivityTab, { ...ctx }) }
1752
+ { id: "activity", label: "Activity", render: () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ActivityTab, { ...ctx }) }
1214
1753
  ];
1215
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mx-auto max-w-6xl space-y-5 px-4 py-6 sm:px-6", children: [
1216
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1217
- import_twreact_ui8.PageHeader,
1754
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mx-auto max-w-6xl space-y-5 px-4 py-6 sm:px-6", children: [
1755
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1756
+ import_twreact_ui10.PageHeader,
1218
1757
  {
1219
1758
  title: company.displayName,
1220
- subtitle: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-wrap items-center gap-2", children: [
1221
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: companyLoginPath(company.slug) }),
1222
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: company.status }),
1223
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ModePill, { mode: database.configured ? database.mode : null })
1759
+ subtitle: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex flex-wrap items-center gap-2", children: [
1760
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-mono", children: companyLoginPath(company.slug) }),
1761
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusPill, { status: company.status }),
1762
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ModePill, { mode: database.configured ? database.mode : null })
1224
1763
  ] }),
1225
1764
  back: { href: href("companies"), label: "Companies" },
1226
1765
  linkAs: Link
1227
1766
  }
1228
1767
  ),
1229
- flash && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
1230
- secret && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OneTimeSecret, { title: secret.title, loginUrl: companyLoginUrl(company.slug), email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
1231
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_twreact_ui8.Tabs, { index: tab, onChange: setTab, variant: "line", children: [
1232
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Tabs.List, { children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Tabs.Tab, { children: t.label }, t.id)) }),
1233
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Tabs.Panels, { children: tabs.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Tabs.Panel, { children: i === tab ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "pt-4", children: t.render() }) : null }, t.id)) })
1768
+ flash && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
1769
+ secret && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(OneTimeSecret, { title: secret.title, loginUrl: companyLoginUrl(company.slug), email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_twreact_ui10.Tabs, { index: tab, onChange: setTab, variant: "line", children: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Tabs.List, { children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Tabs.Tab, { children: t.label }, t.id)) }),
1772
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Tabs.Panels, { children: tabs.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Tabs.Panel, { children: i === tab ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "pt-4", children: t.render() }) : null }, t.id)) })
1234
1773
  ] })
1235
1774
  ] });
1236
1775
  }
1237
1776
  function OverviewTab({ company, users, run, setSecret, client, can }) {
1238
1777
  var _a, _b, _c, _d, _e, _f;
1239
1778
  const { companyLoginUrl } = usePlatform();
1240
- const [confirm, setConfirm] = (0, import_react9.useState)(null);
1241
- const [editing, setEditing] = (0, import_react9.useState)(false);
1779
+ const [confirm, setConfirm] = (0, import_react11.useState)(null);
1780
+ const [editing, setEditing] = (0, import_react11.useState)(false);
1242
1781
  const setup = SETUP_STATES.includes(company.status);
1243
1782
  const writable = can("companies.write");
1244
1783
  const db = (_a = company.database) != null ? _a : {};
1245
1784
  const url = companyLoginUrl(company.slug);
1246
1785
  const LABEL = { active: "Activate", suspended: "Suspend", archived: "Archive" };
1247
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
1248
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-5", children: [
1249
- setup && writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SetupPanel, { company, run, setSecret, client }),
1250
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1786
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
1787
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-5", children: [
1788
+ setup && writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SetupPanel, { company, run, setSecret, client }),
1789
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1251
1790
  Card,
1252
1791
  {
1253
1792
  title: "Company",
1254
- actions: writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", variant: "outline", onClick: () => setEditing(true), children: "Edit details" }),
1255
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1793
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", variant: "outline", onClick: () => setEditing(true), children: "Edit details" }),
1794
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1256
1795
  Facts,
1257
1796
  {
1258
1797
  rows: [
1259
1798
  ["Name", company.displayName],
1260
1799
  [
1261
1800
  "Sign-in address",
1262
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-2", children: [
1263
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "truncate font-mono text-xs", children: url }),
1264
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CopyButton, { value: url })
1801
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-2", children: [
1802
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate font-mono text-xs", children: url }),
1803
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CopyButton, { value: url })
1265
1804
  ] }, "l")
1266
1805
  ],
1267
1806
  ["Branches", (_c = (_b = company.branches) == null ? void 0 : _b.length) != null ? _c : "\u2014"],
@@ -1274,17 +1813,17 @@ function OverviewTab({ company, users, run, setSecret, client, can }) {
1274
1813
  }
1275
1814
  )
1276
1815
  ] }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Lifecycle", children: setup ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "Activation, suspension and archiving become available once the company's database is set up." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1278
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Suspending immediately blocks every sign-in and request for this company. Data is kept." }),
1279
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
1280
- writable && company.status !== "active" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconPlay, {}), onClick: () => setConfirm("active"), children: "Activate" }),
1281
- writable && company.status === "active" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconPause, {}), onClick: () => setConfirm("suspended"), children: "Suspend" }),
1282
- writable && company.status !== "archived" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconArchive, {}), onClick: () => setConfirm("archived"), children: "Archive" })
1816
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Lifecycle", children: setup ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "Activation, suspension and archiving become available once the company's database is set up." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1817
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Suspending immediately blocks every sign-in and request for this company. Data is kept." }),
1818
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-2", children: [
1819
+ writable && company.status !== "active" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconPlay, {}), onClick: () => setConfirm("active"), children: "Activate" }),
1820
+ writable && company.status === "active" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconPause, {}), onClick: () => setConfirm("suspended"), children: "Suspend" }),
1821
+ writable && company.status !== "archived" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconArchive, {}), onClick: () => setConfirm("archived"), children: "Archive" })
1283
1822
  ] })
1284
1823
  ] }) }),
1285
- editing && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EditDetails, { company, run, client, onClose: () => setEditing(false) }),
1286
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1287
- import_twreact_ui8.ConfirmDialog,
1824
+ editing && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EditDetails, { company, run, client, onClose: () => setEditing(false) }),
1825
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1826
+ import_twreact_ui10.ConfirmDialog,
1288
1827
  {
1289
1828
  isOpen: confirm !== null,
1290
1829
  title: `${(_e = LABEL[confirm]) != null ? _e : ""} ${company.displayName}?`,
@@ -1303,11 +1842,11 @@ function OverviewTab({ company, users, run, setSecret, client, can }) {
1303
1842
  }
1304
1843
  function EditDetails({ company, run, client, onClose }) {
1305
1844
  const { companyLoginPath } = usePlatform();
1306
- const [name, setName] = (0, import_react9.useState)(company.displayName);
1307
- const [slug, setSlug] = (0, import_react9.useState)(company.slug);
1308
- const [slugState, setSlugState] = (0, import_react9.useState)({ available: true });
1309
- const [busy, setBusy] = (0, import_react9.useState)(false);
1310
- (0, import_react9.useEffect)(() => {
1845
+ const [name, setName] = (0, import_react11.useState)(company.displayName);
1846
+ const [slug, setSlug] = (0, import_react11.useState)(company.slug);
1847
+ const [slugState, setSlugState] = (0, import_react11.useState)({ available: true });
1848
+ const [busy, setBusy] = (0, import_react11.useState)(false);
1849
+ (0, import_react11.useEffect)(() => {
1311
1850
  if (slug === company.slug) {
1312
1851
  setSlugState({ available: true });
1313
1852
  return void 0;
@@ -1321,17 +1860,17 @@ function EditDetails({ company, run, client, onClose }) {
1321
1860
  }, 350);
1322
1861
  return () => clearTimeout(t);
1323
1862
  }, [slug, company.slug, client]);
1324
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1325
- import_twreact_ui8.Dialog,
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1864
+ import_twreact_ui10.Dialog,
1326
1865
  {
1327
1866
  isOpen: true,
1328
1867
  onClose,
1329
1868
  title: "Edit company details",
1330
1869
  size: "md",
1331
- footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1332
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: onClose, children: "Cancel" }),
1333
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1334
- import_twreact_ui8.Button,
1870
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-end gap-2", children: [
1871
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: onClose, children: "Cancel" }),
1872
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1873
+ import_twreact_ui10.Button,
1335
1874
  {
1336
1875
  loading: busy,
1337
1876
  disabled: !name.trim() || slugState.available !== true,
@@ -1349,10 +1888,10 @@ function EditDetails({ company, run, client, onClose }) {
1349
1888
  }
1350
1889
  )
1351
1890
  ] }),
1352
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1353
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Company name", value: name, onChange: (e) => setName(e.target.value) }),
1354
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1355
- import_twreact_ui8.Input,
1891
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-4", children: [
1892
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Company name", value: name, onChange: (e) => setName(e.target.value) }),
1893
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1894
+ import_twreact_ui10.Input,
1356
1895
  {
1357
1896
  label: "URL slug",
1358
1897
  value: slug,
@@ -1370,10 +1909,10 @@ function SetupPanel({ company, run, setSecret, client }) {
1370
1909
  const provisioner = useProvisioner();
1371
1910
  const job = company.lastJob;
1372
1911
  const stored = ((_a = company.database) == null ? void 0 : _a.configured) ? company.database.mode : null;
1373
- const [db, setDb] = (0, import_react9.useState)({ mode: stored === "external" ? "external" : "managed", url: "", report: null, testedUrl: null });
1374
- const [adminName, setAdminName] = (0, import_react9.useState)("");
1375
- const [adminEmail, setAdminEmail] = (0, import_react9.useState)("");
1376
- const [busy, setBusy] = (0, import_react9.useState)(false);
1912
+ const [db, setDb] = (0, import_react11.useState)({ mode: stored === "external" ? "external" : "managed", url: "", report: null, testedUrl: null });
1913
+ const [adminName, setAdminName] = (0, import_react11.useState)("");
1914
+ const [adminEmail, setAdminEmail] = (0, import_react11.useState)("");
1915
+ const [busy, setBusy] = (0, import_react11.useState)(false);
1377
1916
  const ready = databaseReady(db, provisioner, { acceptStates: ["empty", "company"], keepStored: stored === "external" });
1378
1917
  const adminOk = !adminName.trim() && !adminEmail.trim() || adminName.trim() && EMAIL_RE.test(adminEmail.trim());
1379
1918
  const provision = async () => {
@@ -1394,13 +1933,13 @@ function SetupPanel({ company, run, setSecret, client }) {
1394
1933
  setBusy(false);
1395
1934
  };
1396
1935
  const lastError = (_b = job == null ? void 0 : job.lastError) != null ? _b : job == null ? void 0 : job.last_error;
1397
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Finish setup", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1398
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "This company's database is not ready yet, so nobody can sign in." }),
1399
- (job == null ? void 0 : job.status) === "failed" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_twreact_ui8.Alert, { variant: "error", title: `Last attempt failed${job.step ? ` at \u201C${job.step}\u201D` : ""}`, children: [
1400
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "block break-words font-mono text-xs", children: lastError }),
1401
- jobErrorHint(lastError) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "mt-1 block", children: jobErrorHint(lastError) })
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Finish setup", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-4", children: [
1937
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "This company's database is not ready yet, so nobody can sign in." }),
1938
+ (job == null ? void 0 : job.status) === "failed" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_twreact_ui10.Alert, { variant: "error", title: `Last attempt failed${job.step ? ` at \u201C${job.step}\u201D` : ""}`, children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block break-words font-mono text-xs", children: lastError }),
1940
+ jobErrorHint(lastError) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "mt-1 block", children: jobErrorHint(lastError) })
1402
1941
  ] }),
1403
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1404
1943
  DatabaseChoice,
1405
1944
  {
1406
1945
  value: db,
@@ -1412,25 +1951,25 @@ function SetupPanel({ company, run, setSecret, client }) {
1412
1951
  compact: true
1413
1952
  }
1414
1953
  ),
1415
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
1416
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-sm font-medium text-foreground", children: "First admin (recommended)" }),
1417
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mb-2 text-xs text-muted", children: "A temporary password is generated and shown once when setup finishes." }),
1418
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid gap-3 [grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))]", children: [
1419
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { "aria-label": "First admin full name", placeholder: "Full name", value: adminName, onChange: (e) => setAdminName(e.target.value) }),
1420
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { "aria-label": "First admin email", type: "email", placeholder: "email@company.com", value: adminEmail, onChange: (e) => setAdminEmail(e.target.value) })
1954
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
1955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-sm font-medium text-foreground", children: "First admin (recommended)" }),
1956
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mb-2 text-xs text-muted", children: "A temporary password is generated and shown once when setup finishes." }),
1957
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid gap-3 [grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))]", children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { "aria-label": "First admin full name", placeholder: "Full name", value: adminName, onChange: (e) => setAdminName(e.target.value) }),
1959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { "aria-label": "First admin email", type: "email", placeholder: "email@company.com", value: adminEmail, onChange: (e) => setAdminEmail(e.target.value) })
1421
1960
  ] })
1422
1961
  ] }),
1423
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
1424
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { loading: busy, disabled: !ready || !adminOk, leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconRefresh, {}), onClick: provision, children: (job == null ? void 0 : job.status) === "failed" ? "Retry setup" : "Provision database" }),
1425
- busy && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-muted", children: "Applying the schema. This can take a minute\u2026" })
1962
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { loading: busy, disabled: !ready || !adminOk, leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconRefresh, {}), onClick: provision, children: (job == null ? void 0 : job.status) === "failed" ? "Retry setup" : "Provision database" }),
1964
+ busy && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted", children: "Applying the schema. This can take a minute\u2026" })
1426
1965
  ] })
1427
1966
  ] }) });
1428
1967
  }
1429
1968
  function BranchesTab({ company, run, client, can }) {
1430
1969
  var _a;
1431
- const [editing, setEditing] = (0, import_react9.useState)(null);
1432
- const [code, setCode] = (0, import_react9.useState)("");
1433
- const [name, setName] = (0, import_react9.useState)("");
1970
+ const [editing, setEditing] = (0, import_react11.useState)(null);
1971
+ const [code, setCode] = (0, import_react11.useState)("");
1972
+ const [name, setName] = (0, import_react11.useState)("");
1434
1973
  const writable = can("companies.write");
1435
1974
  const branches = (_a = company.branches) != null ? _a : [];
1436
1975
  const open = (b) => {
@@ -1443,23 +1982,23 @@ function BranchesTab({ company, run, client, can }) {
1443
1982
  const ok = editing.id ? await run(() => client.patch(`companies/${company.id}/branches/${editing.id}`, { name: name.trim() }), "Branch renamed") : await run(() => client.post(`companies/${company.id}/branches`, { code: code.trim().toUpperCase(), name: name.trim() }), "Branch added");
1444
1983
  if (ok) setEditing(null);
1445
1984
  };
1446
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1985
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1447
1986
  Card,
1448
1987
  {
1449
1988
  title: "Branches",
1450
- actions: writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconPlus, {}), onClick: () => open(null), children: "Add branch" }),
1989
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconPlus, {}), onClick: () => open(null), children: "Add branch" }),
1451
1990
  children: [
1452
- branches.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.EmptyState, { title: "No branches", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "divide-y divide-line", children: branches.map((b) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
1453
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "w-20 shrink-0 font-mono text-xs text-muted", children: b.code }),
1454
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
1991
+ branches.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.EmptyState, { title: "No branches", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "divide-y divide-line", children: branches.map((b) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
1992
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-20 shrink-0 font-mono text-xs text-muted", children: b.code }),
1993
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
1455
1994
  b.name,
1456
- b.id === company.defaultBranchId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ml-2 text-xs text-muted", children: "default" })
1995
+ b.id === company.defaultBranchId && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ml-2 text-xs text-muted", children: "default" })
1457
1996
  ] }),
1458
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: b.status }),
1459
- writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1", children: [
1460
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", variant: "ghost", onClick: () => open(b), children: "Rename" }),
1461
- b.id !== company.defaultBranchId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1462
- import_twreact_ui8.Button,
1997
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusPill, { status: b.status }),
1998
+ writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-1", children: [
1999
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", variant: "ghost", onClick: () => open(b), children: "Rename" }),
2000
+ b.id !== company.defaultBranchId && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2001
+ import_twreact_ui10.Button,
1463
2002
  {
1464
2003
  size: "sm",
1465
2004
  variant: "ghost",
@@ -1472,20 +2011,20 @@ function BranchesTab({ company, run, client, can }) {
1472
2011
  )
1473
2012
  ] })
1474
2013
  ] }, b.id)) }),
1475
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1476
- import_twreact_ui8.Dialog,
2014
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2015
+ import_twreact_ui10.Dialog,
1477
2016
  {
1478
2017
  isOpen: editing !== null,
1479
2018
  onClose: () => setEditing(null),
1480
2019
  title: (editing == null ? void 0 : editing.id) ? `Rename ${editing.code}` : "Add branch",
1481
2020
  size: "sm",
1482
- footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1483
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
1484
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { disabled: !name.trim() || !(editing == null ? void 0 : editing.id) && !/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(code), onClick: save, children: "Save" })
2021
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-end gap-2", children: [
2022
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
2023
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { disabled: !name.trim() || !(editing == null ? void 0 : editing.id) && !/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(code), onClick: save, children: "Save" })
1485
2024
  ] }),
1486
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1487
- !(editing == null ? void 0 : editing.id) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Code", value: code, onChange: (e) => setCode(e.target.value.toUpperCase()), helperText: "Letters, digits, - and _. Cannot be changed later." }),
1488
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Name", value: name, onChange: (e) => setName(e.target.value) })
2025
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-4", children: [
2026
+ !(editing == null ? void 0 : editing.id) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Code", value: code, onChange: (e) => setCode(e.target.value.toUpperCase()), helperText: "Letters, digits, - and _. Cannot be changed later." }),
2027
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Name", value: name, onChange: (e) => setName(e.target.value) })
1489
2028
  ] })
1490
2029
  }
1491
2030
  )
@@ -1496,29 +2035,29 @@ function BranchesTab({ company, run, client, can }) {
1496
2035
  function DatabaseTab({ company, run, setFlash, client, can }) {
1497
2036
  var _a, _b, _c, _d, _e;
1498
2037
  const db = (_a = company.database) != null ? _a : { configured: false };
1499
- const [busy, setBusy] = (0, import_react9.useState)(null);
1500
- const [steps, setSteps] = (0, import_react9.useState)(null);
1501
- const [replacing, setReplacing] = (0, import_react9.useState)(false);
1502
- const [newUrl, setNewUrl] = (0, import_react9.useState)("");
1503
- const [report, setReport] = (0, import_react9.useState)(null);
1504
- const [revealing, setRevealing] = (0, import_react9.useState)(false);
1505
- const [reason, setReason] = (0, import_react9.useState)("");
1506
- const [revealed, setRevealed] = (0, import_react9.useState)(null);
1507
- if (!db.configured) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Database", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "No database yet. Provision it from the Overview tab." }) });
2038
+ const [busy, setBusy] = (0, import_react11.useState)(null);
2039
+ const [steps, setSteps] = (0, import_react11.useState)(null);
2040
+ const [replacing, setReplacing] = (0, import_react11.useState)(false);
2041
+ const [newUrl, setNewUrl] = (0, import_react11.useState)("");
2042
+ const [report, setReport] = (0, import_react11.useState)(null);
2043
+ const [revealing, setRevealing] = (0, import_react11.useState)(false);
2044
+ const [reason, setReason] = (0, import_react11.useState)("");
2045
+ const [revealed, setRevealed] = (0, import_react11.useState)(null);
2046
+ if (!db.configured) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Database", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "No database yet. Provision it from the Overview tab." }) });
1508
2047
  const writable = can("db.write");
1509
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
1510
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Card, { title: "Connection", children: [
1511
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2048
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
2049
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Card, { title: "Connection", children: [
2050
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1512
2051
  Facts,
1513
2052
  {
1514
2053
  rows: [
1515
- ["Type", /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ModePill, { mode: db.mode }, "m")],
1516
- ["Target", /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono text-xs", children: (_b = db.target) != null ? _b : "\u2014" }, "t")],
1517
- ["Status", /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: db.status }, "s")],
1518
- ["Health", /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1519
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(HealthDot, { ok: db.lastCheckOk }),
2054
+ ["Type", /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ModePill, { mode: db.mode }, "m")],
2055
+ ["Target", /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-mono text-xs", children: (_b = db.target) != null ? _b : "\u2014" }, "t")],
2056
+ ["Status", /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusPill, { status: db.status }, "s")],
2057
+ ["Health", /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(HealthDot, { ok: db.lastCheckOk }),
1520
2059
  " ",
1521
- db.lastCheckAt && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-xs text-muted", children: [
2060
+ db.lastCheckAt && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-xs text-muted", children: [
1522
2061
  "\xB7 ",
1523
2062
  timeAgo(db.lastCheckAt)
1524
2063
  ] })
@@ -1530,23 +2069,23 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1530
2069
  ]
1531
2070
  }
1532
2071
  ),
1533
- db.lastCheckOk === false && db.lastCheckError && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-4 rounded-lg bg-error-50 p-3 font-mono text-xs text-error-800", children: db.lastCheckError }),
1534
- steps && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "mt-4 space-y-1 rounded-lg bg-background p-3 text-xs text-muted", children: steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { children: [
2072
+ db.lastCheckOk === false && db.lastCheckError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-4 rounded-lg bg-error-50 p-3 font-mono text-xs text-error-800", children: db.lastCheckError }),
2073
+ steps && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "mt-4 space-y-1 rounded-lg bg-background p-3 text-xs text-muted", children: steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { children: [
1535
2074
  "\u2713 ",
1536
2075
  s
1537
2076
  ] }, s)) }),
1538
- revealed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mt-4 rounded-lg border border-warning-200 bg-warning-50 p-3", children: [
1539
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mb-1 text-xs font-medium text-warning-800", children: "Database URL (audited, shown once)" }),
1540
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
1541
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { className: "min-w-0 flex-1 break-all text-xs text-foreground", children: revealed }),
1542
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CopyButton, { value: revealed })
2077
+ revealed && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-4 rounded-lg border border-warning-200 bg-warning-50 p-3", children: [
2078
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mb-1 text-xs font-medium text-warning-800", children: "Database URL (audited, shown once)" }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
2080
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { className: "min-w-0 flex-1 break-all text-xs text-foreground", children: revealed }),
2081
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CopyButton, { value: revealed })
1543
2082
  ] }),
1544
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "mt-2 text-xs text-muted underline", onClick: () => setRevealed(null), children: "Hide" })
2083
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "mt-2 text-xs text-muted underline", onClick: () => setRevealed(null), children: "Hide" })
1545
2084
  ] })
1546
2085
  ] }),
1547
- writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Actions", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
1548
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1549
- import_twreact_ui8.Button,
2086
+ writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Actions", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-2", children: [
2087
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2088
+ import_twreact_ui10.Button,
1550
2089
  {
1551
2090
  variant: "outline",
1552
2091
  loading: busy === "test",
@@ -1563,8 +2102,8 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1563
2102
  children: "Test connection"
1564
2103
  }
1565
2104
  ),
1566
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1567
- import_twreact_ui8.Button,
2105
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2106
+ import_twreact_ui10.Button,
1568
2107
  {
1569
2108
  variant: "outline",
1570
2109
  loading: busy === "sync",
@@ -1577,12 +2116,12 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1577
2116
  children: "Sync schema"
1578
2117
  }
1579
2118
  ),
1580
- db.mode === "external" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: () => setReplacing(true), children: "Replace URL" }),
1581
- can("db.reveal") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "ghost", onClick: () => setRevealing(true), children: "Reveal URL\u2026" }),
1582
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-1 text-xs text-muted", children: "Sync adds missing tables, columns and indexes. It never drops or renames anything." })
2119
+ db.mode === "external" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: () => setReplacing(true), children: "Replace URL" }),
2120
+ can("db.reveal") && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "ghost", onClick: () => setRevealing(true), children: "Reveal URL\u2026" }),
2121
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mt-1 text-xs text-muted", children: "Sync adds missing tables, columns and indexes. It never drops or renames anything." })
1583
2122
  ] }) }),
1584
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1585
- import_twreact_ui8.Dialog,
2123
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2124
+ import_twreact_ui10.Dialog,
1586
2125
  {
1587
2126
  isOpen: replacing,
1588
2127
  onClose: () => {
@@ -1592,10 +2131,10 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1592
2131
  },
1593
2132
  title: "Replace database URL",
1594
2133
  size: "md",
1595
- footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1596
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: () => setReplacing(false), children: "Cancel" }),
1597
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1598
- import_twreact_ui8.Button,
2134
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-end gap-2", children: [
2135
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: () => setReplacing(false), children: "Cancel" }),
2136
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2137
+ import_twreact_ui10.Button,
1599
2138
  {
1600
2139
  disabled: !(report == null ? void 0 : report.ok) || report.state !== "company",
1601
2140
  loading: busy === "replace",
@@ -1614,11 +2153,11 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1614
2153
  )
1615
2154
  ] }),
1616
2155
  children: [
1617
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Use this after moving the database or rotating its password. The new URL must point at this company's existing database." }),
1618
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-end gap-2", children: [
1619
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "New database URL", type: "password", autoComplete: "off", value: newUrl, onChange: (e) => (setNewUrl(e.target.value), setReport(null)) }) }),
1620
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1621
- import_twreact_ui8.Button,
2156
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Use this after moving the database or rotating its password. The new URL must point at this company's existing database." }),
2157
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-end gap-2", children: [
2158
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "New database URL", type: "password", autoComplete: "off", value: newUrl, onChange: (e) => (setNewUrl(e.target.value), setReport(null)) }) }),
2159
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2160
+ import_twreact_ui10.Button,
1622
2161
  {
1623
2162
  variant: "outline",
1624
2163
  disabled: !newUrl.trim(),
@@ -1633,21 +2172,21 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1633
2172
  }
1634
2173
  )
1635
2174
  ] }),
1636
- report && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InspectResult, { report })
2175
+ report && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(InspectResult, { report })
1637
2176
  ]
1638
2177
  }
1639
2178
  ),
1640
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1641
- import_twreact_ui8.Dialog,
2179
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2180
+ import_twreact_ui10.Dialog,
1642
2181
  {
1643
2182
  isOpen: revealing,
1644
2183
  onClose: () => setRevealing(false),
1645
2184
  title: "Reveal database URL",
1646
2185
  size: "md",
1647
- footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1648
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: () => setRevealing(false), children: "Cancel" }),
1649
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1650
- import_twreact_ui8.Button,
2186
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-end gap-2", children: [
2187
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: () => setRevealing(false), children: "Cancel" }),
2188
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2189
+ import_twreact_ui10.Button,
1651
2190
  {
1652
2191
  variant: "destructive",
1653
2192
  disabled: reason.trim().length < 8,
@@ -1664,8 +2203,8 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
1664
2203
  )
1665
2204
  ] }),
1666
2205
  children: [
1667
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Only credential custodians can do this. The reveal is recorded in the audit log with your reason before the URL is read." }),
1668
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Textarea, { label: "Reason", rows: 3, value: reason, onChange: (e) => setReason(e.target.value), helperText: "At least 8 characters, e.g. the incident or ticket number." })
2206
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mb-3 text-sm text-muted", children: "Only credential custodians can do this. The reveal is recorded in the audit log with your reason before the URL is read." }),
2207
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Textarea, { label: "Reason", rows: 3, value: reason, onChange: (e) => setReason(e.target.value), helperText: "At least 8 characters, e.g. the incident or ticket number." })
1669
2208
  ]
1670
2209
  }
1671
2210
  )
@@ -1676,13 +2215,13 @@ function AdminsTab({ company, setSecret, setFlash, client, can }) {
1676
2215
  const sensitive = useSensitive();
1677
2216
  const ready = ((_a = company.database) == null ? void 0 : _a.configured) && ((_b = company.database) == null ? void 0 : _b.status) === "active";
1678
2217
  const { data, error, reload } = useLoad(ready ? `companies/${company.id}/admins` : null);
1679
- const [adding, setAdding] = (0, import_react9.useState)(false);
1680
- const [fullName, setFullName] = (0, import_react9.useState)("");
1681
- const [email, setEmail] = (0, import_react9.useState)("");
1682
- const [phone, setPhone] = (0, import_react9.useState)("");
1683
- const [busy, setBusy] = (0, import_react9.useState)(false);
2218
+ const [adding, setAdding] = (0, import_react11.useState)(false);
2219
+ const [fullName, setFullName] = (0, import_react11.useState)("");
2220
+ const [email, setEmail] = (0, import_react11.useState)("");
2221
+ const [phone, setPhone] = (0, import_react11.useState)("");
2222
+ const [busy, setBusy] = (0, import_react11.useState)(false);
1684
2223
  const writable = can("companies.write");
1685
- if (!ready) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Company admins", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "Admins can be added once the company's database is provisioned." }) });
2224
+ if (!ready) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Company admins", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "Admins can be added once the company's database is provisioned." }) });
1686
2225
  const act = async (a, action) => {
1687
2226
  var _a2;
1688
2227
  try {
@@ -1711,38 +2250,38 @@ function AdminsTab({ company, setSecret, setFlash, client, can }) {
1711
2250
  setBusy(false);
1712
2251
  }
1713
2252
  };
1714
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2253
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1715
2254
  Card,
1716
2255
  {
1717
2256
  title: "Company admins",
1718
- actions: writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconPlus, {}), onClick: () => setAdding(true), children: "Add admin" }),
2257
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconPlus, {}), onClick: () => setAdding(true), children: "Add admin" }),
1719
2258
  children: [
1720
- error && !data ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.ErrorState, { description: error.message, onRetry: reload }) : !data ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.LoadingState, { variant: "table", rows: 2 }) : !((_c = data.admins) == null ? void 0 : _c.length) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.EmptyState, { title: "No administrators yet", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "divide-y divide-line", children: data.admins.map((a) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
1721
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "min-w-0 flex-1", children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "truncate text-sm font-medium text-foreground", children: a.fullName }),
1723
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "truncate text-xs text-muted", children: a.email })
2259
+ error && !data ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.ErrorState, { description: error.message, onRetry: reload }) : !data ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.LoadingState, { variant: "table", rows: 2 }) : !((_c = data.admins) == null ? void 0 : _c.length) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.EmptyState, { title: "No administrators yet", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "divide-y divide-line", children: data.admins.map((a) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
2260
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "min-w-0 flex-1", children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "truncate text-sm font-medium text-foreground", children: a.fullName }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "truncate text-xs text-muted", children: a.email })
1724
2263
  ] }),
1725
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: a.status }),
1726
- writable && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1", children: [
1727
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
1728
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { size: "sm", variant: "ghost", onClick: () => act(a, a.status === "active" ? "disable" : "enable"), children: a.status === "active" ? "Disable" : "Enable" })
2264
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusPill, { status: a.status }),
2265
+ writable && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-1", children: [
2266
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { size: "sm", variant: "ghost", onClick: () => act(a, a.status === "active" ? "disable" : "enable"), children: a.status === "active" ? "Disable" : "Enable" })
1729
2268
  ] })
1730
2269
  ] }, a.id)) }),
1731
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1732
- import_twreact_ui8.Dialog,
2270
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2271
+ import_twreact_ui10.Dialog,
1733
2272
  {
1734
2273
  isOpen: adding,
1735
2274
  onClose: () => setAdding(false),
1736
2275
  title: "Add company admin",
1737
2276
  size: "sm",
1738
- footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-end gap-2", children: [
1739
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
1740
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Button, { loading: busy, disabled: !fullName.trim() || !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
2277
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-end gap-2", children: [
2278
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
2279
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { loading: busy, disabled: !fullName.trim() || !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
1741
2280
  ] }),
1742
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
1743
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Full name", autoFocus: true, value: fullName, onChange: (e) => setFullName(e.target.value) }),
1744
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Email", type: "email", value: email, onChange: (e) => setEmail(e.target.value) }),
1745
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Input, { label: "Phone (optional)", type: "tel", value: phone, onChange: (e) => setPhone(e.target.value) })
2281
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-4", children: [
2282
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Full name", autoFocus: true, value: fullName, onChange: (e) => setFullName(e.target.value) }),
2283
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Email", type: "email", value: email, onChange: (e) => setEmail(e.target.value) }),
2284
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { label: "Phone (optional)", type: "tel", value: phone, onChange: (e) => setPhone(e.target.value) })
1746
2285
  ] })
1747
2286
  }
1748
2287
  )
@@ -1754,7 +2293,7 @@ function ActivityTab({ company, client, can }) {
1754
2293
  var _a, _b;
1755
2294
  const sensitive = useSensitive();
1756
2295
  const { data, error, reload } = useLoad(`companies/${company.id}/activity`);
1757
- const [retryError, setRetryError] = (0, import_react9.useState)(null);
2296
+ const [retryError, setRetryError] = (0, import_react11.useState)(null);
1758
2297
  const modeOf = (payload) => {
1759
2298
  try {
1760
2299
  const p = typeof payload === "string" ? JSON.parse(payload) : payload;
@@ -1763,27 +2302,27 @@ function ActivityTab({ company, client, can }) {
1763
2302
  return "managed";
1764
2303
  }
1765
2304
  };
1766
- if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.ErrorState, { description: error.message, onRetry: reload });
1767
- if (!data) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.LoadingState, { variant: "table", rows: 4 });
1768
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
1769
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Card, { title: "Provisioning jobs", children: [
1770
- retryError && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_twreact_ui8.Alert, { variant: "error", children: retryError }),
1771
- !((_a = data.jobs) == null ? void 0 : _a.length) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "No provisioning jobs." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "divide-y divide-line", children: data.jobs.map((j) => {
2305
+ if (error && !data) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.ErrorState, { description: error.message, onRetry: reload });
2306
+ if (!data) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.LoadingState, { variant: "table", rows: 4 });
2307
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
2308
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Card, { title: "Provisioning jobs", children: [
2309
+ retryError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Alert, { variant: "error", children: retryError }),
2310
+ !((_a = data.jobs) == null ? void 0 : _a.length) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "No provisioning jobs." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "divide-y divide-line", children: data.jobs.map((j) => {
1772
2311
  var _a2, _b2;
1773
2312
  const lastError = (_a2 = j.lastError) != null ? _a2 : j.last_error;
1774
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "py-2.5", children: [
1775
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
1776
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusPill, { status: j.status }),
1777
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-xs text-muted", children: [
2313
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "py-2.5", children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
2315
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusPill, { status: j.status }),
2316
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-xs text-muted", children: [
1778
2317
  modeOf(j.payload),
1779
2318
  " \xB7 step ",
1780
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: j.step }),
2319
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-mono", children: j.step }),
1781
2320
  " \xB7 ",
1782
2321
  j.attempts,
1783
2322
  " attempt(s)"
1784
2323
  ] }),
1785
- j.status === "failed" && can("companies.write") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1786
- import_twreact_ui8.Button,
2324
+ j.status === "failed" && can("companies.write") && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2325
+ import_twreact_ui10.Button,
1787
2326
  {
1788
2327
  size: "sm",
1789
2328
  variant: "ghost",
@@ -1801,33 +2340,33 @@ function ActivityTab({ company, client, can }) {
1801
2340
  }
1802
2341
  )
1803
2342
  ] }),
1804
- lastError && j.status !== "succeeded" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 break-words font-mono text-xs text-error-700", children: lastError }),
1805
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-0.5 text-xs text-muted", children: formatDate((_b2 = j.updatedAt) != null ? _b2 : j.updated_at) })
2343
+ lastError && j.status !== "succeeded" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 break-words font-mono text-xs text-error-700", children: lastError }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-0.5 text-xs text-muted", children: formatDate((_b2 = j.updatedAt) != null ? _b2 : j.updated_at) })
1806
2345
  ] }, j.id);
1807
2346
  }) })
1808
2347
  ] }),
1809
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { title: "Audit trail", children: !((_b = data.events) == null ? void 0 : _b.length) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted", children: "No events." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ol", { className: "divide-y divide-line", children: data.events.map(normalizeEvent).map((e, i) => {
2348
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { title: "Audit trail", children: !((_b = data.events) == null ? void 0 : _b.length) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted", children: "No events." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ol", { className: "divide-y divide-line", children: data.events.map(normalizeEvent).map((e, i) => {
1810
2349
  var _a2;
1811
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "py-2.5", children: [
1812
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "text-sm text-foreground", children: [
1813
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-medium", children: e.actor }),
2350
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "py-2.5", children: [
2351
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-sm text-foreground", children: [
2352
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-medium", children: e.actor }),
1814
2353
  " ",
1815
2354
  describeAction(e.action),
1816
- e.target && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-muted", children: [
2355
+ e.target && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-muted", children: [
1817
2356
  " \xB7 ",
1818
2357
  e.target
1819
2358
  ] })
1820
2359
  ] }),
1821
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-xs text-muted", children: formatDate(e.at) })
2360
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-xs text-muted", children: formatDate(e.at) })
1822
2361
  ] }, (_a2 = e.id) != null ? _a2 : i);
1823
2362
  }) }) })
1824
2363
  ] });
1825
2364
  }
1826
2365
 
1827
2366
  // src/platform/views/Admins.jsx
1828
- var import_react10 = __toESM(require("react"), 1);
1829
- var import_twreact_ui9 = require("@dreamtree-org/twreact-ui");
1830
- var import_jsx_runtime11 = require("react/jsx-runtime");
2367
+ var import_react12 = __toESM(require("react"), 1);
2368
+ var import_twreact_ui11 = require("@dreamtree-org/twreact-ui");
2369
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1831
2370
  var ROLE_OPTIONS = [
1832
2371
  { value: "superadmin", label: "Superadmin: everything" },
1833
2372
  { value: "support", label: "Support: read-only" }
@@ -1837,13 +2376,13 @@ function AdminsView() {
1837
2376
  const { client, can, loginUrl, Link } = usePlatform();
1838
2377
  const sensitive = useSensitive();
1839
2378
  const { data, error, loading, reload } = useLoad("admins");
1840
- const [flash, setFlash] = (0, import_react10.useState)(null);
1841
- const [secret, setSecret] = (0, import_react10.useState)(null);
1842
- const [adding, setAdding] = (0, import_react10.useState)(false);
1843
- const [email, setEmail] = (0, import_react10.useState)("");
1844
- const [role, setRole] = (0, import_react10.useState)("superadmin");
1845
- const [busy, setBusy] = (0, import_react10.useState)(false);
1846
- const [confirm, setConfirm] = (0, import_react10.useState)(null);
2379
+ const [flash, setFlash] = (0, import_react12.useState)(null);
2380
+ const [secret, setSecret] = (0, import_react12.useState)(null);
2381
+ const [adding, setAdding] = (0, import_react12.useState)(false);
2382
+ const [email, setEmail] = (0, import_react12.useState)("");
2383
+ const [role, setRole] = (0, import_react12.useState)("superadmin");
2384
+ const [busy, setBusy] = (0, import_react12.useState)(false);
2385
+ const [confirm, setConfirm] = (0, import_react12.useState)(null);
1847
2386
  const writable = can("admins.write");
1848
2387
  const act = async (a, action, extra = {}) => {
1849
2388
  var _a2;
@@ -1874,63 +2413,63 @@ function AdminsView() {
1874
2413
  setBusy(false);
1875
2414
  }
1876
2415
  };
1877
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
1878
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1879
- import_twreact_ui9.PageHeader,
2416
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2418
+ import_twreact_ui11.PageHeader,
1880
2419
  {
1881
2420
  title: "Platform admins",
1882
2421
  subtitle: "People who can create companies and manage the platform",
1883
2422
  linkAs: Link,
1884
- actions: writable && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { leftIcon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconPlus, {}), onClick: () => setAdding(true), children: "Add platform admin" })
2423
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { leftIcon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconPlus, {}), onClick: () => setAdding(true), children: "Add platform admin" })
1885
2424
  }
1886
2425
  ),
1887
- flash && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
1888
- secret && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(OneTimeSecret, { title: secret.title, loginUrl, email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
1889
- error && !data ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.ErrorState, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.LoadingState, { variant: "table", rows: 3 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: ((_a = data == null ? void 0 : data.admins) != null ? _a : []).map((a) => {
2426
+ flash && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
2427
+ secret && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(OneTimeSecret, { title: secret.title, loginUrl, email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
2428
+ error && !data ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.ErrorState, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.LoadingState, { variant: "table", rows: 3 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: ((_a = data == null ? void 0 : data.admins) != null ? _a : []).map((a) => {
1890
2429
  var _a2, _b, _c;
1891
2430
  const me = a.id === data.me;
1892
2431
  const roles = Array.isArray(a.roles) ? a.roles : [];
1893
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
1894
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0 flex-1", children: [
1895
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium text-foreground", children: [
1896
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "truncate", children: a.email }),
1897
- me && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Badge, { variant: "primary", size: "sm", children: "you" })
2432
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("li", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
2433
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "min-w-0 flex-1", children: [
2434
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium text-foreground", children: [
2435
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate", children: a.email }),
2436
+ me && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Badge, { variant: "primary", size: "sm", children: "you" })
1898
2437
  ] }),
1899
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-xs text-muted", children: [
2438
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "text-xs text-muted", children: [
1900
2439
  roles.length ? roles.join(", ") : "No role",
1901
2440
  " \xB7 last sign-in ",
1902
2441
  timeAgo((_a2 = a.lastLoginAt) != null ? _a2 : a.last_login_at),
1903
2442
  !((_b = a.totpEnrolled) != null ? _b : Number(a.strong_factor_enrolled)) && " \xB7 no authenticator yet"
1904
2443
  ] })
1905
2444
  ] }),
1906
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { status: a.status }),
1907
- writable && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
1908
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
1909
- !me && ((_c = a.totpEnrolled) != null ? _c : Number(a.strong_factor_enrolled)) ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "reset-totp" }), children: "Reset authenticator" }) : null,
1910
- !me && (a.status === "active" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "disable" }), children: "Disable" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "enable"), children: "Enable" }))
2445
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StatusPill, { status: a.status }),
2446
+ writable && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
2447
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
2448
+ !me && ((_c = a.totpEnrolled) != null ? _c : Number(a.strong_factor_enrolled)) ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "reset-totp" }), children: "Reset authenticator" }) : null,
2449
+ !me && (a.status === "active" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "disable" }), children: "Disable" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { size: "sm", variant: "ghost", onClick: () => act(a, "enable"), children: "Enable" }))
1911
2450
  ] })
1912
2451
  ] }, a.id);
1913
2452
  }) }),
1914
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1915
- import_twreact_ui9.Dialog,
2453
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2454
+ import_twreact_ui11.Dialog,
1916
2455
  {
1917
2456
  isOpen: adding,
1918
2457
  onClose: () => setAdding(false),
1919
2458
  title: "Add platform admin",
1920
2459
  size: "sm",
1921
- footer: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-end gap-2", children: [
1922
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
1923
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Button, { loading: busy, disabled: !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
2460
+ footer: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex justify-end gap-2", children: [
2461
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
2462
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { loading: busy, disabled: !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
1924
2463
  ] }),
1925
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1926
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm text-muted", children: "A temporary password is generated and shown once. They set up an authenticator app at first sign-in." }),
1927
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Input, { label: "Email", type: "email", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
1928
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_twreact_ui9.Select, { label: "Role", options: ROLE_OPTIONS, value: role, onChange: (v) => setRole(String(v)) })
2464
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
2465
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted", children: "A temporary password is generated and shown once. They set up an authenticator app at first sign-in." }),
2466
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Input, { label: "Email", type: "email", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
2467
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Select, { label: "Role", options: ROLE_OPTIONS, value: role, onChange: (v) => setRole(String(v)) })
1929
2468
  ] })
1930
2469
  }
1931
2470
  ),
1932
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1933
- import_twreact_ui9.ConfirmDialog,
2471
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2472
+ import_twreact_ui11.ConfirmDialog,
1934
2473
  {
1935
2474
  isOpen: confirm !== null,
1936
2475
  title: (confirm == null ? void 0 : confirm.action) === "disable" ? `Disable ${confirm == null ? void 0 : confirm.admin.email}?` : `Reset ${confirm == null ? void 0 : confirm.admin.email}'s authenticator?`,
@@ -1949,17 +2488,17 @@ function AdminsView() {
1949
2488
  }
1950
2489
 
1951
2490
  // src/platform/views/Audit.jsx
1952
- var import_react11 = __toESM(require("react"), 1);
1953
- var import_twreact_ui10 = require("@dreamtree-org/twreact-ui");
1954
- var import_jsx_runtime12 = require("react/jsx-runtime");
2491
+ var import_react13 = __toESM(require("react"), 1);
2492
+ var import_twreact_ui12 = require("@dreamtree-org/twreact-ui");
2493
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1955
2494
  function AuditView() {
1956
2495
  const { client, Link, href } = usePlatform();
1957
- const [q, setQ] = (0, import_react11.useState)("");
1958
- const [applied, setApplied] = (0, import_react11.useState)("");
1959
- const [events, setEvents] = (0, import_react11.useState)([]);
1960
- const [cursor, setCursor] = (0, import_react11.useState)(null);
1961
- const [state, setState] = (0, import_react11.useState)({ loading: true, error: null });
1962
- const [exportError, setExportError] = (0, import_react11.useState)(null);
2496
+ const [q, setQ] = (0, import_react13.useState)("");
2497
+ const [applied, setApplied] = (0, import_react13.useState)("");
2498
+ const [events, setEvents] = (0, import_react13.useState)([]);
2499
+ const [cursor, setCursor] = (0, import_react13.useState)(null);
2500
+ const [state, setState] = (0, import_react13.useState)({ loading: true, error: null });
2501
+ const [exportError, setExportError] = (0, import_react13.useState)(null);
1963
2502
  const load = async (more = false) => {
1964
2503
  setState({ loading: true, error: null });
1965
2504
  try {
@@ -1971,7 +2510,7 @@ function AuditView() {
1971
2510
  setState({ loading: false, error: e });
1972
2511
  }
1973
2512
  };
1974
- (0, import_react11.useEffect)(() => {
2513
+ (0, import_react13.useEffect)(() => {
1975
2514
  void load(false);
1976
2515
  }, [applied]);
1977
2516
  const exportCsv = async () => {
@@ -1990,18 +2529,18 @@ function AuditView() {
1990
2529
  }
1991
2530
  };
1992
2531
  const rows = events.map(normalizeEvent);
1993
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mx-auto max-w-6xl space-y-6 px-4 py-6 sm:px-6", children: [
1994
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1995
- import_twreact_ui10.PageHeader,
2532
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mx-auto max-w-6xl space-y-6 px-4 py-6 sm:px-6", children: [
2533
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2534
+ import_twreact_ui12.PageHeader,
1996
2535
  {
1997
2536
  title: "Audit log",
1998
2537
  subtitle: "Every platform action: who did what, to which company",
1999
2538
  linkAs: Link,
2000
- actions: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconDownload, {}), onClick: exportCsv, children: "Export CSV" })
2539
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.Button, { variant: "outline", leftIcon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconDownload, {}), onClick: exportCsv, children: "Export CSV" })
2001
2540
  }
2002
2541
  ),
2003
- exportError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Alert, { variant: "error", children: exportError }),
2004
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2542
+ exportError && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.Alert, { variant: "error", children: exportError }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2005
2544
  "form",
2006
2545
  {
2007
2546
  className: "flex gap-2",
@@ -2010,50 +2549,50 @@ function AuditView() {
2010
2549
  setApplied(q.trim());
2011
2550
  },
2012
2551
  children: [
2013
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Input, { type: "search", "aria-label": "Search the audit log", placeholder: "Search action, target or company", value: q, onChange: (e) => setQ(e.target.value) }) }),
2014
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { type: "submit", variant: "outline", children: "Search" })
2552
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.Input, { type: "search", "aria-label": "Search the audit log", placeholder: "Search action, target or company", value: q, onChange: (e) => setQ(e.target.value) }) }),
2553
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.Button, { type: "submit", variant: "outline", children: "Search" })
2015
2554
  ]
2016
2555
  }
2017
2556
  ),
2018
- state.error && !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.ErrorState, { description: state.error.message, onRetry: () => load(false) }) : state.loading && !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.LoadingState, { variant: "table", rows: 8 }) : !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconList, { size: 24 }), title: applied ? "Nothing matches" : "No events yet", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2019
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ol", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: rows.map((e, i) => {
2557
+ state.error && !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.ErrorState, { description: state.error.message, onRetry: () => load(false) }) : state.loading && !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.LoadingState, { variant: "table", rows: 8 }) : !rows.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconList, { size: 24 }), title: applied ? "Nothing matches" : "No events yet", compact: true }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ol", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: rows.map((e, i) => {
2020
2559
  var _a;
2021
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "flex flex-wrap items-baseline gap-x-3 gap-y-1 px-4 py-2.5", children: [
2022
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-40 shrink-0 text-xs text-muted", children: formatDate(e.at) }),
2023
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
2024
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-medium", children: e.actor }),
2560
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("li", { className: "flex flex-wrap items-baseline gap-x-3 gap-y-1 px-4 py-2.5", children: [
2561
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-40 shrink-0 text-xs text-muted", children: formatDate(e.at) }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
2563
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "font-medium", children: e.actor }),
2025
2564
  " ",
2026
2565
  describeAction(e.action),
2027
- e.company && e.companyId && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2566
+ e.company && e.companyId && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2028
2567
  " \xB7 ",
2029
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Link, { href: href(`companies/${e.companyId}`), className: "text-primary-600 hover:underline", children: e.company })
2568
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Link, { href: href(`companies/${e.companyId}`), className: "text-primary-600 hover:underline", children: e.company })
2030
2569
  ] }),
2031
- e.target && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-muted", children: [
2570
+ e.target && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-muted", children: [
2032
2571
  " \xB7 ",
2033
2572
  e.target
2034
2573
  ] })
2035
2574
  ] }),
2036
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { className: "text-xs text-muted", children: e.action })
2575
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("code", { className: "text-xs text-muted", children: e.action })
2037
2576
  ] }, (_a = e.id) != null ? _a : i);
2038
2577
  }) }),
2039
- cursor && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_twreact_ui10.Button, { variant: "outline", loading: state.loading, onClick: () => load(true), children: "Load older" }) })
2578
+ cursor && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.Button, { variant: "outline", loading: state.loading, onClick: () => load(true), children: "Load older" }) })
2040
2579
  ] })
2041
2580
  ] });
2042
2581
  }
2043
2582
 
2044
2583
  // src/platform/views/Security.jsx
2045
- var import_react12 = __toESM(require("react"), 1);
2046
- var import_twreact_ui11 = require("@dreamtree-org/twreact-ui");
2047
- var import_jsx_runtime13 = require("react/jsx-runtime");
2584
+ var import_react14 = __toESM(require("react"), 1);
2585
+ var import_twreact_ui13 = require("@dreamtree-org/twreact-ui");
2586
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2048
2587
  var grouped = (s) => String(s != null ? s : "").replace(/(.{4})/g, "$1 ").trim();
2049
2588
  function SecurityView({ gate = false }) {
2050
2589
  var _a, _b;
2051
2590
  const { client, me, reloadMe, Link } = usePlatform();
2052
2591
  const sensitive = useSensitive();
2053
- const [setup, setSetup] = (0, import_react12.useState)(null);
2054
- const [code, setCode] = (0, import_react12.useState)("");
2055
- const [busy, setBusy] = (0, import_react12.useState)(false);
2056
- const [error, setError] = (0, import_react12.useState)(null);
2592
+ const [setup, setSetup] = (0, import_react14.useState)(null);
2593
+ const [code, setCode] = (0, import_react14.useState)("");
2594
+ const [busy, setBusy] = (0, import_react14.useState)(false);
2595
+ const [error, setError] = (0, import_react14.useState)(null);
2057
2596
  const enrolled = (_a = me == null ? void 0 : me.admin) == null ? void 0 : _a.totpEnrolled;
2058
2597
  const begin = async () => {
2059
2598
  setBusy(true);
@@ -2081,36 +2620,187 @@ function SecurityView({ gate = false }) {
2081
2620
  setBusy(false);
2082
2621
  }
2083
2622
  };
2084
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mx-auto max-w-2xl space-y-6 px-4 py-6 sm:px-6", children: [
2085
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.PageHeader, { title: gate ? "Set up your authenticator" : "Security", subtitle: (_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.email, linkAs: Link }),
2086
- gate && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Alert, { variant: "info", title: "One more step", children: "The platform console needs a second factor. Set up an authenticator app (Google Authenticator, 1Password, Authy, \u2026) to continue." }),
2087
- error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Alert, { variant: "error", children: error }),
2088
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Card, { title: "Authenticator app", children: enrolled ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { className: "flex items-center gap-2 text-sm text-success-700", children: [
2089
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconCheck, {}),
2623
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mx-auto max-w-2xl space-y-6 px-4 py-6 sm:px-6", children: [
2624
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.PageHeader, { title: gate ? "Set up your authenticator" : "Security", subtitle: (_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.email, linkAs: Link }),
2625
+ gate && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.Alert, { variant: "info", title: "One more step", children: "The platform console needs a second factor. Set up an authenticator app (Google Authenticator, 1Password, Authy, \u2026) to continue." }),
2626
+ error && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.Alert, { variant: "error", children: error }),
2627
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Card, { title: "Authenticator app", children: enrolled ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "flex items-center gap-2 text-sm text-success-700", children: [
2628
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconCheck, {}),
2090
2629
  " An authenticator is set up. Sign-in and sensitive actions ask for its code. If you lose the device, ask another platform admin to reset it."
2091
- ] }) : !setup ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-3", children: [
2092
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted", children: "You'll scan or type a setup key into the app, then enter the 6-digit code it shows." }),
2093
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { leftIcon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconShield, {}), loading: busy, onClick: begin, children: "Start setup" })
2094
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("form", { className: "space-y-4", onSubmit: verify, children: [
2095
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("ol", { className: "list-decimal space-y-3 pl-5 text-sm text-foreground", children: [
2096
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("li", { children: [
2630
+ ] }) : !setup ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "space-y-3", children: [
2631
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted", children: "You'll scan or type a setup key into the app, then enter the 6-digit code it shows." }),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.Button, { leftIcon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconShield, {}), loading: busy, onClick: begin, children: "Start setup" })
2633
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("form", { className: "space-y-4", onSubmit: verify, children: [
2634
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("ol", { className: "list-decimal space-y-3 pl-5 text-sm text-foreground", children: [
2635
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("li", { children: [
2097
2636
  "Add an account in your authenticator app with this setup key:",
2098
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
2099
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { className: "rounded-md bg-background px-2 py-1 font-mono text-sm tracking-wider", children: grouped(setup.secret) }),
2100
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CopyButton, { value: setup.secret, label: "Copy key" }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("a", { href: setup.otpauthUrl, className: "text-xs text-primary-600 underline", children: "Open in authenticator app" })
2637
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
2638
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("code", { className: "rounded-md bg-background px-2 py-1 font-mono text-sm tracking-wider", children: grouped(setup.secret) }),
2639
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CopyButton, { value: setup.secret, label: "Copy key" }),
2640
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { href: setup.otpauthUrl, className: "text-xs text-primary-600 underline", children: "Open in authenticator app" })
2102
2641
  ] })
2103
2642
  ] }),
2104
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { children: "Enter the 6-digit code it shows." })
2643
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: "Enter the 6-digit code it shows." })
2105
2644
  ] }),
2106
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "max-w-xs", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Input, { label: "Code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }) }),
2107
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_twreact_ui11.Button, { type: "submit", loading: busy, disabled: code.length < 6, children: "Verify and finish" })
2645
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "max-w-xs", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.Input, { label: "Code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }) }),
2646
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_twreact_ui13.Button, { type: "submit", loading: busy, disabled: code.length < 6, children: "Verify and finish" })
2108
2647
  ] }) })
2109
2648
  ] });
2110
2649
  }
2111
2650
 
2651
+ // src/platform/views/Plans.jsx
2652
+ var import_react15 = __toESM(require("react"), 1);
2653
+ var import_twreact_ui14 = require("@dreamtree-org/twreact-ui");
2654
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2655
+ var INTERVALS = [
2656
+ { value: "month", label: "Monthly" },
2657
+ { value: "year", label: "Yearly" },
2658
+ { value: "one-time", label: "One-time" }
2659
+ ];
2660
+ var blank = { code: "", name: "", price: "0", currency: "INR", interval: "month", seats: "", limits: "", features: "" };
2661
+ function toForm(plan) {
2662
+ var _a, _b, _c, _d, _e;
2663
+ return {
2664
+ code: plan.code,
2665
+ name: plan.name,
2666
+ price: String((Number((_a = plan.priceMinor) != null ? _a : 0) / 100).toFixed(2)).replace(/\.00$/, ""),
2667
+ currency: (_b = plan.currency) != null ? _b : "INR",
2668
+ interval: (_c = plan.interval) != null ? _c : "month",
2669
+ seats: plan.seats == null ? "" : String(plan.seats),
2670
+ limits: Object.entries((_d = plan.limits) != null ? _d : {}).map(([k, v]) => `${k} = ${v != null ? v : ""}`).join("\n"),
2671
+ features: ((_e = plan.features) != null ? _e : []).join(", ")
2672
+ };
2673
+ }
2674
+ function planBody(form) {
2675
+ const limits = {};
2676
+ for (const line of form.limits.split("\n")) {
2677
+ const [k, ...rest] = line.split("=");
2678
+ if (!k.trim()) continue;
2679
+ const v = rest.join("=").trim();
2680
+ limits[k.trim()] = v === "" ? null : Number(v);
2681
+ }
2682
+ return {
2683
+ code: form.code.trim().toLowerCase(),
2684
+ name: form.name.trim(),
2685
+ priceMinor: Math.round(Number(form.price || 0) * 100),
2686
+ currency: form.currency.trim().toUpperCase(),
2687
+ interval: form.interval,
2688
+ seats: form.seats.trim() === "" ? null : Number(form.seats),
2689
+ limits,
2690
+ features: form.features.split(",").map((f) => f.trim()).filter(Boolean)
2691
+ };
2692
+ }
2693
+ function PlansView() {
2694
+ var _a;
2695
+ const { client, can, Link } = usePlatform();
2696
+ const sensitive = useSensitive();
2697
+ const { data, error, loading, reload } = useLoad("plans");
2698
+ const [flash, setFlash] = (0, import_react15.useState)(null);
2699
+ const [editing, setEditing] = (0, import_react15.useState)(null);
2700
+ const [busy, setBusy] = (0, import_react15.useState)(false);
2701
+ const writable = can("plans.write");
2702
+ const plans = (_a = data == null ? void 0 : data.plans) != null ? _a : [];
2703
+ const save = async () => {
2704
+ setBusy(true);
2705
+ setFlash(null);
2706
+ try {
2707
+ const body = planBody(editing.form);
2708
+ if (editing.plan) {
2709
+ const { code: _code, ...patch } = body;
2710
+ await sensitive(() => client.patch(`plans/${encodeURIComponent(editing.plan.id)}`, patch));
2711
+ } else {
2712
+ await sensitive(() => client.post("plans", body));
2713
+ }
2714
+ setFlash({ kind: "success", msg: `${body.name} saved` });
2715
+ setEditing(null);
2716
+ await reload();
2717
+ } catch (e) {
2718
+ const msg = errorText(e);
2719
+ if (msg) setFlash({ kind: "error", msg });
2720
+ } finally {
2721
+ setBusy(false);
2722
+ }
2723
+ };
2724
+ const setStatus = async (plan, status) => {
2725
+ setFlash(null);
2726
+ try {
2727
+ await sensitive(() => client.patch(`plans/${encodeURIComponent(plan.id)}`, { status }));
2728
+ setFlash({ kind: "success", msg: `${plan.name} ${status === "archived" ? "archived" : "restored"}` });
2729
+ await reload();
2730
+ } catch (e) {
2731
+ const msg = errorText(e);
2732
+ if (msg) setFlash({ kind: "error", msg });
2733
+ }
2734
+ };
2735
+ const field = (key) => {
2736
+ var _a2;
2737
+ return {
2738
+ value: (_a2 = editing == null ? void 0 : editing.form[key]) != null ? _a2 : "",
2739
+ onChange: (e) => setEditing((s) => ({ ...s, form: { ...s.form, [key]: e.target.value } }))
2740
+ };
2741
+ };
2742
+ const valid = editing && /^[a-z0-9][a-z0-9_-]{0,47}$/.test(editing.form.code.trim().toLowerCase()) && editing.form.name.trim() && Number(editing.form.price) >= 0;
2743
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
2744
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2745
+ import_twreact_ui14.PageHeader,
2746
+ {
2747
+ title: "Plans",
2748
+ subtitle: "What companies can subscribe to. Archived plans stay on the companies already using them.",
2749
+ linkAs: Link,
2750
+ actions: writable && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Button, { leftIcon: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconPlus, {}), onClick: () => setEditing({ form: { ...blank } }), children: "New plan" })
2751
+ }
2752
+ ),
2753
+ flash && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Alert, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
2754
+ error && !data ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.ErrorState, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.LoadingState, { variant: "table", rows: 3 }) : plans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.EmptyState, { icon: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconTag, { size: 24 }), title: "No plans yet", description: writable ? "Create the first plan companies can subscribe to." : "A superadmin creates plans." }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: plans.map((p) => {
2755
+ var _a2, _b;
2756
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
2757
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-0 flex-1", children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium text-foreground", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: p.name }),
2760
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-mono text-xs text-muted", children: p.code })
2761
+ ] }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "text-xs text-muted", children: [
2763
+ formatPrice(p.priceMinor, p.currency, p.interval),
2764
+ p.seats ? ` \xB7 ${p.seats} seats` : "",
2765
+ Object.keys((_a2 = p.limits) != null ? _a2 : {}).length ? ` \xB7 ${Object.entries(p.limits).map(([k, v]) => `${k} ${v != null ? v : "\u221E"}`).join(", ")}` : ""
2766
+ ] }),
2767
+ ((_b = p.features) == null ? void 0 : _b.length) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "mt-1 text-xs text-muted", children: p.features.join(" \xB7 ") }) : null
2768
+ ] }),
2769
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatusPill, { status: p.status }),
2770
+ writable && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex gap-1", children: [
2771
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Button, { size: "sm", variant: "ghost", onClick: () => setEditing({ plan: p, form: toForm(p) }), children: "Edit" }),
2772
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Button, { size: "sm", variant: "ghost", onClick: () => setStatus(p, p.status === "archived" ? "active" : "archived"), children: p.status === "archived" ? "Restore" : "Archive" })
2773
+ ] })
2774
+ ] }, p.id);
2775
+ }) }),
2776
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2777
+ import_twreact_ui14.Dialog,
2778
+ {
2779
+ isOpen: editing !== null,
2780
+ onClose: () => setEditing(null),
2781
+ title: (editing == null ? void 0 : editing.plan) ? `Edit ${editing.plan.name}` : "New plan",
2782
+ size: "md",
2783
+ footer: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2", children: [
2784
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Button, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
2785
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Button, { loading: busy, disabled: !valid, onClick: save, children: "Save plan" })
2786
+ ] }),
2787
+ children: editing && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "grid gap-4 sm:grid-cols-2", children: [
2788
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Name", autoFocus: true, ...field("name") }),
2789
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Code", disabled: Boolean(editing.plan), helperText: editing.plan ? "Fixed once created" : "Lowercase, e.g. pro or team-yearly", ...field("code") }),
2790
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Price", type: "number", min: "0", step: "0.01", ...field("price") }),
2791
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Currency", maxLength: 3, ...field("currency") }),
2792
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Select, { label: "Billing", options: INTERVALS, value: editing.form.interval, onChange: (v) => setEditing((s) => ({ ...s, form: { ...s.form, interval: String(v) } })) }),
2793
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Seats", type: "number", min: "1", helperText: "Leave empty for no seat limit", ...field("seats") }),
2794
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "sm:col-span-2", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Textarea, { label: "Limits", rows: 3, placeholder: "users = 10\nbranches =", helperText: "One per line: name = number. Leave the number empty for unlimited.", ...field("limits") }) }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "sm:col-span-2", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_twreact_ui14.Input, { label: "Features", placeholder: "gst, reports, api", helperText: "Comma-separated", ...field("features") }) })
2796
+ ] })
2797
+ }
2798
+ )
2799
+ ] });
2800
+ }
2801
+
2112
2802
  // src/platform/PlatformConsole.jsx
2113
- var import_jsx_runtime14 = require("react/jsx-runtime");
2803
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2114
2804
  var trim = (s) => String(s != null ? s : "").replace(/^\/+|\/+$/g, "");
2115
2805
  function matchConsoleRoute(rel, pages = []) {
2116
2806
  const parts = trim(rel).split("/").filter(Boolean);
@@ -2122,7 +2812,8 @@ function matchConsoleRoute(rel, pages = []) {
2122
2812
  if (parts[1] === "new" && parts.length === 2) return { view: "new-company" };
2123
2813
  if (parts.length === 2) return { view: "company", id: decodeURIComponent(parts[1]) };
2124
2814
  }
2125
- if (parts.length === 1 && ["admins", "audit", "security"].includes(parts[0])) return { view: parts[0] };
2815
+ if (parts.length === 1 && ["admins", "audit", "security", "plans"].includes(parts[0])) return { view: parts[0] };
2816
+ if (parts.length === 1 && parts[0] === "support-access") return { view: "support" };
2126
2817
  return { view: "not-found" };
2127
2818
  }
2128
2819
  function PlatformConsole({
@@ -2148,25 +2839,25 @@ function PlatformConsole({
2148
2839
  const browser = useBrowserLocation(!routed);
2149
2840
  const pathname = routed ? givenPath : browser.pathname;
2150
2841
  const navigate = givenNavigate != null ? givenNavigate : browser.navigate;
2151
- const base2 = `/${trim(basePath)}`.replace(/\/$/, "") || "";
2152
- const client = (0, import_react13.useMemo)(
2153
- () => given != null ? given : createPlatformClient({ basePath: apiBasePath, loginPath: loginPath != null ? loginPath : `${base2}/login` }),
2154
- [given, apiBasePath, loginPath, base2]
2842
+ const base3 = `/${trim(basePath)}`.replace(/\/$/, "") || "";
2843
+ const client = (0, import_react16.useMemo)(
2844
+ () => given != null ? given : createPlatformClient({ basePath: apiBasePath, loginPath: loginPath != null ? loginPath : `${base3}/login` }),
2845
+ [given, apiBasePath, loginPath, base3]
2155
2846
  );
2156
- const Link = (0, import_react13.useMemo)(() => makeLink(linkAs, navigate), [linkAs, navigate]);
2157
- const [me, setMe] = (0, import_react13.useState)(initialMe);
2158
- const reloadMe = (0, import_react13.useCallback)(async () => {
2847
+ const Link = (0, import_react16.useMemo)(() => makeLink(linkAs, navigate), [linkAs, navigate]);
2848
+ const [me, setMe] = (0, import_react16.useState)(initialMe);
2849
+ const reloadMe = (0, import_react16.useCallback)(async () => {
2159
2850
  try {
2160
2851
  setMe(await client.get("me"));
2161
2852
  } catch (e) {
2162
2853
  if (e.status !== 401) setMe(null);
2163
2854
  }
2164
2855
  }, [client]);
2165
- (0, import_react13.useEffect)(() => {
2856
+ (0, import_react16.useEffect)(() => {
2166
2857
  void reloadMe();
2167
2858
  }, [reloadMe]);
2168
- const href = (0, import_react13.useCallback)((rel2 = "") => `${base2}/${trim(rel2)}`.replace(/\/$/, "") || "/", [base2]);
2169
- const companyLoginPath = (0, import_react13.useCallback)(
2859
+ const href = (0, import_react16.useCallback)((rel2 = "") => `${base3}/${trim(rel2)}`.replace(/\/$/, "") || "/", [base3]);
2860
+ const companyLoginPath = (0, import_react16.useCallback)(
2170
2861
  (slug) => {
2171
2862
  var _a2, _b2;
2172
2863
  const pattern = (_b2 = loginPathOpt != null ? loginPathOpt : (_a2 = me == null ? void 0 : me.app) == null ? void 0 : _a2.companyLoginPath) != null ? _b2 : "/c/:slug/login";
@@ -2175,7 +2866,7 @@ function PlatformConsole({
2175
2866
  [loginPathOpt, me]
2176
2867
  );
2177
2868
  const origin = typeof window === "undefined" ? "" : window.location.origin;
2178
- const can = (0, import_react13.useCallback)((perm) => (me == null ? void 0 : me.permissions) ? me.permissions.includes(perm) : true, [me]);
2869
+ const can = (0, import_react16.useCallback)((perm) => (me == null ? void 0 : me.permissions) ? me.permissions.includes(perm) : true, [me]);
2179
2870
  const enabled = enabledFeatures(features, me);
2180
2871
  const visiblePages = pages.filter((p) => (!p.feature || enabled.includes(p.feature)) && (!p.permission || can(p.permission)));
2181
2872
  const signOut = async () => {
@@ -2183,20 +2874,22 @@ function PlatformConsole({
2183
2874
  await client.post("auth/logout", {}, { skipAuthRedirect: true });
2184
2875
  } finally {
2185
2876
  if (onSignOut) onSignOut();
2186
- else if (typeof window !== "undefined") window.location.assign(loginPath != null ? loginPath : `${base2}/login`);
2877
+ else if (typeof window !== "undefined") window.location.assign(loginPath != null ? loginPath : `${base3}/login`);
2187
2878
  }
2188
2879
  };
2189
- const rel = pathname.startsWith(base2) ? pathname.slice(base2.length) : pathname;
2880
+ const rel = pathname.startsWith(base3) ? pathname.slice(base3.length) : pathname;
2190
2881
  const route = matchConsoleRoute(rel, visiblePages);
2191
2882
  const gate = me && me.access && me.access.console === false;
2192
2883
  const active = (prefix) => prefix === "" ? route.view === "overview" : trim(rel) === prefix || trim(rel).startsWith(`${prefix}/`);
2193
- const nav = gate ? [{ id: "security", label: "Set up authenticator", href: href("security"), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconShield, {}), active: true }] : [
2194
- enabled.includes("overview") && { id: "overview", label: "Overview", href: href(""), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconDashboard, {}), active: active("") },
2195
- enabled.includes("companies") && { id: "companies", label: "Companies", href: href("companies"), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconBuilding, {}), active: active("companies") },
2884
+ const nav = gate ? [{ id: "security", label: "Set up authenticator", href: href("security"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconShield, {}), active: true }] : [
2885
+ enabled.includes("overview") && { id: "overview", label: "Overview", href: href(""), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconDashboard, {}), active: active("") },
2886
+ enabled.includes("companies") && { id: "companies", label: "Companies", href: href("companies"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconBuilding, {}), active: active("companies") },
2196
2887
  ...visiblePages.map((p) => ({ id: `page:${p.path}`, label: p.label, href: href(p.path), icon: p.icon, group: p.group, active: active(trim(p.path)) })),
2197
- enabled.includes("audit") && can("audit.read") && { id: "audit", label: "Audit log", href: href("audit"), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconList, {}), active: active("audit"), group: "Platform" },
2198
- enabled.includes("admins") && can("admins.read") && { id: "admins", label: "Platform admins", href: href("admins"), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconUsers, {}), active: active("admins"), group: "Platform" },
2199
- { id: "security", label: "Security", href: href("security"), icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconShield, {}), active: active("security"), group: "Platform" }
2888
+ enabled.includes("plans") && can("plans.read") && { id: "plans", label: "Plans", href: href("plans"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconTag, {}), active: active("plans") },
2889
+ enabled.includes("support") && can("support.request") && { id: "support", label: "Support access", href: href("support-access"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconLifebuoy, {}), active: active("support-access") },
2890
+ enabled.includes("audit") && can("audit.read") && { id: "audit", label: "Audit log", href: href("audit"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconList, {}), active: active("audit"), group: "Platform" },
2891
+ enabled.includes("admins") && can("admins.read") && { id: "admins", label: "Platform admins", href: href("admins"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconUsers, {}), active: active("admins"), group: "Platform" },
2892
+ { id: "security", label: "Security", href: href("security"), icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconShield, {}), active: active("security"), group: "Platform" }
2200
2893
  ].filter(Boolean);
2201
2894
  const value = {
2202
2895
  client,
@@ -2207,36 +2900,39 @@ function PlatformConsole({
2207
2900
  navigate,
2208
2901
  can,
2209
2902
  companyTabs,
2903
+ features: enabled,
2210
2904
  companyLoginPath,
2211
2905
  companyLoginUrl: (slug) => `${origin}${companyLoginPath(slug)}`,
2212
- loginUrl: `${origin}${loginPath != null ? loginPath : `${base2}/login`}`,
2906
+ loginUrl: `${origin}${loginPath != null ? loginPath : `${base3}/login`}`,
2213
2907
  brand,
2214
2908
  appName: appName != null ? appName : (_a = me == null ? void 0 : me.app) == null ? void 0 : _a.name
2215
2909
  };
2216
2910
  let body;
2217
- if (me === void 0) body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "p-6", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.LoadingState, { variant: "stats" }) });
2218
- else if (gate) body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SecurityView, { gate: true });
2219
- else if (route.view === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(OverviewView, {});
2220
- else if (route.view === "companies") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CompaniesView, {});
2221
- else if (route.view === "new-company") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(NewCompanyView, {});
2222
- else if (route.view === "company") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CompanyDetailView, { companyId: route.id }, route.id);
2223
- else if (route.view === "admins") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AdminsView, {});
2224
- else if (route.view === "audit") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AuditView, {});
2225
- else if (route.view === "security") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SecurityView, {});
2911
+ if (me === void 0) body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "p-6", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui15.LoadingState, { variant: "stats" }) });
2912
+ else if (gate) body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SecurityView, { gate: true });
2913
+ else if (route.view === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(OverviewView, {});
2914
+ else if (route.view === "companies") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CompaniesView, {});
2915
+ else if (route.view === "new-company") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(NewCompanyView, {});
2916
+ else if (route.view === "company") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CompanyDetailView, { companyId: route.id }, route.id);
2917
+ else if (route.view === "admins") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AdminsView, {});
2918
+ else if (route.view === "audit") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AuditView, {});
2919
+ else if (route.view === "security") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SecurityView, {});
2920
+ else if (route.view === "plans" && enabled.includes("plans") && can("plans.read")) body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlansView, {});
2921
+ else if (route.view === "support" && enabled.includes("support") && can("support.request")) body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SupportAccessView, {});
2226
2922
  else if (route.view === "page") body = route.page.render({ client, path: route.rest, navigate, href, me });
2227
- else body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "p-6", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_twreact_ui12.EmptyState, { title: "Page not found", description: "This console page does not exist." }) });
2228
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PlatformProvider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(StepUpProvider, { client, totpEnrolled: Boolean((_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.totpEnrolled), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PlatformShell, { brand, appName: value.appName, nav, adminEmail: (_d = (_c = me == null ? void 0 : me.admin) == null ? void 0 : _c.email) != null ? _d : adminEmail, onSignOut: signOut, linkAs: Link, pageTitle: (_e = nav.find((n) => n.active)) == null ? void 0 : _e.label, children: body }) }) });
2923
+ else body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "p-6", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui15.EmptyState, { title: "Page not found", description: "This console page does not exist." }) });
2924
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlatformProvider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StepUpProvider, { client, totpEnrolled: Boolean((_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.totpEnrolled), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlatformShell, { brand, appName: value.appName, nav, adminEmail: (_d = (_c = me == null ? void 0 : me.admin) == null ? void 0 : _c.email) != null ? _d : adminEmail, onSignOut: signOut, linkAs: Link, pageTitle: (_e = nav.find((n) => n.active)) == null ? void 0 : _e.label, children: body }) }) });
2229
2925
  }
2230
2926
 
2231
2927
  // src/platform/PlatformLogin.jsx
2232
- var import_react17 = __toESM(require("react"), 1);
2233
- var import_twreact_ui13 = require("@dreamtree-org/twreact-ui");
2928
+ var import_react22 = __toESM(require("react"), 1);
2929
+ var import_twreact_ui16 = require("@dreamtree-org/twreact-ui");
2234
2930
 
2235
2931
  // src/AuthShell.jsx
2236
- var import_react16 = require("react");
2932
+ var import_react21 = require("react");
2237
2933
 
2238
2934
  // src/DemoAccounts.jsx
2239
- var import_react14 = require("react");
2935
+ var import_react17 = require("react");
2240
2936
 
2241
2937
  // src/demoFill.js
2242
2938
  function normalizeDemoAccounts(data) {
@@ -2250,12 +2946,12 @@ function normalizeDemoAccounts(data) {
2250
2946
  }
2251
2947
 
2252
2948
  // src/DemoAccounts.jsx
2253
- var import_jsx_runtime15 = require("react/jsx-runtime");
2949
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2254
2950
  function DemoAccounts({ load, onSelect, className = "" }) {
2255
- const [state, setState] = (0, import_react14.useState)({ status: "loading", accounts: [] });
2256
- const [picked, setPicked] = (0, import_react14.useState)(null);
2257
- const loadRef = (0, import_react14.useRef)(load);
2258
- (0, import_react14.useEffect)(() => {
2951
+ const [state, setState] = (0, import_react17.useState)({ status: "loading", accounts: [] });
2952
+ const [picked, setPicked] = (0, import_react17.useState)(null);
2953
+ const loadRef = (0, import_react17.useRef)(load);
2954
+ (0, import_react17.useEffect)(() => {
2259
2955
  let live = true;
2260
2956
  Promise.resolve().then(() => loadRef.current()).then((data) => live && setState({ status: "ready", ...normalizeDemoAccounts(data) })).catch(() => live && setState({ status: "ready", accounts: [] }));
2261
2957
  return () => {
@@ -2267,10 +2963,10 @@ function DemoAccounts({ load, onSelect, className = "" }) {
2267
2963
  setPicked(i);
2268
2964
  onSelect == null ? void 0 : onSelect(account);
2269
2965
  };
2270
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `dt-auth-demo ${className}`, "aria-busy": state.status === "loading" || void 0, children: [
2271
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "dt-auth-demo-label", children: "Demo accounts" }),
2272
- state.status === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dt-auth-demo-chips", "aria-hidden": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "dt-auth-demo-chip dt-auth-demo-chip--skeleton" }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2273
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dt-auth-demo-chips", children: state.accounts.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2966
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `dt-auth-demo ${className}`, "aria-busy": state.status === "loading" || void 0, children: [
2967
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "dt-auth-demo-label", children: "Demo accounts" }),
2968
+ state.status === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dt-auth-demo-chips", "aria-hidden": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dt-auth-demo-chip dt-auth-demo-chip--skeleton" }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2969
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dt-auth-demo-chips", children: state.accounts.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2274
2970
  "button",
2275
2971
  {
2276
2972
  type: "button",
@@ -2282,22 +2978,125 @@ function DemoAccounts({ load, onSelect, className = "" }) {
2282
2978
  },
2283
2979
  `${a.label}-${a.identifier}`
2284
2980
  )) }),
2285
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "dt-auth-demo-note", children: state.sharedPassword ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2981
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "dt-auth-demo-note", children: state.sharedPassword ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2286
2982
  "Shared password ",
2287
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("code", { children: state.sharedPassword }),
2983
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("code", { children: state.sharedPassword }),
2288
2984
  " \u2014 tap a role to fill."
2289
2985
  ] }) : "Tap a role to fill." })
2290
2986
  ] })
2291
2987
  ] });
2292
2988
  }
2293
2989
 
2990
+ // src/DreamtreeSso.jsx
2991
+ var import_react19 = require("react");
2992
+
2993
+ // src/sso.js
2994
+ function decodeKey(part) {
2995
+ const key = part.split("=")[0];
2996
+ try {
2997
+ return decodeURIComponent(key.replace(/\+/g, " "));
2998
+ } catch {
2999
+ return key;
3000
+ }
3001
+ }
3002
+ function buildSsoUrl(startUrl, { returnTo, companySlug } = {}) {
3003
+ const raw = String(startUrl != null ? startUrl : "");
3004
+ const hashAt = raw.indexOf("#");
3005
+ const hash = hashAt === -1 ? "" : raw.slice(hashAt);
3006
+ const beforeHash = hashAt === -1 ? raw : raw.slice(0, hashAt);
3007
+ const queryAt = beforeHash.indexOf("?");
3008
+ const path = queryAt === -1 ? beforeHash : beforeHash.slice(0, queryAt);
3009
+ const query = queryAt === -1 ? "" : beforeHash.slice(queryAt + 1);
3010
+ const add = [
3011
+ ["return_to", returnTo],
3012
+ ["dt_company", companySlug]
3013
+ ].filter(([, v]) => v != null && v !== "");
3014
+ const replaced = new Set(add.map(([k]) => k));
3015
+ const params = query.split("&").filter((p) => p && !replaced.has(decodeKey(p))).concat(add.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`));
3016
+ return `${path}${params.length ? `?${params.join("&")}` : ""}${hash}`;
3017
+ }
3018
+
3019
+ // src/icons.jsx
3020
+ var import_react18 = require("react");
3021
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3022
+ var base2 = {
3023
+ viewBox: "0 0 24 24",
3024
+ fill: "none",
3025
+ stroke: "currentColor",
3026
+ strokeWidth: 2,
3027
+ strokeLinecap: "round",
3028
+ strokeLinejoin: "round",
3029
+ "aria-hidden": "true",
3030
+ focusable: "false"
3031
+ };
3032
+ var make2 = (paths) => function Icon({ size = 16, className = "dt-lp-icon" }) {
3033
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { ...base2, width: size, height: size, className, children: paths });
3034
+ };
3035
+ var ArrowRight = make2(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M5 12h14M13 6l6 6-6 6" }));
3036
+ var ChevronRight = make2(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "m9 6 6 6-6 6" }));
3037
+ var Check = make2(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M20 6 9 17l-5-5" }));
3038
+ var Help = make2(
3039
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
3040
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
3041
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01" })
3042
+ ] })
3043
+ );
3044
+ var Alert9 = make2(
3045
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
3046
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
3047
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M12 9v4M12 17h.01" })
3048
+ ] })
3049
+ );
3050
+ var MenuIcon = make2(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M4 6h16M4 12h16M4 18h16" }));
3051
+ var Close = make2(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }));
3052
+ var Calendar = make2(
3053
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
3054
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
3055
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M16 2v4M8 2v4M3 10h18m-12 6 2 2 4-4" })
3056
+ ] })
3057
+ );
3058
+
3059
+ // src/DreamtreeSso.jsx
3060
+ var import_jsx_runtime20 = require("react/jsx-runtime");
3061
+ var DEFAULT_SSO_LABEL = "Continue with Dreamtree";
3062
+ function DreamtreeSso({ provider, children }) {
3063
+ const { startUrl, mode, label = DEFAULT_SSO_LABEL, companySlug, returnTo } = provider;
3064
+ const isSso = mode === "sso";
3065
+ const autoRedirect = isSso && provider.autoRedirect !== false;
3066
+ const href = buildSsoUrl(startUrl, { returnTo, companySlug });
3067
+ const redirected = (0, import_react19.useRef)(false);
3068
+ (0, import_react19.useEffect)(() => {
3069
+ if (!autoRedirect || redirected.current) return;
3070
+ redirected.current = true;
3071
+ window.location.assign(href);
3072
+ }, [autoRedirect, href]);
3073
+ const button = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("a", { className: "dt-auth-sso-btn", href, children: [
3074
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: label }),
3075
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ArrowRight, { size: 16, className: "dt-auth-sso-icon" })
3076
+ ] });
3077
+ if (isSso) {
3078
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dt-auth-sso dt-auth-sso--only", children: [
3079
+ autoRedirect ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "dt-auth-sso-status", role: "status", "aria-live": "polite", children: [
3080
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "dt-auth-sso-spinner", "aria-hidden": "true" }),
3081
+ "Redirecting to Dreamtree ID\u2026"
3082
+ ] }) : null,
3083
+ button
3084
+ ] });
3085
+ }
3086
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
3087
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dt-auth-sso", children: button }),
3088
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dt-auth-or", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: "or" }) }),
3089
+ children
3090
+ ] });
3091
+ }
3092
+
2294
3093
  // src/useTapReveal.js
2295
- var import_react15 = require("react");
3094
+ var import_react20 = require("react");
2296
3095
  function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
2297
- const [revealed, setRevealed] = (0, import_react15.useState)(false);
2298
- const count = (0, import_react15.useRef)(0);
2299
- const last = (0, import_react15.useRef)(0);
2300
- const onClick = (0, import_react15.useCallback)(() => {
3096
+ const [revealed, setRevealed] = (0, import_react20.useState)(false);
3097
+ const count = (0, import_react20.useRef)(0);
3098
+ const last = (0, import_react20.useRef)(0);
3099
+ const onClick = (0, import_react20.useCallback)(() => {
2301
3100
  if (revealed) return;
2302
3101
  const now = Date.now();
2303
3102
  count.current = now - last.current <= windowMs ? count.current + 1 : 1;
@@ -2308,7 +3107,7 @@ function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
2308
3107
  }
2309
3108
 
2310
3109
  // src/AuthShell.jsx
2311
- var import_jsx_runtime16 = require("react/jsx-runtime");
3110
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2312
3111
  var DEFAULT_LEGAL = [
2313
3112
  { label: "Privacy Policy", href: "/privacy" },
2314
3113
  { label: "Terms", href: "/terms" },
@@ -2316,10 +3115,10 @@ var DEFAULT_LEGAL = [
2316
3115
  ];
2317
3116
  function BrandMark({ brand, size }) {
2318
3117
  if (brand.logoSrc) {
2319
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("img", { className: "dt-auth-mark-img", src: brand.logoSrc, alt: "", width: size, height: size });
3118
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { className: "dt-auth-mark-img", src: brand.logoSrc, alt: "", width: size, height: size });
2320
3119
  }
2321
- if (brand.mark) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-mark-node", children: brand.mark });
2322
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-monogram", style: { width: size, height: size }, "aria-hidden": "true", children: brand.name.charAt(0) });
3120
+ if (brand.mark) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-mark-node", children: brand.mark });
3121
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-monogram", style: { width: size, height: size }, "aria-hidden": "true", children: brand.name.charAt(0) });
2323
3122
  }
2324
3123
  function brandStyle(brand) {
2325
3124
  return {
@@ -2338,52 +3137,53 @@ function AuthShell({
2338
3137
  legalLinks = DEFAULT_LEGAL,
2339
3138
  linkComponent: LinkComponent,
2340
3139
  className = "",
2341
- demoAccounts
3140
+ demoAccounts,
3141
+ ssoProvider
2342
3142
  }) {
2343
3143
  var _a, _b;
2344
- const headingId = (0, import_react16.useId)();
3144
+ const headingId = (0, import_react21.useId)();
2345
3145
  const { revealed, tapProps } = useTapReveal({ taps: (_a = demoAccounts == null ? void 0 : demoAccounts.taps) != null ? _a : 4 });
2346
3146
  const isPlatform = variant === "platform";
2347
3147
  const headline = isPlatform ? brand.platformHeadline || "Platform administration" : brand.headline;
2348
3148
  const pitch = isPlatform ? brand.platformPitch || `Super administrator console for ${brand.name}. Every action here is audited.` : brand.pitch;
2349
- const renderLink = (l) => LinkComponent ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(LinkComponent, { to: l.href, href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("a", { href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href);
2350
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3149
+ const renderLink = (l) => LinkComponent ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LinkComponent, { to: l.href, href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("a", { href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href);
3150
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2351
3151
  "div",
2352
3152
  {
2353
3153
  className: `dt-auth ${isPlatform ? "dt-auth--platform" : ""} ${className}`,
2354
3154
  style: brandStyle(brand),
2355
3155
  "data-product": brand.id,
2356
3156
  children: [
2357
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("aside", { className: "dt-auth-panel", children: [
2358
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("a", { href: "/", className: "dt-auth-brand", children: [
2359
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BrandMark, { brand, size: 28 }),
2360
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-brand-name", children: brand.name }),
2361
- isPlatform ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-badge", children: "Platform" }) : null
3157
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("aside", { className: "dt-auth-panel", children: [
3158
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("a", { href: "/", className: "dt-auth-brand", children: [
3159
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BrandMark, { brand, size: 28 }),
3160
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-brand-name", children: brand.name }),
3161
+ isPlatform ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-badge", children: "Platform" }) : null
2362
3162
  ] }),
2363
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "dt-auth-pitch", children: [
2364
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h2", { children: headline }),
2365
- pitch ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: pitch }) : null,
2366
- !isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { children: brand.points.map((p) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: p }, p)) }) : null
3163
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "dt-auth-pitch", children: [
3164
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h2", { children: headline }),
3165
+ pitch ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: pitch }) : null,
3166
+ !isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { children: brand.points.map((p) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: p }, p)) }) : null
2367
3167
  ] }),
2368
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "dt-auth-panel-foot", children: brand.category || "by Dreamtree-Org" })
3168
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "dt-auth-panel-foot", children: brand.category || "by Dreamtree-Org" })
2369
3169
  ] }),
2370
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("main", { className: "dt-auth-main", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "dt-auth-column", children: [
2371
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("a", { href: "/", className: "dt-auth-brand dt-auth-brand--mobile", children: [
2372
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BrandMark, { brand, size: 24 }),
2373
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-brand-name", children: brand.name }),
2374
- isPlatform ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dt-auth-badge", children: "Platform" }) : null
3170
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("main", { className: "dt-auth-main", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "dt-auth-column", children: [
3171
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("a", { href: "/", className: "dt-auth-brand dt-auth-brand--mobile", children: [
3172
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BrandMark, { brand, size: 24 }),
3173
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-brand-name", children: brand.name }),
3174
+ isPlatform ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "dt-auth-badge", children: "Platform" }) : null
2375
3175
  ] }),
2376
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "dt-auth-card", "aria-labelledby": headingId, children: [
2377
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "dt-auth-head", children: [
2378
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h1", { id: headingId, ...demoAccounts ? tapProps : {}, children: title }),
2379
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: subtitle }) : null
3176
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: "dt-auth-card", "aria-labelledby": headingId, children: [
3177
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("header", { className: "dt-auth-head", children: [
3178
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h1", { id: headingId, ...demoAccounts ? tapProps : {}, children: title }),
3179
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: subtitle }) : null
2380
3180
  ] }),
2381
- children,
2382
- demoAccounts && revealed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DemoAccounts, { load: demoAccounts.load, onSelect: demoAccounts.onSelect }) : null
3181
+ ssoProvider ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DreamtreeSso, { provider: ssoProvider, children }) : children,
3182
+ demoAccounts && revealed && (ssoProvider == null ? void 0 : ssoProvider.mode) !== "sso" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DemoAccounts, { load: demoAccounts.load, onSelect: demoAccounts.onSelect }) : null
2383
3183
  ] }),
2384
- footer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "dt-auth-footer", children: footer }) : null,
2385
- (legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("nav", { className: "dt-auth-legal", "aria-label": "Legal", children: legalLinks.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "dt-auth-legal-item", children: [
2386
- i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { "aria-hidden": "true", children: "\xB7" }) : null,
3184
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "dt-auth-footer", children: footer }) : null,
3185
+ (legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("nav", { className: "dt-auth-legal", "aria-label": "Legal", children: legalLinks.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "dt-auth-legal-item", children: [
3186
+ i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": "true", children: "\xB7" }) : null,
2387
3187
  renderLink(l)
2388
3188
  ] }, l.href)) }) : null
2389
3189
  ] }) })
@@ -2393,17 +3193,17 @@ function AuthShell({
2393
3193
  }
2394
3194
 
2395
3195
  // src/platform/PlatformLogin.jsx
2396
- var import_jsx_runtime17 = require("react/jsx-runtime");
3196
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2397
3197
  function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo = "/platform", title = "Platform administration", subtitle = "Administrator sign-in", linkComponent, legalLinks }) {
2398
- const client = (0, import_react17.useMemo)(() => given != null ? given : createPlatformClient({ basePath, onUnauthorized: () => {
3198
+ const client = (0, import_react22.useMemo)(() => given != null ? given : createPlatformClient({ basePath, onUnauthorized: () => {
2399
3199
  } }), [given, basePath]);
2400
- const [step, setStep] = (0, import_react17.useState)("password");
2401
- const [email, setEmail] = (0, import_react17.useState)("");
2402
- const [password, setPassword] = (0, import_react17.useState)("");
2403
- const [ticket, setTicket] = (0, import_react17.useState)(null);
2404
- const [code, setCode] = (0, import_react17.useState)("");
2405
- const [busy, setBusy] = (0, import_react17.useState)(false);
2406
- const [error, setError] = (0, import_react17.useState)(null);
3200
+ const [step, setStep] = (0, import_react22.useState)("password");
3201
+ const [email, setEmail] = (0, import_react22.useState)("");
3202
+ const [password, setPassword] = (0, import_react22.useState)("");
3203
+ const [ticket, setTicket] = (0, import_react22.useState)(null);
3204
+ const [code, setCode] = (0, import_react22.useState)("");
3205
+ const [busy, setBusy] = (0, import_react22.useState)(false);
3206
+ const [error, setError] = (0, import_react22.useState)(null);
2407
3207
  const done = (res) => {
2408
3208
  if (onSignedIn) onSignedIn(res);
2409
3209
  else if (typeof window !== "undefined") window.location.assign(redirectTo);
@@ -2443,18 +3243,18 @@ function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo
2443
3243
  setBusy(false);
2444
3244
  }
2445
3245
  };
2446
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AuthShell, { brand, variant: "platform", title, subtitle, linkComponent, legalLinks, children: step === "password" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("form", { onSubmit: submitPassword, className: "flex flex-col gap-4", children: [
2447
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Input, { label: "Admin email", type: "email", autoComplete: "username", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
2448
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Input, { label: "Password", type: "password", autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value) }),
2449
- error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Alert, { variant: "error", children: error }),
2450
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Button, { type: "submit", loading: busy, disabled: !email || !password, fullWidth: true, children: "Sign in" })
2451
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("form", { onSubmit: submitCode, className: "flex flex-col gap-4", children: [
2452
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm", children: "Enter the 6-digit code from your authenticator app." }),
2453
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Input, { label: "Authenticator code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }),
2454
- error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Alert, { variant: "error", children: error }),
2455
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_twreact_ui13.Button, { type: "submit", loading: busy, disabled: code.length < 6, fullWidth: true, children: "Verify" }),
2456
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2457
- import_twreact_ui13.Button,
3246
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AuthShell, { brand, variant: "platform", title, subtitle, linkComponent, legalLinks, children: step === "password" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("form", { onSubmit: submitPassword, className: "flex flex-col gap-4", children: [
3247
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Input, { label: "Admin email", type: "email", autoComplete: "username", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
3248
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Input, { label: "Password", type: "password", autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value) }),
3249
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Alert, { variant: "error", children: error }),
3250
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Button, { type: "submit", loading: busy, disabled: !email || !password, fullWidth: true, children: "Sign in" })
3251
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("form", { onSubmit: submitCode, className: "flex flex-col gap-4", children: [
3252
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-sm", children: "Enter the 6-digit code from your authenticator app." }),
3253
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Input, { label: "Authenticator code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }),
3254
+ error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Alert, { variant: "error", children: error }),
3255
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_twreact_ui16.Button, { type: "submit", loading: busy, disabled: code.length < 6, fullWidth: true, children: "Verify" }),
3256
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3257
+ import_twreact_ui16.Button,
2458
3258
  {
2459
3259
  variant: "ghost",
2460
3260
  onClick: () => {