@odla-ai/chapter 0.21.0 → 0.22.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/README.md +79 -10
- package/dist/{chunk-AOTWFOH4.js → chunk-IWW5VAAC.js} +275 -90
- package/dist/chunk-IWW5VAAC.js.map +1 -0
- package/dist/{chunk-3JG5X2LT.js → chunk-VZPBTTO3.js} +13 -2
- package/dist/chunk-VZPBTTO3.js.map +1 -0
- package/dist/index.cjs +12 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/{types-CXDiDj_s.d.ts → types-CgWUQ2MH.d.ts} +14 -0
- package/dist/ui/admin/index.d.ts +77 -11
- package/dist/ui/admin/index.js +8 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +8 -2
- package/dist/ui/member/index.d.ts +1 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.d.cts +14 -0
- package/dist/worker/index.d.ts +14 -0
- package/package.json +3 -3
- package/runbooks/adopt-existing.md +13 -4
- package/runbooks/greenfield.md +4 -4
- package/dist/chunk-3JG5X2LT.js.map +0 -1
- package/dist/chunk-AOTWFOH4.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BrandStyle
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VZPBTTO3.js";
|
|
4
4
|
|
|
5
5
|
// src/ui/admin.tsx
|
|
6
6
|
import { useEffect as useEffect7, useMemo, useState as useState11 } from "react";
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
clerkAppearanceFromTokens
|
|
14
14
|
} from "@odla-ai/auth-clerk";
|
|
15
15
|
import { CrmClient } from "@odla-ai/crm";
|
|
16
|
-
import { ThemeScope } from "@odla-ai/ui/components";
|
|
17
16
|
|
|
18
17
|
// src/ui/chrome.tsx
|
|
19
18
|
import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
|
|
@@ -22,7 +21,14 @@ import { Tabs, TopBar, TopBarLink } from "@odla-ai/ui/components";
|
|
|
22
21
|
// src/ui/admin-layout.tsx
|
|
23
22
|
import { useEffect, useState } from "react";
|
|
24
23
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
25
|
-
var PAGE = {
|
|
24
|
+
var PAGE = {
|
|
25
|
+
maxWidth: "var(--ui-content-width, 1120px)",
|
|
26
|
+
margin: "0 auto",
|
|
27
|
+
padding: "var(--chapter-admin-page-padding, 28px 20px 64px)",
|
|
28
|
+
display: "flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
gap: 20
|
|
31
|
+
};
|
|
26
32
|
var HEAD = { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 16, flexWrap: "wrap" };
|
|
27
33
|
function AdminPage({ children }) {
|
|
28
34
|
return /* @__PURE__ */ jsx("div", { style: PAGE, children });
|
|
@@ -155,7 +161,11 @@ var S = {
|
|
|
155
161
|
fontSize: 22,
|
|
156
162
|
textDecoration: "none"
|
|
157
163
|
},
|
|
158
|
-
workspaceTabs: {
|
|
164
|
+
workspaceTabs: {
|
|
165
|
+
maxWidth: "var(--ui-content-width, 1160px)",
|
|
166
|
+
margin: "0 auto",
|
|
167
|
+
padding: "var(--chapter-admin-workspace-padding, 18px 20px 0)"
|
|
168
|
+
}
|
|
159
169
|
};
|
|
160
170
|
function Gate(props) {
|
|
161
171
|
const { brand, tagline, children } = props;
|
|
@@ -284,28 +294,51 @@ function AdminShell({
|
|
|
284
294
|
getToken,
|
|
285
295
|
signOut,
|
|
286
296
|
email,
|
|
297
|
+
currentUser,
|
|
287
298
|
routing = "fragment",
|
|
288
|
-
chrome = "
|
|
289
|
-
renderHeader
|
|
299
|
+
chrome = "embedded",
|
|
300
|
+
renderHeader,
|
|
301
|
+
renderAccountMenu,
|
|
302
|
+
renderWorkspaceNav
|
|
290
303
|
}) {
|
|
304
|
+
const normalizeRoute = useCallback((candidate) => {
|
|
305
|
+
if (candidate.viewId) return candidate;
|
|
306
|
+
const workspace = workspaces.find((item) => item.id === candidate.workspaceId);
|
|
307
|
+
return workspace?.defaultViewId ? { ...candidate, viewId: workspace.defaultViewId } : candidate;
|
|
308
|
+
}, [workspaces]);
|
|
291
309
|
const routeFromLocation = useCallback(() => {
|
|
292
|
-
if (typeof window === "undefined")
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
if (typeof window === "undefined") {
|
|
311
|
+
return normalizeRoute({ workspaceId: workspaces[0]?.id ?? "" });
|
|
312
|
+
}
|
|
313
|
+
return normalizeRoute(
|
|
314
|
+
adminRouteFromUrl(new URL(window.location.href), basePath, workspaces.map((workspace) => workspace.id))
|
|
315
|
+
);
|
|
316
|
+
}, [workspaces, basePath, normalizeRoute]);
|
|
295
317
|
const [route, setRoute] = useState2(routeFromLocation);
|
|
296
318
|
const href = useCallback(
|
|
297
|
-
(target) =>
|
|
298
|
-
|
|
319
|
+
(target) => {
|
|
320
|
+
const next = normalizeRoute(mergeRoute(route, target));
|
|
321
|
+
if (typeof window === "undefined") {
|
|
322
|
+
return `#${[
|
|
323
|
+
next.workspaceId,
|
|
324
|
+
next.viewId,
|
|
325
|
+
next.recordId,
|
|
326
|
+
next.detailTab
|
|
327
|
+
].filter((part) => Boolean(part)).map(encodeURIComponent).join("/")}`;
|
|
328
|
+
}
|
|
329
|
+
return adminRouteHref(new URL(window.location.href), basePath, next, routing);
|
|
330
|
+
},
|
|
331
|
+
[route, basePath, routing, normalizeRoute]
|
|
299
332
|
);
|
|
300
333
|
const navigate = useCallback(
|
|
301
334
|
(target) => {
|
|
302
|
-
const next = mergeRoute(route, target);
|
|
335
|
+
const next = normalizeRoute(mergeRoute(route, target));
|
|
303
336
|
if (typeof window !== "undefined") {
|
|
304
337
|
window.history.pushState(null, "", adminRouteHref(new URL(window.location.href), basePath, next, routing));
|
|
305
338
|
}
|
|
306
339
|
setRoute(next);
|
|
307
340
|
},
|
|
308
|
-
[route, basePath, routing]
|
|
341
|
+
[route, basePath, routing, normalizeRoute]
|
|
309
342
|
);
|
|
310
343
|
useEffect2(() => {
|
|
311
344
|
const sync = () => setRoute(routeFromLocation());
|
|
@@ -322,9 +355,22 @@ function AdminShell({
|
|
|
322
355
|
const actual = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
323
356
|
if (canonical !== actual) window.history.replaceState(null, "", canonical);
|
|
324
357
|
}, []);
|
|
325
|
-
const context = {
|
|
358
|
+
const context = {
|
|
359
|
+
client,
|
|
360
|
+
getToken,
|
|
361
|
+
...currentUser ? { currentUser } : {},
|
|
362
|
+
route,
|
|
363
|
+
navigate,
|
|
364
|
+
href
|
|
365
|
+
};
|
|
326
366
|
const active = workspaces.find((workspace) => workspace.id === route.workspaceId) ?? workspaces[0];
|
|
327
|
-
const
|
|
367
|
+
const accountProps = {
|
|
368
|
+
brand,
|
|
369
|
+
email,
|
|
370
|
+
...currentUser ? { currentUser } : {},
|
|
371
|
+
signOut
|
|
372
|
+
};
|
|
373
|
+
const identity = renderAccountMenu?.(accountProps) ?? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
328
374
|
/* @__PURE__ */ jsx3("span", { style: { ...S.role, color: "inherit", borderColor: "currentColor", background: "transparent" }, children: "admin" }),
|
|
329
375
|
email ? /* @__PURE__ */ jsx3("span", { style: { opacity: 0.72, fontSize: 12 }, children: email }) : null,
|
|
330
376
|
/* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: "Sign out" })
|
|
@@ -346,7 +392,7 @@ function AdminShell({
|
|
|
346
392
|
] })
|
|
347
393
|
] });
|
|
348
394
|
}
|
|
349
|
-
const header = renderHeader ? renderHeader(
|
|
395
|
+
const header = renderHeader ? renderHeader(accountProps) : chrome === "standalone" || chrome === "editorial" ? /* @__PURE__ */ jsxs3("header", { style: S.masthead, children: [
|
|
350
396
|
/* @__PURE__ */ jsxs3("a", { href: "/", style: S.mastheadBrand, children: [
|
|
351
397
|
/* @__PURE__ */ jsx3("span", { style: S.badgeSm, children: badgeText(brand) }),
|
|
352
398
|
brandDisplay(brand)
|
|
@@ -359,18 +405,22 @@ function AdminShell({
|
|
|
359
405
|
header,
|
|
360
406
|
/* @__PURE__ */ jsxs3("main", { className: "shell-main", style: S.main, children: [
|
|
361
407
|
/* @__PURE__ */ jsx3(ThemeWarning, {}),
|
|
362
|
-
/* @__PURE__ */
|
|
408
|
+
renderWorkspaceNav ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
409
|
+
/* @__PURE__ */ jsx3("div", { style: S.workspaceTabs, children: renderWorkspaceNav({ workspaces, route, navigate, href }) }),
|
|
410
|
+
active?.render(context)
|
|
411
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
363
412
|
Tabs,
|
|
364
413
|
{
|
|
365
414
|
style: S.workspaceTabs,
|
|
366
415
|
ariaLabel: "Admin workspaces",
|
|
367
416
|
value: route.workspaceId,
|
|
368
|
-
|
|
417
|
+
mount: "active",
|
|
418
|
+
onValueChange: (workspaceId) => setRoute(normalizeRoute({ workspaceId })),
|
|
369
419
|
items: workspaces.map((workspace) => ({
|
|
370
420
|
value: workspace.id,
|
|
371
421
|
label: workspace.label,
|
|
372
422
|
href: href(workspace.id),
|
|
373
|
-
panel: workspace.render(context)
|
|
423
|
+
panel: workspace.id === route.workspaceId ? workspace.render(context) : null
|
|
374
424
|
}))
|
|
375
425
|
}
|
|
376
426
|
)
|
|
@@ -685,11 +735,8 @@ function emailSection(options = {}) {
|
|
|
685
735
|
return { id, label, render: (ctx) => /* @__PURE__ */ jsx7(EmailBody, { ctx }) };
|
|
686
736
|
}
|
|
687
737
|
|
|
688
|
-
// src/ui/admin-people.tsx
|
|
689
|
-
import { BillingCard } from "@odla-ai/crm/ui";
|
|
690
|
-
import {
|
|
691
|
-
CrmWorkspace
|
|
692
|
-
} from "@odla-ai/crm/ui";
|
|
738
|
+
// src/ui/admin-people-workspace.tsx
|
|
739
|
+
import { BillingCard, CrmWorkspace } from "@odla-ai/crm/ui";
|
|
693
740
|
|
|
694
741
|
// src/ui/admin-record-actions.tsx
|
|
695
742
|
import { useEffect as useEffect4, useState as useState6 } from "react";
|
|
@@ -955,13 +1002,51 @@ function RecordSchedulingPanel(props) {
|
|
|
955
1002
|
] });
|
|
956
1003
|
}
|
|
957
1004
|
|
|
958
|
-
// src/ui/admin-people.tsx
|
|
1005
|
+
// src/ui/admin-people-workspace.tsx
|
|
959
1006
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
960
|
-
|
|
1007
|
+
function applicationLifecycleAdapter(ctx) {
|
|
1008
|
+
return {
|
|
1009
|
+
transitionStage: async ({ record, to }) => {
|
|
1010
|
+
const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
|
|
1011
|
+
if (!applicationId) {
|
|
1012
|
+
throw new Error("This record is not linked to an application workflow.");
|
|
1013
|
+
}
|
|
1014
|
+
const path = `/api/admin/applications/${encodeURIComponent(applicationId)}`;
|
|
1015
|
+
if (to === "approved") {
|
|
1016
|
+
await adminFetch(ctx.getToken, `${path}/approve`, { method: "POST" });
|
|
1017
|
+
} else if (to === "refunded") {
|
|
1018
|
+
await adminFetch(ctx.getToken, `${path}/refund`, { method: "POST" });
|
|
1019
|
+
} else {
|
|
1020
|
+
await adminFetch(ctx.getToken, path, {
|
|
1021
|
+
method: "PATCH",
|
|
1022
|
+
body: JSON.stringify({ status: to })
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
961
1028
|
function PeopleBody(props) {
|
|
962
|
-
const {
|
|
1029
|
+
const {
|
|
1030
|
+
crm,
|
|
1031
|
+
client,
|
|
1032
|
+
type,
|
|
1033
|
+
ctx,
|
|
1034
|
+
roles,
|
|
1035
|
+
lifecycle,
|
|
1036
|
+
networkSharing,
|
|
1037
|
+
lifecycleAdapter,
|
|
1038
|
+
requireLifecycleAdapter,
|
|
1039
|
+
renderSummary,
|
|
1040
|
+
renderMaster,
|
|
1041
|
+
renderDetailHeader,
|
|
1042
|
+
renderEmptyDetail,
|
|
1043
|
+
hrefForRecord,
|
|
1044
|
+
extendRecordTabs
|
|
1045
|
+
} = props;
|
|
963
1046
|
const def = crm.type(type);
|
|
964
1047
|
const recordId = ctx.route?.recordId ?? null;
|
|
1048
|
+
const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx) : void 0;
|
|
1049
|
+
const resolvedLifecycle = typeof lifecycleAdapter === "function" ? lifecycleAdapter(ctx) : lifecycleAdapter ?? builtInLifecycle;
|
|
965
1050
|
const workspaceClient = client;
|
|
966
1051
|
return /* @__PURE__ */ jsx13(AdminPage, { children: /* @__PURE__ */ jsx13(
|
|
967
1052
|
CrmWorkspace,
|
|
@@ -973,7 +1058,15 @@ function PeopleBody(props) {
|
|
|
973
1058
|
detailTab: ctx.route?.detailTab,
|
|
974
1059
|
defaultDetailTab: "profile",
|
|
975
1060
|
onRecordIdChange: (id) => ctx.navigate({ recordId: id, detailTab: null }),
|
|
1061
|
+
hrefForRecord: (record) => hrefForRecord?.(record, ctx) ?? ctx.href({ recordId: record.id, detailTab: "profile" }),
|
|
1062
|
+
hrefForList: ctx.href({ recordId: null, detailTab: null }),
|
|
976
1063
|
hrefForDetailTab: (detailTab) => ctx.href({ detailTab }),
|
|
1064
|
+
lifecycle: resolvedLifecycle,
|
|
1065
|
+
requireLifecycleAdapter: requireLifecycleAdapter ?? lifecycle,
|
|
1066
|
+
renderSummary: renderSummary ? (context) => renderSummary(context, ctx) : void 0,
|
|
1067
|
+
renderMaster: renderMaster ? (context) => renderMaster(context, ctx) : void 0,
|
|
1068
|
+
renderDetailHeader: renderDetailHeader ? (context) => renderDetailHeader(context, ctx) : void 0,
|
|
1069
|
+
renderEmptyDetail: renderEmptyDetail ? (context) => renderEmptyDetail(context, ctx) : void 0,
|
|
977
1070
|
extendRecordTabs: (base, context) => {
|
|
978
1071
|
const record = context.detail.record;
|
|
979
1072
|
const applicationId = typeof record.fields?.applicationId === "string" ? String(record.fields.applicationId) : null;
|
|
@@ -1028,7 +1121,8 @@ function PeopleBody(props) {
|
|
|
1028
1121
|
context.refreshList();
|
|
1029
1122
|
}
|
|
1030
1123
|
}
|
|
1031
|
-
)
|
|
1124
|
+
),
|
|
1125
|
+
visible: () => ctx.currentUser?.superAdmin === true
|
|
1032
1126
|
});
|
|
1033
1127
|
}
|
|
1034
1128
|
if (networkSharing) {
|
|
@@ -1043,6 +1137,10 @@ function PeopleBody(props) {
|
|
|
1043
1137
|
}
|
|
1044
1138
|
) });
|
|
1045
1139
|
}
|
|
1140
|
+
|
|
1141
|
+
// src/ui/admin-people.tsx
|
|
1142
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1143
|
+
var DEFAULT_ROLES = ["provisional", "member", "admin"];
|
|
1046
1144
|
function collectionSection(options) {
|
|
1047
1145
|
const { crm, type } = options;
|
|
1048
1146
|
let fallbackLabel = type;
|
|
@@ -1056,12 +1154,19 @@ function collectionSection(options) {
|
|
|
1056
1154
|
lifecycle = type === "person",
|
|
1057
1155
|
roles = DEFAULT_ROLES,
|
|
1058
1156
|
networkSharing = false,
|
|
1157
|
+
lifecycleAdapter,
|
|
1158
|
+
requireLifecycleAdapter,
|
|
1159
|
+
renderSummary,
|
|
1160
|
+
renderMaster,
|
|
1161
|
+
renderDetailHeader,
|
|
1162
|
+
renderEmptyDetail,
|
|
1163
|
+
hrefForRecord,
|
|
1059
1164
|
extendRecordTabs
|
|
1060
1165
|
} = options;
|
|
1061
1166
|
return {
|
|
1062
1167
|
id,
|
|
1063
1168
|
label,
|
|
1064
|
-
render: (ctx) => /* @__PURE__ */
|
|
1169
|
+
render: (ctx) => /* @__PURE__ */ jsx14(
|
|
1065
1170
|
PeopleBody,
|
|
1066
1171
|
{
|
|
1067
1172
|
crm,
|
|
@@ -1071,6 +1176,13 @@ function collectionSection(options) {
|
|
|
1071
1176
|
roles,
|
|
1072
1177
|
lifecycle,
|
|
1073
1178
|
networkSharing,
|
|
1179
|
+
lifecycleAdapter,
|
|
1180
|
+
requireLifecycleAdapter,
|
|
1181
|
+
renderSummary,
|
|
1182
|
+
renderMaster,
|
|
1183
|
+
renderDetailHeader,
|
|
1184
|
+
renderEmptyDetail,
|
|
1185
|
+
hrefForRecord,
|
|
1074
1186
|
extendRecordTabs
|
|
1075
1187
|
}
|
|
1076
1188
|
)
|
|
@@ -1084,6 +1196,13 @@ function peopleSection(options) {
|
|
|
1084
1196
|
type = "person",
|
|
1085
1197
|
roles,
|
|
1086
1198
|
networkSharing,
|
|
1199
|
+
lifecycleAdapter,
|
|
1200
|
+
requireLifecycleAdapter,
|
|
1201
|
+
renderSummary,
|
|
1202
|
+
renderMaster,
|
|
1203
|
+
renderDetailHeader,
|
|
1204
|
+
renderEmptyDetail,
|
|
1205
|
+
hrefForRecord,
|
|
1087
1206
|
extendRecordTabs
|
|
1088
1207
|
} = options;
|
|
1089
1208
|
return collectionSection({
|
|
@@ -1093,12 +1212,19 @@ function peopleSection(options) {
|
|
|
1093
1212
|
label,
|
|
1094
1213
|
...roles ? { roles } : {},
|
|
1095
1214
|
...networkSharing != null ? { networkSharing } : {},
|
|
1215
|
+
...lifecycleAdapter ? { lifecycleAdapter } : {},
|
|
1216
|
+
...requireLifecycleAdapter != null ? { requireLifecycleAdapter } : {},
|
|
1217
|
+
...renderSummary ? { renderSummary } : {},
|
|
1218
|
+
...renderMaster ? { renderMaster } : {},
|
|
1219
|
+
...renderDetailHeader ? { renderDetailHeader } : {},
|
|
1220
|
+
...renderEmptyDetail ? { renderEmptyDetail } : {},
|
|
1221
|
+
...hrefForRecord ? { hrefForRecord } : {},
|
|
1096
1222
|
...extendRecordTabs ? { extendRecordTabs } : {}
|
|
1097
1223
|
});
|
|
1098
1224
|
}
|
|
1099
1225
|
|
|
1100
1226
|
// src/ui/admin-workspaces.tsx
|
|
1101
|
-
import { jsx as
|
|
1227
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1102
1228
|
var adminViewContext = (ctx, viewId, active) => {
|
|
1103
1229
|
const route = {
|
|
1104
1230
|
workspaceId: ctx.route.workspaceId,
|
|
@@ -1123,11 +1249,12 @@ function DashboardWorkspace({ ctx }) {
|
|
|
1123
1249
|
const active = ctx.route.viewId === "billing" ? "billing" : "overview";
|
|
1124
1250
|
const overview = dashboardSection({ id: "overview", label: "Overview" });
|
|
1125
1251
|
const billing = billingSection();
|
|
1126
|
-
return /* @__PURE__ */
|
|
1252
|
+
return /* @__PURE__ */ jsx15(
|
|
1127
1253
|
Tabs2,
|
|
1128
1254
|
{
|
|
1129
1255
|
ariaLabel: "Dashboard views",
|
|
1130
1256
|
value: active,
|
|
1257
|
+
mount: "active",
|
|
1131
1258
|
items: [
|
|
1132
1259
|
{
|
|
1133
1260
|
value: "overview",
|
|
@@ -1149,11 +1276,12 @@ function SettingsWorkspace({ ctx }) {
|
|
|
1149
1276
|
const active = ctx.route.viewId === "email" ? "email" : "calendar";
|
|
1150
1277
|
const calendar = availabilitySection({ id: "calendar", label: "Calendar" });
|
|
1151
1278
|
const email = emailSection();
|
|
1152
|
-
return /* @__PURE__ */
|
|
1279
|
+
return /* @__PURE__ */ jsx15(
|
|
1153
1280
|
Tabs2,
|
|
1154
1281
|
{
|
|
1155
1282
|
ariaLabel: "Settings views",
|
|
1156
1283
|
value: active,
|
|
1284
|
+
mount: "active",
|
|
1157
1285
|
items: [
|
|
1158
1286
|
{
|
|
1159
1287
|
value: "calendar",
|
|
@@ -1176,12 +1304,13 @@ function PeopleWorkspace({ chapter, ctx }) {
|
|
|
1176
1304
|
const fallback = types[0]?.[0] ?? "person";
|
|
1177
1305
|
const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
|
|
1178
1306
|
const networkSharing = chapter.network.targets.length > 0;
|
|
1179
|
-
return /* @__PURE__ */
|
|
1307
|
+
return /* @__PURE__ */ jsx15(
|
|
1180
1308
|
Tabs2,
|
|
1181
1309
|
{
|
|
1182
1310
|
ariaLabel: "CRM collections",
|
|
1183
1311
|
value: active,
|
|
1184
1312
|
variant: "pill",
|
|
1313
|
+
mount: "active",
|
|
1185
1314
|
items: types.map(([type, def]) => {
|
|
1186
1315
|
const section = collectionSection({
|
|
1187
1316
|
crm: chapter.crm,
|
|
@@ -1204,18 +1333,30 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
1204
1333
|
const people = {
|
|
1205
1334
|
id: "people",
|
|
1206
1335
|
label: "People",
|
|
1207
|
-
|
|
1336
|
+
defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
|
|
1337
|
+
render: (ctx) => /* @__PURE__ */ jsx15(PeopleWorkspace, { chapter, ctx })
|
|
1208
1338
|
};
|
|
1209
1339
|
if (chapter.mode === "hub") return [people];
|
|
1210
1340
|
return [
|
|
1211
|
-
{
|
|
1341
|
+
{
|
|
1342
|
+
id: "dashboard",
|
|
1343
|
+
label: "Dashboard",
|
|
1344
|
+
defaultViewId: "overview",
|
|
1345
|
+
render: (ctx) => /* @__PURE__ */ jsx15(DashboardWorkspace, { ctx })
|
|
1346
|
+
},
|
|
1212
1347
|
people,
|
|
1213
|
-
{
|
|
1348
|
+
{
|
|
1349
|
+
id: "settings",
|
|
1350
|
+
label: "Settings",
|
|
1351
|
+
defaultViewId: "calendar",
|
|
1352
|
+
render: (ctx) => /* @__PURE__ */ jsx15(SettingsWorkspace, { ctx })
|
|
1353
|
+
}
|
|
1214
1354
|
];
|
|
1215
1355
|
}
|
|
1216
1356
|
|
|
1217
|
-
// src/ui/admin.tsx
|
|
1218
|
-
import {
|
|
1357
|
+
// src/ui/admin-frame.tsx
|
|
1358
|
+
import { ThemeScope } from "@odla-ai/ui/components";
|
|
1359
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1219
1360
|
function AdminTheme(props) {
|
|
1220
1361
|
const brand = props.chapter?.brand;
|
|
1221
1362
|
return /* @__PURE__ */ jsxs12(
|
|
@@ -1225,12 +1366,51 @@ function AdminTheme(props) {
|
|
|
1225
1366
|
theme: brand?.theme,
|
|
1226
1367
|
colorScheme: brand?.colorScheme ?? "light",
|
|
1227
1368
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1369
|
+
/* @__PURE__ */ jsx16(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
|
|
1229
1370
|
props.children
|
|
1230
1371
|
]
|
|
1231
1372
|
}
|
|
1232
1373
|
);
|
|
1233
1374
|
}
|
|
1375
|
+
function signInRedirect(basePath, workspaces) {
|
|
1376
|
+
if (typeof window === "undefined") return basePath;
|
|
1377
|
+
const current = new URL(window.location.href);
|
|
1378
|
+
const inbound = adminRouteFromUrl(
|
|
1379
|
+
current,
|
|
1380
|
+
basePath,
|
|
1381
|
+
workspaces.map((workspace2) => workspace2.id)
|
|
1382
|
+
);
|
|
1383
|
+
const workspace = workspaces.find((item) => item.id === inbound.workspaceId);
|
|
1384
|
+
const route = inbound.viewId || !workspace?.defaultViewId ? inbound : { ...inbound, viewId: workspace.defaultViewId };
|
|
1385
|
+
return adminRouteHref(current, basePath, route, "query");
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/ui/admin-auth.ts
|
|
1389
|
+
var apiUrl = (apiBase, path) => `${apiBase.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1390
|
+
var DEFAULT_ADMIN_AUTH = {};
|
|
1391
|
+
async function loadChapterAdminConfig(adapter, apiBase) {
|
|
1392
|
+
if (adapter.loadConfig) return adapter.loadConfig({ apiBase });
|
|
1393
|
+
const response = await fetch(apiUrl(apiBase, adapter.configPath ?? "/api/config"));
|
|
1394
|
+
const body = await response.json();
|
|
1395
|
+
if (adapter.mapConfig) return adapter.mapConfig(body);
|
|
1396
|
+
return {
|
|
1397
|
+
publishableKey: typeof body.clerkPublishableKey === "string" ? body.clerkPublishableKey : null
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
async function loadChapterAdminUser(adapter, apiBase, getToken) {
|
|
1401
|
+
if (adapter.loadCurrentUser) {
|
|
1402
|
+
return adapter.loadCurrentUser({ apiBase, getToken });
|
|
1403
|
+
}
|
|
1404
|
+
const token = await getToken();
|
|
1405
|
+
const response = await fetch(apiUrl(apiBase, adapter.currentUserPath ?? "/api/me"), {
|
|
1406
|
+
headers: token ? { authorization: `Bearer ${token}` } : {}
|
|
1407
|
+
});
|
|
1408
|
+
const body = await response.json().catch(() => ({}));
|
|
1409
|
+
return adapter.mapCurrentUser?.(body) ?? body;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/ui/admin.tsx
|
|
1413
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1234
1414
|
function Authed(props) {
|
|
1235
1415
|
const {
|
|
1236
1416
|
workspaces,
|
|
@@ -1240,7 +1420,10 @@ function Authed(props) {
|
|
|
1240
1420
|
apiBase,
|
|
1241
1421
|
routing,
|
|
1242
1422
|
chrome,
|
|
1243
|
-
|
|
1423
|
+
auth,
|
|
1424
|
+
renderHeader,
|
|
1425
|
+
renderAccountMenu,
|
|
1426
|
+
renderWorkspaceNav
|
|
1244
1427
|
} = props;
|
|
1245
1428
|
const { getToken, signOut } = useClerkAuth();
|
|
1246
1429
|
const client = useMemo(
|
|
@@ -1258,14 +1441,12 @@ function Authed(props) {
|
|
|
1258
1441
|
let live = true;
|
|
1259
1442
|
void (async () => {
|
|
1260
1443
|
try {
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
1263
|
-
headers: token ? { authorization: `Bearer ${token}` } : {}
|
|
1264
|
-
});
|
|
1265
|
-
const body = await response.json().catch(() => ({}));
|
|
1444
|
+
const body = await loadChapterAdminUser(auth, apiBase, getToken);
|
|
1445
|
+
const authorized = auth.isAuthorized ? auth.isAuthorized(body) : body.authorized === true;
|
|
1266
1446
|
if (live) setState({
|
|
1267
|
-
status:
|
|
1268
|
-
email: body.email
|
|
1447
|
+
status: authorized ? "ok" : "denied",
|
|
1448
|
+
email: typeof body.email === "string" ? body.email : null,
|
|
1449
|
+
user: body
|
|
1269
1450
|
});
|
|
1270
1451
|
} catch {
|
|
1271
1452
|
if (live) setState({ status: "denied" });
|
|
@@ -1274,19 +1455,19 @@ function Authed(props) {
|
|
|
1274
1455
|
return () => {
|
|
1275
1456
|
live = false;
|
|
1276
1457
|
};
|
|
1277
|
-
}, [getToken, apiBase]);
|
|
1278
|
-
if (state.status === "checking") return /* @__PURE__ */
|
|
1458
|
+
}, [getToken, apiBase, auth]);
|
|
1459
|
+
if (state.status === "checking") return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Checking access\u2026" }) });
|
|
1279
1460
|
if (state.status === "denied") {
|
|
1280
|
-
return /* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1461
|
+
return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsxs13("div", { className: "card", style: S.card, children: [
|
|
1462
|
+
/* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: "Not authorized" }),
|
|
1463
|
+
/* @__PURE__ */ jsxs13("p", { style: S.muted, children: [
|
|
1283
1464
|
state.email ? `${state.email} isn't` : "This account isn't",
|
|
1284
1465
|
" on the admin list."
|
|
1285
1466
|
] }),
|
|
1286
|
-
/* @__PURE__ */
|
|
1467
|
+
/* @__PURE__ */ jsx17("button", { className: "btn secondary", onClick: () => signOut(), children: "Sign out" })
|
|
1287
1468
|
] }) });
|
|
1288
1469
|
}
|
|
1289
|
-
return /* @__PURE__ */
|
|
1470
|
+
return /* @__PURE__ */ jsx17(
|
|
1290
1471
|
AdminShell,
|
|
1291
1472
|
{
|
|
1292
1473
|
workspaces,
|
|
@@ -1296,18 +1477,15 @@ function Authed(props) {
|
|
|
1296
1477
|
getToken,
|
|
1297
1478
|
signOut,
|
|
1298
1479
|
email: state.email ?? null,
|
|
1480
|
+
currentUser: state.user,
|
|
1299
1481
|
routing,
|
|
1300
1482
|
chrome,
|
|
1301
|
-
renderHeader
|
|
1483
|
+
renderHeader,
|
|
1484
|
+
renderAccountMenu,
|
|
1485
|
+
renderWorkspaceNav
|
|
1302
1486
|
}
|
|
1303
1487
|
);
|
|
1304
1488
|
}
|
|
1305
|
-
var signInRedirect = (basePath, workspaces) => {
|
|
1306
|
-
if (typeof window === "undefined") return basePath;
|
|
1307
|
-
const current = new URL(window.location.href);
|
|
1308
|
-
const route = adminRouteFromUrl(current, basePath, workspaces.map((workspace) => workspace.id));
|
|
1309
|
-
return adminRouteHref(current, basePath, route, "query");
|
|
1310
|
-
};
|
|
1311
1489
|
function ChapterAdmin(props) {
|
|
1312
1490
|
const defaults = useMemo(
|
|
1313
1491
|
() => props.chapter ? defaultAdminWorkspaces(props.chapter) : [],
|
|
@@ -1326,35 +1504,36 @@ function ChapterAdmin(props) {
|
|
|
1326
1504
|
const crmBasePath = props.crmBasePath ?? "/api/crm";
|
|
1327
1505
|
const apiBase = props.apiBase ?? "";
|
|
1328
1506
|
const routing = props.routing ?? "fragment";
|
|
1329
|
-
const chrome = props.chrome ?? "
|
|
1507
|
+
const chrome = props.chrome ?? "embedded";
|
|
1508
|
+
const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
|
|
1330
1509
|
const redirect = signInRedirect(basePath, workspaces);
|
|
1331
1510
|
const [publishableKey, setPublishableKey] = useState11(void 0);
|
|
1332
1511
|
useEffect7(() => {
|
|
1333
1512
|
let live = true;
|
|
1334
|
-
void
|
|
1513
|
+
void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
|
|
1335
1514
|
return () => {
|
|
1336
1515
|
live = false;
|
|
1337
1516
|
};
|
|
1338
|
-
}, [apiBase]);
|
|
1517
|
+
}, [apiBase, auth]);
|
|
1339
1518
|
let content;
|
|
1340
1519
|
if (publishableKey === void 0) {
|
|
1341
|
-
content = /* @__PURE__ */
|
|
1520
|
+
content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Loading\u2026" }) });
|
|
1342
1521
|
} else if (!publishableKey) {
|
|
1343
|
-
content = /* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1522
|
+
content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsxs13("div", { className: "card", style: S.card, children: [
|
|
1523
|
+
/* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: "Sign-in not configured" }),
|
|
1524
|
+
/* @__PURE__ */ jsx17("p", { style: S.muted, children: "No Clerk publishable key is set for this environment yet." })
|
|
1346
1525
|
] }) });
|
|
1347
1526
|
} else if (workspaces.length === 0) {
|
|
1348
|
-
content = /* @__PURE__ */
|
|
1527
|
+
content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "No admin workspaces are configured." }) });
|
|
1349
1528
|
} else {
|
|
1350
|
-
content = /* @__PURE__ */
|
|
1529
|
+
content = /* @__PURE__ */ jsxs13(
|
|
1351
1530
|
ClerkGate,
|
|
1352
1531
|
{
|
|
1353
1532
|
publishableKey,
|
|
1354
1533
|
appearance: clerkAppearanceFromTokens(),
|
|
1355
1534
|
afterSignOutUrl: "/",
|
|
1356
1535
|
children: [
|
|
1357
|
-
/* @__PURE__ */
|
|
1536
|
+
/* @__PURE__ */ jsx17(SignedOut, { children: /* @__PURE__ */ jsx17(Gate, { brand, tagline: "Admin sign-in \u2014 invite only", children: /* @__PURE__ */ jsx17(
|
|
1358
1537
|
SignIn,
|
|
1359
1538
|
{
|
|
1360
1539
|
routing: "hash",
|
|
@@ -1362,7 +1541,7 @@ function ChapterAdmin(props) {
|
|
|
1362
1541
|
signUpForceRedirectUrl: redirect
|
|
1363
1542
|
}
|
|
1364
1543
|
) }) }),
|
|
1365
|
-
/* @__PURE__ */
|
|
1544
|
+
/* @__PURE__ */ jsx17(SignedIn, { children: /* @__PURE__ */ jsx17(
|
|
1366
1545
|
Authed,
|
|
1367
1546
|
{
|
|
1368
1547
|
workspaces,
|
|
@@ -1372,14 +1551,17 @@ function ChapterAdmin(props) {
|
|
|
1372
1551
|
apiBase,
|
|
1373
1552
|
routing,
|
|
1374
1553
|
chrome,
|
|
1375
|
-
|
|
1554
|
+
auth,
|
|
1555
|
+
renderHeader: props.renderHeader,
|
|
1556
|
+
renderAccountMenu: props.renderAccountMenu,
|
|
1557
|
+
renderWorkspaceNav: props.renderWorkspaceNav
|
|
1376
1558
|
}
|
|
1377
1559
|
) })
|
|
1378
1560
|
]
|
|
1379
1561
|
}
|
|
1380
1562
|
);
|
|
1381
1563
|
}
|
|
1382
|
-
return /* @__PURE__ */
|
|
1564
|
+
return /* @__PURE__ */ jsx17(AdminTheme, { chapter: props.chapter, children: content });
|
|
1383
1565
|
}
|
|
1384
1566
|
|
|
1385
1567
|
// src/ui/admin-defaults.ts
|
|
@@ -1390,14 +1572,14 @@ function defaultAdminSections(chapter) {
|
|
|
1390
1572
|
// src/ui/admin-meetings.tsx
|
|
1391
1573
|
import { useState as useState12 } from "react";
|
|
1392
1574
|
import { Button as Button6 } from "@odla-ai/ui/components";
|
|
1393
|
-
import { Fragment as Fragment4, jsx as
|
|
1575
|
+
import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1394
1576
|
var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
|
|
1395
1577
|
function MeetingsBody({ ctx }) {
|
|
1396
1578
|
const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
|
|
1397
1579
|
const [busy, setBusy] = useState12(null);
|
|
1398
1580
|
const [note, setNote] = useState12(null);
|
|
1399
|
-
if (loading) return /* @__PURE__ */
|
|
1400
|
-
if (error || !data) return /* @__PURE__ */
|
|
1581
|
+
if (loading) return /* @__PURE__ */ jsx18(AdminNote, { children: "Loading\u2026" });
|
|
1582
|
+
if (error || !data) return /* @__PURE__ */ jsxs14(AdminNote, { children: [
|
|
1401
1583
|
"Couldn\u2019t load meetings",
|
|
1402
1584
|
error ? `: ${error}` : "",
|
|
1403
1585
|
"."
|
|
@@ -1428,30 +1610,30 @@ function MeetingsBody({ ctx }) {
|
|
|
1428
1610
|
}
|
|
1429
1611
|
void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
|
|
1430
1612
|
};
|
|
1431
|
-
return /* @__PURE__ */
|
|
1613
|
+
return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(Panel, { title: /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
1432
1614
|
"Agenda ",
|
|
1433
|
-
/* @__PURE__ */
|
|
1615
|
+
/* @__PURE__ */ jsxs14("span", { className: "muted", style: { fontWeight: 400 }, children: [
|
|
1434
1616
|
"\xB7 ",
|
|
1435
1617
|
data.timezone
|
|
1436
1618
|
] })
|
|
1437
|
-
] }), actions: note ? /* @__PURE__ */
|
|
1438
|
-
/* @__PURE__ */
|
|
1439
|
-
/* @__PURE__ */
|
|
1440
|
-
/* @__PURE__ */
|
|
1441
|
-
m.drift && m.drift !== "none" ? /* @__PURE__ */
|
|
1619
|
+
] }), actions: note ? /* @__PURE__ */ jsx18("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: "No meetings." }) : /* @__PURE__ */ jsx18("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs14("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
|
|
1620
|
+
/* @__PURE__ */ jsx18("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
|
|
1621
|
+
/* @__PURE__ */ jsx18("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
|
|
1622
|
+
/* @__PURE__ */ jsx18("span", { className: "badge", children: m.status }),
|
|
1623
|
+
m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs14("span", { className: "badge", children: [
|
|
1442
1624
|
"drift: ",
|
|
1443
1625
|
m.drift
|
|
1444
1626
|
] }) : null,
|
|
1445
|
-
m.meetUrl ? /* @__PURE__ */
|
|
1446
|
-
m.status === "scheduled" ? /* @__PURE__ */
|
|
1447
|
-
/* @__PURE__ */
|
|
1448
|
-
/* @__PURE__ */
|
|
1627
|
+
m.meetUrl ? /* @__PURE__ */ jsx18("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
|
|
1628
|
+
m.status === "scheduled" ? /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
1629
|
+
/* @__PURE__ */ jsx18(Button6, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: "Reschedule" }),
|
|
1630
|
+
/* @__PURE__ */ jsx18(Button6, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: "Cancel" })
|
|
1449
1631
|
] }) : null
|
|
1450
1632
|
] }, m.id)) }) }) });
|
|
1451
1633
|
}
|
|
1452
1634
|
function meetingsSection(options = {}) {
|
|
1453
1635
|
const { id = "meetings", label = "Meetings" } = options;
|
|
1454
|
-
return { id, label, render: (ctx) => /* @__PURE__ */
|
|
1636
|
+
return { id, label, render: (ctx) => /* @__PURE__ */ jsx18(MeetingsBody, { ctx }) };
|
|
1455
1637
|
}
|
|
1456
1638
|
|
|
1457
1639
|
export {
|
|
@@ -1471,11 +1653,14 @@ export {
|
|
|
1471
1653
|
emailSection,
|
|
1472
1654
|
RecordActions,
|
|
1473
1655
|
NetworkShareActions,
|
|
1656
|
+
applicationLifecycleAdapter,
|
|
1474
1657
|
collectionSection,
|
|
1475
1658
|
peopleSection,
|
|
1476
1659
|
defaultAdminWorkspaces,
|
|
1660
|
+
loadChapterAdminConfig,
|
|
1661
|
+
loadChapterAdminUser,
|
|
1477
1662
|
ChapterAdmin,
|
|
1478
1663
|
defaultAdminSections,
|
|
1479
1664
|
meetingsSection
|
|
1480
1665
|
};
|
|
1481
|
-
//# sourceMappingURL=chunk-
|
|
1666
|
+
//# sourceMappingURL=chunk-IWW5VAAC.js.map
|