@dreamtree-org/landing-kit 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +16 -0
- package/PLATFORM-API.md +17 -13
- package/README.md +1 -1
- package/ai-skills/landing-kit.md +7 -6
- package/dist/platform.cjs +1094 -398
- package/dist/platform.js +1092 -396
- package/package.json +1 -1
- package/platform.d.ts +37 -3
package/dist/platform.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var PLATFORM_API_BASE = "/api/platform";
|
|
5
5
|
var PLATFORM_LOGIN_PATH = "/platform/login";
|
|
6
6
|
var STEP_UP_REQUIRED = "STEP_UP_REQUIRED";
|
|
7
|
-
var PLATFORM_FEATURES = ["overview", "companies", "jobs", "plans", "audit", "support", "settings", "admins"];
|
|
7
|
+
var PLATFORM_FEATURES = ["overview", "companies", "jobs", "plans", "audit", "support", "settings", "domains", "admins"];
|
|
8
8
|
var SHIPPED_FEATURES = ["overview", "companies", "jobs", "admins"];
|
|
9
9
|
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
10
10
|
var CSRF_HEADER = "x-csrf-token";
|
|
@@ -147,8 +147,8 @@ function timeAgo(value, now = Date.now()) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// src/platform/PlatformConsole.jsx
|
|
150
|
-
import
|
|
151
|
-
import { EmptyState as
|
|
150
|
+
import React16, { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo3, useState as useState14 } from "react";
|
|
151
|
+
import { EmptyState as EmptyState7, LoadingState as LoadingState9 } from "@dreamtree-org/twreact-ui";
|
|
152
152
|
|
|
153
153
|
// src/platform/context.jsx
|
|
154
154
|
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
@@ -400,6 +400,23 @@ var IconArchive = make(/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
|
400
400
|
/* @__PURE__ */ jsx3("path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8M10 12h4" })
|
|
401
401
|
] }));
|
|
402
402
|
var IconDownload = make(/* @__PURE__ */ jsx3("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" }));
|
|
403
|
+
var IconTag = make(/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
404
|
+
/* @__PURE__ */ jsx3("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" }),
|
|
405
|
+
/* @__PURE__ */ jsx3("circle", { cx: "7.5", cy: "7.5", r: "1.5" })
|
|
406
|
+
] }));
|
|
407
|
+
var IconLifebuoy = make(/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
408
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "10" }),
|
|
409
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "4" }),
|
|
410
|
+
/* @__PURE__ */ jsx3("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" })
|
|
411
|
+
] }));
|
|
412
|
+
var IconGlobe = make(/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
413
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "10" }),
|
|
414
|
+
/* @__PURE__ */ jsx3("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" })
|
|
415
|
+
] }));
|
|
416
|
+
var IconSettings = make(/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
417
|
+
/* @__PURE__ */ jsx3("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" }),
|
|
418
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "3" })
|
|
419
|
+
] }));
|
|
403
420
|
|
|
404
421
|
// src/platform/PlatformShell.jsx
|
|
405
422
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -473,7 +490,13 @@ var STATUS_MAP = {
|
|
|
473
490
|
running: { label: "Running", tone: "info" },
|
|
474
491
|
pending: { label: "Pending", tone: "neutral" },
|
|
475
492
|
queued: { label: "Queued", tone: "neutral" },
|
|
476
|
-
cancelled: { label: "Cancelled", tone: "muted" }
|
|
493
|
+
cancelled: { label: "Cancelled", tone: "muted" },
|
|
494
|
+
trial: { label: "Trial", tone: "info" },
|
|
495
|
+
past_due: { label: "Past due", tone: "warning" },
|
|
496
|
+
expired: { label: "Expired", tone: "muted" },
|
|
497
|
+
requested: { label: "Awaiting approval", tone: "warning" },
|
|
498
|
+
revoked: { label: "Revoked", tone: "muted" },
|
|
499
|
+
verified: { label: "Verified", tone: "success" }
|
|
477
500
|
};
|
|
478
501
|
function StatusPill({ status }) {
|
|
479
502
|
return /* @__PURE__ */ jsx5(TwStatusPill, { status: String(status != null ? status : ""), map: STATUS_MAP });
|
|
@@ -630,8 +653,31 @@ var ACTION_LABEL = {
|
|
|
630
653
|
"platform.admin.roles": "changed platform admin roles",
|
|
631
654
|
"platform.admin.disable": "disabled platform admin",
|
|
632
655
|
"platform.admin.enable": "enabled platform admin",
|
|
633
|
-
"audit.export": "exported the audit log"
|
|
656
|
+
"audit.export": "exported the audit log",
|
|
657
|
+
"plan.create": "created plan",
|
|
658
|
+
"plan.update": "updated plan",
|
|
659
|
+
"subscription.update": "changed subscription",
|
|
660
|
+
"settings.update": "changed settings",
|
|
661
|
+
"support.grant.request": "requested support access",
|
|
662
|
+
"support.grant.approve": "approved support access",
|
|
663
|
+
"support.grant.revoke": "revoked support access",
|
|
664
|
+
"domain.add": "added domain",
|
|
665
|
+
"domain.verify": "verified domain",
|
|
666
|
+
"domain.primary": "set primary domain",
|
|
667
|
+
"domain.remove": "removed domain"
|
|
634
668
|
};
|
|
669
|
+
function formatPrice(priceMinor, currency = "INR", interval) {
|
|
670
|
+
var _a;
|
|
671
|
+
const amount = Number(priceMinor != null ? priceMinor : 0) / 100;
|
|
672
|
+
let text;
|
|
673
|
+
try {
|
|
674
|
+
text = new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
|
|
675
|
+
} catch {
|
|
676
|
+
text = `${currency} ${amount.toFixed(2)}`;
|
|
677
|
+
}
|
|
678
|
+
const per = (_a = { month: " / month", year: " / year", "one-time": " once" }[interval]) != null ? _a : "";
|
|
679
|
+
return `${text}${per}`;
|
|
680
|
+
}
|
|
635
681
|
function describeAction(action) {
|
|
636
682
|
var _a;
|
|
637
683
|
return (_a = ACTION_LABEL[action]) != null ? _a : String(action != null ? action : "").replace(/[._]/g, " ");
|
|
@@ -1108,30 +1154,514 @@ function NewCompanyView() {
|
|
|
1108
1154
|
}
|
|
1109
1155
|
|
|
1110
1156
|
// src/platform/views/CompanyDetail.jsx
|
|
1111
|
-
import
|
|
1157
|
+
import React11, { useCallback as useCallback3, useEffect as useEffect5, useState as useState9 } from "react";
|
|
1112
1158
|
import {
|
|
1113
|
-
Alert as
|
|
1114
|
-
Button as
|
|
1115
|
-
ConfirmDialog,
|
|
1116
|
-
Dialog as
|
|
1117
|
-
EmptyState as
|
|
1118
|
-
ErrorState as
|
|
1119
|
-
Input as
|
|
1120
|
-
LoadingState as
|
|
1121
|
-
PageHeader as
|
|
1159
|
+
Alert as Alert4,
|
|
1160
|
+
Button as Button8,
|
|
1161
|
+
ConfirmDialog as ConfirmDialog2,
|
|
1162
|
+
Dialog as Dialog4,
|
|
1163
|
+
EmptyState as EmptyState4,
|
|
1164
|
+
ErrorState as ErrorState5,
|
|
1165
|
+
Input as Input6,
|
|
1166
|
+
LoadingState as LoadingState5,
|
|
1167
|
+
PageHeader as PageHeader5,
|
|
1122
1168
|
Tabs,
|
|
1123
|
-
Textarea
|
|
1169
|
+
Textarea as Textarea3
|
|
1124
1170
|
} from "@dreamtree-org/twreact-ui";
|
|
1125
|
-
|
|
1171
|
+
|
|
1172
|
+
// src/platform/views/CompanyFeatures.jsx
|
|
1173
|
+
import React10, { useEffect as useEffect4, useState as useState8 } from "react";
|
|
1174
|
+
import { Badge as Badge2, Button as Button7, ConfirmDialog, Dialog as Dialog3, EmptyState as EmptyState3, ErrorState as ErrorState4, Input as Input5, LoadingState as LoadingState4, Select as Select2, Switch, Textarea as Textarea2 } from "@dreamtree-org/twreact-ui";
|
|
1175
|
+
|
|
1176
|
+
// src/platform/views/SupportAccess.jsx
|
|
1177
|
+
import React9, { useState as useState7 } from "react";
|
|
1178
|
+
import { Alert as Alert3, Button as Button6, Dialog as Dialog2, EmptyState as EmptyState2, ErrorState as ErrorState3, Input as Input4, LoadingState as LoadingState3, PageHeader as PageHeader4, SegmentedControl as SegmentedControl2, Select, Textarea } from "@dreamtree-org/twreact-ui";
|
|
1179
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1180
|
+
var FILTERS2 = [
|
|
1181
|
+
{ value: "", label: "All" },
|
|
1182
|
+
{ value: "requested", label: "Awaiting approval" },
|
|
1183
|
+
{ value: "active", label: "Active" },
|
|
1184
|
+
{ value: "expired", label: "Expired" },
|
|
1185
|
+
{ value: "revoked", label: "Revoked" }
|
|
1186
|
+
];
|
|
1187
|
+
var DURATIONS = [
|
|
1188
|
+
{ value: "30", label: "30 minutes" },
|
|
1189
|
+
{ value: "60", label: "1 hour" },
|
|
1190
|
+
{ value: "120", label: "2 hours" },
|
|
1191
|
+
{ value: "240", label: "4 hours" },
|
|
1192
|
+
{ value: "480", label: "8 hours" }
|
|
1193
|
+
];
|
|
1194
|
+
function until(value) {
|
|
1195
|
+
const ms = new Date(value).getTime() - Date.now();
|
|
1196
|
+
if (!Number.isFinite(ms) || ms <= 0) return "now";
|
|
1197
|
+
const m = Math.round(ms / 6e4);
|
|
1198
|
+
return m < 60 ? `in ${m} min` : `in ${Math.floor(m / 60)} h ${m % 60} min`;
|
|
1199
|
+
}
|
|
1200
|
+
function SupportGrants({ companyId, companyName }) {
|
|
1201
|
+
var _a, _b;
|
|
1202
|
+
const { client, can, me } = usePlatform();
|
|
1203
|
+
const sensitive = useSensitive();
|
|
1204
|
+
const [status, setStatus] = useState7(companyId ? "" : "requested");
|
|
1205
|
+
const { data, error, loading, reload } = useLoad("support-access", { company: companyId || void 0, status: status || void 0 });
|
|
1206
|
+
const [flash, setFlash] = useState7(null);
|
|
1207
|
+
const [requesting, setRequesting] = useState7(false);
|
|
1208
|
+
const canGrant = can("support.grant");
|
|
1209
|
+
const myId = (_a = me == null ? void 0 : me.admin) == null ? void 0 : _a.id;
|
|
1210
|
+
const grants = (_b = data == null ? void 0 : data.grants) != null ? _b : [];
|
|
1211
|
+
const act = async (g, action) => {
|
|
1212
|
+
var _a2;
|
|
1213
|
+
setFlash(null);
|
|
1214
|
+
try {
|
|
1215
|
+
await sensitive(() => client.post(`support-access/${encodeURIComponent(g.id)}`, { action }));
|
|
1216
|
+
setFlash({ kind: "success", msg: action === "approve" ? `Approved access to ${(_a2 = g.companyName) != null ? _a2 : "the company"}` : "Access revoked" });
|
|
1217
|
+
await reload();
|
|
1218
|
+
} catch (e) {
|
|
1219
|
+
const msg = errorText(e);
|
|
1220
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
|
|
1224
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
1225
|
+
/* @__PURE__ */ jsx10("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsx10(SegmentedControl2, { "aria-label": "Filter by status", options: FILTERS2, value: status, onChange: (v) => setStatus(String(v)) }) }),
|
|
1226
|
+
can("support.request") && /* @__PURE__ */ jsx10(Button6, { variant: companyId ? "outline" : "primary", leftIcon: /* @__PURE__ */ jsx10(IconPlus, {}), onClick: () => setRequesting(true), children: "Request access" })
|
|
1227
|
+
] }),
|
|
1228
|
+
flash && /* @__PURE__ */ jsx10(Alert3, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
|
|
1229
|
+
error && !data ? /* @__PURE__ */ jsx10(ErrorState3, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ jsx10(LoadingState3, { variant: "table", rows: 3 }) : grants.length === 0 ? /* @__PURE__ */ jsx10(
|
|
1230
|
+
EmptyState2,
|
|
1231
|
+
{
|
|
1232
|
+
compact: true,
|
|
1233
|
+
icon: /* @__PURE__ */ jsx10(IconLifebuoy, { size: 24 }),
|
|
1234
|
+
title: status ? "No grants with this status" : "No support access yet",
|
|
1235
|
+
description: "Support access lets a platform admin work in one company for a limited time, after another admin approves it."
|
|
1236
|
+
}
|
|
1237
|
+
) : /* @__PURE__ */ jsx10("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: grants.map((g) => {
|
|
1238
|
+
var _a2, _b2;
|
|
1239
|
+
const mine = myId && g.requestedById === myId;
|
|
1240
|
+
return /* @__PURE__ */ jsxs9("li", { className: "flex flex-wrap items-start gap-3 px-4 py-3", children: [
|
|
1241
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
|
|
1242
|
+
/* @__PURE__ */ jsxs9("div", { className: "text-sm font-medium text-foreground", children: [
|
|
1243
|
+
g.requestedBy,
|
|
1244
|
+
!companyId && g.companyName ? /* @__PURE__ */ jsxs9("span", { className: "text-muted", children: [
|
|
1245
|
+
" \u2192 ",
|
|
1246
|
+
g.companyName
|
|
1247
|
+
] }) : null
|
|
1248
|
+
] }),
|
|
1249
|
+
/* @__PURE__ */ jsx10("div", { className: "mt-0.5 break-words text-sm text-foreground", children: g.reason }),
|
|
1250
|
+
/* @__PURE__ */ jsxs9("div", { className: "mt-0.5 text-xs text-muted", children: [
|
|
1251
|
+
g.status === "requested" && `${(_a2 = g.minutes) != null ? _a2 : "\u2014"} min once approved \xB7 requested ${timeAgo(g.createdAt)}`,
|
|
1252
|
+
g.status === "active" && `Approved by ${(_b2 = g.approvedBy) != null ? _b2 : "\u2014"} \xB7 ends ${until(g.expiresAt)}`,
|
|
1253
|
+
g.status === "expired" && `Ended ${formatDate(g.expiresAt)}`,
|
|
1254
|
+
g.status === "revoked" && `Revoked ${formatDate(g.revokedAt)}`
|
|
1255
|
+
] })
|
|
1256
|
+
] }),
|
|
1257
|
+
/* @__PURE__ */ jsx10(StatusPill, { status: g.status }),
|
|
1258
|
+
canGrant && (g.status === "requested" || g.status === "active") && /* @__PURE__ */ jsxs9("div", { className: "flex gap-1", children: [
|
|
1259
|
+
g.status === "requested" && !mine && /* @__PURE__ */ jsx10(Button6, { size: "sm", onClick: () => act(g, "approve"), children: "Approve" }),
|
|
1260
|
+
/* @__PURE__ */ jsx10(Button6, { size: "sm", variant: "ghost", onClick: () => act(g, "revoke"), children: g.status === "requested" ? "Decline" : "Revoke" })
|
|
1261
|
+
] })
|
|
1262
|
+
] }, g.id);
|
|
1263
|
+
}) }),
|
|
1264
|
+
requesting && /* @__PURE__ */ jsx10(
|
|
1265
|
+
RequestDialog,
|
|
1266
|
+
{
|
|
1267
|
+
companyId,
|
|
1268
|
+
companyName,
|
|
1269
|
+
onClose: () => setRequesting(false),
|
|
1270
|
+
onDone: async (g) => {
|
|
1271
|
+
var _a2;
|
|
1272
|
+
setRequesting(false);
|
|
1273
|
+
setFlash({ kind: "success", msg: `Requested access to ${(_a2 = g.companyName) != null ? _a2 : "the company"}. Another admin has to approve it.` });
|
|
1274
|
+
await reload();
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
)
|
|
1278
|
+
] });
|
|
1279
|
+
}
|
|
1280
|
+
function RequestDialog({ companyId, companyName, onClose, onDone }) {
|
|
1281
|
+
var _a, _b;
|
|
1282
|
+
const { client } = usePlatform();
|
|
1283
|
+
const companies = useLoad(companyId ? null : "companies");
|
|
1284
|
+
const [company, setCompany] = useState7(companyId != null ? companyId : "");
|
|
1285
|
+
const [reason, setReason] = useState7("");
|
|
1286
|
+
const [minutes, setMinutes] = useState7("60");
|
|
1287
|
+
const [busy, setBusy] = useState7(false);
|
|
1288
|
+
const [err, setErr] = useState7(null);
|
|
1289
|
+
const options = ((_b = (_a = companies.data) == null ? void 0 : _a.companies) != null ? _b : []).map((c) => {
|
|
1290
|
+
var _a2;
|
|
1291
|
+
return { value: c.id, label: (_a2 = c.displayName) != null ? _a2 : c.slug };
|
|
1292
|
+
});
|
|
1293
|
+
const submit = async () => {
|
|
1294
|
+
setBusy(true);
|
|
1295
|
+
setErr(null);
|
|
1296
|
+
try {
|
|
1297
|
+
const r = await client.post("support-access", { companyId: company, reason: reason.trim(), minutes: Number(minutes) });
|
|
1298
|
+
await onDone(r.grant);
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
setErr(errorText(e));
|
|
1301
|
+
} finally {
|
|
1302
|
+
setBusy(false);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
return /* @__PURE__ */ jsx10(
|
|
1306
|
+
Dialog2,
|
|
1307
|
+
{
|
|
1308
|
+
isOpen: true,
|
|
1309
|
+
onClose,
|
|
1310
|
+
title: "Request support access",
|
|
1311
|
+
size: "sm",
|
|
1312
|
+
footer: /* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-2", children: [
|
|
1313
|
+
/* @__PURE__ */ jsx10(Button6, { variant: "outline", onClick: onClose, children: "Cancel" }),
|
|
1314
|
+
/* @__PURE__ */ jsx10(Button6, { loading: busy, disabled: !company || reason.trim().length < 8, onClick: submit, children: "Request" })
|
|
1315
|
+
] }),
|
|
1316
|
+
children: /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
|
|
1317
|
+
err && /* @__PURE__ */ jsx10(Alert3, { variant: "error", children: err }),
|
|
1318
|
+
companyId ? /* @__PURE__ */ jsx10(Input4, { label: "Company", value: companyName != null ? companyName : companyId, disabled: true }) : /* @__PURE__ */ jsx10(Select, { label: "Company", searchable: true, options, loading: companies.loading, value: company, onChange: (v) => setCompany(String(v)), placeholder: "Pick a company" }),
|
|
1319
|
+
/* @__PURE__ */ jsx10(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." }),
|
|
1320
|
+
/* @__PURE__ */ jsx10(Select, { label: "For", options: DURATIONS, value: minutes, onChange: (v) => setMinutes(String(v)), helperText: "Counted from approval" })
|
|
1321
|
+
] })
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
function SupportAccessView() {
|
|
1326
|
+
const { Link } = usePlatform();
|
|
1327
|
+
return /* @__PURE__ */ jsxs9("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
|
|
1328
|
+
/* @__PURE__ */ jsx10(PageHeader4, { title: "Support access", subtitle: "Time-boxed access to one company, approved by a second platform admin", linkAs: Link }),
|
|
1329
|
+
/* @__PURE__ */ jsx10(SupportGrants, {})
|
|
1330
|
+
] });
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/platform/views/CompanyFeatures.jsx
|
|
1334
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1335
|
+
var SUB_STATUSES = [
|
|
1336
|
+
{ value: "active", label: "Active" },
|
|
1337
|
+
{ value: "trial", label: "Trial" },
|
|
1338
|
+
{ value: "past_due", label: "Past due" },
|
|
1339
|
+
{ value: "cancelled", label: "Cancelled" },
|
|
1340
|
+
{ value: "expired", label: "Expired" }
|
|
1341
|
+
];
|
|
1342
|
+
var day = (v) => v ? String(v).slice(0, 10) : "";
|
|
1343
|
+
function featureTabs({ features, can }) {
|
|
1344
|
+
return [
|
|
1345
|
+
features.includes("plans") && can("plans.read") && { id: "subscription", label: "Subscription", Component: SubscriptionTab },
|
|
1346
|
+
features.includes("domains") && can("domains.read") && { id: "domains", label: "Domains", Component: DomainsTab },
|
|
1347
|
+
features.includes("settings") && can("settings.read") && { id: "settings", label: "Settings", Component: SettingsTab },
|
|
1348
|
+
features.includes("support") && can("support.request") && { id: "support", label: "Support", Component: SupportTab }
|
|
1349
|
+
].filter(Boolean);
|
|
1350
|
+
}
|
|
1351
|
+
function SubscriptionTab({ company, setFlash }) {
|
|
1352
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1353
|
+
const { client, can } = usePlatform();
|
|
1354
|
+
const sensitive = useSensitive();
|
|
1355
|
+
const id = encodeURIComponent(company.id);
|
|
1356
|
+
const { data, error, loading, reload } = useLoad(`companies/${id}/subscription`);
|
|
1357
|
+
const plans = useLoad(can("plans.write") ? "plans" : null);
|
|
1358
|
+
const [editing, setEditing] = useState8(null);
|
|
1359
|
+
const [busy, setBusy] = useState8(false);
|
|
1360
|
+
const current = data == null ? void 0 : data.subscription;
|
|
1361
|
+
const history = (_a = data == null ? void 0 : data.history) != null ? _a : [];
|
|
1362
|
+
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)}` }));
|
|
1363
|
+
const save = async () => {
|
|
1364
|
+
setBusy(true);
|
|
1365
|
+
try {
|
|
1366
|
+
await sensitive(
|
|
1367
|
+
() => client.put(`companies/${id}/subscription`, {
|
|
1368
|
+
planId: editing.planId,
|
|
1369
|
+
status: editing.status,
|
|
1370
|
+
startsAt: editing.startsAt || void 0,
|
|
1371
|
+
endsAt: editing.endsAt || null,
|
|
1372
|
+
seats: editing.seats === "" ? null : Number(editing.seats),
|
|
1373
|
+
notes: editing.notes || null
|
|
1374
|
+
})
|
|
1375
|
+
);
|
|
1376
|
+
setEditing(null);
|
|
1377
|
+
setFlash({ kind: "success", msg: "Subscription updated" });
|
|
1378
|
+
await reload();
|
|
1379
|
+
} catch (e) {
|
|
1380
|
+
const msg = errorText(e);
|
|
1381
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
1382
|
+
} finally {
|
|
1383
|
+
setBusy(false);
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
if (error && !data) return /* @__PURE__ */ jsx11(ErrorState4, { description: error.message, onRetry: reload });
|
|
1387
|
+
if (loading && !data) return /* @__PURE__ */ jsx11(LoadingState4, { variant: "detail" });
|
|
1388
|
+
const open = () => {
|
|
1389
|
+
var _a2, _b2;
|
|
1390
|
+
return setEditing({
|
|
1391
|
+
planId: (_a2 = current == null ? void 0 : current.planId) != null ? _a2 : "",
|
|
1392
|
+
status: (_b2 = current == null ? void 0 : current.status) != null ? _b2 : "active",
|
|
1393
|
+
startsAt: "",
|
|
1394
|
+
endsAt: day(current == null ? void 0 : current.endsAt),
|
|
1395
|
+
seats: (current == null ? void 0 : current.seats) == null ? "" : String(current.seats),
|
|
1396
|
+
notes: ""
|
|
1397
|
+
});
|
|
1398
|
+
};
|
|
1399
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-4", children: [
|
|
1400
|
+
/* @__PURE__ */ jsx11(
|
|
1401
|
+
Card,
|
|
1402
|
+
{
|
|
1403
|
+
title: "Current plan",
|
|
1404
|
+
actions: can("plans.write") && /* @__PURE__ */ jsx11(Button7, { size: "sm", variant: "outline", onClick: open, children: current ? "Change" : "Assign a plan" }),
|
|
1405
|
+
children: current ? /* @__PURE__ */ jsx11(
|
|
1406
|
+
Facts,
|
|
1407
|
+
{
|
|
1408
|
+
rows: [
|
|
1409
|
+
["Plan", (_e = (_d = current.planName) != null ? _d : current.planCode) != null ? _e : current.planId],
|
|
1410
|
+
["Status", /* @__PURE__ */ jsx11(StatusPill, { status: current.status }, "s")],
|
|
1411
|
+
["Started", formatDate(current.startsAt)],
|
|
1412
|
+
["Ends", current.endsAt ? formatDate(current.endsAt) : "No end date"],
|
|
1413
|
+
["Seats", (_f = current.seats) != null ? _f : "Plan default"],
|
|
1414
|
+
...current.notes ? [["Notes", current.notes]] : []
|
|
1415
|
+
]
|
|
1416
|
+
}
|
|
1417
|
+
) : /* @__PURE__ */ jsx11(EmptyState3, { compact: true, icon: /* @__PURE__ */ jsx11(IconTag, { size: 24 }), title: "No plan yet", description: "This company is not on a plan." })
|
|
1418
|
+
}
|
|
1419
|
+
),
|
|
1420
|
+
history.length > 0 && /* @__PURE__ */ jsx11(Card, { title: "History", children: /* @__PURE__ */ jsx11("ul", { className: "divide-y divide-line", children: history.map((s) => {
|
|
1421
|
+
var _a2, _b2;
|
|
1422
|
+
return /* @__PURE__ */ jsxs10("li", { className: "flex flex-wrap items-center gap-3 py-2 text-sm", children: [
|
|
1423
|
+
/* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 text-foreground", children: (_b2 = (_a2 = s.planName) != null ? _a2 : s.planCode) != null ? _b2 : s.planId }),
|
|
1424
|
+
/* @__PURE__ */ jsx11(StatusPill, { status: s.status }),
|
|
1425
|
+
/* @__PURE__ */ jsxs10("span", { className: "text-xs text-muted", children: [
|
|
1426
|
+
"from ",
|
|
1427
|
+
formatDate(s.startsAt)
|
|
1428
|
+
] })
|
|
1429
|
+
] }, s.id);
|
|
1430
|
+
}) }) }),
|
|
1431
|
+
/* @__PURE__ */ jsx11(
|
|
1432
|
+
Dialog3,
|
|
1433
|
+
{
|
|
1434
|
+
isOpen: editing !== null,
|
|
1435
|
+
onClose: () => setEditing(null),
|
|
1436
|
+
title: current ? "Change subscription" : "Assign a plan",
|
|
1437
|
+
size: "sm",
|
|
1438
|
+
footer: /* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2", children: [
|
|
1439
|
+
/* @__PURE__ */ jsx11(Button7, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
|
|
1440
|
+
/* @__PURE__ */ jsx11(Button7, { loading: busy, disabled: !(editing == null ? void 0 : editing.planId), onClick: save, children: "Save" })
|
|
1441
|
+
] }),
|
|
1442
|
+
children: editing && /* @__PURE__ */ jsxs10("div", { className: "space-y-4", children: [
|
|
1443
|
+
/* @__PURE__ */ jsx11(Select2, { label: "Plan", options: planOptions, loading: plans.loading, value: editing.planId, onChange: (v) => setEditing((s) => ({ ...s, planId: String(v) })), placeholder: "Pick a plan" }),
|
|
1444
|
+
/* @__PURE__ */ jsx11(Select2, { label: "Status", options: SUB_STATUSES, value: editing.status, onChange: (v) => setEditing((s) => ({ ...s, status: String(v) })) }),
|
|
1445
|
+
/* @__PURE__ */ jsxs10("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
1446
|
+
/* @__PURE__ */ jsx11(Input5, { label: "Starts", type: "date", value: editing.startsAt, onChange: (e) => setEditing((s) => ({ ...s, startsAt: e.target.value })), helperText: "Empty: today" }),
|
|
1447
|
+
/* @__PURE__ */ jsx11(Input5, { label: "Ends", type: "date", value: editing.endsAt, onChange: (e) => setEditing((s) => ({ ...s, endsAt: e.target.value })), helperText: "Empty: no end date" })
|
|
1448
|
+
] }),
|
|
1449
|
+
/* @__PURE__ */ jsx11(Input5, { label: "Seats", type: "number", min: "1", value: editing.seats, onChange: (e) => setEditing((s) => ({ ...s, seats: e.target.value })), helperText: "Empty: the plan's default" }),
|
|
1450
|
+
/* @__PURE__ */ jsx11(Textarea2, { label: "Notes", rows: 2, value: editing.notes, onChange: (e) => setEditing((s) => ({ ...s, notes: e.target.value })) })
|
|
1451
|
+
] })
|
|
1452
|
+
}
|
|
1453
|
+
)
|
|
1454
|
+
] });
|
|
1455
|
+
}
|
|
1456
|
+
function DomainsTab({ company, setFlash }) {
|
|
1457
|
+
var _a;
|
|
1458
|
+
const { client, can } = usePlatform();
|
|
1459
|
+
const sensitive = useSensitive();
|
|
1460
|
+
const id = encodeURIComponent(company.id);
|
|
1461
|
+
const { data, error, loading, reload } = useLoad(`companies/${id}/domains`);
|
|
1462
|
+
const [host, setHost] = useState8("");
|
|
1463
|
+
const [adding, setAdding] = useState8(false);
|
|
1464
|
+
const [busy, setBusy] = useState8(null);
|
|
1465
|
+
const [removing, setRemoving] = useState8(null);
|
|
1466
|
+
const writable = can("domains.write");
|
|
1467
|
+
const domains = (_a = data == null ? void 0 : data.domains) != null ? _a : [];
|
|
1468
|
+
const act = async (d, action) => {
|
|
1469
|
+
setBusy(`${d.id}:${action}`);
|
|
1470
|
+
setFlash(null);
|
|
1471
|
+
try {
|
|
1472
|
+
const call = () => client.post(`companies/${id}/domains/${encodeURIComponent(d.id)}`, { action });
|
|
1473
|
+
const r = await (action === "remove" ? sensitive(call) : call());
|
|
1474
|
+
if (action === "verify") {
|
|
1475
|
+
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.` });
|
|
1476
|
+
} else setFlash({ kind: "success", msg: action === "remove" ? `${d.host} removed` : `${d.host} is now the primary domain` });
|
|
1477
|
+
await reload();
|
|
1478
|
+
} catch (e) {
|
|
1479
|
+
const msg = errorText(e);
|
|
1480
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
1481
|
+
} finally {
|
|
1482
|
+
setBusy(null);
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
const add = async () => {
|
|
1486
|
+
setBusy("add");
|
|
1487
|
+
setFlash(null);
|
|
1488
|
+
try {
|
|
1489
|
+
await client.post(`companies/${id}/domains`, { host: host.trim() });
|
|
1490
|
+
setHost("");
|
|
1491
|
+
setAdding(false);
|
|
1492
|
+
setFlash({ kind: "success", msg: "Domain added. Publish the TXT record below, then verify." });
|
|
1493
|
+
await reload();
|
|
1494
|
+
} catch (e) {
|
|
1495
|
+
const msg = errorText(e);
|
|
1496
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
1497
|
+
} finally {
|
|
1498
|
+
setBusy(null);
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
if (error && !data) return /* @__PURE__ */ jsx11(ErrorState4, { description: error.message, onRetry: reload });
|
|
1502
|
+
if (loading && !data) return /* @__PURE__ */ jsx11(LoadingState4, { variant: "table", rows: 2 });
|
|
1503
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-4", children: [
|
|
1504
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
1505
|
+
/* @__PURE__ */ jsx11("p", { className: "text-sm text-muted", children: "Custom host names for this company. A domain routes only after its TXT record is verified." }),
|
|
1506
|
+
writable && /* @__PURE__ */ jsx11(Button7, { size: "sm", variant: "outline", leftIcon: /* @__PURE__ */ jsx11(IconPlus, {}), onClick: () => setAdding(true), children: "Add domain" })
|
|
1507
|
+
] }),
|
|
1508
|
+
domains.length === 0 ? /* @__PURE__ */ jsx11(EmptyState3, { compact: true, icon: /* @__PURE__ */ jsx11(IconGlobe, { size: 24 }), title: "No custom domains", description: "The company signs in on the platform's own address." }) : /* @__PURE__ */ jsx11("ul", { className: "space-y-3", children: domains.map((d) => {
|
|
1509
|
+
var _a2;
|
|
1510
|
+
return /* @__PURE__ */ jsxs10("li", { className: "rounded-xl border border-line bg-surface p-4", children: [
|
|
1511
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1512
|
+
/* @__PURE__ */ jsx11("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 }),
|
|
1513
|
+
d.primary && /* @__PURE__ */ jsx11(Badge2, { variant: "primary", size: "sm", children: "Primary" }),
|
|
1514
|
+
/* @__PURE__ */ jsx11(StatusPill, { status: (_a2 = d.status) != null ? _a2 : d.verifiedAt ? "verified" : "pending" }),
|
|
1515
|
+
writable && /* @__PURE__ */ jsxs10("div", { className: "flex gap-1", children: [
|
|
1516
|
+
!d.verifiedAt && /* @__PURE__ */ jsx11(Button7, { size: "sm", loading: busy === `${d.id}:verify`, onClick: () => act(d, "verify"), children: "Verify" }),
|
|
1517
|
+
d.verifiedAt && !d.primary && /* @__PURE__ */ jsx11(Button7, { size: "sm", variant: "ghost", loading: busy === `${d.id}:make-primary`, onClick: () => act(d, "make-primary"), children: "Make primary" }),
|
|
1518
|
+
/* @__PURE__ */ jsx11(Button7, { size: "sm", variant: "ghost", onClick: () => setRemoving(d), children: "Remove" })
|
|
1519
|
+
] })
|
|
1520
|
+
] }),
|
|
1521
|
+
d.verifiedAt && /* @__PURE__ */ jsxs10("p", { className: "mt-1 text-xs text-muted", children: [
|
|
1522
|
+
"Verified ",
|
|
1523
|
+
formatDate(d.verifiedAt)
|
|
1524
|
+
] }),
|
|
1525
|
+
!d.verifiedAt && d.verification && /* @__PURE__ */ jsxs10("div", { className: "mt-3 space-y-2 rounded-lg bg-background p-3", children: [
|
|
1526
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs text-muted", children: "Add this TXT record at the domain's DNS provider:" }),
|
|
1527
|
+
[
|
|
1528
|
+
["Name", d.verification.name],
|
|
1529
|
+
["Value", d.verification.value]
|
|
1530
|
+
].map(([k, v]) => /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs text-muted", children: k }),
|
|
1532
|
+
/* @__PURE__ */ jsxs10("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1533
|
+
/* @__PURE__ */ jsx11("span", { className: "break-all font-mono text-xs text-foreground", children: v }),
|
|
1534
|
+
/* @__PURE__ */ jsx11(CopyButton, { value: v })
|
|
1535
|
+
] })
|
|
1536
|
+
] }, k))
|
|
1537
|
+
] })
|
|
1538
|
+
] }, d.id);
|
|
1539
|
+
}) }),
|
|
1540
|
+
/* @__PURE__ */ jsx11(
|
|
1541
|
+
Dialog3,
|
|
1542
|
+
{
|
|
1543
|
+
isOpen: adding,
|
|
1544
|
+
onClose: () => setAdding(false),
|
|
1545
|
+
title: "Add domain",
|
|
1546
|
+
size: "sm",
|
|
1547
|
+
footer: /* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2", children: [
|
|
1548
|
+
/* @__PURE__ */ jsx11(Button7, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
|
|
1549
|
+
/* @__PURE__ */ jsx11(Button7, { loading: busy === "add", disabled: !host.trim().includes("."), onClick: add, children: "Add domain" })
|
|
1550
|
+
] }),
|
|
1551
|
+
children: /* @__PURE__ */ jsx11(Input5, { 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." })
|
|
1552
|
+
}
|
|
1553
|
+
),
|
|
1554
|
+
/* @__PURE__ */ jsx11(
|
|
1555
|
+
ConfirmDialog,
|
|
1556
|
+
{
|
|
1557
|
+
isOpen: removing !== null,
|
|
1558
|
+
title: `Remove ${removing == null ? void 0 : removing.host}?`,
|
|
1559
|
+
description: "The company can no longer sign in on this host.",
|
|
1560
|
+
confirmLabel: "Remove",
|
|
1561
|
+
destructive: true,
|
|
1562
|
+
onCancel: () => setRemoving(null),
|
|
1563
|
+
onConfirm: () => {
|
|
1564
|
+
const d = removing;
|
|
1565
|
+
setRemoving(null);
|
|
1566
|
+
void act(d, "remove");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
)
|
|
1570
|
+
] });
|
|
1571
|
+
}
|
|
1572
|
+
function SettingsTab({ company, setFlash }) {
|
|
1573
|
+
var _a;
|
|
1574
|
+
const { can } = usePlatform();
|
|
1575
|
+
const { data, error, loading, reload } = useLoad("settings", { company: company.id });
|
|
1576
|
+
if (error && !data) return /* @__PURE__ */ jsx11(ErrorState4, { description: error.message, onRetry: reload });
|
|
1577
|
+
if (loading && !data) return /* @__PURE__ */ jsx11(LoadingState4, { variant: "detail" });
|
|
1578
|
+
const families = (_a = data == null ? void 0 : data.families) != null ? _a : [];
|
|
1579
|
+
if (!families.length) return /* @__PURE__ */ jsx11(EmptyState3, { compact: true, icon: /* @__PURE__ */ jsx11(IconSettings, { size: 24 }), title: "No settings", description: "This product defines no company settings." });
|
|
1580
|
+
return /* @__PURE__ */ jsx11("div", { className: "space-y-4", children: families.map((f) => /* @__PURE__ */ jsx11(SettingsFamily, { family: f, companyId: company.id, version: data.version, writable: can("settings.write"), setFlash, onSaved: reload }, f.family)) });
|
|
1581
|
+
}
|
|
1582
|
+
var initialValues = (family) => Object.fromEntries(family.fields.map((f) => {
|
|
1583
|
+
var _a;
|
|
1584
|
+
return [f.key, f.type === "secret" ? "" : (_a = f.value) != null ? _a : f.type === "boolean" ? false : ""];
|
|
1585
|
+
}));
|
|
1586
|
+
function SettingsFamily({ family, companyId, version, writable, setFlash, onSaved }) {
|
|
1587
|
+
const { client } = usePlatform();
|
|
1588
|
+
const sensitive = useSensitive();
|
|
1589
|
+
const [values, setValues] = useState8(() => initialValues(family));
|
|
1590
|
+
const [busy, setBusy] = useState8(false);
|
|
1591
|
+
useEffect4(() => setValues(initialValues(family)), [family]);
|
|
1592
|
+
const changed = family.fields.filter((f) => {
|
|
1593
|
+
var _a;
|
|
1594
|
+
return f.type === "secret" ? values[f.key] !== "" : JSON.stringify(values[f.key]) !== JSON.stringify((_a = f.value) != null ? _a : f.type === "boolean" ? false : "");
|
|
1595
|
+
});
|
|
1596
|
+
const save = async () => {
|
|
1597
|
+
setBusy(true);
|
|
1598
|
+
setFlash(null);
|
|
1599
|
+
try {
|
|
1600
|
+
const body = Object.fromEntries(changed.map((f) => [f.key, f.type === "number" ? values[f.key] === "" ? null : Number(values[f.key]) : values[f.key]]));
|
|
1601
|
+
await sensitive(() => client.put(`settings/${encodeURIComponent(family.family)}`, { companyId, version, values: body }));
|
|
1602
|
+
setFlash({ kind: "success", msg: `${family.label} saved` });
|
|
1603
|
+
await onSaved();
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
const msg = errorText(e);
|
|
1606
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
1607
|
+
} finally {
|
|
1608
|
+
setBusy(false);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
const set = (key) => (v) => setValues((s) => ({ ...s, [key]: v }));
|
|
1612
|
+
return /* @__PURE__ */ jsx11(
|
|
1613
|
+
Card,
|
|
1614
|
+
{
|
|
1615
|
+
title: family.label,
|
|
1616
|
+
actions: writable && /* @__PURE__ */ jsx11(Button7, { size: "sm", loading: busy, disabled: !changed.length, onClick: save, children: "Save" }),
|
|
1617
|
+
children: /* @__PURE__ */ jsx11("div", { className: "grid gap-4 sm:grid-cols-2", children: family.fields.map((f) => {
|
|
1618
|
+
var _a, _b, _c;
|
|
1619
|
+
const help = [f.help, f.source === "default" ? "Using the default" : null].filter(Boolean).join(" \xB7 ") || void 0;
|
|
1620
|
+
if (f.type === "boolean") {
|
|
1621
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-line px-3 py-2", children: [
|
|
1622
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
|
|
1623
|
+
/* @__PURE__ */ jsx11("div", { className: "text-sm text-foreground", children: f.label }),
|
|
1624
|
+
help && /* @__PURE__ */ jsx11("div", { className: "text-xs text-muted", children: help })
|
|
1625
|
+
] }),
|
|
1626
|
+
/* @__PURE__ */ jsx11(Switch, { checked: Boolean(values[f.key]), disabled: !writable, onChange: set(f.key), ariaLabel: f.label })
|
|
1627
|
+
] }, f.key);
|
|
1628
|
+
}
|
|
1629
|
+
if (f.type === "select") {
|
|
1630
|
+
return /* @__PURE__ */ jsx11(Select2, { 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);
|
|
1631
|
+
}
|
|
1632
|
+
return /* @__PURE__ */ jsx11(
|
|
1633
|
+
Input5,
|
|
1634
|
+
{
|
|
1635
|
+
label: f.label,
|
|
1636
|
+
type: f.type === "secret" ? "password" : f.type === "number" ? "number" : f.type === "email" ? "email" : f.type === "url" ? "url" : "text",
|
|
1637
|
+
autoComplete: f.type === "secret" ? "new-password" : void 0,
|
|
1638
|
+
placeholder: f.type === "secret" ? f.set ? "Configured \xB7 leave blank to keep" : "Not set" : void 0,
|
|
1639
|
+
value: (_c = values[f.key]) != null ? _c : "",
|
|
1640
|
+
disabled: !writable,
|
|
1641
|
+
onChange: (e) => set(f.key)(e.target.value),
|
|
1642
|
+
helperText: help
|
|
1643
|
+
},
|
|
1644
|
+
f.key
|
|
1645
|
+
);
|
|
1646
|
+
}) })
|
|
1647
|
+
}
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
function SupportTab({ company }) {
|
|
1651
|
+
return /* @__PURE__ */ jsx11(SupportGrants, { companyId: company.id, companyName: company.displayName });
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/platform/views/CompanyDetail.jsx
|
|
1655
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1126
1656
|
var SETUP_STATES = ["draft", "provisioning", "provisioning_failed"];
|
|
1127
1657
|
function CompanyDetailView({ companyId }) {
|
|
1128
1658
|
var _a;
|
|
1129
|
-
const { client, Link, href, can, companyTabs, companyLoginUrl, companyLoginPath } = usePlatform();
|
|
1659
|
+
const { client, Link, href, can, companyTabs, companyLoginUrl, companyLoginPath, features = [] } = usePlatform();
|
|
1130
1660
|
const sensitive = useSensitive();
|
|
1131
1661
|
const { data, error, reload } = useLoad(`companies/${encodeURIComponent(companyId)}`);
|
|
1132
|
-
const [tab, setTab] =
|
|
1133
|
-
const [flash, setFlash] =
|
|
1134
|
-
const [secret, setSecret] =
|
|
1662
|
+
const [tab, setTab] = useState9(0);
|
|
1663
|
+
const [flash, setFlash] = useState9(null);
|
|
1664
|
+
const [secret, setSecret] = useState9(null);
|
|
1135
1665
|
const run = useCallback3(
|
|
1136
1666
|
async (fn, ok, { stepUp = false } = {}) => {
|
|
1137
1667
|
setFlash(null);
|
|
@@ -1148,69 +1678,70 @@ function CompanyDetailView({ companyId }) {
|
|
|
1148
1678
|
},
|
|
1149
1679
|
[reload, sensitive]
|
|
1150
1680
|
);
|
|
1151
|
-
if (error && !data) return /* @__PURE__ */
|
|
1152
|
-
if (!data) return /* @__PURE__ */
|
|
1681
|
+
if (error && !data) return /* @__PURE__ */ jsx12("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ jsx12(ErrorState5, { description: error.message, onRetry: reload }) });
|
|
1682
|
+
if (!data) return /* @__PURE__ */ jsx12("div", { className: "mx-auto max-w-6xl px-4 py-6 sm:px-6", children: /* @__PURE__ */ jsx12(LoadingState5, { variant: "detail" }) });
|
|
1153
1683
|
const company = data.company;
|
|
1154
1684
|
const database = (_a = company.database) != null ? _a : { configured: false };
|
|
1155
1685
|
const ctx = { company, users: data.users, run, setFlash, setSecret, client, can, reload };
|
|
1156
1686
|
const tabs = [
|
|
1157
|
-
{ id: "overview", label: "Overview", render: () => /* @__PURE__ */
|
|
1158
|
-
{ id: "branches", label: "Branches", render: () => /* @__PURE__ */
|
|
1159
|
-
{ id: "database", label: "Database", render: () => /* @__PURE__ */
|
|
1160
|
-
{ id: "admins", label: "Admins", render: () => /* @__PURE__ */
|
|
1687
|
+
{ id: "overview", label: "Overview", render: () => /* @__PURE__ */ jsx12(OverviewTab, { ...ctx }) },
|
|
1688
|
+
{ id: "branches", label: "Branches", render: () => /* @__PURE__ */ jsx12(BranchesTab, { ...ctx }) },
|
|
1689
|
+
{ id: "database", label: "Database", render: () => /* @__PURE__ */ jsx12(DatabaseTab, { ...ctx }) },
|
|
1690
|
+
{ id: "admins", label: "Admins", render: () => /* @__PURE__ */ jsx12(AdminsTab, { ...ctx }) },
|
|
1691
|
+
...featureTabs({ features, can }).map(({ id, label, Component }) => ({ id, label, render: () => /* @__PURE__ */ jsx12(Component, { ...ctx }) })),
|
|
1161
1692
|
...(companyTabs != null ? companyTabs : []).map((t) => ({ id: t.id, label: t.label, render: () => t.render({ company, reload, client, sensitive }) })),
|
|
1162
|
-
{ id: "activity", label: "Activity", render: () => /* @__PURE__ */
|
|
1693
|
+
{ id: "activity", label: "Activity", render: () => /* @__PURE__ */ jsx12(ActivityTab, { ...ctx }) }
|
|
1163
1694
|
];
|
|
1164
|
-
return /* @__PURE__ */
|
|
1165
|
-
/* @__PURE__ */
|
|
1166
|
-
|
|
1695
|
+
return /* @__PURE__ */ jsxs11("div", { className: "mx-auto max-w-6xl space-y-5 px-4 py-6 sm:px-6", children: [
|
|
1696
|
+
/* @__PURE__ */ jsx12(
|
|
1697
|
+
PageHeader5,
|
|
1167
1698
|
{
|
|
1168
1699
|
title: company.displayName,
|
|
1169
|
-
subtitle: /* @__PURE__ */
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1172
|
-
/* @__PURE__ */
|
|
1700
|
+
subtitle: /* @__PURE__ */ jsxs11("span", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1701
|
+
/* @__PURE__ */ jsx12("span", { className: "font-mono", children: companyLoginPath(company.slug) }),
|
|
1702
|
+
/* @__PURE__ */ jsx12(StatusPill, { status: company.status }),
|
|
1703
|
+
/* @__PURE__ */ jsx12(ModePill, { mode: database.configured ? database.mode : null })
|
|
1173
1704
|
] }),
|
|
1174
1705
|
back: { href: href("companies"), label: "Companies" },
|
|
1175
1706
|
linkAs: Link
|
|
1176
1707
|
}
|
|
1177
1708
|
),
|
|
1178
|
-
flash && /* @__PURE__ */
|
|
1179
|
-
secret && /* @__PURE__ */
|
|
1180
|
-
/* @__PURE__ */
|
|
1181
|
-
/* @__PURE__ */
|
|
1182
|
-
/* @__PURE__ */
|
|
1709
|
+
flash && /* @__PURE__ */ jsx12(Alert4, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
|
|
1710
|
+
secret && /* @__PURE__ */ jsx12(OneTimeSecret, { title: secret.title, loginUrl: companyLoginUrl(company.slug), email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
|
|
1711
|
+
/* @__PURE__ */ jsxs11(Tabs, { index: tab, onChange: setTab, variant: "line", children: [
|
|
1712
|
+
/* @__PURE__ */ jsx12(Tabs.List, { children: tabs.map((t) => /* @__PURE__ */ jsx12(Tabs.Tab, { children: t.label }, t.id)) }),
|
|
1713
|
+
/* @__PURE__ */ jsx12(Tabs.Panels, { children: tabs.map((t, i) => /* @__PURE__ */ jsx12(Tabs.Panel, { children: i === tab ? /* @__PURE__ */ jsx12("div", { className: "pt-4", children: t.render() }) : null }, t.id)) })
|
|
1183
1714
|
] })
|
|
1184
1715
|
] });
|
|
1185
1716
|
}
|
|
1186
1717
|
function OverviewTab({ company, users, run, setSecret, client, can }) {
|
|
1187
1718
|
var _a, _b, _c, _d, _e, _f;
|
|
1188
1719
|
const { companyLoginUrl } = usePlatform();
|
|
1189
|
-
const [confirm, setConfirm] =
|
|
1190
|
-
const [editing, setEditing] =
|
|
1720
|
+
const [confirm, setConfirm] = useState9(null);
|
|
1721
|
+
const [editing, setEditing] = useState9(false);
|
|
1191
1722
|
const setup = SETUP_STATES.includes(company.status);
|
|
1192
1723
|
const writable = can("companies.write");
|
|
1193
1724
|
const db = (_a = company.database) != null ? _a : {};
|
|
1194
1725
|
const url = companyLoginUrl(company.slug);
|
|
1195
1726
|
const LABEL = { active: "Activate", suspended: "Suspend", archived: "Archive" };
|
|
1196
|
-
return /* @__PURE__ */
|
|
1197
|
-
/* @__PURE__ */
|
|
1198
|
-
setup && writable && /* @__PURE__ */
|
|
1199
|
-
/* @__PURE__ */
|
|
1727
|
+
return /* @__PURE__ */ jsxs11("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
|
|
1728
|
+
/* @__PURE__ */ jsxs11("div", { className: "space-y-5", children: [
|
|
1729
|
+
setup && writable && /* @__PURE__ */ jsx12(SetupPanel, { company, run, setSecret, client }),
|
|
1730
|
+
/* @__PURE__ */ jsx12(
|
|
1200
1731
|
Card,
|
|
1201
1732
|
{
|
|
1202
1733
|
title: "Company",
|
|
1203
|
-
actions: writable && /* @__PURE__ */
|
|
1204
|
-
children: /* @__PURE__ */
|
|
1734
|
+
actions: writable && /* @__PURE__ */ jsx12(Button8, { size: "sm", variant: "outline", onClick: () => setEditing(true), children: "Edit details" }),
|
|
1735
|
+
children: /* @__PURE__ */ jsx12(
|
|
1205
1736
|
Facts,
|
|
1206
1737
|
{
|
|
1207
1738
|
rows: [
|
|
1208
1739
|
["Name", company.displayName],
|
|
1209
1740
|
[
|
|
1210
1741
|
"Sign-in address",
|
|
1211
|
-
/* @__PURE__ */
|
|
1212
|
-
/* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1742
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-2", children: [
|
|
1743
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate font-mono text-xs", children: url }),
|
|
1744
|
+
/* @__PURE__ */ jsx12(CopyButton, { value: url })
|
|
1214
1745
|
] }, "l")
|
|
1215
1746
|
],
|
|
1216
1747
|
["Branches", (_c = (_b = company.branches) == null ? void 0 : _b.length) != null ? _c : "\u2014"],
|
|
@@ -1223,17 +1754,17 @@ function OverviewTab({ company, users, run, setSecret, client, can }) {
|
|
|
1223
1754
|
}
|
|
1224
1755
|
)
|
|
1225
1756
|
] }),
|
|
1226
|
-
/* @__PURE__ */
|
|
1227
|
-
/* @__PURE__ */
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
writable && company.status !== "active" && /* @__PURE__ */
|
|
1230
|
-
writable && company.status === "active" && /* @__PURE__ */
|
|
1231
|
-
writable && company.status !== "archived" && /* @__PURE__ */
|
|
1757
|
+
/* @__PURE__ */ jsx12(Card, { title: "Lifecycle", children: setup ? /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "Activation, suspension and archiving become available once the company's database is set up." }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
1758
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-3 text-sm text-muted", children: "Suspending immediately blocks every sign-in and request for this company. Data is kept." }),
|
|
1759
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
|
|
1760
|
+
writable && company.status !== "active" && /* @__PURE__ */ jsx12(Button8, { variant: "outline", leftIcon: /* @__PURE__ */ jsx12(IconPlay, {}), onClick: () => setConfirm("active"), children: "Activate" }),
|
|
1761
|
+
writable && company.status === "active" && /* @__PURE__ */ jsx12(Button8, { variant: "outline", leftIcon: /* @__PURE__ */ jsx12(IconPause, {}), onClick: () => setConfirm("suspended"), children: "Suspend" }),
|
|
1762
|
+
writable && company.status !== "archived" && /* @__PURE__ */ jsx12(Button8, { variant: "outline", leftIcon: /* @__PURE__ */ jsx12(IconArchive, {}), onClick: () => setConfirm("archived"), children: "Archive" })
|
|
1232
1763
|
] })
|
|
1233
1764
|
] }) }),
|
|
1234
|
-
editing && /* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1236
|
-
|
|
1765
|
+
editing && /* @__PURE__ */ jsx12(EditDetails, { company, run, client, onClose: () => setEditing(false) }),
|
|
1766
|
+
/* @__PURE__ */ jsx12(
|
|
1767
|
+
ConfirmDialog2,
|
|
1237
1768
|
{
|
|
1238
1769
|
isOpen: confirm !== null,
|
|
1239
1770
|
title: `${(_e = LABEL[confirm]) != null ? _e : ""} ${company.displayName}?`,
|
|
@@ -1252,11 +1783,11 @@ function OverviewTab({ company, users, run, setSecret, client, can }) {
|
|
|
1252
1783
|
}
|
|
1253
1784
|
function EditDetails({ company, run, client, onClose }) {
|
|
1254
1785
|
const { companyLoginPath } = usePlatform();
|
|
1255
|
-
const [name, setName] =
|
|
1256
|
-
const [slug, setSlug] =
|
|
1257
|
-
const [slugState, setSlugState] =
|
|
1258
|
-
const [busy, setBusy] =
|
|
1259
|
-
|
|
1786
|
+
const [name, setName] = useState9(company.displayName);
|
|
1787
|
+
const [slug, setSlug] = useState9(company.slug);
|
|
1788
|
+
const [slugState, setSlugState] = useState9({ available: true });
|
|
1789
|
+
const [busy, setBusy] = useState9(false);
|
|
1790
|
+
useEffect5(() => {
|
|
1260
1791
|
if (slug === company.slug) {
|
|
1261
1792
|
setSlugState({ available: true });
|
|
1262
1793
|
return void 0;
|
|
@@ -1270,17 +1801,17 @@ function EditDetails({ company, run, client, onClose }) {
|
|
|
1270
1801
|
}, 350);
|
|
1271
1802
|
return () => clearTimeout(t);
|
|
1272
1803
|
}, [slug, company.slug, client]);
|
|
1273
|
-
return /* @__PURE__ */
|
|
1274
|
-
|
|
1804
|
+
return /* @__PURE__ */ jsx12(
|
|
1805
|
+
Dialog4,
|
|
1275
1806
|
{
|
|
1276
1807
|
isOpen: true,
|
|
1277
1808
|
onClose,
|
|
1278
1809
|
title: "Edit company details",
|
|
1279
1810
|
size: "md",
|
|
1280
|
-
footer: /* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1283
|
-
|
|
1811
|
+
footer: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2", children: [
|
|
1812
|
+
/* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: onClose, children: "Cancel" }),
|
|
1813
|
+
/* @__PURE__ */ jsx12(
|
|
1814
|
+
Button8,
|
|
1284
1815
|
{
|
|
1285
1816
|
loading: busy,
|
|
1286
1817
|
disabled: !name.trim() || slugState.available !== true,
|
|
@@ -1298,10 +1829,10 @@ function EditDetails({ company, run, client, onClose }) {
|
|
|
1298
1829
|
}
|
|
1299
1830
|
)
|
|
1300
1831
|
] }),
|
|
1301
|
-
children: /* @__PURE__ */
|
|
1302
|
-
/* @__PURE__ */
|
|
1303
|
-
/* @__PURE__ */
|
|
1304
|
-
|
|
1832
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
1833
|
+
/* @__PURE__ */ jsx12(Input6, { label: "Company name", value: name, onChange: (e) => setName(e.target.value) }),
|
|
1834
|
+
/* @__PURE__ */ jsx12(
|
|
1835
|
+
Input6,
|
|
1305
1836
|
{
|
|
1306
1837
|
label: "URL slug",
|
|
1307
1838
|
value: slug,
|
|
@@ -1319,10 +1850,10 @@ function SetupPanel({ company, run, setSecret, client }) {
|
|
|
1319
1850
|
const provisioner = useProvisioner();
|
|
1320
1851
|
const job = company.lastJob;
|
|
1321
1852
|
const stored = ((_a = company.database) == null ? void 0 : _a.configured) ? company.database.mode : null;
|
|
1322
|
-
const [db, setDb] =
|
|
1323
|
-
const [adminName, setAdminName] =
|
|
1324
|
-
const [adminEmail, setAdminEmail] =
|
|
1325
|
-
const [busy, setBusy] =
|
|
1853
|
+
const [db, setDb] = useState9({ mode: stored === "external" ? "external" : "managed", url: "", report: null, testedUrl: null });
|
|
1854
|
+
const [adminName, setAdminName] = useState9("");
|
|
1855
|
+
const [adminEmail, setAdminEmail] = useState9("");
|
|
1856
|
+
const [busy, setBusy] = useState9(false);
|
|
1326
1857
|
const ready = databaseReady(db, provisioner, { acceptStates: ["empty", "company"], keepStored: stored === "external" });
|
|
1327
1858
|
const adminOk = !adminName.trim() && !adminEmail.trim() || adminName.trim() && EMAIL_RE.test(adminEmail.trim());
|
|
1328
1859
|
const provision = async () => {
|
|
@@ -1343,13 +1874,13 @@ function SetupPanel({ company, run, setSecret, client }) {
|
|
|
1343
1874
|
setBusy(false);
|
|
1344
1875
|
};
|
|
1345
1876
|
const lastError = (_b = job == null ? void 0 : job.lastError) != null ? _b : job == null ? void 0 : job.last_error;
|
|
1346
|
-
return /* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1348
|
-
(job == null ? void 0 : job.status) === "failed" && /* @__PURE__ */
|
|
1349
|
-
/* @__PURE__ */
|
|
1350
|
-
jobErrorHint(lastError) && /* @__PURE__ */
|
|
1877
|
+
return /* @__PURE__ */ jsx12(Card, { title: "Finish setup", children: /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "This company's database is not ready yet, so nobody can sign in." }),
|
|
1879
|
+
(job == null ? void 0 : job.status) === "failed" && /* @__PURE__ */ jsxs11(Alert4, { variant: "error", title: `Last attempt failed${job.step ? ` at \u201C${job.step}\u201D` : ""}`, children: [
|
|
1880
|
+
/* @__PURE__ */ jsx12("span", { className: "block break-words font-mono text-xs", children: lastError }),
|
|
1881
|
+
jobErrorHint(lastError) && /* @__PURE__ */ jsx12("span", { className: "mt-1 block", children: jobErrorHint(lastError) })
|
|
1351
1882
|
] }),
|
|
1352
|
-
/* @__PURE__ */
|
|
1883
|
+
/* @__PURE__ */ jsx12(
|
|
1353
1884
|
DatabaseChoice,
|
|
1354
1885
|
{
|
|
1355
1886
|
value: db,
|
|
@@ -1361,25 +1892,25 @@ function SetupPanel({ company, run, setSecret, client }) {
|
|
|
1361
1892
|
compact: true
|
|
1362
1893
|
}
|
|
1363
1894
|
),
|
|
1364
|
-
/* @__PURE__ */
|
|
1365
|
-
/* @__PURE__ */
|
|
1366
|
-
/* @__PURE__ */
|
|
1367
|
-
/* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1895
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1896
|
+
/* @__PURE__ */ jsx12("div", { className: "text-sm font-medium text-foreground", children: "First admin (recommended)" }),
|
|
1897
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-2 text-xs text-muted", children: "A temporary password is generated and shown once when setup finishes." }),
|
|
1898
|
+
/* @__PURE__ */ jsxs11("div", { className: "grid gap-3 [grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))]", children: [
|
|
1899
|
+
/* @__PURE__ */ jsx12(Input6, { "aria-label": "First admin full name", placeholder: "Full name", value: adminName, onChange: (e) => setAdminName(e.target.value) }),
|
|
1900
|
+
/* @__PURE__ */ jsx12(Input6, { "aria-label": "First admin email", type: "email", placeholder: "email@company.com", value: adminEmail, onChange: (e) => setAdminEmail(e.target.value) })
|
|
1370
1901
|
] })
|
|
1371
1902
|
] }),
|
|
1372
|
-
/* @__PURE__ */
|
|
1373
|
-
/* @__PURE__ */
|
|
1374
|
-
busy && /* @__PURE__ */
|
|
1903
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
1904
|
+
/* @__PURE__ */ jsx12(Button8, { loading: busy, disabled: !ready || !adminOk, leftIcon: /* @__PURE__ */ jsx12(IconRefresh, {}), onClick: provision, children: (job == null ? void 0 : job.status) === "failed" ? "Retry setup" : "Provision database" }),
|
|
1905
|
+
busy && /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted", children: "Applying the schema. This can take a minute\u2026" })
|
|
1375
1906
|
] })
|
|
1376
1907
|
] }) });
|
|
1377
1908
|
}
|
|
1378
1909
|
function BranchesTab({ company, run, client, can }) {
|
|
1379
1910
|
var _a;
|
|
1380
|
-
const [editing, setEditing] =
|
|
1381
|
-
const [code, setCode] =
|
|
1382
|
-
const [name, setName] =
|
|
1911
|
+
const [editing, setEditing] = useState9(null);
|
|
1912
|
+
const [code, setCode] = useState9("");
|
|
1913
|
+
const [name, setName] = useState9("");
|
|
1383
1914
|
const writable = can("companies.write");
|
|
1384
1915
|
const branches = (_a = company.branches) != null ? _a : [];
|
|
1385
1916
|
const open = (b) => {
|
|
@@ -1392,23 +1923,23 @@ function BranchesTab({ company, run, client, can }) {
|
|
|
1392
1923
|
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");
|
|
1393
1924
|
if (ok) setEditing(null);
|
|
1394
1925
|
};
|
|
1395
|
-
return /* @__PURE__ */
|
|
1926
|
+
return /* @__PURE__ */ jsxs11(
|
|
1396
1927
|
Card,
|
|
1397
1928
|
{
|
|
1398
1929
|
title: "Branches",
|
|
1399
|
-
actions: writable && /* @__PURE__ */
|
|
1930
|
+
actions: writable && /* @__PURE__ */ jsx12(Button8, { size: "sm", leftIcon: /* @__PURE__ */ jsx12(IconPlus, {}), onClick: () => open(null), children: "Add branch" }),
|
|
1400
1931
|
children: [
|
|
1401
|
-
branches.length === 0 ? /* @__PURE__ */
|
|
1402
|
-
/* @__PURE__ */
|
|
1403
|
-
/* @__PURE__ */
|
|
1932
|
+
branches.length === 0 ? /* @__PURE__ */ jsx12(EmptyState4, { title: "No branches", compact: true }) : /* @__PURE__ */ jsx12("ul", { className: "divide-y divide-line", children: branches.map((b) => /* @__PURE__ */ jsxs11("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
|
|
1933
|
+
/* @__PURE__ */ jsx12("span", { className: "w-20 shrink-0 font-mono text-xs text-muted", children: b.code }),
|
|
1934
|
+
/* @__PURE__ */ jsxs11("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
|
|
1404
1935
|
b.name,
|
|
1405
|
-
b.id === company.defaultBranchId && /* @__PURE__ */
|
|
1936
|
+
b.id === company.defaultBranchId && /* @__PURE__ */ jsx12("span", { className: "ml-2 text-xs text-muted", children: "default" })
|
|
1406
1937
|
] }),
|
|
1407
|
-
/* @__PURE__ */
|
|
1408
|
-
writable && /* @__PURE__ */
|
|
1409
|
-
/* @__PURE__ */
|
|
1410
|
-
b.id !== company.defaultBranchId && /* @__PURE__ */
|
|
1411
|
-
|
|
1938
|
+
/* @__PURE__ */ jsx12(StatusPill, { status: b.status }),
|
|
1939
|
+
writable && /* @__PURE__ */ jsxs11("div", { className: "flex gap-1", children: [
|
|
1940
|
+
/* @__PURE__ */ jsx12(Button8, { size: "sm", variant: "ghost", onClick: () => open(b), children: "Rename" }),
|
|
1941
|
+
b.id !== company.defaultBranchId && /* @__PURE__ */ jsx12(
|
|
1942
|
+
Button8,
|
|
1412
1943
|
{
|
|
1413
1944
|
size: "sm",
|
|
1414
1945
|
variant: "ghost",
|
|
@@ -1421,20 +1952,20 @@ function BranchesTab({ company, run, client, can }) {
|
|
|
1421
1952
|
)
|
|
1422
1953
|
] })
|
|
1423
1954
|
] }, b.id)) }),
|
|
1424
|
-
/* @__PURE__ */
|
|
1425
|
-
|
|
1955
|
+
/* @__PURE__ */ jsx12(
|
|
1956
|
+
Dialog4,
|
|
1426
1957
|
{
|
|
1427
1958
|
isOpen: editing !== null,
|
|
1428
1959
|
onClose: () => setEditing(null),
|
|
1429
1960
|
title: (editing == null ? void 0 : editing.id) ? `Rename ${editing.code}` : "Add branch",
|
|
1430
1961
|
size: "sm",
|
|
1431
|
-
footer: /* @__PURE__ */
|
|
1432
|
-
/* @__PURE__ */
|
|
1433
|
-
/* @__PURE__ */
|
|
1962
|
+
footer: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2", children: [
|
|
1963
|
+
/* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
|
|
1964
|
+
/* @__PURE__ */ jsx12(Button8, { 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" })
|
|
1434
1965
|
] }),
|
|
1435
|
-
children: /* @__PURE__ */
|
|
1436
|
-
!(editing == null ? void 0 : editing.id) && /* @__PURE__ */
|
|
1437
|
-
/* @__PURE__ */
|
|
1966
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
1967
|
+
!(editing == null ? void 0 : editing.id) && /* @__PURE__ */ jsx12(Input6, { label: "Code", value: code, onChange: (e) => setCode(e.target.value.toUpperCase()), helperText: "Letters, digits, - and _. Cannot be changed later." }),
|
|
1968
|
+
/* @__PURE__ */ jsx12(Input6, { label: "Name", value: name, onChange: (e) => setName(e.target.value) })
|
|
1438
1969
|
] })
|
|
1439
1970
|
}
|
|
1440
1971
|
)
|
|
@@ -1445,29 +1976,29 @@ function BranchesTab({ company, run, client, can }) {
|
|
|
1445
1976
|
function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
1446
1977
|
var _a, _b, _c, _d, _e;
|
|
1447
1978
|
const db = (_a = company.database) != null ? _a : { configured: false };
|
|
1448
|
-
const [busy, setBusy] =
|
|
1449
|
-
const [steps, setSteps] =
|
|
1450
|
-
const [replacing, setReplacing] =
|
|
1451
|
-
const [newUrl, setNewUrl] =
|
|
1452
|
-
const [report, setReport] =
|
|
1453
|
-
const [revealing, setRevealing] =
|
|
1454
|
-
const [reason, setReason] =
|
|
1455
|
-
const [revealed, setRevealed] =
|
|
1456
|
-
if (!db.configured) return /* @__PURE__ */
|
|
1979
|
+
const [busy, setBusy] = useState9(null);
|
|
1980
|
+
const [steps, setSteps] = useState9(null);
|
|
1981
|
+
const [replacing, setReplacing] = useState9(false);
|
|
1982
|
+
const [newUrl, setNewUrl] = useState9("");
|
|
1983
|
+
const [report, setReport] = useState9(null);
|
|
1984
|
+
const [revealing, setRevealing] = useState9(false);
|
|
1985
|
+
const [reason, setReason] = useState9("");
|
|
1986
|
+
const [revealed, setRevealed] = useState9(null);
|
|
1987
|
+
if (!db.configured) return /* @__PURE__ */ jsx12(Card, { title: "Database", children: /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "No database yet. Provision it from the Overview tab." }) });
|
|
1457
1988
|
const writable = can("db.write");
|
|
1458
|
-
return /* @__PURE__ */
|
|
1459
|
-
/* @__PURE__ */
|
|
1460
|
-
/* @__PURE__ */
|
|
1989
|
+
return /* @__PURE__ */ jsxs11("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
|
|
1990
|
+
/* @__PURE__ */ jsxs11(Card, { title: "Connection", children: [
|
|
1991
|
+
/* @__PURE__ */ jsx12(
|
|
1461
1992
|
Facts,
|
|
1462
1993
|
{
|
|
1463
1994
|
rows: [
|
|
1464
|
-
["Type", /* @__PURE__ */
|
|
1465
|
-
["Target", /* @__PURE__ */
|
|
1466
|
-
["Status", /* @__PURE__ */
|
|
1467
|
-
["Health", /* @__PURE__ */
|
|
1468
|
-
/* @__PURE__ */
|
|
1995
|
+
["Type", /* @__PURE__ */ jsx12(ModePill, { mode: db.mode }, "m")],
|
|
1996
|
+
["Target", /* @__PURE__ */ jsx12("span", { className: "font-mono text-xs", children: (_b = db.target) != null ? _b : "\u2014" }, "t")],
|
|
1997
|
+
["Status", /* @__PURE__ */ jsx12(StatusPill, { status: db.status }, "s")],
|
|
1998
|
+
["Health", /* @__PURE__ */ jsxs11("span", { children: [
|
|
1999
|
+
/* @__PURE__ */ jsx12(HealthDot, { ok: db.lastCheckOk }),
|
|
1469
2000
|
" ",
|
|
1470
|
-
db.lastCheckAt && /* @__PURE__ */
|
|
2001
|
+
db.lastCheckAt && /* @__PURE__ */ jsxs11("span", { className: "text-xs text-muted", children: [
|
|
1471
2002
|
"\xB7 ",
|
|
1472
2003
|
timeAgo(db.lastCheckAt)
|
|
1473
2004
|
] })
|
|
@@ -1479,23 +2010,23 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1479
2010
|
]
|
|
1480
2011
|
}
|
|
1481
2012
|
),
|
|
1482
|
-
db.lastCheckOk === false && db.lastCheckError && /* @__PURE__ */
|
|
1483
|
-
steps && /* @__PURE__ */
|
|
2013
|
+
db.lastCheckOk === false && db.lastCheckError && /* @__PURE__ */ jsx12("div", { className: "mt-4 rounded-lg bg-error-50 p-3 font-mono text-xs text-error-800", children: db.lastCheckError }),
|
|
2014
|
+
steps && /* @__PURE__ */ jsx12("ul", { className: "mt-4 space-y-1 rounded-lg bg-background p-3 text-xs text-muted", children: steps.map((s) => /* @__PURE__ */ jsxs11("li", { children: [
|
|
1484
2015
|
"\u2713 ",
|
|
1485
2016
|
s
|
|
1486
2017
|
] }, s)) }),
|
|
1487
|
-
revealed && /* @__PURE__ */
|
|
1488
|
-
/* @__PURE__ */
|
|
1489
|
-
/* @__PURE__ */
|
|
1490
|
-
/* @__PURE__ */
|
|
1491
|
-
/* @__PURE__ */
|
|
2018
|
+
revealed && /* @__PURE__ */ jsxs11("div", { className: "mt-4 rounded-lg border border-warning-200 bg-warning-50 p-3", children: [
|
|
2019
|
+
/* @__PURE__ */ jsx12("div", { className: "mb-1 text-xs font-medium text-warning-800", children: "Database URL (audited, shown once)" }),
|
|
2020
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
2021
|
+
/* @__PURE__ */ jsx12("code", { className: "min-w-0 flex-1 break-all text-xs text-foreground", children: revealed }),
|
|
2022
|
+
/* @__PURE__ */ jsx12(CopyButton, { value: revealed })
|
|
1492
2023
|
] }),
|
|
1493
|
-
/* @__PURE__ */
|
|
2024
|
+
/* @__PURE__ */ jsx12("button", { type: "button", className: "mt-2 text-xs text-muted underline", onClick: () => setRevealed(null), children: "Hide" })
|
|
1494
2025
|
] })
|
|
1495
2026
|
] }),
|
|
1496
|
-
writable && /* @__PURE__ */
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
|
|
2027
|
+
writable && /* @__PURE__ */ jsx12(Card, { title: "Actions", children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
|
|
2028
|
+
/* @__PURE__ */ jsx12(
|
|
2029
|
+
Button8,
|
|
1499
2030
|
{
|
|
1500
2031
|
variant: "outline",
|
|
1501
2032
|
loading: busy === "test",
|
|
@@ -1512,8 +2043,8 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1512
2043
|
children: "Test connection"
|
|
1513
2044
|
}
|
|
1514
2045
|
),
|
|
1515
|
-
/* @__PURE__ */
|
|
1516
|
-
|
|
2046
|
+
/* @__PURE__ */ jsx12(
|
|
2047
|
+
Button8,
|
|
1517
2048
|
{
|
|
1518
2049
|
variant: "outline",
|
|
1519
2050
|
loading: busy === "sync",
|
|
@@ -1526,12 +2057,12 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1526
2057
|
children: "Sync schema"
|
|
1527
2058
|
}
|
|
1528
2059
|
),
|
|
1529
|
-
db.mode === "external" && /* @__PURE__ */
|
|
1530
|
-
can("db.reveal") && /* @__PURE__ */
|
|
1531
|
-
/* @__PURE__ */
|
|
2060
|
+
db.mode === "external" && /* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: () => setReplacing(true), children: "Replace URL" }),
|
|
2061
|
+
can("db.reveal") && /* @__PURE__ */ jsx12(Button8, { variant: "ghost", onClick: () => setRevealing(true), children: "Reveal URL\u2026" }),
|
|
2062
|
+
/* @__PURE__ */ jsx12("p", { className: "mt-1 text-xs text-muted", children: "Sync adds missing tables, columns and indexes. It never drops or renames anything." })
|
|
1532
2063
|
] }) }),
|
|
1533
|
-
/* @__PURE__ */
|
|
1534
|
-
|
|
2064
|
+
/* @__PURE__ */ jsxs11(
|
|
2065
|
+
Dialog4,
|
|
1535
2066
|
{
|
|
1536
2067
|
isOpen: replacing,
|
|
1537
2068
|
onClose: () => {
|
|
@@ -1541,10 +2072,10 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1541
2072
|
},
|
|
1542
2073
|
title: "Replace database URL",
|
|
1543
2074
|
size: "md",
|
|
1544
|
-
footer: /* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
1546
|
-
/* @__PURE__ */
|
|
1547
|
-
|
|
2075
|
+
footer: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2", children: [
|
|
2076
|
+
/* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: () => setReplacing(false), children: "Cancel" }),
|
|
2077
|
+
/* @__PURE__ */ jsx12(
|
|
2078
|
+
Button8,
|
|
1548
2079
|
{
|
|
1549
2080
|
disabled: !(report == null ? void 0 : report.ok) || report.state !== "company",
|
|
1550
2081
|
loading: busy === "replace",
|
|
@@ -1563,11 +2094,11 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1563
2094
|
)
|
|
1564
2095
|
] }),
|
|
1565
2096
|
children: [
|
|
1566
|
-
/* @__PURE__ */
|
|
1567
|
-
/* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
|
|
2097
|
+
/* @__PURE__ */ jsx12("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." }),
|
|
2098
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-end gap-2", children: [
|
|
2099
|
+
/* @__PURE__ */ jsx12("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx12(Input6, { label: "New database URL", type: "password", autoComplete: "off", value: newUrl, onChange: (e) => (setNewUrl(e.target.value), setReport(null)) }) }),
|
|
2100
|
+
/* @__PURE__ */ jsx12(
|
|
2101
|
+
Button8,
|
|
1571
2102
|
{
|
|
1572
2103
|
variant: "outline",
|
|
1573
2104
|
disabled: !newUrl.trim(),
|
|
@@ -1582,21 +2113,21 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1582
2113
|
}
|
|
1583
2114
|
)
|
|
1584
2115
|
] }),
|
|
1585
|
-
report && /* @__PURE__ */
|
|
2116
|
+
report && /* @__PURE__ */ jsx12(InspectResult, { report })
|
|
1586
2117
|
]
|
|
1587
2118
|
}
|
|
1588
2119
|
),
|
|
1589
|
-
/* @__PURE__ */
|
|
1590
|
-
|
|
2120
|
+
/* @__PURE__ */ jsxs11(
|
|
2121
|
+
Dialog4,
|
|
1591
2122
|
{
|
|
1592
2123
|
isOpen: revealing,
|
|
1593
2124
|
onClose: () => setRevealing(false),
|
|
1594
2125
|
title: "Reveal database URL",
|
|
1595
2126
|
size: "md",
|
|
1596
|
-
footer: /* @__PURE__ */
|
|
1597
|
-
/* @__PURE__ */
|
|
1598
|
-
/* @__PURE__ */
|
|
1599
|
-
|
|
2127
|
+
footer: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2", children: [
|
|
2128
|
+
/* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: () => setRevealing(false), children: "Cancel" }),
|
|
2129
|
+
/* @__PURE__ */ jsx12(
|
|
2130
|
+
Button8,
|
|
1600
2131
|
{
|
|
1601
2132
|
variant: "destructive",
|
|
1602
2133
|
disabled: reason.trim().length < 8,
|
|
@@ -1613,8 +2144,8 @@ function DatabaseTab({ company, run, setFlash, client, can }) {
|
|
|
1613
2144
|
)
|
|
1614
2145
|
] }),
|
|
1615
2146
|
children: [
|
|
1616
|
-
/* @__PURE__ */
|
|
1617
|
-
/* @__PURE__ */
|
|
2147
|
+
/* @__PURE__ */ jsx12("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." }),
|
|
2148
|
+
/* @__PURE__ */ jsx12(Textarea3, { label: "Reason", rows: 3, value: reason, onChange: (e) => setReason(e.target.value), helperText: "At least 8 characters, e.g. the incident or ticket number." })
|
|
1618
2149
|
]
|
|
1619
2150
|
}
|
|
1620
2151
|
)
|
|
@@ -1625,13 +2156,13 @@ function AdminsTab({ company, setSecret, setFlash, client, can }) {
|
|
|
1625
2156
|
const sensitive = useSensitive();
|
|
1626
2157
|
const ready = ((_a = company.database) == null ? void 0 : _a.configured) && ((_b = company.database) == null ? void 0 : _b.status) === "active";
|
|
1627
2158
|
const { data, error, reload } = useLoad(ready ? `companies/${company.id}/admins` : null);
|
|
1628
|
-
const [adding, setAdding] =
|
|
1629
|
-
const [fullName, setFullName] =
|
|
1630
|
-
const [email, setEmail] =
|
|
1631
|
-
const [phone, setPhone] =
|
|
1632
|
-
const [busy, setBusy] =
|
|
2159
|
+
const [adding, setAdding] = useState9(false);
|
|
2160
|
+
const [fullName, setFullName] = useState9("");
|
|
2161
|
+
const [email, setEmail] = useState9("");
|
|
2162
|
+
const [phone, setPhone] = useState9("");
|
|
2163
|
+
const [busy, setBusy] = useState9(false);
|
|
1633
2164
|
const writable = can("companies.write");
|
|
1634
|
-
if (!ready) return /* @__PURE__ */
|
|
2165
|
+
if (!ready) return /* @__PURE__ */ jsx12(Card, { title: "Company admins", children: /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "Admins can be added once the company's database is provisioned." }) });
|
|
1635
2166
|
const act = async (a, action) => {
|
|
1636
2167
|
var _a2;
|
|
1637
2168
|
try {
|
|
@@ -1660,38 +2191,38 @@ function AdminsTab({ company, setSecret, setFlash, client, can }) {
|
|
|
1660
2191
|
setBusy(false);
|
|
1661
2192
|
}
|
|
1662
2193
|
};
|
|
1663
|
-
return /* @__PURE__ */
|
|
2194
|
+
return /* @__PURE__ */ jsxs11(
|
|
1664
2195
|
Card,
|
|
1665
2196
|
{
|
|
1666
2197
|
title: "Company admins",
|
|
1667
|
-
actions: writable && /* @__PURE__ */
|
|
2198
|
+
actions: writable && /* @__PURE__ */ jsx12(Button8, { size: "sm", leftIcon: /* @__PURE__ */ jsx12(IconPlus, {}), onClick: () => setAdding(true), children: "Add admin" }),
|
|
1668
2199
|
children: [
|
|
1669
|
-
error && !data ? /* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
1671
|
-
/* @__PURE__ */
|
|
1672
|
-
/* @__PURE__ */
|
|
2200
|
+
error && !data ? /* @__PURE__ */ jsx12(ErrorState5, { description: error.message, onRetry: reload }) : !data ? /* @__PURE__ */ jsx12(LoadingState5, { variant: "table", rows: 2 }) : !((_c = data.admins) == null ? void 0 : _c.length) ? /* @__PURE__ */ jsx12(EmptyState4, { title: "No administrators yet", compact: true }) : /* @__PURE__ */ jsx12("ul", { className: "divide-y divide-line", children: data.admins.map((a) => /* @__PURE__ */ jsxs11("li", { className: "flex flex-wrap items-center gap-3 py-2.5", children: [
|
|
2201
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
|
|
2202
|
+
/* @__PURE__ */ jsx12("div", { className: "truncate text-sm font-medium text-foreground", children: a.fullName }),
|
|
2203
|
+
/* @__PURE__ */ jsx12("div", { className: "truncate text-xs text-muted", children: a.email })
|
|
1673
2204
|
] }),
|
|
1674
|
-
/* @__PURE__ */
|
|
1675
|
-
writable && /* @__PURE__ */
|
|
1676
|
-
/* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
2205
|
+
/* @__PURE__ */ jsx12(StatusPill, { status: a.status }),
|
|
2206
|
+
writable && /* @__PURE__ */ jsxs11("div", { className: "flex gap-1", children: [
|
|
2207
|
+
/* @__PURE__ */ jsx12(Button8, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
|
|
2208
|
+
/* @__PURE__ */ jsx12(Button8, { size: "sm", variant: "ghost", onClick: () => act(a, a.status === "active" ? "disable" : "enable"), children: a.status === "active" ? "Disable" : "Enable" })
|
|
1678
2209
|
] })
|
|
1679
2210
|
] }, a.id)) }),
|
|
1680
|
-
/* @__PURE__ */
|
|
1681
|
-
|
|
2211
|
+
/* @__PURE__ */ jsx12(
|
|
2212
|
+
Dialog4,
|
|
1682
2213
|
{
|
|
1683
2214
|
isOpen: adding,
|
|
1684
2215
|
onClose: () => setAdding(false),
|
|
1685
2216
|
title: "Add company admin",
|
|
1686
2217
|
size: "sm",
|
|
1687
|
-
footer: /* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
2218
|
+
footer: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-2", children: [
|
|
2219
|
+
/* @__PURE__ */ jsx12(Button8, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
|
|
2220
|
+
/* @__PURE__ */ jsx12(Button8, { loading: busy, disabled: !fullName.trim() || !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
|
|
1690
2221
|
] }),
|
|
1691
|
-
children: /* @__PURE__ */
|
|
1692
|
-
/* @__PURE__ */
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
2222
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
2223
|
+
/* @__PURE__ */ jsx12(Input6, { label: "Full name", autoFocus: true, value: fullName, onChange: (e) => setFullName(e.target.value) }),
|
|
2224
|
+
/* @__PURE__ */ jsx12(Input6, { label: "Email", type: "email", value: email, onChange: (e) => setEmail(e.target.value) }),
|
|
2225
|
+
/* @__PURE__ */ jsx12(Input6, { label: "Phone (optional)", type: "tel", value: phone, onChange: (e) => setPhone(e.target.value) })
|
|
1695
2226
|
] })
|
|
1696
2227
|
}
|
|
1697
2228
|
)
|
|
@@ -1703,7 +2234,7 @@ function ActivityTab({ company, client, can }) {
|
|
|
1703
2234
|
var _a, _b;
|
|
1704
2235
|
const sensitive = useSensitive();
|
|
1705
2236
|
const { data, error, reload } = useLoad(`companies/${company.id}/activity`);
|
|
1706
|
-
const [retryError, setRetryError] =
|
|
2237
|
+
const [retryError, setRetryError] = useState9(null);
|
|
1707
2238
|
const modeOf = (payload) => {
|
|
1708
2239
|
try {
|
|
1709
2240
|
const p = typeof payload === "string" ? JSON.parse(payload) : payload;
|
|
@@ -1712,27 +2243,27 @@ function ActivityTab({ company, client, can }) {
|
|
|
1712
2243
|
return "managed";
|
|
1713
2244
|
}
|
|
1714
2245
|
};
|
|
1715
|
-
if (error && !data) return /* @__PURE__ */
|
|
1716
|
-
if (!data) return /* @__PURE__ */
|
|
1717
|
-
return /* @__PURE__ */
|
|
1718
|
-
/* @__PURE__ */
|
|
1719
|
-
retryError && /* @__PURE__ */
|
|
1720
|
-
!((_a = data.jobs) == null ? void 0 : _a.length) ? /* @__PURE__ */
|
|
2246
|
+
if (error && !data) return /* @__PURE__ */ jsx12(ErrorState5, { description: error.message, onRetry: reload });
|
|
2247
|
+
if (!data) return /* @__PURE__ */ jsx12(LoadingState5, { variant: "table", rows: 4 });
|
|
2248
|
+
return /* @__PURE__ */ jsxs11("div", { className: "grid gap-5 [grid-template-columns:repeat(auto-fit,minmax(min(20rem,100%),1fr))]", children: [
|
|
2249
|
+
/* @__PURE__ */ jsxs11(Card, { title: "Provisioning jobs", children: [
|
|
2250
|
+
retryError && /* @__PURE__ */ jsx12(Alert4, { variant: "error", children: retryError }),
|
|
2251
|
+
!((_a = data.jobs) == null ? void 0 : _a.length) ? /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "No provisioning jobs." }) : /* @__PURE__ */ jsx12("ul", { className: "divide-y divide-line", children: data.jobs.map((j) => {
|
|
1721
2252
|
var _a2, _b2;
|
|
1722
2253
|
const lastError = (_a2 = j.lastError) != null ? _a2 : j.last_error;
|
|
1723
|
-
return /* @__PURE__ */
|
|
1724
|
-
/* @__PURE__ */
|
|
1725
|
-
/* @__PURE__ */
|
|
1726
|
-
/* @__PURE__ */
|
|
2254
|
+
return /* @__PURE__ */ jsxs11("li", { className: "py-2.5", children: [
|
|
2255
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2256
|
+
/* @__PURE__ */ jsx12(StatusPill, { status: j.status }),
|
|
2257
|
+
/* @__PURE__ */ jsxs11("span", { className: "text-xs text-muted", children: [
|
|
1727
2258
|
modeOf(j.payload),
|
|
1728
2259
|
" \xB7 step ",
|
|
1729
|
-
/* @__PURE__ */
|
|
2260
|
+
/* @__PURE__ */ jsx12("span", { className: "font-mono", children: j.step }),
|
|
1730
2261
|
" \xB7 ",
|
|
1731
2262
|
j.attempts,
|
|
1732
2263
|
" attempt(s)"
|
|
1733
2264
|
] }),
|
|
1734
|
-
j.status === "failed" && can("companies.write") && /* @__PURE__ */
|
|
1735
|
-
|
|
2265
|
+
j.status === "failed" && can("companies.write") && /* @__PURE__ */ jsx12(
|
|
2266
|
+
Button8,
|
|
1736
2267
|
{
|
|
1737
2268
|
size: "sm",
|
|
1738
2269
|
variant: "ghost",
|
|
@@ -1750,33 +2281,33 @@ function ActivityTab({ company, client, can }) {
|
|
|
1750
2281
|
}
|
|
1751
2282
|
)
|
|
1752
2283
|
] }),
|
|
1753
|
-
lastError && j.status !== "succeeded" && /* @__PURE__ */
|
|
1754
|
-
/* @__PURE__ */
|
|
2284
|
+
lastError && j.status !== "succeeded" && /* @__PURE__ */ jsx12("div", { className: "mt-1 break-words font-mono text-xs text-error-700", children: lastError }),
|
|
2285
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-0.5 text-xs text-muted", children: formatDate((_b2 = j.updatedAt) != null ? _b2 : j.updated_at) })
|
|
1755
2286
|
] }, j.id);
|
|
1756
2287
|
}) })
|
|
1757
2288
|
] }),
|
|
1758
|
-
/* @__PURE__ */
|
|
2289
|
+
/* @__PURE__ */ jsx12(Card, { title: "Audit trail", children: !((_b = data.events) == null ? void 0 : _b.length) ? /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted", children: "No events." }) : /* @__PURE__ */ jsx12("ol", { className: "divide-y divide-line", children: data.events.map(normalizeEvent).map((e, i) => {
|
|
1759
2290
|
var _a2;
|
|
1760
|
-
return /* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */
|
|
2291
|
+
return /* @__PURE__ */ jsxs11("li", { className: "py-2.5", children: [
|
|
2292
|
+
/* @__PURE__ */ jsxs11("div", { className: "text-sm text-foreground", children: [
|
|
2293
|
+
/* @__PURE__ */ jsx12("span", { className: "font-medium", children: e.actor }),
|
|
1763
2294
|
" ",
|
|
1764
2295
|
describeAction(e.action),
|
|
1765
|
-
e.target && /* @__PURE__ */
|
|
2296
|
+
e.target && /* @__PURE__ */ jsxs11("span", { className: "text-muted", children: [
|
|
1766
2297
|
" \xB7 ",
|
|
1767
2298
|
e.target
|
|
1768
2299
|
] })
|
|
1769
2300
|
] }),
|
|
1770
|
-
/* @__PURE__ */
|
|
2301
|
+
/* @__PURE__ */ jsx12("div", { className: "text-xs text-muted", children: formatDate(e.at) })
|
|
1771
2302
|
] }, (_a2 = e.id) != null ? _a2 : i);
|
|
1772
2303
|
}) }) })
|
|
1773
2304
|
] });
|
|
1774
2305
|
}
|
|
1775
2306
|
|
|
1776
2307
|
// src/platform/views/Admins.jsx
|
|
1777
|
-
import
|
|
1778
|
-
import { Alert as
|
|
1779
|
-
import { jsx as
|
|
2308
|
+
import React12, { useState as useState10 } from "react";
|
|
2309
|
+
import { Alert as Alert5, Badge as Badge3, Button as Button9, ConfirmDialog as ConfirmDialog3, Dialog as Dialog5, ErrorState as ErrorState6, Input as Input7, LoadingState as LoadingState6, PageHeader as PageHeader6, Select as Select3 } from "@dreamtree-org/twreact-ui";
|
|
2310
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1780
2311
|
var ROLE_OPTIONS = [
|
|
1781
2312
|
{ value: "superadmin", label: "Superadmin: everything" },
|
|
1782
2313
|
{ value: "support", label: "Support: read-only" }
|
|
@@ -1786,13 +2317,13 @@ function AdminsView() {
|
|
|
1786
2317
|
const { client, can, loginUrl, Link } = usePlatform();
|
|
1787
2318
|
const sensitive = useSensitive();
|
|
1788
2319
|
const { data, error, loading, reload } = useLoad("admins");
|
|
1789
|
-
const [flash, setFlash] =
|
|
1790
|
-
const [secret, setSecret] =
|
|
1791
|
-
const [adding, setAdding] =
|
|
1792
|
-
const [email, setEmail] =
|
|
1793
|
-
const [role, setRole] =
|
|
1794
|
-
const [busy, setBusy] =
|
|
1795
|
-
const [confirm, setConfirm] =
|
|
2320
|
+
const [flash, setFlash] = useState10(null);
|
|
2321
|
+
const [secret, setSecret] = useState10(null);
|
|
2322
|
+
const [adding, setAdding] = useState10(false);
|
|
2323
|
+
const [email, setEmail] = useState10("");
|
|
2324
|
+
const [role, setRole] = useState10("superadmin");
|
|
2325
|
+
const [busy, setBusy] = useState10(false);
|
|
2326
|
+
const [confirm, setConfirm] = useState10(null);
|
|
1796
2327
|
const writable = can("admins.write");
|
|
1797
2328
|
const act = async (a, action, extra = {}) => {
|
|
1798
2329
|
var _a2;
|
|
@@ -1823,63 +2354,63 @@ function AdminsView() {
|
|
|
1823
2354
|
setBusy(false);
|
|
1824
2355
|
}
|
|
1825
2356
|
};
|
|
1826
|
-
return /* @__PURE__ */
|
|
1827
|
-
/* @__PURE__ */
|
|
1828
|
-
|
|
2357
|
+
return /* @__PURE__ */ jsxs12("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
|
|
2358
|
+
/* @__PURE__ */ jsx13(
|
|
2359
|
+
PageHeader6,
|
|
1829
2360
|
{
|
|
1830
2361
|
title: "Platform admins",
|
|
1831
2362
|
subtitle: "People who can create companies and manage the platform",
|
|
1832
2363
|
linkAs: Link,
|
|
1833
|
-
actions: writable && /* @__PURE__ */
|
|
2364
|
+
actions: writable && /* @__PURE__ */ jsx13(Button9, { leftIcon: /* @__PURE__ */ jsx13(IconPlus, {}), onClick: () => setAdding(true), children: "Add platform admin" })
|
|
1834
2365
|
}
|
|
1835
2366
|
),
|
|
1836
|
-
flash && /* @__PURE__ */
|
|
1837
|
-
secret && /* @__PURE__ */
|
|
1838
|
-
error && !data ? /* @__PURE__ */
|
|
2367
|
+
flash && /* @__PURE__ */ jsx13(Alert5, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
|
|
2368
|
+
secret && /* @__PURE__ */ jsx13(OneTimeSecret, { title: secret.title, loginUrl, email: secret.email, password: secret.password, onDone: () => setSecret(null) }),
|
|
2369
|
+
error && !data ? /* @__PURE__ */ jsx13(ErrorState6, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ jsx13(LoadingState6, { variant: "table", rows: 3 }) : /* @__PURE__ */ jsx13("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) => {
|
|
1839
2370
|
var _a2, _b, _c;
|
|
1840
2371
|
const me = a.id === data.me;
|
|
1841
2372
|
const roles = Array.isArray(a.roles) ? a.roles : [];
|
|
1842
|
-
return /* @__PURE__ */
|
|
1843
|
-
/* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
me && /* @__PURE__ */
|
|
2373
|
+
return /* @__PURE__ */ jsxs12("li", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
|
|
2374
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
|
|
2375
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
2376
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate", children: a.email }),
|
|
2377
|
+
me && /* @__PURE__ */ jsx13(Badge3, { variant: "primary", size: "sm", children: "you" })
|
|
1847
2378
|
] }),
|
|
1848
|
-
/* @__PURE__ */
|
|
2379
|
+
/* @__PURE__ */ jsxs12("div", { className: "text-xs text-muted", children: [
|
|
1849
2380
|
roles.length ? roles.join(", ") : "No role",
|
|
1850
2381
|
" \xB7 last sign-in ",
|
|
1851
2382
|
timeAgo((_a2 = a.lastLoginAt) != null ? _a2 : a.last_login_at),
|
|
1852
2383
|
!((_b = a.totpEnrolled) != null ? _b : Number(a.strong_factor_enrolled)) && " \xB7 no authenticator yet"
|
|
1853
2384
|
] })
|
|
1854
2385
|
] }),
|
|
1855
|
-
/* @__PURE__ */
|
|
1856
|
-
writable && /* @__PURE__ */
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
!me && ((_c = a.totpEnrolled) != null ? _c : Number(a.strong_factor_enrolled)) ? /* @__PURE__ */
|
|
1859
|
-
!me && (a.status === "active" ? /* @__PURE__ */
|
|
2386
|
+
/* @__PURE__ */ jsx13(StatusPill, { status: a.status }),
|
|
2387
|
+
writable && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap gap-1", children: [
|
|
2388
|
+
/* @__PURE__ */ jsx13(Button9, { size: "sm", variant: "ghost", onClick: () => act(a, "reset-password"), children: "Reset password" }),
|
|
2389
|
+
!me && ((_c = a.totpEnrolled) != null ? _c : Number(a.strong_factor_enrolled)) ? /* @__PURE__ */ jsx13(Button9, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "reset-totp" }), children: "Reset authenticator" }) : null,
|
|
2390
|
+
!me && (a.status === "active" ? /* @__PURE__ */ jsx13(Button9, { size: "sm", variant: "ghost", onClick: () => setConfirm({ admin: a, action: "disable" }), children: "Disable" }) : /* @__PURE__ */ jsx13(Button9, { size: "sm", variant: "ghost", onClick: () => act(a, "enable"), children: "Enable" }))
|
|
1860
2391
|
] })
|
|
1861
2392
|
] }, a.id);
|
|
1862
2393
|
}) }),
|
|
1863
|
-
/* @__PURE__ */
|
|
1864
|
-
|
|
2394
|
+
/* @__PURE__ */ jsx13(
|
|
2395
|
+
Dialog5,
|
|
1865
2396
|
{
|
|
1866
2397
|
isOpen: adding,
|
|
1867
2398
|
onClose: () => setAdding(false),
|
|
1868
2399
|
title: "Add platform admin",
|
|
1869
2400
|
size: "sm",
|
|
1870
|
-
footer: /* @__PURE__ */
|
|
1871
|
-
/* @__PURE__ */
|
|
1872
|
-
/* @__PURE__ */
|
|
2401
|
+
footer: /* @__PURE__ */ jsxs12("div", { className: "flex justify-end gap-2", children: [
|
|
2402
|
+
/* @__PURE__ */ jsx13(Button9, { variant: "outline", onClick: () => setAdding(false), children: "Cancel" }),
|
|
2403
|
+
/* @__PURE__ */ jsx13(Button9, { loading: busy, disabled: !EMAIL_RE.test(email.trim()), onClick: add, children: "Add admin" })
|
|
1873
2404
|
] }),
|
|
1874
|
-
children: /* @__PURE__ */
|
|
1875
|
-
/* @__PURE__ */
|
|
1876
|
-
/* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
2405
|
+
children: /* @__PURE__ */ jsxs12("div", { className: "space-y-4", children: [
|
|
2406
|
+
/* @__PURE__ */ jsx13("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." }),
|
|
2407
|
+
/* @__PURE__ */ jsx13(Input7, { label: "Email", type: "email", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
|
|
2408
|
+
/* @__PURE__ */ jsx13(Select3, { label: "Role", options: ROLE_OPTIONS, value: role, onChange: (v) => setRole(String(v)) })
|
|
1878
2409
|
] })
|
|
1879
2410
|
}
|
|
1880
2411
|
),
|
|
1881
|
-
/* @__PURE__ */
|
|
1882
|
-
|
|
2412
|
+
/* @__PURE__ */ jsx13(
|
|
2413
|
+
ConfirmDialog3,
|
|
1883
2414
|
{
|
|
1884
2415
|
isOpen: confirm !== null,
|
|
1885
2416
|
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?`,
|
|
@@ -1898,17 +2429,17 @@ function AdminsView() {
|
|
|
1898
2429
|
}
|
|
1899
2430
|
|
|
1900
2431
|
// src/platform/views/Audit.jsx
|
|
1901
|
-
import
|
|
1902
|
-
import { Alert as
|
|
1903
|
-
import { Fragment as Fragment5, jsx as
|
|
2432
|
+
import React13, { useEffect as useEffect6, useState as useState11 } from "react";
|
|
2433
|
+
import { Alert as Alert6, Button as Button10, EmptyState as EmptyState5, ErrorState as ErrorState7, Input as Input8, LoadingState as LoadingState7, PageHeader as PageHeader7 } from "@dreamtree-org/twreact-ui";
|
|
2434
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1904
2435
|
function AuditView() {
|
|
1905
2436
|
const { client, Link, href } = usePlatform();
|
|
1906
|
-
const [q, setQ] =
|
|
1907
|
-
const [applied, setApplied] =
|
|
1908
|
-
const [events, setEvents] =
|
|
1909
|
-
const [cursor, setCursor] =
|
|
1910
|
-
const [state, setState] =
|
|
1911
|
-
const [exportError, setExportError] =
|
|
2437
|
+
const [q, setQ] = useState11("");
|
|
2438
|
+
const [applied, setApplied] = useState11("");
|
|
2439
|
+
const [events, setEvents] = useState11([]);
|
|
2440
|
+
const [cursor, setCursor] = useState11(null);
|
|
2441
|
+
const [state, setState] = useState11({ loading: true, error: null });
|
|
2442
|
+
const [exportError, setExportError] = useState11(null);
|
|
1912
2443
|
const load = async (more = false) => {
|
|
1913
2444
|
setState({ loading: true, error: null });
|
|
1914
2445
|
try {
|
|
@@ -1920,7 +2451,7 @@ function AuditView() {
|
|
|
1920
2451
|
setState({ loading: false, error: e });
|
|
1921
2452
|
}
|
|
1922
2453
|
};
|
|
1923
|
-
|
|
2454
|
+
useEffect6(() => {
|
|
1924
2455
|
void load(false);
|
|
1925
2456
|
}, [applied]);
|
|
1926
2457
|
const exportCsv = async () => {
|
|
@@ -1939,18 +2470,18 @@ function AuditView() {
|
|
|
1939
2470
|
}
|
|
1940
2471
|
};
|
|
1941
2472
|
const rows = events.map(normalizeEvent);
|
|
1942
|
-
return /* @__PURE__ */
|
|
1943
|
-
/* @__PURE__ */
|
|
1944
|
-
|
|
2473
|
+
return /* @__PURE__ */ jsxs13("div", { className: "mx-auto max-w-6xl space-y-6 px-4 py-6 sm:px-6", children: [
|
|
2474
|
+
/* @__PURE__ */ jsx14(
|
|
2475
|
+
PageHeader7,
|
|
1945
2476
|
{
|
|
1946
2477
|
title: "Audit log",
|
|
1947
2478
|
subtitle: "Every platform action: who did what, to which company",
|
|
1948
2479
|
linkAs: Link,
|
|
1949
|
-
actions: /* @__PURE__ */
|
|
2480
|
+
actions: /* @__PURE__ */ jsx14(Button10, { variant: "outline", leftIcon: /* @__PURE__ */ jsx14(IconDownload, {}), onClick: exportCsv, children: "Export CSV" })
|
|
1950
2481
|
}
|
|
1951
2482
|
),
|
|
1952
|
-
exportError && /* @__PURE__ */
|
|
1953
|
-
/* @__PURE__ */
|
|
2483
|
+
exportError && /* @__PURE__ */ jsx14(Alert6, { variant: "error", children: exportError }),
|
|
2484
|
+
/* @__PURE__ */ jsxs13(
|
|
1954
2485
|
"form",
|
|
1955
2486
|
{
|
|
1956
2487
|
className: "flex gap-2",
|
|
@@ -1959,50 +2490,50 @@ function AuditView() {
|
|
|
1959
2490
|
setApplied(q.trim());
|
|
1960
2491
|
},
|
|
1961
2492
|
children: [
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
2493
|
+
/* @__PURE__ */ jsx14("div", { className: "flex-1", children: /* @__PURE__ */ jsx14(Input8, { type: "search", "aria-label": "Search the audit log", placeholder: "Search action, target or company", value: q, onChange: (e) => setQ(e.target.value) }) }),
|
|
2494
|
+
/* @__PURE__ */ jsx14(Button10, { type: "submit", variant: "outline", children: "Search" })
|
|
1964
2495
|
]
|
|
1965
2496
|
}
|
|
1966
2497
|
),
|
|
1967
|
-
state.error && !rows.length ? /* @__PURE__ */
|
|
1968
|
-
/* @__PURE__ */
|
|
2498
|
+
state.error && !rows.length ? /* @__PURE__ */ jsx14(ErrorState7, { description: state.error.message, onRetry: () => load(false) }) : state.loading && !rows.length ? /* @__PURE__ */ jsx14(LoadingState7, { variant: "table", rows: 8 }) : !rows.length ? /* @__PURE__ */ jsx14(EmptyState5, { icon: /* @__PURE__ */ jsx14(IconList, { size: 24 }), title: applied ? "Nothing matches" : "No events yet", compact: true }) : /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
2499
|
+
/* @__PURE__ */ jsx14("ol", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: rows.map((e, i) => {
|
|
1969
2500
|
var _a;
|
|
1970
|
-
return /* @__PURE__ */
|
|
1971
|
-
/* @__PURE__ */
|
|
1972
|
-
/* @__PURE__ */
|
|
1973
|
-
/* @__PURE__ */
|
|
2501
|
+
return /* @__PURE__ */ jsxs13("li", { className: "flex flex-wrap items-baseline gap-x-3 gap-y-1 px-4 py-2.5", children: [
|
|
2502
|
+
/* @__PURE__ */ jsx14("span", { className: "w-40 shrink-0 text-xs text-muted", children: formatDate(e.at) }),
|
|
2503
|
+
/* @__PURE__ */ jsxs13("span", { className: "min-w-0 flex-1 text-sm text-foreground", children: [
|
|
2504
|
+
/* @__PURE__ */ jsx14("span", { className: "font-medium", children: e.actor }),
|
|
1974
2505
|
" ",
|
|
1975
2506
|
describeAction(e.action),
|
|
1976
|
-
e.company && e.companyId && /* @__PURE__ */
|
|
2507
|
+
e.company && e.companyId && /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
1977
2508
|
" \xB7 ",
|
|
1978
|
-
/* @__PURE__ */
|
|
2509
|
+
/* @__PURE__ */ jsx14(Link, { href: href(`companies/${e.companyId}`), className: "text-primary-600 hover:underline", children: e.company })
|
|
1979
2510
|
] }),
|
|
1980
|
-
e.target && /* @__PURE__ */
|
|
2511
|
+
e.target && /* @__PURE__ */ jsxs13("span", { className: "text-muted", children: [
|
|
1981
2512
|
" \xB7 ",
|
|
1982
2513
|
e.target
|
|
1983
2514
|
] })
|
|
1984
2515
|
] }),
|
|
1985
|
-
/* @__PURE__ */
|
|
2516
|
+
/* @__PURE__ */ jsx14("code", { className: "text-xs text-muted", children: e.action })
|
|
1986
2517
|
] }, (_a = e.id) != null ? _a : i);
|
|
1987
2518
|
}) }),
|
|
1988
|
-
cursor && /* @__PURE__ */
|
|
2519
|
+
cursor && /* @__PURE__ */ jsx14("div", { className: "text-center", children: /* @__PURE__ */ jsx14(Button10, { variant: "outline", loading: state.loading, onClick: () => load(true), children: "Load older" }) })
|
|
1989
2520
|
] })
|
|
1990
2521
|
] });
|
|
1991
2522
|
}
|
|
1992
2523
|
|
|
1993
2524
|
// src/platform/views/Security.jsx
|
|
1994
|
-
import
|
|
1995
|
-
import { Alert as
|
|
1996
|
-
import { jsx as
|
|
2525
|
+
import React14, { useState as useState12 } from "react";
|
|
2526
|
+
import { Alert as Alert7, Button as Button11, Input as Input9, PageHeader as PageHeader8 } from "@dreamtree-org/twreact-ui";
|
|
2527
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1997
2528
|
var grouped = (s) => String(s != null ? s : "").replace(/(.{4})/g, "$1 ").trim();
|
|
1998
2529
|
function SecurityView({ gate = false }) {
|
|
1999
2530
|
var _a, _b;
|
|
2000
2531
|
const { client, me, reloadMe, Link } = usePlatform();
|
|
2001
2532
|
const sensitive = useSensitive();
|
|
2002
|
-
const [setup, setSetup] =
|
|
2003
|
-
const [code, setCode] =
|
|
2004
|
-
const [busy, setBusy] =
|
|
2005
|
-
const [error, setError] =
|
|
2533
|
+
const [setup, setSetup] = useState12(null);
|
|
2534
|
+
const [code, setCode] = useState12("");
|
|
2535
|
+
const [busy, setBusy] = useState12(false);
|
|
2536
|
+
const [error, setError] = useState12(null);
|
|
2006
2537
|
const enrolled = (_a = me == null ? void 0 : me.admin) == null ? void 0 : _a.totpEnrolled;
|
|
2007
2538
|
const begin = async () => {
|
|
2008
2539
|
setBusy(true);
|
|
@@ -2030,36 +2561,187 @@ function SecurityView({ gate = false }) {
|
|
|
2030
2561
|
setBusy(false);
|
|
2031
2562
|
}
|
|
2032
2563
|
};
|
|
2033
|
-
return /* @__PURE__ */
|
|
2034
|
-
/* @__PURE__ */
|
|
2035
|
-
gate && /* @__PURE__ */
|
|
2036
|
-
error && /* @__PURE__ */
|
|
2037
|
-
/* @__PURE__ */
|
|
2038
|
-
/* @__PURE__ */
|
|
2564
|
+
return /* @__PURE__ */ jsxs14("div", { className: "mx-auto max-w-2xl space-y-6 px-4 py-6 sm:px-6", children: [
|
|
2565
|
+
/* @__PURE__ */ jsx15(PageHeader8, { title: gate ? "Set up your authenticator" : "Security", subtitle: (_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.email, linkAs: Link }),
|
|
2566
|
+
gate && /* @__PURE__ */ jsx15(Alert7, { 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." }),
|
|
2567
|
+
error && /* @__PURE__ */ jsx15(Alert7, { variant: "error", children: error }),
|
|
2568
|
+
/* @__PURE__ */ jsx15(Card, { title: "Authenticator app", children: enrolled ? /* @__PURE__ */ jsxs14("p", { className: "flex items-center gap-2 text-sm text-success-700", children: [
|
|
2569
|
+
/* @__PURE__ */ jsx15(IconCheck, {}),
|
|
2039
2570
|
" 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."
|
|
2040
|
-
] }) : !setup ? /* @__PURE__ */
|
|
2041
|
-
/* @__PURE__ */
|
|
2042
|
-
/* @__PURE__ */
|
|
2043
|
-
] }) : /* @__PURE__ */
|
|
2044
|
-
/* @__PURE__ */
|
|
2045
|
-
/* @__PURE__ */
|
|
2571
|
+
] }) : !setup ? /* @__PURE__ */ jsxs14("div", { className: "space-y-3", children: [
|
|
2572
|
+
/* @__PURE__ */ jsx15("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." }),
|
|
2573
|
+
/* @__PURE__ */ jsx15(Button11, { leftIcon: /* @__PURE__ */ jsx15(IconShield, {}), loading: busy, onClick: begin, children: "Start setup" })
|
|
2574
|
+
] }) : /* @__PURE__ */ jsxs14("form", { className: "space-y-4", onSubmit: verify, children: [
|
|
2575
|
+
/* @__PURE__ */ jsxs14("ol", { className: "list-decimal space-y-3 pl-5 text-sm text-foreground", children: [
|
|
2576
|
+
/* @__PURE__ */ jsxs14("li", { children: [
|
|
2046
2577
|
"Add an account in your authenticator app with this setup key:",
|
|
2047
|
-
/* @__PURE__ */
|
|
2048
|
-
/* @__PURE__ */
|
|
2049
|
-
/* @__PURE__ */
|
|
2050
|
-
/* @__PURE__ */
|
|
2578
|
+
/* @__PURE__ */ jsxs14("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
|
|
2579
|
+
/* @__PURE__ */ jsx15("code", { className: "rounded-md bg-background px-2 py-1 font-mono text-sm tracking-wider", children: grouped(setup.secret) }),
|
|
2580
|
+
/* @__PURE__ */ jsx15(CopyButton, { value: setup.secret, label: "Copy key" }),
|
|
2581
|
+
/* @__PURE__ */ jsx15("a", { href: setup.otpauthUrl, className: "text-xs text-primary-600 underline", children: "Open in authenticator app" })
|
|
2051
2582
|
] })
|
|
2052
2583
|
] }),
|
|
2053
|
-
/* @__PURE__ */
|
|
2584
|
+
/* @__PURE__ */ jsx15("li", { children: "Enter the 6-digit code it shows." })
|
|
2054
2585
|
] }),
|
|
2055
|
-
/* @__PURE__ */
|
|
2056
|
-
/* @__PURE__ */
|
|
2586
|
+
/* @__PURE__ */ jsx15("div", { className: "max-w-xs", children: /* @__PURE__ */ jsx15(Input9, { label: "Code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }) }),
|
|
2587
|
+
/* @__PURE__ */ jsx15(Button11, { type: "submit", loading: busy, disabled: code.length < 6, children: "Verify and finish" })
|
|
2057
2588
|
] }) })
|
|
2058
2589
|
] });
|
|
2059
2590
|
}
|
|
2060
2591
|
|
|
2592
|
+
// src/platform/views/Plans.jsx
|
|
2593
|
+
import React15, { useState as useState13 } from "react";
|
|
2594
|
+
import { Alert as Alert8, Button as Button12, Dialog as Dialog6, EmptyState as EmptyState6, ErrorState as ErrorState8, Input as Input10, LoadingState as LoadingState8, PageHeader as PageHeader9, Select as Select4, Textarea as Textarea4 } from "@dreamtree-org/twreact-ui";
|
|
2595
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2596
|
+
var INTERVALS = [
|
|
2597
|
+
{ value: "month", label: "Monthly" },
|
|
2598
|
+
{ value: "year", label: "Yearly" },
|
|
2599
|
+
{ value: "one-time", label: "One-time" }
|
|
2600
|
+
];
|
|
2601
|
+
var blank = { code: "", name: "", price: "0", currency: "INR", interval: "month", seats: "", limits: "", features: "" };
|
|
2602
|
+
function toForm(plan) {
|
|
2603
|
+
var _a, _b, _c, _d, _e;
|
|
2604
|
+
return {
|
|
2605
|
+
code: plan.code,
|
|
2606
|
+
name: plan.name,
|
|
2607
|
+
price: String((Number((_a = plan.priceMinor) != null ? _a : 0) / 100).toFixed(2)).replace(/\.00$/, ""),
|
|
2608
|
+
currency: (_b = plan.currency) != null ? _b : "INR",
|
|
2609
|
+
interval: (_c = plan.interval) != null ? _c : "month",
|
|
2610
|
+
seats: plan.seats == null ? "" : String(plan.seats),
|
|
2611
|
+
limits: Object.entries((_d = plan.limits) != null ? _d : {}).map(([k, v]) => `${k} = ${v != null ? v : ""}`).join("\n"),
|
|
2612
|
+
features: ((_e = plan.features) != null ? _e : []).join(", ")
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
function planBody(form) {
|
|
2616
|
+
const limits = {};
|
|
2617
|
+
for (const line of form.limits.split("\n")) {
|
|
2618
|
+
const [k, ...rest] = line.split("=");
|
|
2619
|
+
if (!k.trim()) continue;
|
|
2620
|
+
const v = rest.join("=").trim();
|
|
2621
|
+
limits[k.trim()] = v === "" ? null : Number(v);
|
|
2622
|
+
}
|
|
2623
|
+
return {
|
|
2624
|
+
code: form.code.trim().toLowerCase(),
|
|
2625
|
+
name: form.name.trim(),
|
|
2626
|
+
priceMinor: Math.round(Number(form.price || 0) * 100),
|
|
2627
|
+
currency: form.currency.trim().toUpperCase(),
|
|
2628
|
+
interval: form.interval,
|
|
2629
|
+
seats: form.seats.trim() === "" ? null : Number(form.seats),
|
|
2630
|
+
limits,
|
|
2631
|
+
features: form.features.split(",").map((f) => f.trim()).filter(Boolean)
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
function PlansView() {
|
|
2635
|
+
var _a;
|
|
2636
|
+
const { client, can, Link } = usePlatform();
|
|
2637
|
+
const sensitive = useSensitive();
|
|
2638
|
+
const { data, error, loading, reload } = useLoad("plans");
|
|
2639
|
+
const [flash, setFlash] = useState13(null);
|
|
2640
|
+
const [editing, setEditing] = useState13(null);
|
|
2641
|
+
const [busy, setBusy] = useState13(false);
|
|
2642
|
+
const writable = can("plans.write");
|
|
2643
|
+
const plans = (_a = data == null ? void 0 : data.plans) != null ? _a : [];
|
|
2644
|
+
const save = async () => {
|
|
2645
|
+
setBusy(true);
|
|
2646
|
+
setFlash(null);
|
|
2647
|
+
try {
|
|
2648
|
+
const body = planBody(editing.form);
|
|
2649
|
+
if (editing.plan) {
|
|
2650
|
+
const { code: _code, ...patch } = body;
|
|
2651
|
+
await sensitive(() => client.patch(`plans/${encodeURIComponent(editing.plan.id)}`, patch));
|
|
2652
|
+
} else {
|
|
2653
|
+
await sensitive(() => client.post("plans", body));
|
|
2654
|
+
}
|
|
2655
|
+
setFlash({ kind: "success", msg: `${body.name} saved` });
|
|
2656
|
+
setEditing(null);
|
|
2657
|
+
await reload();
|
|
2658
|
+
} catch (e) {
|
|
2659
|
+
const msg = errorText(e);
|
|
2660
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
2661
|
+
} finally {
|
|
2662
|
+
setBusy(false);
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
const setStatus = async (plan, status) => {
|
|
2666
|
+
setFlash(null);
|
|
2667
|
+
try {
|
|
2668
|
+
await sensitive(() => client.patch(`plans/${encodeURIComponent(plan.id)}`, { status }));
|
|
2669
|
+
setFlash({ kind: "success", msg: `${plan.name} ${status === "archived" ? "archived" : "restored"}` });
|
|
2670
|
+
await reload();
|
|
2671
|
+
} catch (e) {
|
|
2672
|
+
const msg = errorText(e);
|
|
2673
|
+
if (msg) setFlash({ kind: "error", msg });
|
|
2674
|
+
}
|
|
2675
|
+
};
|
|
2676
|
+
const field = (key) => {
|
|
2677
|
+
var _a2;
|
|
2678
|
+
return {
|
|
2679
|
+
value: (_a2 = editing == null ? void 0 : editing.form[key]) != null ? _a2 : "",
|
|
2680
|
+
onChange: (e) => setEditing((s) => ({ ...s, form: { ...s.form, [key]: e.target.value } }))
|
|
2681
|
+
};
|
|
2682
|
+
};
|
|
2683
|
+
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;
|
|
2684
|
+
return /* @__PURE__ */ jsxs15("div", { className: "mx-auto max-w-5xl space-y-6 px-4 py-6 sm:px-6", children: [
|
|
2685
|
+
/* @__PURE__ */ jsx16(
|
|
2686
|
+
PageHeader9,
|
|
2687
|
+
{
|
|
2688
|
+
title: "Plans",
|
|
2689
|
+
subtitle: "What companies can subscribe to. Archived plans stay on the companies already using them.",
|
|
2690
|
+
linkAs: Link,
|
|
2691
|
+
actions: writable && /* @__PURE__ */ jsx16(Button12, { leftIcon: /* @__PURE__ */ jsx16(IconPlus, {}), onClick: () => setEditing({ form: { ...blank } }), children: "New plan" })
|
|
2692
|
+
}
|
|
2693
|
+
),
|
|
2694
|
+
flash && /* @__PURE__ */ jsx16(Alert8, { variant: flash.kind, dismissible: true, onDismiss: () => setFlash(null), children: flash.msg }),
|
|
2695
|
+
error && !data ? /* @__PURE__ */ jsx16(ErrorState8, { description: error.message, onRetry: reload }) : loading && !data ? /* @__PURE__ */ jsx16(LoadingState8, { variant: "table", rows: 3 }) : plans.length === 0 ? /* @__PURE__ */ jsx16(EmptyState6, { icon: /* @__PURE__ */ jsx16(IconTag, { size: 24 }), title: "No plans yet", description: writable ? "Create the first plan companies can subscribe to." : "A superadmin creates plans." }) : /* @__PURE__ */ jsx16("ul", { className: "divide-y divide-line rounded-xl border border-line bg-surface", children: plans.map((p) => {
|
|
2696
|
+
var _a2, _b;
|
|
2697
|
+
return /* @__PURE__ */ jsxs15("li", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
|
|
2698
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-w-0 flex-1", children: [
|
|
2699
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
2700
|
+
/* @__PURE__ */ jsx16("span", { children: p.name }),
|
|
2701
|
+
/* @__PURE__ */ jsx16("span", { className: "font-mono text-xs text-muted", children: p.code })
|
|
2702
|
+
] }),
|
|
2703
|
+
/* @__PURE__ */ jsxs15("div", { className: "text-xs text-muted", children: [
|
|
2704
|
+
formatPrice(p.priceMinor, p.currency, p.interval),
|
|
2705
|
+
p.seats ? ` \xB7 ${p.seats} seats` : "",
|
|
2706
|
+
Object.keys((_a2 = p.limits) != null ? _a2 : {}).length ? ` \xB7 ${Object.entries(p.limits).map(([k, v]) => `${k} ${v != null ? v : "\u221E"}`).join(", ")}` : ""
|
|
2707
|
+
] }),
|
|
2708
|
+
((_b = p.features) == null ? void 0 : _b.length) ? /* @__PURE__ */ jsx16("div", { className: "mt-1 text-xs text-muted", children: p.features.join(" \xB7 ") }) : null
|
|
2709
|
+
] }),
|
|
2710
|
+
/* @__PURE__ */ jsx16(StatusPill, { status: p.status }),
|
|
2711
|
+
writable && /* @__PURE__ */ jsxs15("div", { className: "flex gap-1", children: [
|
|
2712
|
+
/* @__PURE__ */ jsx16(Button12, { size: "sm", variant: "ghost", onClick: () => setEditing({ plan: p, form: toForm(p) }), children: "Edit" }),
|
|
2713
|
+
/* @__PURE__ */ jsx16(Button12, { size: "sm", variant: "ghost", onClick: () => setStatus(p, p.status === "archived" ? "active" : "archived"), children: p.status === "archived" ? "Restore" : "Archive" })
|
|
2714
|
+
] })
|
|
2715
|
+
] }, p.id);
|
|
2716
|
+
}) }),
|
|
2717
|
+
/* @__PURE__ */ jsx16(
|
|
2718
|
+
Dialog6,
|
|
2719
|
+
{
|
|
2720
|
+
isOpen: editing !== null,
|
|
2721
|
+
onClose: () => setEditing(null),
|
|
2722
|
+
title: (editing == null ? void 0 : editing.plan) ? `Edit ${editing.plan.name}` : "New plan",
|
|
2723
|
+
size: "md",
|
|
2724
|
+
footer: /* @__PURE__ */ jsxs15("div", { className: "flex justify-end gap-2", children: [
|
|
2725
|
+
/* @__PURE__ */ jsx16(Button12, { variant: "outline", onClick: () => setEditing(null), children: "Cancel" }),
|
|
2726
|
+
/* @__PURE__ */ jsx16(Button12, { loading: busy, disabled: !valid, onClick: save, children: "Save plan" })
|
|
2727
|
+
] }),
|
|
2728
|
+
children: editing && /* @__PURE__ */ jsxs15("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
2729
|
+
/* @__PURE__ */ jsx16(Input10, { label: "Name", autoFocus: true, ...field("name") }),
|
|
2730
|
+
/* @__PURE__ */ jsx16(Input10, { label: "Code", disabled: Boolean(editing.plan), helperText: editing.plan ? "Fixed once created" : "Lowercase, e.g. pro or team-yearly", ...field("code") }),
|
|
2731
|
+
/* @__PURE__ */ jsx16(Input10, { label: "Price", type: "number", min: "0", step: "0.01", ...field("price") }),
|
|
2732
|
+
/* @__PURE__ */ jsx16(Input10, { label: "Currency", maxLength: 3, ...field("currency") }),
|
|
2733
|
+
/* @__PURE__ */ jsx16(Select4, { label: "Billing", options: INTERVALS, value: editing.form.interval, onChange: (v) => setEditing((s) => ({ ...s, form: { ...s.form, interval: String(v) } })) }),
|
|
2734
|
+
/* @__PURE__ */ jsx16(Input10, { label: "Seats", type: "number", min: "1", helperText: "Leave empty for no seat limit", ...field("seats") }),
|
|
2735
|
+
/* @__PURE__ */ jsx16("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx16(Textarea4, { label: "Limits", rows: 3, placeholder: "users = 10\nbranches =", helperText: "One per line: name = number. Leave the number empty for unlimited.", ...field("limits") }) }),
|
|
2736
|
+
/* @__PURE__ */ jsx16("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx16(Input10, { label: "Features", placeholder: "gst, reports, api", helperText: "Comma-separated", ...field("features") }) })
|
|
2737
|
+
] })
|
|
2738
|
+
}
|
|
2739
|
+
)
|
|
2740
|
+
] });
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2061
2743
|
// src/platform/PlatformConsole.jsx
|
|
2062
|
-
import { jsx as
|
|
2744
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2063
2745
|
var trim = (s) => String(s != null ? s : "").replace(/^\/+|\/+$/g, "");
|
|
2064
2746
|
function matchConsoleRoute(rel, pages = []) {
|
|
2065
2747
|
const parts = trim(rel).split("/").filter(Boolean);
|
|
@@ -2071,7 +2753,8 @@ function matchConsoleRoute(rel, pages = []) {
|
|
|
2071
2753
|
if (parts[1] === "new" && parts.length === 2) return { view: "new-company" };
|
|
2072
2754
|
if (parts.length === 2) return { view: "company", id: decodeURIComponent(parts[1]) };
|
|
2073
2755
|
}
|
|
2074
|
-
if (parts.length === 1 && ["admins", "audit", "security"].includes(parts[0])) return { view: parts[0] };
|
|
2756
|
+
if (parts.length === 1 && ["admins", "audit", "security", "plans"].includes(parts[0])) return { view: parts[0] };
|
|
2757
|
+
if (parts.length === 1 && parts[0] === "support-access") return { view: "support" };
|
|
2075
2758
|
return { view: "not-found" };
|
|
2076
2759
|
}
|
|
2077
2760
|
function PlatformConsole({
|
|
@@ -2103,7 +2786,7 @@ function PlatformConsole({
|
|
|
2103
2786
|
[given, apiBasePath, loginPath, base2]
|
|
2104
2787
|
);
|
|
2105
2788
|
const Link = useMemo3(() => makeLink(linkAs, navigate), [linkAs, navigate]);
|
|
2106
|
-
const [me, setMe] =
|
|
2789
|
+
const [me, setMe] = useState14(initialMe);
|
|
2107
2790
|
const reloadMe = useCallback4(async () => {
|
|
2108
2791
|
try {
|
|
2109
2792
|
setMe(await client.get("me"));
|
|
@@ -2111,7 +2794,7 @@ function PlatformConsole({
|
|
|
2111
2794
|
if (e.status !== 401) setMe(null);
|
|
2112
2795
|
}
|
|
2113
2796
|
}, [client]);
|
|
2114
|
-
|
|
2797
|
+
useEffect7(() => {
|
|
2115
2798
|
void reloadMe();
|
|
2116
2799
|
}, [reloadMe]);
|
|
2117
2800
|
const href = useCallback4((rel2 = "") => `${base2}/${trim(rel2)}`.replace(/\/$/, "") || "/", [base2]);
|
|
@@ -2139,13 +2822,15 @@ function PlatformConsole({
|
|
|
2139
2822
|
const route = matchConsoleRoute(rel, visiblePages);
|
|
2140
2823
|
const gate = me && me.access && me.access.console === false;
|
|
2141
2824
|
const active = (prefix) => prefix === "" ? route.view === "overview" : trim(rel) === prefix || trim(rel).startsWith(`${prefix}/`);
|
|
2142
|
-
const nav = gate ? [{ id: "security", label: "Set up authenticator", href: href("security"), icon: /* @__PURE__ */
|
|
2143
|
-
enabled.includes("overview") && { id: "overview", label: "Overview", href: href(""), icon: /* @__PURE__ */
|
|
2144
|
-
enabled.includes("companies") && { id: "companies", label: "Companies", href: href("companies"), icon: /* @__PURE__ */
|
|
2825
|
+
const nav = gate ? [{ id: "security", label: "Set up authenticator", href: href("security"), icon: /* @__PURE__ */ jsx17(IconShield, {}), active: true }] : [
|
|
2826
|
+
enabled.includes("overview") && { id: "overview", label: "Overview", href: href(""), icon: /* @__PURE__ */ jsx17(IconDashboard, {}), active: active("") },
|
|
2827
|
+
enabled.includes("companies") && { id: "companies", label: "Companies", href: href("companies"), icon: /* @__PURE__ */ jsx17(IconBuilding, {}), active: active("companies") },
|
|
2145
2828
|
...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)) })),
|
|
2146
|
-
enabled.includes("
|
|
2147
|
-
enabled.includes("
|
|
2148
|
-
{ id: "
|
|
2829
|
+
enabled.includes("plans") && can("plans.read") && { id: "plans", label: "Plans", href: href("plans"), icon: /* @__PURE__ */ jsx17(IconTag, {}), active: active("plans") },
|
|
2830
|
+
enabled.includes("support") && can("support.request") && { id: "support", label: "Support access", href: href("support-access"), icon: /* @__PURE__ */ jsx17(IconLifebuoy, {}), active: active("support-access") },
|
|
2831
|
+
enabled.includes("audit") && can("audit.read") && { id: "audit", label: "Audit log", href: href("audit"), icon: /* @__PURE__ */ jsx17(IconList, {}), active: active("audit"), group: "Platform" },
|
|
2832
|
+
enabled.includes("admins") && can("admins.read") && { id: "admins", label: "Platform admins", href: href("admins"), icon: /* @__PURE__ */ jsx17(IconUsers, {}), active: active("admins"), group: "Platform" },
|
|
2833
|
+
{ id: "security", label: "Security", href: href("security"), icon: /* @__PURE__ */ jsx17(IconShield, {}), active: active("security"), group: "Platform" }
|
|
2149
2834
|
].filter(Boolean);
|
|
2150
2835
|
const value = {
|
|
2151
2836
|
client,
|
|
@@ -2156,6 +2841,7 @@ function PlatformConsole({
|
|
|
2156
2841
|
navigate,
|
|
2157
2842
|
can,
|
|
2158
2843
|
companyTabs,
|
|
2844
|
+
features: enabled,
|
|
2159
2845
|
companyLoginPath,
|
|
2160
2846
|
companyLoginUrl: (slug) => `${origin}${companyLoginPath(slug)}`,
|
|
2161
2847
|
loginUrl: `${origin}${loginPath != null ? loginPath : `${base2}/login`}`,
|
|
@@ -2163,29 +2849,31 @@ function PlatformConsole({
|
|
|
2163
2849
|
appName: appName != null ? appName : (_a = me == null ? void 0 : me.app) == null ? void 0 : _a.name
|
|
2164
2850
|
};
|
|
2165
2851
|
let body;
|
|
2166
|
-
if (me === void 0) body = /* @__PURE__ */
|
|
2167
|
-
else if (gate) body = /* @__PURE__ */
|
|
2168
|
-
else if (route.view === "overview") body = /* @__PURE__ */
|
|
2169
|
-
else if (route.view === "companies") body = /* @__PURE__ */
|
|
2170
|
-
else if (route.view === "new-company") body = /* @__PURE__ */
|
|
2171
|
-
else if (route.view === "company") body = /* @__PURE__ */
|
|
2172
|
-
else if (route.view === "admins") body = /* @__PURE__ */
|
|
2173
|
-
else if (route.view === "audit") body = /* @__PURE__ */
|
|
2174
|
-
else if (route.view === "security") body = /* @__PURE__ */
|
|
2852
|
+
if (me === void 0) body = /* @__PURE__ */ jsx17("div", { className: "p-6", children: /* @__PURE__ */ jsx17(LoadingState9, { variant: "stats" }) });
|
|
2853
|
+
else if (gate) body = /* @__PURE__ */ jsx17(SecurityView, { gate: true });
|
|
2854
|
+
else if (route.view === "overview") body = /* @__PURE__ */ jsx17(OverviewView, {});
|
|
2855
|
+
else if (route.view === "companies") body = /* @__PURE__ */ jsx17(CompaniesView, {});
|
|
2856
|
+
else if (route.view === "new-company") body = /* @__PURE__ */ jsx17(NewCompanyView, {});
|
|
2857
|
+
else if (route.view === "company") body = /* @__PURE__ */ jsx17(CompanyDetailView, { companyId: route.id }, route.id);
|
|
2858
|
+
else if (route.view === "admins") body = /* @__PURE__ */ jsx17(AdminsView, {});
|
|
2859
|
+
else if (route.view === "audit") body = /* @__PURE__ */ jsx17(AuditView, {});
|
|
2860
|
+
else if (route.view === "security") body = /* @__PURE__ */ jsx17(SecurityView, {});
|
|
2861
|
+
else if (route.view === "plans" && enabled.includes("plans") && can("plans.read")) body = /* @__PURE__ */ jsx17(PlansView, {});
|
|
2862
|
+
else if (route.view === "support" && enabled.includes("support") && can("support.request")) body = /* @__PURE__ */ jsx17(SupportAccessView, {});
|
|
2175
2863
|
else if (route.view === "page") body = route.page.render({ client, path: route.rest, navigate, href, me });
|
|
2176
|
-
else body = /* @__PURE__ */
|
|
2177
|
-
return /* @__PURE__ */
|
|
2864
|
+
else body = /* @__PURE__ */ jsx17("div", { className: "p-6", children: /* @__PURE__ */ jsx17(EmptyState7, { title: "Page not found", description: "This console page does not exist." }) });
|
|
2865
|
+
return /* @__PURE__ */ jsx17(PlatformProvider, { value, children: /* @__PURE__ */ jsx17(StepUpProvider, { client, totpEnrolled: Boolean((_b = me == null ? void 0 : me.admin) == null ? void 0 : _b.totpEnrolled), children: /* @__PURE__ */ jsx17(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 }) }) });
|
|
2178
2866
|
}
|
|
2179
2867
|
|
|
2180
2868
|
// src/platform/PlatformLogin.jsx
|
|
2181
|
-
import
|
|
2182
|
-
import { Alert as
|
|
2869
|
+
import React17, { useMemo as useMemo4, useState as useState17 } from "react";
|
|
2870
|
+
import { Alert as Alert9, Button as Button13, Input as Input11 } from "@dreamtree-org/twreact-ui";
|
|
2183
2871
|
|
|
2184
2872
|
// src/AuthShell.jsx
|
|
2185
2873
|
import { useId } from "react";
|
|
2186
2874
|
|
|
2187
2875
|
// src/DemoAccounts.jsx
|
|
2188
|
-
import { useEffect as
|
|
2876
|
+
import { useEffect as useEffect8, useRef as useRef3, useState as useState15 } from "react";
|
|
2189
2877
|
|
|
2190
2878
|
// src/demoFill.js
|
|
2191
2879
|
function normalizeDemoAccounts(data) {
|
|
@@ -2199,12 +2887,12 @@ function normalizeDemoAccounts(data) {
|
|
|
2199
2887
|
}
|
|
2200
2888
|
|
|
2201
2889
|
// src/DemoAccounts.jsx
|
|
2202
|
-
import { Fragment as Fragment6, jsx as
|
|
2890
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2203
2891
|
function DemoAccounts({ load, onSelect, className = "" }) {
|
|
2204
|
-
const [state, setState] =
|
|
2205
|
-
const [picked, setPicked] =
|
|
2892
|
+
const [state, setState] = useState15({ status: "loading", accounts: [] });
|
|
2893
|
+
const [picked, setPicked] = useState15(null);
|
|
2206
2894
|
const loadRef = useRef3(load);
|
|
2207
|
-
|
|
2895
|
+
useEffect8(() => {
|
|
2208
2896
|
let live = true;
|
|
2209
2897
|
Promise.resolve().then(() => loadRef.current()).then((data) => live && setState({ status: "ready", ...normalizeDemoAccounts(data) })).catch(() => live && setState({ status: "ready", accounts: [] }));
|
|
2210
2898
|
return () => {
|
|
@@ -2216,10 +2904,10 @@ function DemoAccounts({ load, onSelect, className = "" }) {
|
|
|
2216
2904
|
setPicked(i);
|
|
2217
2905
|
onSelect == null ? void 0 : onSelect(account);
|
|
2218
2906
|
};
|
|
2219
|
-
return /* @__PURE__ */
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
state.status === "loading" ? /* @__PURE__ */
|
|
2222
|
-
/* @__PURE__ */
|
|
2907
|
+
return /* @__PURE__ */ jsxs16("div", { className: `dt-auth-demo ${className}`, "aria-busy": state.status === "loading" || void 0, children: [
|
|
2908
|
+
/* @__PURE__ */ jsx18("p", { className: "dt-auth-demo-label", children: "Demo accounts" }),
|
|
2909
|
+
state.status === "loading" ? /* @__PURE__ */ jsx18("div", { className: "dt-auth-demo-chips", "aria-hidden": "true", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx18("span", { className: "dt-auth-demo-chip dt-auth-demo-chip--skeleton" }, i)) }) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
2910
|
+
/* @__PURE__ */ jsx18("div", { className: "dt-auth-demo-chips", children: state.accounts.map((a, i) => /* @__PURE__ */ jsx18(
|
|
2223
2911
|
"button",
|
|
2224
2912
|
{
|
|
2225
2913
|
type: "button",
|
|
@@ -2231,9 +2919,9 @@ function DemoAccounts({ load, onSelect, className = "" }) {
|
|
|
2231
2919
|
},
|
|
2232
2920
|
`${a.label}-${a.identifier}`
|
|
2233
2921
|
)) }),
|
|
2234
|
-
/* @__PURE__ */
|
|
2922
|
+
/* @__PURE__ */ jsx18("p", { className: "dt-auth-demo-note", children: state.sharedPassword ? /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
2235
2923
|
"Shared password ",
|
|
2236
|
-
/* @__PURE__ */
|
|
2924
|
+
/* @__PURE__ */ jsx18("code", { children: state.sharedPassword }),
|
|
2237
2925
|
" \u2014 tap a role to fill."
|
|
2238
2926
|
] }) : "Tap a role to fill." })
|
|
2239
2927
|
] })
|
|
@@ -2241,9 +2929,9 @@ function DemoAccounts({ load, onSelect, className = "" }) {
|
|
|
2241
2929
|
}
|
|
2242
2930
|
|
|
2243
2931
|
// src/useTapReveal.js
|
|
2244
|
-
import { useCallback as useCallback5, useRef as useRef4, useState as
|
|
2932
|
+
import { useCallback as useCallback5, useRef as useRef4, useState as useState16 } from "react";
|
|
2245
2933
|
function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
|
|
2246
|
-
const [revealed, setRevealed] =
|
|
2934
|
+
const [revealed, setRevealed] = useState16(false);
|
|
2247
2935
|
const count = useRef4(0);
|
|
2248
2936
|
const last = useRef4(0);
|
|
2249
2937
|
const onClick = useCallback5(() => {
|
|
@@ -2257,7 +2945,7 @@ function useTapReveal({ taps = 4, windowMs = 1500 } = {}) {
|
|
|
2257
2945
|
}
|
|
2258
2946
|
|
|
2259
2947
|
// src/AuthShell.jsx
|
|
2260
|
-
import { jsx as
|
|
2948
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2261
2949
|
var DEFAULT_LEGAL = [
|
|
2262
2950
|
{ label: "Privacy Policy", href: "/privacy" },
|
|
2263
2951
|
{ label: "Terms", href: "/terms" },
|
|
@@ -2265,10 +2953,10 @@ var DEFAULT_LEGAL = [
|
|
|
2265
2953
|
];
|
|
2266
2954
|
function BrandMark({ brand, size }) {
|
|
2267
2955
|
if (brand.logoSrc) {
|
|
2268
|
-
return /* @__PURE__ */
|
|
2956
|
+
return /* @__PURE__ */ jsx19("img", { className: "dt-auth-mark-img", src: brand.logoSrc, alt: "", width: size, height: size });
|
|
2269
2957
|
}
|
|
2270
|
-
if (brand.mark) return /* @__PURE__ */
|
|
2271
|
-
return /* @__PURE__ */
|
|
2958
|
+
if (brand.mark) return /* @__PURE__ */ jsx19("span", { className: "dt-auth-mark-node", children: brand.mark });
|
|
2959
|
+
return /* @__PURE__ */ jsx19("span", { className: "dt-auth-monogram", style: { width: size, height: size }, "aria-hidden": "true", children: brand.name.charAt(0) });
|
|
2272
2960
|
}
|
|
2273
2961
|
function brandStyle(brand) {
|
|
2274
2962
|
return {
|
|
@@ -2295,44 +2983,44 @@ function AuthShell({
|
|
|
2295
2983
|
const isPlatform = variant === "platform";
|
|
2296
2984
|
const headline = isPlatform ? brand.platformHeadline || "Platform administration" : brand.headline;
|
|
2297
2985
|
const pitch = isPlatform ? brand.platformPitch || `Super administrator console for ${brand.name}. Every action here is audited.` : brand.pitch;
|
|
2298
|
-
const renderLink = (l) => LinkComponent ? /* @__PURE__ */
|
|
2299
|
-
return /* @__PURE__ */
|
|
2986
|
+
const renderLink = (l) => LinkComponent ? /* @__PURE__ */ jsx19(LinkComponent, { to: l.href, href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href) : /* @__PURE__ */ jsx19("a", { href: l.href, className: "dt-auth-legal-link", children: l.label }, l.href);
|
|
2987
|
+
return /* @__PURE__ */ jsxs17(
|
|
2300
2988
|
"div",
|
|
2301
2989
|
{
|
|
2302
2990
|
className: `dt-auth ${isPlatform ? "dt-auth--platform" : ""} ${className}`,
|
|
2303
2991
|
style: brandStyle(brand),
|
|
2304
2992
|
"data-product": brand.id,
|
|
2305
2993
|
children: [
|
|
2306
|
-
/* @__PURE__ */
|
|
2307
|
-
/* @__PURE__ */
|
|
2308
|
-
/* @__PURE__ */
|
|
2309
|
-
/* @__PURE__ */
|
|
2310
|
-
isPlatform ? /* @__PURE__ */
|
|
2994
|
+
/* @__PURE__ */ jsxs17("aside", { className: "dt-auth-panel", children: [
|
|
2995
|
+
/* @__PURE__ */ jsxs17("a", { href: "/", className: "dt-auth-brand", children: [
|
|
2996
|
+
/* @__PURE__ */ jsx19(BrandMark, { brand, size: 28 }),
|
|
2997
|
+
/* @__PURE__ */ jsx19("span", { className: "dt-auth-brand-name", children: brand.name }),
|
|
2998
|
+
isPlatform ? /* @__PURE__ */ jsx19("span", { className: "dt-auth-badge", children: "Platform" }) : null
|
|
2311
2999
|
] }),
|
|
2312
|
-
/* @__PURE__ */
|
|
2313
|
-
/* @__PURE__ */
|
|
2314
|
-
pitch ? /* @__PURE__ */
|
|
2315
|
-
!isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */
|
|
3000
|
+
/* @__PURE__ */ jsxs17("div", { className: "dt-auth-pitch", children: [
|
|
3001
|
+
/* @__PURE__ */ jsx19("h2", { children: headline }),
|
|
3002
|
+
pitch ? /* @__PURE__ */ jsx19("p", { children: pitch }) : null,
|
|
3003
|
+
!isPlatform && ((_b = brand.points) == null ? void 0 : _b.length) ? /* @__PURE__ */ jsx19("ul", { children: brand.points.map((p) => /* @__PURE__ */ jsx19("li", { children: p }, p)) }) : null
|
|
2316
3004
|
] }),
|
|
2317
|
-
/* @__PURE__ */
|
|
3005
|
+
/* @__PURE__ */ jsx19("p", { className: "dt-auth-panel-foot", children: brand.category || "by Dreamtree-Org" })
|
|
2318
3006
|
] }),
|
|
2319
|
-
/* @__PURE__ */
|
|
2320
|
-
/* @__PURE__ */
|
|
2321
|
-
/* @__PURE__ */
|
|
2322
|
-
/* @__PURE__ */
|
|
2323
|
-
isPlatform ? /* @__PURE__ */
|
|
3007
|
+
/* @__PURE__ */ jsx19("main", { className: "dt-auth-main", children: /* @__PURE__ */ jsxs17("div", { className: "dt-auth-column", children: [
|
|
3008
|
+
/* @__PURE__ */ jsxs17("a", { href: "/", className: "dt-auth-brand dt-auth-brand--mobile", children: [
|
|
3009
|
+
/* @__PURE__ */ jsx19(BrandMark, { brand, size: 24 }),
|
|
3010
|
+
/* @__PURE__ */ jsx19("span", { className: "dt-auth-brand-name", children: brand.name }),
|
|
3011
|
+
isPlatform ? /* @__PURE__ */ jsx19("span", { className: "dt-auth-badge", children: "Platform" }) : null
|
|
2324
3012
|
] }),
|
|
2325
|
-
/* @__PURE__ */
|
|
2326
|
-
/* @__PURE__ */
|
|
2327
|
-
/* @__PURE__ */
|
|
2328
|
-
subtitle ? /* @__PURE__ */
|
|
3013
|
+
/* @__PURE__ */ jsxs17("section", { className: "dt-auth-card", "aria-labelledby": headingId, children: [
|
|
3014
|
+
/* @__PURE__ */ jsxs17("header", { className: "dt-auth-head", children: [
|
|
3015
|
+
/* @__PURE__ */ jsx19("h1", { id: headingId, ...demoAccounts ? tapProps : {}, children: title }),
|
|
3016
|
+
subtitle ? /* @__PURE__ */ jsx19("p", { children: subtitle }) : null
|
|
2329
3017
|
] }),
|
|
2330
3018
|
children,
|
|
2331
|
-
demoAccounts && revealed ? /* @__PURE__ */
|
|
3019
|
+
demoAccounts && revealed ? /* @__PURE__ */ jsx19(DemoAccounts, { load: demoAccounts.load, onSelect: demoAccounts.onSelect }) : null
|
|
2332
3020
|
] }),
|
|
2333
|
-
footer ? /* @__PURE__ */
|
|
2334
|
-
(legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */
|
|
2335
|
-
i > 0 ? /* @__PURE__ */
|
|
3021
|
+
footer ? /* @__PURE__ */ jsx19("div", { className: "dt-auth-footer", children: footer }) : null,
|
|
3022
|
+
(legalLinks == null ? void 0 : legalLinks.length) ? /* @__PURE__ */ jsx19("nav", { className: "dt-auth-legal", "aria-label": "Legal", children: legalLinks.map((l, i) => /* @__PURE__ */ jsxs17("span", { className: "dt-auth-legal-item", children: [
|
|
3023
|
+
i > 0 ? /* @__PURE__ */ jsx19("span", { "aria-hidden": "true", children: "\xB7" }) : null,
|
|
2336
3024
|
renderLink(l)
|
|
2337
3025
|
] }, l.href)) }) : null
|
|
2338
3026
|
] }) })
|
|
@@ -2342,17 +3030,17 @@ function AuthShell({
|
|
|
2342
3030
|
}
|
|
2343
3031
|
|
|
2344
3032
|
// src/platform/PlatformLogin.jsx
|
|
2345
|
-
import { jsx as
|
|
3033
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2346
3034
|
function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo = "/platform", title = "Platform administration", subtitle = "Administrator sign-in", linkComponent, legalLinks }) {
|
|
2347
3035
|
const client = useMemo4(() => given != null ? given : createPlatformClient({ basePath, onUnauthorized: () => {
|
|
2348
3036
|
} }), [given, basePath]);
|
|
2349
|
-
const [step, setStep] =
|
|
2350
|
-
const [email, setEmail] =
|
|
2351
|
-
const [password, setPassword] =
|
|
2352
|
-
const [ticket, setTicket] =
|
|
2353
|
-
const [code, setCode] =
|
|
2354
|
-
const [busy, setBusy] =
|
|
2355
|
-
const [error, setError] =
|
|
3037
|
+
const [step, setStep] = useState17("password");
|
|
3038
|
+
const [email, setEmail] = useState17("");
|
|
3039
|
+
const [password, setPassword] = useState17("");
|
|
3040
|
+
const [ticket, setTicket] = useState17(null);
|
|
3041
|
+
const [code, setCode] = useState17("");
|
|
3042
|
+
const [busy, setBusy] = useState17(false);
|
|
3043
|
+
const [error, setError] = useState17(null);
|
|
2356
3044
|
const done = (res) => {
|
|
2357
3045
|
if (onSignedIn) onSignedIn(res);
|
|
2358
3046
|
else if (typeof window !== "undefined") window.location.assign(redirectTo);
|
|
@@ -2392,18 +3080,18 @@ function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo
|
|
|
2392
3080
|
setBusy(false);
|
|
2393
3081
|
}
|
|
2394
3082
|
};
|
|
2395
|
-
return /* @__PURE__ */
|
|
2396
|
-
/* @__PURE__ */
|
|
2397
|
-
/* @__PURE__ */
|
|
2398
|
-
error && /* @__PURE__ */
|
|
2399
|
-
/* @__PURE__ */
|
|
2400
|
-
] }) : /* @__PURE__ */
|
|
2401
|
-
/* @__PURE__ */
|
|
2402
|
-
/* @__PURE__ */
|
|
2403
|
-
error && /* @__PURE__ */
|
|
2404
|
-
/* @__PURE__ */
|
|
2405
|
-
/* @__PURE__ */
|
|
2406
|
-
|
|
3083
|
+
return /* @__PURE__ */ jsx20(AuthShell, { brand, variant: "platform", title, subtitle, linkComponent, legalLinks, children: step === "password" ? /* @__PURE__ */ jsxs18("form", { onSubmit: submitPassword, className: "flex flex-col gap-4", children: [
|
|
3084
|
+
/* @__PURE__ */ jsx20(Input11, { label: "Admin email", type: "email", autoComplete: "username", autoFocus: true, value: email, onChange: (e) => setEmail(e.target.value) }),
|
|
3085
|
+
/* @__PURE__ */ jsx20(Input11, { label: "Password", type: "password", autoComplete: "current-password", value: password, onChange: (e) => setPassword(e.target.value) }),
|
|
3086
|
+
error && /* @__PURE__ */ jsx20(Alert9, { variant: "error", children: error }),
|
|
3087
|
+
/* @__PURE__ */ jsx20(Button13, { type: "submit", loading: busy, disabled: !email || !password, fullWidth: true, children: "Sign in" })
|
|
3088
|
+
] }) : /* @__PURE__ */ jsxs18("form", { onSubmit: submitCode, className: "flex flex-col gap-4", children: [
|
|
3089
|
+
/* @__PURE__ */ jsx20("p", { className: "text-sm", children: "Enter the 6-digit code from your authenticator app." }),
|
|
3090
|
+
/* @__PURE__ */ jsx20(Input11, { label: "Authenticator code", otp: true, autoFocus: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }),
|
|
3091
|
+
error && /* @__PURE__ */ jsx20(Alert9, { variant: "error", children: error }),
|
|
3092
|
+
/* @__PURE__ */ jsx20(Button13, { type: "submit", loading: busy, disabled: code.length < 6, fullWidth: true, children: "Verify" }),
|
|
3093
|
+
/* @__PURE__ */ jsx20(
|
|
3094
|
+
Button13,
|
|
2407
3095
|
{
|
|
2408
3096
|
variant: "ghost",
|
|
2409
3097
|
onClick: () => {
|
|
@@ -2419,6 +3107,7 @@ function PlatformLogin({ brand, basePath, client: given, onSignedIn, redirectTo
|
|
|
2419
3107
|
export {
|
|
2420
3108
|
Card,
|
|
2421
3109
|
CopyButton,
|
|
3110
|
+
DomainsTab,
|
|
2422
3111
|
Facts,
|
|
2423
3112
|
HealthDot,
|
|
2424
3113
|
InspectResult,
|
|
@@ -2427,19 +3116,26 @@ export {
|
|
|
2427
3116
|
PLATFORM_API_BASE,
|
|
2428
3117
|
PLATFORM_FEATURES,
|
|
2429
3118
|
PLATFORM_LOGIN_PATH,
|
|
3119
|
+
PlansView,
|
|
2430
3120
|
PlatformApiError,
|
|
2431
3121
|
PlatformConsole,
|
|
2432
3122
|
PlatformLogin,
|
|
2433
3123
|
PlatformShell,
|
|
2434
3124
|
SHIPPED_FEATURES,
|
|
2435
3125
|
STEP_UP_REQUIRED,
|
|
3126
|
+
SettingsTab,
|
|
2436
3127
|
StatusPill,
|
|
2437
3128
|
StepUpProvider,
|
|
3129
|
+
SubscriptionTab,
|
|
3130
|
+
SupportAccessView,
|
|
3131
|
+
SupportGrants,
|
|
3132
|
+
SupportTab,
|
|
2438
3133
|
createPlatformClient,
|
|
2439
3134
|
createStepUp,
|
|
2440
3135
|
describeAction,
|
|
2441
3136
|
enabledFeatures,
|
|
2442
3137
|
formatDate,
|
|
3138
|
+
formatPrice,
|
|
2443
3139
|
isStepUpRequired,
|
|
2444
3140
|
matchConsoleRoute,
|
|
2445
3141
|
readPlatformCsrf,
|