@odla-ai/chapter 0.47.10 → 0.48.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 +231 -19
- package/dist/{chunk-VWDEHUNI.js → chunk-KZ2AXEZW.js} +497 -204
- package/dist/chunk-KZ2AXEZW.js.map +1 -0
- package/dist/{chunk-7MEVZEL2.js → chunk-MQ4OPZCS.js} +969 -516
- package/dist/chunk-MQ4OPZCS.js.map +1 -0
- package/dist/{copy-context-Dorxp-WK.d.ts → copy-context-BveBZglM.d.ts} +18 -7
- package/dist/index.cjs +3789 -761
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +753 -70
- package/dist/index.d.ts +753 -70
- package/dist/index.js +3782 -754
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +144 -42
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +92 -2
- package/dist/testing.d.ts +92 -2
- package/dist/testing.js +144 -42
- package/dist/testing.js.map +1 -1
- package/dist/ui/admin/index.d.ts +75 -5
- package/dist/ui/admin/index.js +3 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +4 -2
- package/dist/ui/member/index.d.ts +9 -2
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +9695 -4707
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +509 -9
- package/dist/worker/index.d.ts +509 -9
- package/dist/worker/index.js +8377 -3390
- package/dist/worker/index.js.map +1 -1
- package/package.json +5 -5
- package/runbooks/adopt-existing.md +44 -8
- package/dist/chunk-7MEVZEL2.js.map +0 -1
- package/dist/chunk-VWDEHUNI.js.map +0 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-4HS2JPE2.js";
|
|
8
8
|
|
|
9
9
|
// src/ui/admin.tsx
|
|
10
|
-
import { useEffect as
|
|
10
|
+
import { useEffect as useEffect12, useMemo as useMemo3, useRef as useRef3, useState as useState22 } from "preact/hooks";
|
|
11
11
|
import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
|
|
12
12
|
import { CrmClient } from "@odla-ai/crm";
|
|
13
13
|
|
|
@@ -176,6 +176,24 @@ function Gate(props) {
|
|
|
176
176
|
] }) });
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// src/ui/admin-route-merge.ts
|
|
180
|
+
function mergeAdminRoute(current, target) {
|
|
181
|
+
if (typeof target === "string") return { workspaceId: target };
|
|
182
|
+
const changingWorkspace = target.workspaceId != null && target.workspaceId !== current.workspaceId;
|
|
183
|
+
const next = changingWorkspace ? { workspaceId: target.workspaceId } : { ...current, ...target.workspaceId ? { workspaceId: target.workspaceId } : {} };
|
|
184
|
+
for (const key of ["viewId", "recordId", "detailTab"]) {
|
|
185
|
+
const value = target[key];
|
|
186
|
+
if (value === null) delete next[key];
|
|
187
|
+
else if (value !== void 0) next[key] = value;
|
|
188
|
+
}
|
|
189
|
+
if (target.viewId !== void 0 && target.recordId === void 0) {
|
|
190
|
+
delete next.recordId;
|
|
191
|
+
delete next.detailTab;
|
|
192
|
+
}
|
|
193
|
+
if (target.recordId === null) delete next.detailTab;
|
|
194
|
+
return next;
|
|
195
|
+
}
|
|
196
|
+
|
|
179
197
|
// src/ui/admin-route.ts
|
|
180
198
|
import { parseCrmDiscussionReference } from "@odla-ai/crm";
|
|
181
199
|
var DEFAULT_ALIASES = {
|
|
@@ -276,22 +294,6 @@ function adminSectionHref(current, basePath, id, routing = "fragment") {
|
|
|
276
294
|
|
|
277
295
|
// src/ui/chrome.tsx
|
|
278
296
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
279
|
-
var mergeRoute = (current, target) => {
|
|
280
|
-
if (typeof target === "string") return { workspaceId: target };
|
|
281
|
-
const changingWorkspace = target.workspaceId != null && target.workspaceId !== current.workspaceId;
|
|
282
|
-
const next = changingWorkspace ? { workspaceId: target.workspaceId } : { ...current, ...target.workspaceId ? { workspaceId: target.workspaceId } : {} };
|
|
283
|
-
for (const key of ["viewId", "recordId", "detailTab"]) {
|
|
284
|
-
const value = target[key];
|
|
285
|
-
if (value === null) delete next[key];
|
|
286
|
-
else if (value !== void 0) next[key] = value;
|
|
287
|
-
}
|
|
288
|
-
if (target.viewId !== void 0 && target.recordId === void 0) {
|
|
289
|
-
delete next.recordId;
|
|
290
|
-
delete next.detailTab;
|
|
291
|
-
}
|
|
292
|
-
if (target.recordId === null) delete next.detailTab;
|
|
293
|
-
return next;
|
|
294
|
-
};
|
|
295
297
|
function AdminShell({
|
|
296
298
|
workspaces,
|
|
297
299
|
basePath,
|
|
@@ -305,7 +307,8 @@ function AdminShell({
|
|
|
305
307
|
chrome = "embedded",
|
|
306
308
|
renderHeader,
|
|
307
309
|
renderAccountMenu,
|
|
308
|
-
renderWorkspaceNav
|
|
310
|
+
renderWorkspaceNav,
|
|
311
|
+
renderWorkspaceFrame
|
|
309
312
|
}) {
|
|
310
313
|
const copy = useChapterCopy().admin;
|
|
311
314
|
const normalizeRoute = useCallback((candidate) => {
|
|
@@ -324,7 +327,7 @@ function AdminShell({
|
|
|
324
327
|
const [route, setRoute] = useState2(routeFromLocation);
|
|
325
328
|
const href = useCallback(
|
|
326
329
|
(target) => {
|
|
327
|
-
const next = normalizeRoute(
|
|
330
|
+
const next = normalizeRoute(mergeAdminRoute(route, target));
|
|
328
331
|
if (typeof window === "undefined") {
|
|
329
332
|
return `#${[
|
|
330
333
|
next.workspaceId,
|
|
@@ -339,7 +342,7 @@ function AdminShell({
|
|
|
339
342
|
);
|
|
340
343
|
const navigate = useCallback(
|
|
341
344
|
(target) => {
|
|
342
|
-
const next = normalizeRoute(
|
|
345
|
+
const next = normalizeRoute(mergeAdminRoute(route, target));
|
|
343
346
|
if (typeof window !== "undefined") {
|
|
344
347
|
window.history.pushState(null, "", adminRouteHref(new URL(window.location.href), basePath, next, routing));
|
|
345
348
|
}
|
|
@@ -384,7 +387,7 @@ function AdminShell({
|
|
|
384
387
|
] });
|
|
385
388
|
const activeContent = active?.render(context);
|
|
386
389
|
const workspaceContent = active?.accent ? /* @__PURE__ */ jsx3(ThemeScope, { accent: active.accent, children: activeContent }) : activeContent;
|
|
387
|
-
if (chrome === "topbar") {
|
|
390
|
+
if (chrome === "topbar" && !renderWorkspaceFrame) {
|
|
388
391
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
389
392
|
/* @__PURE__ */ jsxs3(TopBar, { children: [
|
|
390
393
|
/* @__PURE__ */ jsxs3("a", { className: "topbar-brand", href: "/", children: [
|
|
@@ -410,6 +413,20 @@ function AdminShell({
|
|
|
410
413
|
/* @__PURE__ */ jsx3("span", { style: { flex: 1 } }),
|
|
411
414
|
/* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: identity })
|
|
412
415
|
] }) : null;
|
|
416
|
+
if (renderWorkspaceFrame) return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
417
|
+
header,
|
|
418
|
+
/* @__PURE__ */ jsx3(ThemeWarning, {}),
|
|
419
|
+
renderWorkspaceFrame({
|
|
420
|
+
...accountProps,
|
|
421
|
+
workspaces,
|
|
422
|
+
route,
|
|
423
|
+
navigate,
|
|
424
|
+
href,
|
|
425
|
+
activeWorkspace: active,
|
|
426
|
+
context,
|
|
427
|
+
children: workspaceContent
|
|
428
|
+
})
|
|
429
|
+
] });
|
|
413
430
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
414
431
|
header,
|
|
415
432
|
/* @__PURE__ */ jsxs3("main", { className: "shell-main", style: S.main, children: [
|
|
@@ -429,7 +446,7 @@ function AdminShell({
|
|
|
429
446
|
value: workspace.id,
|
|
430
447
|
label: workspace.label,
|
|
431
448
|
href: href(workspace.id),
|
|
432
|
-
panel: workspace.id === route.workspaceId ?
|
|
449
|
+
panel: workspace.id === route.workspaceId ? workspaceContent : null
|
|
433
450
|
}))
|
|
434
451
|
}
|
|
435
452
|
)
|
|
@@ -480,15 +497,23 @@ function useAdminResource(getToken, path) {
|
|
|
480
497
|
}
|
|
481
498
|
|
|
482
499
|
// src/ui/admin-billing.tsx
|
|
483
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
484
|
-
var money = (cents) => `$${(cents / 100).toLocaleString()}`;
|
|
500
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
501
|
+
var money = (cents) => cents === null ? "Unknown" : `$${(cents / 100).toLocaleString()}`;
|
|
485
502
|
var date = (t) => t ? new Date(t).toLocaleDateString([], { dateStyle: "medium" }) : "\u2014";
|
|
486
503
|
var columns = (copy) => [
|
|
487
504
|
{ key: "name", header: copy.name, cell: (r) => r.name, sortAs: "string" },
|
|
488
505
|
{ key: "email", header: copy.email, cell: (r) => r.email },
|
|
489
506
|
{ key: "applicationStatus", header: copy.application, cell: (r) => r.applicationStatus },
|
|
490
507
|
{ key: "subscriptionStatus", header: copy.subscription, cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? ` \xB7 ${copy.cancelling}` : ""}` : "\u2014" },
|
|
491
|
-
{ key: "amountCents", header:
|
|
508
|
+
{ key: "amountCents", header: "Next-term forecast", cell: (r) => r.amountCents === null ? "Unknown" : /* @__PURE__ */ jsxs4("span", { children: [
|
|
509
|
+
r.listAmountCents != null && r.listAmountCents !== r.amountCents ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
510
|
+
/* @__PURE__ */ jsx4("s", { children: money(r.listAmountCents) }),
|
|
511
|
+
" "
|
|
512
|
+
] }) : null,
|
|
513
|
+
money(r.amountCents),
|
|
514
|
+
"/",
|
|
515
|
+
r.interval === "month" ? "mo" : "yr"
|
|
516
|
+
] }), sortAs: "number", sortValue: (r) => r.amountCents ?? -1 },
|
|
492
517
|
{ key: "renewalAt", header: copy.renews, cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
|
|
493
518
|
];
|
|
494
519
|
function BillingBody({ ctx }) {
|
|
@@ -523,7 +548,7 @@ function billingSection(options = {}) {
|
|
|
523
548
|
|
|
524
549
|
// src/ui/admin-dashboard.tsx
|
|
525
550
|
import { MetricWidget, StepPipeline } from "@odla-ai/ui/components";
|
|
526
|
-
import { Fragment as
|
|
551
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
527
552
|
var number = (value) => Math.round(value).toLocaleString();
|
|
528
553
|
var money2 = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
|
|
529
554
|
var when = (time) => new Date(time).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
|
|
@@ -546,10 +571,11 @@ function DashboardBody({ ctx }) {
|
|
|
546
571
|
}));
|
|
547
572
|
const exceptions = ["declined", "refunded"].filter((stage) => stage in data.pipeline).map((stage) => `${stage} ${data.pipeline[stage]}`).join(" \xB7 ");
|
|
548
573
|
return /* @__PURE__ */ jsxs5(AdminPage, { children: [
|
|
574
|
+
data.incomplete ? /* @__PURE__ */ jsx5(AdminNote, { children: "This dashboard snapshot is incomplete. Counts and trends below cover only the loaded records, not the full chapter." }) : null,
|
|
549
575
|
/* @__PURE__ */ jsx5(
|
|
550
576
|
Panel,
|
|
551
577
|
{
|
|
552
|
-
title: /* @__PURE__ */ jsxs5(
|
|
578
|
+
title: /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
553
579
|
copy.upcomingCalls,
|
|
554
580
|
" ",
|
|
555
581
|
/* @__PURE__ */ jsxs5("span", { className: "muted", style: { fontWeight: 400 }, children: [
|
|
@@ -592,7 +618,7 @@ function DashboardBody({ ctx }) {
|
|
|
592
618
|
copy.annualRunRate,
|
|
593
619
|
":",
|
|
594
620
|
" ",
|
|
595
|
-
money2(data.revenue.annualRunRateCents
|
|
621
|
+
data.revenue.annualRunRateCents == null ? "Unknown" : money2(data.revenue.annualRunRateCents),
|
|
596
622
|
data.revenue.testMode ? ` \xB7 ${copy.testMode}` : ""
|
|
597
623
|
] })
|
|
598
624
|
] }) : /* @__PURE__ */ jsx5(Panel, { title: copy.revenue, children: /* @__PURE__ */ jsx5("p", { className: "muted", children: copy.revenueUnavailable }) }),
|
|
@@ -610,17 +636,229 @@ function dashboardSection(options = {}) {
|
|
|
610
636
|
// src/ui/admin-people-workspace.tsx
|
|
611
637
|
import { BillingCard, CrmWorkspace } from "@odla-ai/crm/ui";
|
|
612
638
|
|
|
613
|
-
// src/ui/admin-
|
|
614
|
-
import {
|
|
615
|
-
import { Button
|
|
639
|
+
// src/ui/admin-application-repair.tsx
|
|
640
|
+
import { useState as useState4 } from "preact/hooks";
|
|
641
|
+
import { Button } from "@odla-ai/ui/components";
|
|
616
642
|
import { jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
643
|
+
function ApplicationLinkRepair({ recordId, getToken, onChanged }) {
|
|
644
|
+
const [busy, setBusy] = useState4(false);
|
|
645
|
+
const [error, setError] = useState4(null);
|
|
646
|
+
return /* @__PURE__ */ jsxs6("section", { className: "panel", children: [
|
|
647
|
+
/* @__PURE__ */ jsx6("p", { children: "This person is missing an application link. Repair checks for one matching chapter application; it does not change membership or profile details." }),
|
|
648
|
+
/* @__PURE__ */ jsx6(Button, { variant: "secondary", disabled: busy, onClick: async () => {
|
|
649
|
+
setBusy(true);
|
|
650
|
+
setError(null);
|
|
651
|
+
try {
|
|
652
|
+
await adminFetch(getToken, "/api/admin/crm/sync", { method: "POST", body: JSON.stringify({ recordId }) });
|
|
653
|
+
onChanged();
|
|
654
|
+
} catch (cause) {
|
|
655
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
656
|
+
} finally {
|
|
657
|
+
setBusy(false);
|
|
658
|
+
}
|
|
659
|
+
}, children: busy ? "Checking application link\u2026" : "Repair application link" }),
|
|
660
|
+
error ? /* @__PURE__ */ jsx6("p", { role: "alert", children: error }) : null
|
|
661
|
+
] });
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/ui/admin-application-stage.tsx
|
|
665
|
+
import { useState as useState5 } from "preact/hooks";
|
|
666
|
+
import { Button as Button2, Field, Select } from "@odla-ai/ui/components";
|
|
667
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
|
|
668
|
+
function ApplicationStagePanel(props) {
|
|
669
|
+
const actions = useAdminResource(
|
|
670
|
+
props.getToken,
|
|
671
|
+
`/api/admin/applications/${encodeURIComponent(props.applicationId)}/actions`
|
|
672
|
+
);
|
|
673
|
+
const [selected, setSelected] = useState5("");
|
|
674
|
+
const [busy, setBusy] = useState5(false);
|
|
675
|
+
const [error, setError] = useState5(null);
|
|
676
|
+
if (actions.loading) return /* @__PURE__ */ jsx7("p", { role: "status", children: "Loading available actions\u2026" });
|
|
677
|
+
if (actions.error) return /* @__PURE__ */ jsxs7("p", { role: "alert", children: [
|
|
678
|
+
"Could not load application actions: ",
|
|
679
|
+
actions.error
|
|
680
|
+
] });
|
|
681
|
+
const allowed = actions.data?.allowedStages ?? [];
|
|
682
|
+
const target = allowed.includes(selected) ? selected : "";
|
|
683
|
+
return /* @__PURE__ */ jsxs7(Field, { label: "Stage", error, children: [
|
|
684
|
+
/* @__PURE__ */ jsx7("p", { children: actions.data?.status?.replaceAll("_", " ") }),
|
|
685
|
+
allowed.length ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
686
|
+
/* @__PURE__ */ jsx7(
|
|
687
|
+
Select,
|
|
688
|
+
{
|
|
689
|
+
"aria-label": "Move to stage",
|
|
690
|
+
value: target,
|
|
691
|
+
disabled: busy,
|
|
692
|
+
onChange: (event) => setSelected(event.currentTarget.value),
|
|
693
|
+
options: [{ value: "", label: "Move to\u2026" }, ...allowed.map((value) => ({ value, label: value.replaceAll("_", " ") }))]
|
|
694
|
+
}
|
|
695
|
+
),
|
|
696
|
+
/* @__PURE__ */ jsx7(Button2, { mini: true, disabled: busy || !target, onClick: async () => {
|
|
697
|
+
setBusy(true);
|
|
698
|
+
setError(null);
|
|
699
|
+
try {
|
|
700
|
+
await props.onMove(target);
|
|
701
|
+
setSelected("");
|
|
702
|
+
} catch (failure) {
|
|
703
|
+
setError(failure instanceof Error ? failure.message : "Action failed");
|
|
704
|
+
} finally {
|
|
705
|
+
setBusy(false);
|
|
706
|
+
actions.refresh();
|
|
707
|
+
}
|
|
708
|
+
}, children: "Move" })
|
|
709
|
+
] }) : /* @__PURE__ */ jsx7("p", { children: "No stage changes are available under this chapter\u2019s current membership policy." })
|
|
710
|
+
] });
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/ui/operational-profile-form.tsx
|
|
714
|
+
import { Button as Button3, Field as Field2, Input, Select as Select2, Textarea } from "@odla-ai/ui/components";
|
|
715
|
+
import { useRef, useState as useState6 } from "preact/hooks";
|
|
716
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
717
|
+
var valueText = (value) => value == null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
718
|
+
function OperationalProfileForm(props) {
|
|
719
|
+
const [draft, setDraft] = useState6(() => Object.fromEntries(props.fields.map((field) => [field.id, valueText(props.values[field.id])])));
|
|
720
|
+
const [busy, setBusy] = useState6(false);
|
|
721
|
+
const [error, setError] = useState6(null);
|
|
722
|
+
const pending = useRef(null);
|
|
723
|
+
const changed = props.fields.filter((field) => draft[field.id] !== valueText(props.values[field.id]));
|
|
724
|
+
const save = async () => {
|
|
725
|
+
setBusy(true);
|
|
726
|
+
setError(null);
|
|
727
|
+
try {
|
|
728
|
+
const input = Object.fromEntries(changed.map((field) => {
|
|
729
|
+
const text = draft[field.id] ?? "";
|
|
730
|
+
let value = text || null;
|
|
731
|
+
if (text && (field.type === "number" || field.type === "date")) {
|
|
732
|
+
value = Number(text);
|
|
733
|
+
if (!Number.isFinite(value)) throw new Error(`${field.label} must be a number${field.type === "date" ? " (milliseconds since epoch)" : ""}.`);
|
|
734
|
+
}
|
|
735
|
+
if (text && field.type === "boolean") value = text === "true";
|
|
736
|
+
if (text && field.type === "json") value = JSON.parse(text);
|
|
737
|
+
return [field.id, value];
|
|
738
|
+
}));
|
|
739
|
+
const fingerprint = JSON.stringify(input);
|
|
740
|
+
if (pending.current?.fingerprint !== fingerprint) pending.current = { fingerprint, id: crypto.randomUUID() };
|
|
741
|
+
await props.onSave(input, pending.current.id);
|
|
742
|
+
pending.current = null;
|
|
743
|
+
} catch (cause) {
|
|
744
|
+
setError(cause instanceof Error ? cause.message : "Could not save profile.");
|
|
745
|
+
} finally {
|
|
746
|
+
setBusy(false);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
return /* @__PURE__ */ jsxs8("section", { style: { display: "grid", gap: 16 }, children: [
|
|
750
|
+
props.fields.map((field) => /* @__PURE__ */ jsx8(Field2, { label: field.label, children: field.options || field.type === "boolean" ? /* @__PURE__ */ jsxs8(
|
|
751
|
+
Select2,
|
|
752
|
+
{
|
|
753
|
+
"aria-label": field.label,
|
|
754
|
+
value: draft[field.id],
|
|
755
|
+
disabled: busy,
|
|
756
|
+
onChange: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value }),
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsx8("option", { value: "", children: "Not set" }),
|
|
759
|
+
(field.options ?? ["true", "false"]).map((option) => /* @__PURE__ */ jsx8("option", { value: option, children: option }, option))
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
) : field.type === "json" ? /* @__PURE__ */ jsx8(
|
|
763
|
+
Textarea,
|
|
764
|
+
{
|
|
765
|
+
"aria-label": field.label,
|
|
766
|
+
value: draft[field.id],
|
|
767
|
+
disabled: busy,
|
|
768
|
+
onInput: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value })
|
|
769
|
+
}
|
|
770
|
+
) : /* @__PURE__ */ jsx8(
|
|
771
|
+
Input,
|
|
772
|
+
{
|
|
773
|
+
"aria-label": field.label,
|
|
774
|
+
value: draft[field.id],
|
|
775
|
+
disabled: busy,
|
|
776
|
+
required: field.required,
|
|
777
|
+
onInput: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value })
|
|
778
|
+
}
|
|
779
|
+
) }, field.id)),
|
|
780
|
+
error ? /* @__PURE__ */ jsx8("p", { role: "alert", children: error }) : null,
|
|
781
|
+
/* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsx8(Button3, { disabled: busy || !changed.length, onClick: () => void save(), children: busy ? "Saving\u2026" : "Save profile" }) })
|
|
782
|
+
] });
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/ui/admin-person-profile.tsx
|
|
786
|
+
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
787
|
+
function ChapterPersonProfile(props) {
|
|
788
|
+
const path = `/api/admin/people/profile?${new URLSearchParams({ recordId: props.recordId })}`;
|
|
789
|
+
const resource = useAdminResource(props.getToken, path);
|
|
790
|
+
if (resource.loading) return /* @__PURE__ */ jsx9("p", { children: "Loading profile\u2026" });
|
|
791
|
+
if (resource.error) return /* @__PURE__ */ jsx9("p", { role: "alert", children: resource.error });
|
|
792
|
+
const record = resource.data?.record;
|
|
793
|
+
if (!record?.edit) return /* @__PURE__ */ jsx9("p", { children: "Profile editing is unavailable." });
|
|
794
|
+
return /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
795
|
+
/* @__PURE__ */ jsxs9("p", { children: [
|
|
796
|
+
String(record.fields.email ?? ""),
|
|
797
|
+
" \u2014 login email changes use the verified account workflow."
|
|
798
|
+
] }),
|
|
799
|
+
/* @__PURE__ */ jsx9(
|
|
800
|
+
OperationalProfileForm,
|
|
801
|
+
{
|
|
802
|
+
fields: record.edit.fields,
|
|
803
|
+
values: record.fields,
|
|
804
|
+
onSave: async (input, mutationId) => {
|
|
805
|
+
await adminFetch(props.getToken, path, { method: "PATCH", body: JSON.stringify({ input, mutationId, expectedRevision: record.revision }) });
|
|
806
|
+
resource.refresh();
|
|
807
|
+
props.onChanged();
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
`${record.id}:${record.revision}`
|
|
811
|
+
)
|
|
812
|
+
] });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/ui/admin-account-sync.tsx
|
|
816
|
+
import { useState as useState7 } from "preact/hooks";
|
|
817
|
+
import { Button as Button4 } from "@odla-ai/ui/components";
|
|
818
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
|
|
819
|
+
function AccountPeopleSync({ getToken, refresh }) {
|
|
820
|
+
const [busy, setBusy] = useState7(false);
|
|
821
|
+
const [message, setMessage] = useState7("");
|
|
822
|
+
return /* @__PURE__ */ jsxs10("div", { children: [
|
|
823
|
+
/* @__PURE__ */ jsx10(Button4, { mini: true, variant: "secondary", disabled: busy, onClick: async () => {
|
|
824
|
+
setBusy(true);
|
|
825
|
+
setMessage("");
|
|
826
|
+
let after = null, count = 0, errors = 0;
|
|
827
|
+
try {
|
|
828
|
+
do {
|
|
829
|
+
const page = await adminFetch(
|
|
830
|
+
getToken,
|
|
831
|
+
"/api/admin/crm/accounts/sync",
|
|
832
|
+
{ method: "POST", body: JSON.stringify(after ? { after } : {}) }
|
|
833
|
+
);
|
|
834
|
+
count += page.synced;
|
|
835
|
+
errors += page.errors.length;
|
|
836
|
+
if (page.next && after && page.next <= after) throw new Error("Account repair cursor did not advance");
|
|
837
|
+
after = page.next;
|
|
838
|
+
setMessage(`Checked ${count} accounts${errors ? `; ${errors} need identity review` : ""}.`);
|
|
839
|
+
} while (after);
|
|
840
|
+
await refresh();
|
|
841
|
+
} catch {
|
|
842
|
+
setMessage("Account repair could not finish. Retry is safe; membership and billing are unchanged.");
|
|
843
|
+
} finally {
|
|
844
|
+
setBusy(false);
|
|
845
|
+
}
|
|
846
|
+
}, children: busy ? "Syncing accounts\u2026" : "Sync accounts" }),
|
|
847
|
+
message ? /* @__PURE__ */ jsx10("p", { role: "status", children: message }) : null
|
|
848
|
+
] });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/ui/admin-record-actions.tsx
|
|
852
|
+
import { useEffect as useEffect4, useState as useState8 } from "preact/hooks";
|
|
853
|
+
import { Button as Button5, Field as Field3, Select as Select3 } from "@odla-ai/ui/components";
|
|
854
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
|
|
617
855
|
function RecordActions({ getToken, record, roles, onChanged }) {
|
|
618
856
|
const copy = useChapterCopy().admin.records;
|
|
619
857
|
const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
|
|
620
858
|
const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
|
|
621
|
-
const [access, setAccess] =
|
|
622
|
-
const [busy, setBusy] =
|
|
623
|
-
const [note, setNote] =
|
|
859
|
+
const [access, setAccess] = useState8(null);
|
|
860
|
+
const [busy, setBusy] = useState8(null);
|
|
861
|
+
const [note, setNote] = useState8(null);
|
|
624
862
|
useEffect4(() => {
|
|
625
863
|
if (!userId) {
|
|
626
864
|
setAccess(null);
|
|
@@ -649,34 +887,34 @@ function RecordActions({ getToken, record, roles, onChanged }) {
|
|
|
649
887
|
const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), copy.approved);
|
|
650
888
|
const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), copy.refunded);
|
|
651
889
|
if (!userId && !applicationId) return null;
|
|
652
|
-
return /* @__PURE__ */
|
|
653
|
-
/* @__PURE__ */
|
|
654
|
-
userId ? /* @__PURE__ */
|
|
655
|
-
|
|
890
|
+
return /* @__PURE__ */ jsxs11("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
|
|
891
|
+
/* @__PURE__ */ jsx11("h3", { style: { margin: 0, fontSize: 16 }, children: copy.lifecycle }),
|
|
892
|
+
userId ? /* @__PURE__ */ jsx11(Field3, { label: copy.role, hint: access?.superAdmin ? copy.superAdminHint : void 0, children: /* @__PURE__ */ jsx11(
|
|
893
|
+
Select3,
|
|
656
894
|
{
|
|
657
895
|
value: access?.role ?? "",
|
|
658
896
|
disabled: busy === "role" || access?.superAdmin,
|
|
659
897
|
options: roles.map((r) => ({ value: r, label: r })),
|
|
660
898
|
onChange: (e) => void setRole(e.target.value)
|
|
661
899
|
}
|
|
662
|
-
) }) : /* @__PURE__ */
|
|
663
|
-
applicationId ? /* @__PURE__ */
|
|
664
|
-
/* @__PURE__ */
|
|
665
|
-
/* @__PURE__ */
|
|
900
|
+
) }) : /* @__PURE__ */ jsx11("p", { className: "muted", style: { margin: 0 }, children: copy.noAccount }),
|
|
901
|
+
applicationId ? /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 8 }, children: [
|
|
902
|
+
/* @__PURE__ */ jsx11(Button5, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: copy.approve }),
|
|
903
|
+
/* @__PURE__ */ jsx11(Button5, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: copy.refund })
|
|
666
904
|
] }) : null,
|
|
667
|
-
note ? /* @__PURE__ */
|
|
905
|
+
note ? /* @__PURE__ */ jsx11("p", { className: "muted", style: { margin: 0 }, children: note }) : null
|
|
668
906
|
] });
|
|
669
907
|
}
|
|
670
908
|
|
|
671
909
|
// src/ui/admin-network.tsx
|
|
672
|
-
import { useState as
|
|
673
|
-
import { jsx as
|
|
910
|
+
import { useState as useState9 } from "preact/hooks";
|
|
911
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
|
|
674
912
|
function NetworkShareActions(props) {
|
|
675
913
|
const copy = useChapterCopy().admin.network;
|
|
676
914
|
const { recordId, recordType, getToken } = props;
|
|
677
915
|
const targets = useAdminResource(getToken, "/api/admin/network/targets");
|
|
678
|
-
const [busy, setBusy] =
|
|
679
|
-
const [message, setMessage] =
|
|
916
|
+
const [busy, setBusy] = useState9(null);
|
|
917
|
+
const [message, setMessage] = useState9(null);
|
|
680
918
|
const compatible = (targets.data?.targets ?? []).filter((target) => target.types.includes(recordType));
|
|
681
919
|
if (targets.loading || targets.error || compatible.length === 0) return null;
|
|
682
920
|
const push = async (target) => {
|
|
@@ -695,26 +933,26 @@ function NetworkShareActions(props) {
|
|
|
695
933
|
setBusy(null);
|
|
696
934
|
}
|
|
697
935
|
};
|
|
698
|
-
return /* @__PURE__ */
|
|
699
|
-
/* @__PURE__ */
|
|
700
|
-
/* @__PURE__ */
|
|
701
|
-
/* @__PURE__ */
|
|
936
|
+
return /* @__PURE__ */ jsxs12("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
937
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
938
|
+
/* @__PURE__ */ jsx12("h3", { style: { margin: 0, fontSize: 16 }, children: copy.title }),
|
|
939
|
+
/* @__PURE__ */ jsx12("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: copy.allowlistDescription })
|
|
702
940
|
] }),
|
|
703
|
-
/* @__PURE__ */
|
|
704
|
-
message ? /* @__PURE__ */
|
|
941
|
+
/* @__PURE__ */ jsx12("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: compatible.map((target) => /* @__PURE__ */ jsx12("button", { className: "btn secondary mini", disabled: busy !== null, onClick: () => void push(target), children: busy === target.id ? copy.sharing : formatChapterCopy(copy.shareWith, { target: target.name }) }, target.id)) }),
|
|
942
|
+
message ? /* @__PURE__ */ jsx12("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
|
|
705
943
|
] });
|
|
706
944
|
}
|
|
707
945
|
|
|
708
946
|
// src/ui/admin-record-activity.tsx
|
|
709
|
-
import { useCallback as useCallback3, useEffect as useEffect5, useState as
|
|
947
|
+
import { useCallback as useCallback3, useEffect as useEffect5, useState as useState10 } from "preact/hooks";
|
|
710
948
|
import { ActivityFeed } from "@odla-ai/crm/ui";
|
|
711
949
|
import { parseCrmDiscussionReference as parseCrmDiscussionReference2 } from "@odla-ai/crm";
|
|
712
|
-
import { jsx as
|
|
950
|
+
import { jsx as jsx13 } from "preact/jsx-runtime";
|
|
713
951
|
function RecordActivityPanel(props) {
|
|
714
952
|
const { client, recordId } = props;
|
|
715
|
-
const [activities, setActivities] =
|
|
716
|
-
const [busy, setBusy] =
|
|
717
|
-
const [error, setError] =
|
|
953
|
+
const [activities, setActivities] = useState10([]);
|
|
954
|
+
const [busy, setBusy] = useState10(false);
|
|
955
|
+
const [error, setError] = useState10(null);
|
|
718
956
|
const reference = typeof window === "undefined" ? null : parseCrmDiscussionReference2(new URL(window.location.href));
|
|
719
957
|
const focusedActivityId = reference?.kind === "crm:activity" && reference.recordId === recordId ? reference.activityId : void 0;
|
|
720
958
|
const load = useCallback3(async () => {
|
|
@@ -736,8 +974,8 @@ function RecordActivityPanel(props) {
|
|
|
736
974
|
setBusy(false);
|
|
737
975
|
}
|
|
738
976
|
};
|
|
739
|
-
if (error) return /* @__PURE__ */
|
|
740
|
-
return /* @__PURE__ */
|
|
977
|
+
if (error) return /* @__PURE__ */ jsx13("p", { role: "alert", className: "muted", children: error });
|
|
978
|
+
return /* @__PURE__ */ jsx13(
|
|
741
979
|
ActivityFeed,
|
|
742
980
|
{
|
|
743
981
|
activities,
|
|
@@ -751,17 +989,17 @@ function RecordActivityPanel(props) {
|
|
|
751
989
|
}
|
|
752
990
|
|
|
753
991
|
// src/ui/admin-record-comms.tsx
|
|
754
|
-
import { useCallback as useCallback4, useEffect as useEffect6, useState as
|
|
755
|
-
import { Button as
|
|
756
|
-
import { jsx as
|
|
992
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useState as useState11 } from "preact/hooks";
|
|
993
|
+
import { Button as Button6, Field as Field4, Select as Select4 } from "@odla-ai/ui/components";
|
|
994
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "preact/jsx-runtime";
|
|
757
995
|
function RecordCommsPanel(props) {
|
|
758
996
|
const copy = useChapterCopy().admin.records;
|
|
759
997
|
const { client, recordId, getToken } = props;
|
|
760
998
|
const config = useAdminResource(getToken, "/api/admin/group/email");
|
|
761
999
|
const templates = Object.entries(config.data?.emailTemplates ?? {}).filter(([, value]) => value.enabled);
|
|
762
|
-
const [template, setTemplate] =
|
|
763
|
-
const [busy, setBusy] =
|
|
764
|
-
const [note, setNote] =
|
|
1000
|
+
const [template, setTemplate] = useState11("");
|
|
1001
|
+
const [busy, setBusy] = useState11(false);
|
|
1002
|
+
const [note, setNote] = useState11(null);
|
|
765
1003
|
const send = async () => {
|
|
766
1004
|
if (!template) return;
|
|
767
1005
|
setBusy(true);
|
|
@@ -775,11 +1013,11 @@ function RecordCommsPanel(props) {
|
|
|
775
1013
|
setBusy(false);
|
|
776
1014
|
}
|
|
777
1015
|
};
|
|
778
|
-
if (config.loading) return /* @__PURE__ */
|
|
779
|
-
if (config.error) return /* @__PURE__ */
|
|
780
|
-
return /* @__PURE__ */
|
|
781
|
-
/* @__PURE__ */
|
|
782
|
-
|
|
1016
|
+
if (config.loading) return /* @__PURE__ */ jsx14("p", { className: "muted", children: copy.loadingTemplates });
|
|
1017
|
+
if (config.error) return /* @__PURE__ */ jsx14("p", { role: "alert", className: "muted", children: config.error });
|
|
1018
|
+
return /* @__PURE__ */ jsxs13("div", { style: { display: "grid", gap: 14, maxWidth: 560 }, children: [
|
|
1019
|
+
/* @__PURE__ */ jsx14(Field4, { label: copy.template, children: /* @__PURE__ */ jsx14(
|
|
1020
|
+
Select4,
|
|
783
1021
|
{
|
|
784
1022
|
value: template,
|
|
785
1023
|
onChange: (event) => setTemplate(event.currentTarget.value),
|
|
@@ -789,15 +1027,15 @@ function RecordCommsPanel(props) {
|
|
|
789
1027
|
]
|
|
790
1028
|
}
|
|
791
1029
|
) }),
|
|
792
|
-
/* @__PURE__ */
|
|
793
|
-
note ? /* @__PURE__ */
|
|
1030
|
+
/* @__PURE__ */ jsx14("div", { children: /* @__PURE__ */ jsx14(Button6, { disabled: !template || busy, onClick: () => void send(), children: busy ? copy.sending : copy.send }) }),
|
|
1031
|
+
note ? /* @__PURE__ */ jsx14("p", { role: "status", className: "muted", children: note }) : null
|
|
794
1032
|
] });
|
|
795
1033
|
}
|
|
796
1034
|
function RecordCommsHistory(props) {
|
|
797
1035
|
const copy = useChapterCopy().admin.records;
|
|
798
1036
|
const { client, recordId } = props;
|
|
799
|
-
const [rows, setRows] =
|
|
800
|
-
const [error, setError] =
|
|
1037
|
+
const [rows, setRows] = useState11([]);
|
|
1038
|
+
const [error, setError] = useState11(null);
|
|
801
1039
|
const load = useCallback4(async () => {
|
|
802
1040
|
try {
|
|
803
1041
|
const result = await client.emailLog({ recordId, limit: 100 });
|
|
@@ -808,11 +1046,11 @@ function RecordCommsHistory(props) {
|
|
|
808
1046
|
}
|
|
809
1047
|
}, [client, recordId]);
|
|
810
1048
|
useEffect6(() => void load(), [load]);
|
|
811
|
-
if (error) return /* @__PURE__ */
|
|
812
|
-
if (rows.length === 0) return /* @__PURE__ */
|
|
813
|
-
return /* @__PURE__ */
|
|
814
|
-
/* @__PURE__ */
|
|
815
|
-
/* @__PURE__ */
|
|
1049
|
+
if (error) return /* @__PURE__ */ jsx14("p", { role: "alert", className: "muted", children: error });
|
|
1050
|
+
if (rows.length === 0) return /* @__PURE__ */ jsx14("p", { className: "muted", children: copy.noMessages });
|
|
1051
|
+
return /* @__PURE__ */ jsx14("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 10 }, children: rows.map((row, index) => /* @__PURE__ */ jsxs13("li", { className: "panel", children: [
|
|
1052
|
+
/* @__PURE__ */ jsx14("strong", { children: String(row["templateId"] ?? row["template"] ?? copy.message) }),
|
|
1053
|
+
/* @__PURE__ */ jsxs13("div", { className: "muted", children: [
|
|
816
1054
|
row["to"] ? String(row["to"]) : "",
|
|
817
1055
|
row["sentAt"] ? ` \xB7 ${new Date(Number(row["sentAt"])).toLocaleString()}` : ""
|
|
818
1056
|
] })
|
|
@@ -820,9 +1058,9 @@ function RecordCommsHistory(props) {
|
|
|
820
1058
|
}
|
|
821
1059
|
|
|
822
1060
|
// src/ui/admin-record-scheduling.tsx
|
|
823
|
-
import { useState as
|
|
824
|
-
import { Button as
|
|
825
|
-
import { Fragment as
|
|
1061
|
+
import { useState as useState12 } from "preact/hooks";
|
|
1062
|
+
import { Button as Button7 } from "@odla-ai/ui/components";
|
|
1063
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs14 } from "preact/jsx-runtime";
|
|
826
1064
|
function RecordSchedulingPanel(props) {
|
|
827
1065
|
const copy = useChapterCopy().admin.records;
|
|
828
1066
|
const { getToken, applicationId } = props;
|
|
@@ -830,8 +1068,8 @@ function RecordSchedulingPanel(props) {
|
|
|
830
1068
|
getToken,
|
|
831
1069
|
"/api/admin/meetings?all=1"
|
|
832
1070
|
);
|
|
833
|
-
const [busy, setBusy] =
|
|
834
|
-
const [note, setNote] =
|
|
1071
|
+
const [busy, setBusy] = useState12(null);
|
|
1072
|
+
const [note, setNote] = useState12(null);
|
|
835
1073
|
const meetings = (resource.data?.meetings ?? []).filter((meeting) => meeting.applicationId === applicationId);
|
|
836
1074
|
const run = async (meeting, action) => {
|
|
837
1075
|
let body;
|
|
@@ -858,33 +1096,33 @@ function RecordSchedulingPanel(props) {
|
|
|
858
1096
|
setBusy(null);
|
|
859
1097
|
}
|
|
860
1098
|
};
|
|
861
|
-
if (!applicationId) return /* @__PURE__ */
|
|
862
|
-
if (resource.loading) return /* @__PURE__ */
|
|
863
|
-
if (resource.error) return /* @__PURE__ */
|
|
864
|
-
if (meetings.length === 0) return /* @__PURE__ */
|
|
865
|
-
return /* @__PURE__ */
|
|
866
|
-
meetings.map((meeting) => /* @__PURE__ */
|
|
867
|
-
/* @__PURE__ */
|
|
868
|
-
/* @__PURE__ */
|
|
1099
|
+
if (!applicationId) return /* @__PURE__ */ jsx15("p", { className: "muted", children: copy.noApplication });
|
|
1100
|
+
if (resource.loading) return /* @__PURE__ */ jsx15("p", { className: "muted", children: copy.loadingMeetings });
|
|
1101
|
+
if (resource.error) return /* @__PURE__ */ jsx15("p", { role: "alert", className: "muted", children: resource.error });
|
|
1102
|
+
if (meetings.length === 0) return /* @__PURE__ */ jsx15("p", { className: "muted", children: copy.noMeetings });
|
|
1103
|
+
return /* @__PURE__ */ jsxs14("div", { style: { display: "grid", gap: 12 }, children: [
|
|
1104
|
+
meetings.map((meeting) => /* @__PURE__ */ jsxs14("section", { className: "panel", children: [
|
|
1105
|
+
/* @__PURE__ */ jsx15("strong", { children: new Date(meeting.startAt).toLocaleString() }),
|
|
1106
|
+
/* @__PURE__ */ jsxs14("p", { className: "muted", children: [
|
|
869
1107
|
meeting.status,
|
|
870
1108
|
" \xB7 ",
|
|
871
1109
|
resource.data?.timezone
|
|
872
1110
|
] }),
|
|
873
|
-
/* @__PURE__ */
|
|
874
|
-
meeting.meetUrl ? /* @__PURE__ */
|
|
875
|
-
meeting.htmlLink ? /* @__PURE__ */
|
|
876
|
-
meeting.status === "scheduled" ? /* @__PURE__ */
|
|
877
|
-
/* @__PURE__ */
|
|
878
|
-
/* @__PURE__ */
|
|
1111
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
|
|
1112
|
+
meeting.meetUrl ? /* @__PURE__ */ jsx15("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.joinMeeting }) : null,
|
|
1113
|
+
meeting.htmlLink ? /* @__PURE__ */ jsx15("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.openCalendar }) : null,
|
|
1114
|
+
meeting.status === "scheduled" ? /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
1115
|
+
/* @__PURE__ */ jsx15(Button7, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: copy.reschedule }),
|
|
1116
|
+
/* @__PURE__ */ jsx15(Button7, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: copy.cancel })
|
|
879
1117
|
] }) : null
|
|
880
1118
|
] })
|
|
881
1119
|
] }, meeting.id)),
|
|
882
|
-
note ? /* @__PURE__ */
|
|
1120
|
+
note ? /* @__PURE__ */ jsx15("p", { role: "status", className: "muted", children: note }) : null
|
|
883
1121
|
] });
|
|
884
1122
|
}
|
|
885
1123
|
|
|
886
1124
|
// src/ui/admin-people-workspace.tsx
|
|
887
|
-
import { jsx as
|
|
1125
|
+
import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
888
1126
|
function applicationLifecycleAdapter(ctx, copy = DEFAULT_CHAPTER_COPY.admin.records) {
|
|
889
1127
|
return {
|
|
890
1128
|
transitionStage: async ({ record, to }) => {
|
|
@@ -933,7 +1171,7 @@ function PeopleBody(props) {
|
|
|
933
1171
|
const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx, copy) : void 0;
|
|
934
1172
|
const resolvedLifecycle = typeof lifecycleAdapter === "function" ? lifecycleAdapter(ctx) : lifecycleAdapter ?? builtInLifecycle;
|
|
935
1173
|
const workspaceClient = client;
|
|
936
|
-
return /* @__PURE__ */
|
|
1174
|
+
return /* @__PURE__ */ jsx16(AdminPage, { children: /* @__PURE__ */ jsx16(
|
|
937
1175
|
CrmWorkspace,
|
|
938
1176
|
{
|
|
939
1177
|
crm,
|
|
@@ -949,7 +1187,10 @@ function PeopleBody(props) {
|
|
|
949
1187
|
collapseClosedDetail,
|
|
950
1188
|
lifecycle: resolvedLifecycle,
|
|
951
1189
|
requireLifecycleAdapter: requireLifecycleAdapter ?? lifecycle,
|
|
952
|
-
renderSummary:
|
|
1190
|
+
renderSummary: (context) => /* @__PURE__ */ jsxs15(Fragment7, { children: [
|
|
1191
|
+
renderSummary?.(context, ctx),
|
|
1192
|
+
type === "person" ? /* @__PURE__ */ jsx16(AccountPeopleSync, { getToken: ctx.getToken, refresh: context.query.refresh }) : null
|
|
1193
|
+
] }),
|
|
953
1194
|
renderMaster: renderMaster ? (context) => renderMaster(context, ctx) : void 0,
|
|
954
1195
|
renderDetailHeader: renderDetailHeader ? (context) => renderDetailHeader(context, ctx) : void 0,
|
|
955
1196
|
renderDetail: renderDetail ? (context, defaultDetail) => renderDetail(context, defaultDetail, ctx) : void 0,
|
|
@@ -961,7 +1202,43 @@ function PeopleBody(props) {
|
|
|
961
1202
|
const tabs = [];
|
|
962
1203
|
const add = (id) => {
|
|
963
1204
|
const tab = byId.get(id);
|
|
964
|
-
if (tab)
|
|
1205
|
+
if (tab && lifecycle && type === "person" && id === "profile" && !lifecycleAdapter) {
|
|
1206
|
+
tabs.push({ ...tab, panel: /* @__PURE__ */ jsx16(
|
|
1207
|
+
ChapterPersonProfile,
|
|
1208
|
+
{
|
|
1209
|
+
recordId: record.id,
|
|
1210
|
+
getToken: ctx.getToken,
|
|
1211
|
+
onChanged: () => {
|
|
1212
|
+
context.refreshRecord();
|
|
1213
|
+
context.refreshList();
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
record.id
|
|
1217
|
+
) });
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (tab && lifecycle && applicationId && id === "stage" && !lifecycleAdapter) {
|
|
1221
|
+
tabs.push({ ...tab, panel: /* @__PURE__ */ jsx16(
|
|
1222
|
+
ApplicationStagePanel,
|
|
1223
|
+
{
|
|
1224
|
+
applicationId,
|
|
1225
|
+
getToken: ctx.getToken,
|
|
1226
|
+
onMove: context.moveStage
|
|
1227
|
+
},
|
|
1228
|
+
record.id + record.updatedAt
|
|
1229
|
+
) });
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (tab) tabs.push(lifecycle && !applicationId && id === "stage" ? {
|
|
1233
|
+
...tab,
|
|
1234
|
+
panel: record.stage == null ? /* @__PURE__ */ jsx16("p", { children: "Journey stage: Not applicable. No membership application is linked. Administrative access is separate." }) : /* @__PURE__ */ jsxs15(Fragment7, { children: [
|
|
1235
|
+
/* @__PURE__ */ jsx16(ApplicationLinkRepair, { recordId: record.id, getToken: ctx.getToken, onChanged: () => {
|
|
1236
|
+
context.refreshRecord();
|
|
1237
|
+
context.refreshList();
|
|
1238
|
+
} }, record.id),
|
|
1239
|
+
tab.panel
|
|
1240
|
+
] })
|
|
1241
|
+
} : tab);
|
|
965
1242
|
};
|
|
966
1243
|
add("stage");
|
|
967
1244
|
add("profile");
|
|
@@ -969,35 +1246,35 @@ function PeopleBody(props) {
|
|
|
969
1246
|
tabs.push({
|
|
970
1247
|
id: "comms",
|
|
971
1248
|
label: copy.comms,
|
|
972
|
-
panel: /* @__PURE__ */
|
|
1249
|
+
panel: /* @__PURE__ */ jsx16(RecordCommsPanel, { client, recordId: record.id, getToken: ctx.getToken })
|
|
973
1250
|
});
|
|
974
1251
|
tabs.push({
|
|
975
1252
|
id: "history",
|
|
976
1253
|
label: copy.commsHistory,
|
|
977
|
-
panel: /* @__PURE__ */
|
|
1254
|
+
panel: /* @__PURE__ */ jsx16(RecordCommsHistory, { client, recordId: record.id })
|
|
978
1255
|
});
|
|
979
1256
|
}
|
|
980
1257
|
if (lifecycle) {
|
|
981
1258
|
tabs.push({
|
|
982
1259
|
id: "scheduling",
|
|
983
1260
|
label: copy.scheduling,
|
|
984
|
-
panel: /* @__PURE__ */
|
|
1261
|
+
panel: /* @__PURE__ */ jsx16(RecordSchedulingPanel, { getToken: ctx.getToken, applicationId })
|
|
985
1262
|
});
|
|
986
|
-
tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */
|
|
1263
|
+
tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */ jsx16(BillingCard, { record }) });
|
|
987
1264
|
} else {
|
|
988
1265
|
add("billing");
|
|
989
1266
|
}
|
|
990
1267
|
tabs.push({
|
|
991
1268
|
id: "notes",
|
|
992
1269
|
label: copy.notes,
|
|
993
|
-
panel: /* @__PURE__ */
|
|
1270
|
+
panel: /* @__PURE__ */ jsx16(RecordActivityPanel, { client, recordId: record.id })
|
|
994
1271
|
});
|
|
995
1272
|
add("connections");
|
|
996
1273
|
if (lifecycle) {
|
|
997
1274
|
tabs.push({
|
|
998
1275
|
id: "access",
|
|
999
1276
|
label: copy.access,
|
|
1000
|
-
panel: /* @__PURE__ */
|
|
1277
|
+
panel: /* @__PURE__ */ jsx16(
|
|
1001
1278
|
RecordActions,
|
|
1002
1279
|
{
|
|
1003
1280
|
getToken: ctx.getToken,
|
|
@@ -1016,7 +1293,7 @@ function PeopleBody(props) {
|
|
|
1016
1293
|
tabs.push({
|
|
1017
1294
|
id: "sharing",
|
|
1018
1295
|
label: copy.sharing,
|
|
1019
|
-
panel: /* @__PURE__ */
|
|
1296
|
+
panel: /* @__PURE__ */ jsx16(NetworkShareActions, { recordId: record.id, recordType: record.type, getToken: ctx.getToken })
|
|
1020
1297
|
});
|
|
1021
1298
|
}
|
|
1022
1299
|
return extendRecordTabs?.(tabs, context) ?? tabs;
|
|
@@ -1026,7 +1303,7 @@ function PeopleBody(props) {
|
|
|
1026
1303
|
}
|
|
1027
1304
|
|
|
1028
1305
|
// src/ui/admin-people.tsx
|
|
1029
|
-
import { jsx as
|
|
1306
|
+
import { jsx as jsx17 } from "preact/jsx-runtime";
|
|
1030
1307
|
var DEFAULT_ROLES = ["provisional", "member", "admin"];
|
|
1031
1308
|
function collectionSection(options) {
|
|
1032
1309
|
const { crm, type } = options;
|
|
@@ -1056,7 +1333,7 @@ function collectionSection(options) {
|
|
|
1056
1333
|
return {
|
|
1057
1334
|
id,
|
|
1058
1335
|
label,
|
|
1059
|
-
render: (ctx) => /* @__PURE__ */
|
|
1336
|
+
render: (ctx) => /* @__PURE__ */ jsx17(
|
|
1060
1337
|
PeopleBody,
|
|
1061
1338
|
{
|
|
1062
1339
|
crm,
|
|
@@ -1125,17 +1402,17 @@ function peopleSection(options) {
|
|
|
1125
1402
|
// src/ui/admin-network-records.tsx
|
|
1126
1403
|
import {
|
|
1127
1404
|
DataTable as DataTable2,
|
|
1128
|
-
Field as
|
|
1405
|
+
Field as Field6,
|
|
1129
1406
|
MasterDetail,
|
|
1130
1407
|
Pagination,
|
|
1131
|
-
Select as
|
|
1408
|
+
Select as Select5
|
|
1132
1409
|
} from "@odla-ai/ui/components";
|
|
1133
|
-
import { useEffect as useEffect7, useMemo, useState as
|
|
1410
|
+
import { useEffect as useEffect7, useMemo, useState as useState14 } from "preact/hooks";
|
|
1134
1411
|
|
|
1135
1412
|
// src/ui/admin-network-notes.tsx
|
|
1136
|
-
import { Button as
|
|
1137
|
-
import { useState as
|
|
1138
|
-
import { jsx as
|
|
1413
|
+
import { Button as Button8, Field as Field5, Textarea as Textarea2 } from "@odla-ai/ui/components";
|
|
1414
|
+
import { useState as useState13 } from "preact/hooks";
|
|
1415
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "preact/jsx-runtime";
|
|
1139
1416
|
function displayValue(value) {
|
|
1140
1417
|
if (Array.isArray(value)) return value.join(", ");
|
|
1141
1418
|
if (value && typeof value === "object") return JSON.stringify(value);
|
|
@@ -1150,9 +1427,9 @@ function PrivateNotes(props) {
|
|
|
1150
1427
|
});
|
|
1151
1428
|
const path = `/api/admin/network/notes?${query}`;
|
|
1152
1429
|
const notes = useAdminResource(ctx.getToken, path);
|
|
1153
|
-
const [body, setBody] =
|
|
1154
|
-
const [busy, setBusy] =
|
|
1155
|
-
const [error, setError] =
|
|
1430
|
+
const [body, setBody] = useState13("");
|
|
1431
|
+
const [busy, setBusy] = useState13(false);
|
|
1432
|
+
const [error, setError] = useState13(null);
|
|
1156
1433
|
const submit = async () => {
|
|
1157
1434
|
const trimmed = body.trim();
|
|
1158
1435
|
if (!trimmed) return;
|
|
@@ -1171,19 +1448,19 @@ function PrivateNotes(props) {
|
|
|
1171
1448
|
setBusy(false);
|
|
1172
1449
|
}
|
|
1173
1450
|
};
|
|
1174
|
-
return /* @__PURE__ */
|
|
1175
|
-
/* @__PURE__ */
|
|
1451
|
+
return /* @__PURE__ */ jsxs16("section", { style: { display: "grid", gap: 12 }, children: [
|
|
1452
|
+
/* @__PURE__ */ jsxs16("h3", { style: { margin: "8px 0 0" }, children: [
|
|
1176
1453
|
"Private to ",
|
|
1177
1454
|
leaderName
|
|
1178
1455
|
] }),
|
|
1179
|
-
/* @__PURE__ */
|
|
1180
|
-
|
|
1456
|
+
/* @__PURE__ */ jsx18(
|
|
1457
|
+
Field5,
|
|
1181
1458
|
{
|
|
1182
1459
|
label: "Add a note",
|
|
1183
1460
|
hint: `Visible only to ${leaderName} admins. Nothing is written back to the follower.`,
|
|
1184
1461
|
error,
|
|
1185
|
-
children: /* @__PURE__ */
|
|
1186
|
-
|
|
1462
|
+
children: /* @__PURE__ */ jsx18(
|
|
1463
|
+
Textarea2,
|
|
1187
1464
|
{
|
|
1188
1465
|
rows: 4,
|
|
1189
1466
|
value: body,
|
|
@@ -1193,13 +1470,13 @@ function PrivateNotes(props) {
|
|
|
1193
1470
|
)
|
|
1194
1471
|
}
|
|
1195
1472
|
),
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
notes.loading ? /* @__PURE__ */
|
|
1198
|
-
notes.error ? /* @__PURE__ */
|
|
1199
|
-
!notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */
|
|
1200
|
-
(notes.data?.notes ?? []).map((note) => /* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
1473
|
+
/* @__PURE__ */ jsx18("div", { children: /* @__PURE__ */ jsx18(Button8, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save note" }) }),
|
|
1474
|
+
notes.loading ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "Loading notes\u2026" }) : null,
|
|
1475
|
+
notes.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: notes.error }) : null,
|
|
1476
|
+
!notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "No notes yet." }) : null,
|
|
1477
|
+
(notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs16("article", { className: "panel", style: { padding: 14 }, children: [
|
|
1478
|
+
/* @__PURE__ */ jsx18("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
|
|
1479
|
+
/* @__PURE__ */ jsxs16("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
|
|
1203
1480
|
note.authorEmail ?? note.authorId,
|
|
1204
1481
|
" \xB7 ",
|
|
1205
1482
|
new Date(note.createdAt).toLocaleString()
|
|
@@ -1216,9 +1493,9 @@ function SharedNotes(props) {
|
|
|
1216
1493
|
});
|
|
1217
1494
|
const path = `/api/admin/network/shared-notes?${query}`;
|
|
1218
1495
|
const notes = useAdminResource(ctx.getToken, path);
|
|
1219
|
-
const [body, setBody] =
|
|
1220
|
-
const [busy, setBusy] =
|
|
1221
|
-
const [error, setError] =
|
|
1496
|
+
const [body, setBody] = useState13("");
|
|
1497
|
+
const [busy, setBusy] = useState13(false);
|
|
1498
|
+
const [error, setError] = useState13(null);
|
|
1222
1499
|
const submit = async () => {
|
|
1223
1500
|
const trimmed = body.trim();
|
|
1224
1501
|
if (!trimmed) return;
|
|
@@ -1237,19 +1514,19 @@ function SharedNotes(props) {
|
|
|
1237
1514
|
setBusy(false);
|
|
1238
1515
|
}
|
|
1239
1516
|
};
|
|
1240
|
-
return /* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1517
|
+
return /* @__PURE__ */ jsxs16("section", { style: { display: "grid", gap: 12 }, children: [
|
|
1518
|
+
/* @__PURE__ */ jsxs16("h3", { style: { margin: "8px 0 0" }, children: [
|
|
1242
1519
|
"Shared with ",
|
|
1243
1520
|
targetName
|
|
1244
1521
|
] }),
|
|
1245
|
-
/* @__PURE__ */
|
|
1246
|
-
|
|
1522
|
+
/* @__PURE__ */ jsx18(
|
|
1523
|
+
Field5,
|
|
1247
1524
|
{
|
|
1248
1525
|
label: "Add a shared note",
|
|
1249
1526
|
hint: `This is written to the record's Notes feed for ${targetName} admins.`,
|
|
1250
1527
|
error,
|
|
1251
|
-
children: /* @__PURE__ */
|
|
1252
|
-
|
|
1528
|
+
children: /* @__PURE__ */ jsx18(
|
|
1529
|
+
Textarea2,
|
|
1253
1530
|
{
|
|
1254
1531
|
rows: 4,
|
|
1255
1532
|
value: body,
|
|
@@ -1259,13 +1536,13 @@ function SharedNotes(props) {
|
|
|
1259
1536
|
)
|
|
1260
1537
|
}
|
|
1261
1538
|
),
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
notes.loading ? /* @__PURE__ */
|
|
1264
|
-
notes.error ? /* @__PURE__ */
|
|
1265
|
-
!notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */
|
|
1266
|
-
(notes.data?.notes ?? []).map((note) => /* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
/* @__PURE__ */
|
|
1539
|
+
/* @__PURE__ */ jsx18("div", { children: /* @__PURE__ */ jsx18(Button8, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Sharing\u2026" : `Share with ${targetName}` }) }),
|
|
1540
|
+
notes.loading ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "Loading shared notes\u2026" }) : null,
|
|
1541
|
+
notes.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: notes.error }) : null,
|
|
1542
|
+
!notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "No shared notes yet." }) : null,
|
|
1543
|
+
(notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs16("article", { className: "panel", style: { padding: 14 }, children: [
|
|
1544
|
+
/* @__PURE__ */ jsx18("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
|
|
1545
|
+
/* @__PURE__ */ jsxs16("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
|
|
1269
1546
|
"Shared with ",
|
|
1270
1547
|
targetName,
|
|
1271
1548
|
" \xB7 ",
|
|
@@ -1276,23 +1553,23 @@ function SharedNotes(props) {
|
|
|
1276
1553
|
}
|
|
1277
1554
|
function NetworkRecordDetail(props) {
|
|
1278
1555
|
const { ctx, targetId, targetName, leaderName, sharedEnabled, record } = props;
|
|
1279
|
-
return /* @__PURE__ */
|
|
1280
|
-
/* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
record.stage ? /* @__PURE__ */
|
|
1556
|
+
return /* @__PURE__ */ jsxs16("div", { style: { display: "grid", gap: 14 }, children: [
|
|
1557
|
+
/* @__PURE__ */ jsxs16("header", { children: [
|
|
1558
|
+
/* @__PURE__ */ jsx18("h2", { style: { margin: 0 }, children: record.name }),
|
|
1559
|
+
record.stage ? /* @__PURE__ */ jsx18("span", { className: "badge", style: { marginTop: 8 }, children: record.stage.replaceAll("_", " ") }) : null
|
|
1283
1560
|
] }),
|
|
1284
|
-
/* @__PURE__ */
|
|
1285
|
-
Object.entries(record.fields).map(([name, value]) => /* @__PURE__ */
|
|
1286
|
-
/* @__PURE__ */
|
|
1287
|
-
/* @__PURE__ */
|
|
1561
|
+
/* @__PURE__ */ jsxs16("dl", { style: { display: "grid", gridTemplateColumns: "minmax(8rem, auto) 1fr", gap: "8px 16px", margin: 0 }, children: [
|
|
1562
|
+
Object.entries(record.fields).map(([name, value]) => /* @__PURE__ */ jsxs16("div", { style: { display: "contents" }, children: [
|
|
1563
|
+
/* @__PURE__ */ jsx18("dt", { className: "muted", children: name.replaceAll("_", " ") }),
|
|
1564
|
+
/* @__PURE__ */ jsx18("dd", { style: { margin: 0, overflowWrap: "anywhere" }, children: displayValue(value) })
|
|
1288
1565
|
] }, name)),
|
|
1289
|
-
/* @__PURE__ */
|
|
1290
|
-
/* @__PURE__ */
|
|
1291
|
-
/* @__PURE__ */
|
|
1566
|
+
/* @__PURE__ */ jsxs16("div", { style: { display: "contents" }, children: [
|
|
1567
|
+
/* @__PURE__ */ jsx18("dt", { className: "muted", children: "Updated" }),
|
|
1568
|
+
/* @__PURE__ */ jsx18("dd", { style: { margin: 0 }, children: new Date(record.updatedAt).toLocaleString() })
|
|
1292
1569
|
] })
|
|
1293
1570
|
] }),
|
|
1294
|
-
/* @__PURE__ */
|
|
1295
|
-
/* @__PURE__ */
|
|
1571
|
+
/* @__PURE__ */ jsxs16("div", { style: { display: "grid", gap: 22 }, children: [
|
|
1572
|
+
/* @__PURE__ */ jsx18(
|
|
1296
1573
|
PrivateNotes,
|
|
1297
1574
|
{
|
|
1298
1575
|
ctx,
|
|
@@ -1301,7 +1578,7 @@ function NetworkRecordDetail(props) {
|
|
|
1301
1578
|
leaderName
|
|
1302
1579
|
}
|
|
1303
1580
|
),
|
|
1304
|
-
sharedEnabled ? /* @__PURE__ */
|
|
1581
|
+
sharedEnabled ? /* @__PURE__ */ jsx18(
|
|
1305
1582
|
SharedNotes,
|
|
1306
1583
|
{
|
|
1307
1584
|
ctx,
|
|
@@ -1315,11 +1592,11 @@ function NetworkRecordDetail(props) {
|
|
|
1315
1592
|
}
|
|
1316
1593
|
|
|
1317
1594
|
// src/ui/admin-network-records.tsx
|
|
1318
|
-
import { jsx as
|
|
1595
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "preact/jsx-runtime";
|
|
1319
1596
|
function RecordsExplorer(props) {
|
|
1320
1597
|
const { ctx, target, type, leaderName } = props;
|
|
1321
|
-
const [offset, setOffset] =
|
|
1322
|
-
const [search, setSearch] =
|
|
1598
|
+
const [offset, setOffset] = useState14(0);
|
|
1599
|
+
const [search, setSearch] = useState14("");
|
|
1323
1600
|
const query = new URLSearchParams({
|
|
1324
1601
|
targetId: target.id,
|
|
1325
1602
|
type,
|
|
@@ -1336,12 +1613,12 @@ function RecordsExplorer(props) {
|
|
|
1336
1613
|
{
|
|
1337
1614
|
key: "name",
|
|
1338
1615
|
header: "Name",
|
|
1339
|
-
cell: (record) => /* @__PURE__ */
|
|
1616
|
+
cell: (record) => /* @__PURE__ */ jsx19("a", { href: ctx.href({ recordId: record.id, detailTab: null }), children: record.name })
|
|
1340
1617
|
},
|
|
1341
1618
|
{
|
|
1342
1619
|
key: "stage",
|
|
1343
1620
|
header: "Stage",
|
|
1344
|
-
cell: (record) => record.stage ? /* @__PURE__ */
|
|
1621
|
+
cell: (record) => record.stage ? /* @__PURE__ */ jsx19("span", { className: "badge", children: record.stage.replaceAll("_", " ") }) : "\u2014"
|
|
1345
1622
|
},
|
|
1346
1623
|
{
|
|
1347
1624
|
key: "updatedAt",
|
|
@@ -1349,7 +1626,7 @@ function RecordsExplorer(props) {
|
|
|
1349
1626
|
cell: (record) => new Date(record.updatedAt).toLocaleDateString()
|
|
1350
1627
|
}
|
|
1351
1628
|
], [ctx]);
|
|
1352
|
-
return /* @__PURE__ */
|
|
1629
|
+
return /* @__PURE__ */ jsx19(
|
|
1353
1630
|
MasterDetail,
|
|
1354
1631
|
{
|
|
1355
1632
|
collapseClosedDetail: true,
|
|
@@ -1357,13 +1634,13 @@ function RecordsExplorer(props) {
|
|
|
1357
1634
|
detailLabel: "Follower record details, private notes, and shared notes",
|
|
1358
1635
|
detailOpen: Boolean(selected),
|
|
1359
1636
|
closeHref: ctx.href({ recordId: null, detailTab: null }),
|
|
1360
|
-
master: /* @__PURE__ */
|
|
1361
|
-
/* @__PURE__ */
|
|
1637
|
+
master: /* @__PURE__ */ jsxs17("div", { style: { display: "grid", gap: 14, padding: "var(--ui-space-4)" }, children: [
|
|
1638
|
+
/* @__PURE__ */ jsxs17("h2", { style: { margin: 0, fontSize: 18 }, children: [
|
|
1362
1639
|
target.name,
|
|
1363
1640
|
" \xB7 ",
|
|
1364
1641
|
type.replaceAll("_", " ")
|
|
1365
1642
|
] }),
|
|
1366
|
-
/* @__PURE__ */
|
|
1643
|
+
/* @__PURE__ */ jsx19(
|
|
1367
1644
|
DataTable2,
|
|
1368
1645
|
{
|
|
1369
1646
|
manual: true,
|
|
@@ -1383,7 +1660,7 @@ function RecordsExplorer(props) {
|
|
|
1383
1660
|
ariaLabel: `${target.name} ${type} records`
|
|
1384
1661
|
}
|
|
1385
1662
|
),
|
|
1386
|
-
/* @__PURE__ */
|
|
1663
|
+
/* @__PURE__ */ jsx19(
|
|
1387
1664
|
Pagination,
|
|
1388
1665
|
{
|
|
1389
1666
|
offset: page.data?.offset ?? offset,
|
|
@@ -1398,7 +1675,7 @@ function RecordsExplorer(props) {
|
|
|
1398
1675
|
}
|
|
1399
1676
|
)
|
|
1400
1677
|
] }),
|
|
1401
|
-
detail: selected ? /* @__PURE__ */
|
|
1678
|
+
detail: selected ? /* @__PURE__ */ jsx19(
|
|
1402
1679
|
NetworkRecordDetail,
|
|
1403
1680
|
{
|
|
1404
1681
|
ctx,
|
|
@@ -1415,8 +1692,8 @@ function RecordsExplorer(props) {
|
|
|
1415
1692
|
function NetworkRecordsWorkspace(props) {
|
|
1416
1693
|
const { chapter, ctx } = props;
|
|
1417
1694
|
const targets = useAdminResource(ctx.getToken, "/api/admin/network/targets");
|
|
1418
|
-
const [targetId, setTargetId] =
|
|
1419
|
-
const [type, setType] =
|
|
1695
|
+
const [targetId, setTargetId] = useState14("");
|
|
1696
|
+
const [type, setType] = useState14("");
|
|
1420
1697
|
const target = targets.data?.targets.find((candidate) => candidate.id === targetId) ?? targets.data?.targets[0];
|
|
1421
1698
|
useEffect7(() => {
|
|
1422
1699
|
if (target && target.id !== targetId) setTargetId(target.id);
|
|
@@ -1425,17 +1702,17 @@ function NetworkRecordsWorkspace(props) {
|
|
|
1425
1702
|
const first = target?.types[0] ?? "";
|
|
1426
1703
|
if (target && !target.types.includes(type)) setType(first);
|
|
1427
1704
|
}, [target, type]);
|
|
1428
|
-
if (targets.loading) return /* @__PURE__ */
|
|
1429
|
-
if (targets.error || !targets.data) return /* @__PURE__ */
|
|
1705
|
+
if (targets.loading) return /* @__PURE__ */ jsx19(AdminNote, { children: "Loading follower records\u2026" });
|
|
1706
|
+
if (targets.error || !targets.data) return /* @__PURE__ */ jsxs17(AdminNote, { children: [
|
|
1430
1707
|
"Could not load followers",
|
|
1431
1708
|
targets.error ? `: ${targets.error}` : "",
|
|
1432
1709
|
"."
|
|
1433
1710
|
] });
|
|
1434
|
-
if (!target) return /* @__PURE__ */
|
|
1435
|
-
return /* @__PURE__ */
|
|
1436
|
-
/* @__PURE__ */
|
|
1437
|
-
/* @__PURE__ */
|
|
1438
|
-
|
|
1711
|
+
if (!target) return /* @__PURE__ */ jsx19(AdminPage, { children: /* @__PURE__ */ jsx19(Panel, { title: "Follower records", children: /* @__PURE__ */ jsx19("p", { className: "muted", children: "No followers are configured." }) }) });
|
|
1712
|
+
return /* @__PURE__ */ jsxs17(AdminPage, { children: [
|
|
1713
|
+
/* @__PURE__ */ jsxs17("div", { style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "end" }, children: [
|
|
1714
|
+
/* @__PURE__ */ jsx19(Field6, { label: "Chapter", style: { minWidth: 240 }, children: /* @__PURE__ */ jsx19(
|
|
1715
|
+
Select5,
|
|
1439
1716
|
{
|
|
1440
1717
|
value: target.id,
|
|
1441
1718
|
options: targets.data.targets.map((item) => ({ value: item.id, label: item.name })),
|
|
@@ -1446,8 +1723,8 @@ function NetworkRecordsWorkspace(props) {
|
|
|
1446
1723
|
}
|
|
1447
1724
|
}
|
|
1448
1725
|
) }),
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
|
|
1726
|
+
/* @__PURE__ */ jsx19(Field6, { label: "Record type", style: { minWidth: 180 }, children: /* @__PURE__ */ jsx19(
|
|
1727
|
+
Select5,
|
|
1451
1728
|
{
|
|
1452
1729
|
value: type,
|
|
1453
1730
|
options: target.types.map((item) => ({ value: item, label: item.replaceAll("_", " ") })),
|
|
@@ -1458,7 +1735,7 @@ function NetworkRecordsWorkspace(props) {
|
|
|
1458
1735
|
}
|
|
1459
1736
|
) })
|
|
1460
1737
|
] }),
|
|
1461
|
-
type ? /* @__PURE__ */
|
|
1738
|
+
type ? /* @__PURE__ */ jsx19(
|
|
1462
1739
|
RecordsExplorer,
|
|
1463
1740
|
{
|
|
1464
1741
|
ctx,
|
|
@@ -1467,7 +1744,7 @@ function NetworkRecordsWorkspace(props) {
|
|
|
1467
1744
|
leaderName: chapter.name
|
|
1468
1745
|
},
|
|
1469
1746
|
`${target.id}:${type}`
|
|
1470
|
-
) : /* @__PURE__ */
|
|
1747
|
+
) : /* @__PURE__ */ jsx19(Panel, { children: /* @__PURE__ */ jsx19("p", { className: "muted", children: "This follower has no browsable record types configured." }) })
|
|
1471
1748
|
] });
|
|
1472
1749
|
}
|
|
1473
1750
|
function networkRecordsWorkspace(chapter) {
|
|
@@ -1475,63 +1752,63 @@ function networkRecordsWorkspace(chapter) {
|
|
|
1475
1752
|
id: "network",
|
|
1476
1753
|
label: chapter.copy.admin.workspaces.network,
|
|
1477
1754
|
defaultViewId: "records",
|
|
1478
|
-
render: (ctx) => /* @__PURE__ */
|
|
1755
|
+
render: (ctx) => /* @__PURE__ */ jsx19(NetworkRecordsWorkspace, { chapter, ctx })
|
|
1479
1756
|
};
|
|
1480
1757
|
}
|
|
1481
1758
|
|
|
1482
1759
|
// src/ui/admin-portfolio.tsx
|
|
1483
1760
|
import { StatBand as StatBand2, StepPipeline as StepPipeline2 } from "@odla-ai/ui/components";
|
|
1484
|
-
import { jsx as
|
|
1761
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "preact/jsx-runtime";
|
|
1485
1762
|
function LeaderNetworkOverview({ ctx }) {
|
|
1486
1763
|
const copy = useChapterCopy().admin.network;
|
|
1487
1764
|
const { data, loading, error } = useAdminResource(
|
|
1488
1765
|
ctx.getToken,
|
|
1489
1766
|
"/api/admin/network/rollup"
|
|
1490
1767
|
);
|
|
1491
|
-
if (loading) return /* @__PURE__ */
|
|
1492
|
-
if (error || !data) return /* @__PURE__ */
|
|
1768
|
+
if (loading) return /* @__PURE__ */ jsx20(AdminNote, { children: copy.loadingRollup });
|
|
1769
|
+
if (error || !data) return /* @__PURE__ */ jsxs18(AdminNote, { children: [
|
|
1493
1770
|
copy.loadRollupFailed,
|
|
1494
1771
|
error ? `: ${error}` : "",
|
|
1495
1772
|
"."
|
|
1496
1773
|
] });
|
|
1497
|
-
if (data.configured === 0) return /* @__PURE__ */
|
|
1774
|
+
if (data.configured === 0) return /* @__PURE__ */ jsx20(AdminPage, { children: /* @__PURE__ */ jsx20(Panel, { title: copy.overview, children: /* @__PURE__ */ jsx20("p", { className: "muted", children: copy.noFollowers }) }) });
|
|
1498
1775
|
const stats = [
|
|
1499
1776
|
{ value: data.configured.toLocaleString(), label: copy.configuredFollowers },
|
|
1500
1777
|
{ value: data.available.toLocaleString(), label: copy.reachableFollowers },
|
|
1501
1778
|
{ value: data.totalRecords.toLocaleString(), label: copy.recordsAcrossFollowers }
|
|
1502
1779
|
];
|
|
1503
1780
|
const steps = data.types.map((type) => ({
|
|
1504
|
-
icon: /* @__PURE__ */
|
|
1781
|
+
icon: /* @__PURE__ */ jsx20("span", { style: { fontSize: 22, fontWeight: 700 }, children: type.total }),
|
|
1505
1782
|
title: type.type.replaceAll("_", " "),
|
|
1506
1783
|
description: Object.entries(type.stages).filter(([, count]) => count > 0).map(([stage, count]) => `${stage.replaceAll("_", " ")} ${count}`).join(" \xB7 ") || copy.noRecordTotals
|
|
1507
1784
|
}));
|
|
1508
|
-
return /* @__PURE__ */
|
|
1509
|
-
/* @__PURE__ */
|
|
1510
|
-
/* @__PURE__ */
|
|
1511
|
-
/* @__PURE__ */
|
|
1512
|
-
/* @__PURE__ */
|
|
1513
|
-
/* @__PURE__ */
|
|
1514
|
-
/* @__PURE__ */
|
|
1515
|
-
/* @__PURE__ */
|
|
1516
|
-
target.error ? /* @__PURE__ */
|
|
1517
|
-
/* @__PURE__ */
|
|
1785
|
+
return /* @__PURE__ */ jsxs18(AdminPage, { children: [
|
|
1786
|
+
/* @__PURE__ */ jsx20(StatBand2, { stats }),
|
|
1787
|
+
/* @__PURE__ */ jsx20(Panel, { title: copy.followers, children: /* @__PURE__ */ jsx20("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }, children: data.targets.map((target) => /* @__PURE__ */ jsxs18("li", { style: { display: "flex", gap: 12, alignItems: "baseline", flexWrap: "wrap" }, children: [
|
|
1788
|
+
/* @__PURE__ */ jsx20("strong", { style: { minWidth: 180 }, children: target.name }),
|
|
1789
|
+
/* @__PURE__ */ jsx20("span", { className: "badge", children: target.transport.reachable === null ? copy.transportNotChecked : target.transport.reachable ? copy.transportReachable : copy.transportUnavailable }),
|
|
1790
|
+
/* @__PURE__ */ jsx20("span", { className: "muted", children: target.transport.kind === "binding" ? formatChapterCopy(copy.bindingTransport, { binding: target.transport.binding ?? "" }) : copy.urlTransport }),
|
|
1791
|
+
/* @__PURE__ */ jsx20("span", { className: "muted", children: target.configuration.activeRevision === null ? copy.noActiveConfiguration : formatChapterCopy(copy.activeConfiguration, { revision: target.configuration.activeRevision }) }),
|
|
1792
|
+
/* @__PURE__ */ jsx20("span", { className: "muted", children: target.publicSite.available === null ? copy.publicSiteNotChecked : target.publicSite.available ? copy.publicSiteAvailable : copy.publicSiteUnavailable }),
|
|
1793
|
+
target.error ? /* @__PURE__ */ jsx20("span", { className: "muted", children: target.error }) : null,
|
|
1794
|
+
/* @__PURE__ */ jsx20("a", { href: target.url, target: "_blank", rel: "noreferrer", style: { marginLeft: "auto" }, children: target.url })
|
|
1518
1795
|
] }, target.id)) }) }),
|
|
1519
|
-
/* @__PURE__ */
|
|
1796
|
+
/* @__PURE__ */ jsx20(Panel, { title: copy.recordTotals, children: steps.length ? /* @__PURE__ */ jsx20(StepPipeline2, { ariaLabel: copy.recordTotals, steps }) : /* @__PURE__ */ jsx20("p", { className: "muted", children: copy.noRecordTotals }) })
|
|
1520
1797
|
] });
|
|
1521
1798
|
}
|
|
1522
1799
|
|
|
1523
1800
|
// src/ui/admin-admission.tsx
|
|
1524
|
-
import { useState as
|
|
1525
|
-
import { jsx as
|
|
1801
|
+
import { useEffect as useEffect8, useRef as useRef2, useState as useState15 } from "preact/hooks";
|
|
1802
|
+
import { jsx as jsx21, jsxs as jsxs19 } from "preact/jsx-runtime";
|
|
1526
1803
|
function AdmissionGrantsWorkspace(props) {
|
|
1527
1804
|
const copy = useChapterCopy().admin.admission;
|
|
1528
1805
|
const resource = useAdminResource(props.getToken, "/api/admin/admission-grants");
|
|
1529
|
-
const [email, setEmail] =
|
|
1530
|
-
const [decision, setDecision] =
|
|
1531
|
-
const [note, setNote] =
|
|
1532
|
-
const [expires, setExpires] =
|
|
1533
|
-
const [busy, setBusy] =
|
|
1534
|
-
const [error, setError] =
|
|
1806
|
+
const [email, setEmail] = useState15("");
|
|
1807
|
+
const [decision, setDecision] = useState15("waive_interview");
|
|
1808
|
+
const [note, setNote] = useState15("");
|
|
1809
|
+
const [expires, setExpires] = useState15("");
|
|
1810
|
+
const [busy, setBusy] = useState15(false);
|
|
1811
|
+
const [error, setError] = useState15(null);
|
|
1535
1812
|
const submit = async (event) => {
|
|
1536
1813
|
event.preventDefault();
|
|
1537
1814
|
setBusy(true);
|
|
@@ -1568,48 +1845,48 @@ function AdmissionGrantsWorkspace(props) {
|
|
|
1568
1845
|
setBusy(false);
|
|
1569
1846
|
}
|
|
1570
1847
|
};
|
|
1571
|
-
return /* @__PURE__ */
|
|
1572
|
-
/* @__PURE__ */
|
|
1573
|
-
/* @__PURE__ */
|
|
1574
|
-
/* @__PURE__ */
|
|
1575
|
-
/* @__PURE__ */
|
|
1848
|
+
return /* @__PURE__ */ jsxs19(AdminPage, { children: [
|
|
1849
|
+
/* @__PURE__ */ jsxs19(Panel, { title: copy.title, children: [
|
|
1850
|
+
/* @__PURE__ */ jsx21("p", { className: "muted", style: { margin: 0 }, children: copy.description }),
|
|
1851
|
+
/* @__PURE__ */ jsxs19("form", { onSubmit: (event) => void submit(event), style: { display: "grid", gap: 12, gridTemplateColumns: "minmax(220px,2fr) minmax(180px,1fr) minmax(160px,1fr)" }, children: [
|
|
1852
|
+
/* @__PURE__ */ jsxs19("label", { children: [
|
|
1576
1853
|
copy.email,
|
|
1577
|
-
/* @__PURE__ */
|
|
1854
|
+
/* @__PURE__ */ jsx21("input", { required: true, type: "email", value: email, onInput: (event) => setEmail(event.currentTarget.value) })
|
|
1578
1855
|
] }),
|
|
1579
|
-
/* @__PURE__ */
|
|
1856
|
+
/* @__PURE__ */ jsxs19("label", { children: [
|
|
1580
1857
|
copy.decision,
|
|
1581
|
-
/* @__PURE__ */
|
|
1582
|
-
/* @__PURE__ */
|
|
1583
|
-
/* @__PURE__ */
|
|
1858
|
+
/* @__PURE__ */ jsxs19("select", { value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
|
|
1859
|
+
/* @__PURE__ */ jsx21("option", { value: "waive_interview", children: copy.waive }),
|
|
1860
|
+
/* @__PURE__ */ jsx21("option", { value: "require_interview", children: copy.require })
|
|
1584
1861
|
] })
|
|
1585
1862
|
] }),
|
|
1586
|
-
/* @__PURE__ */
|
|
1863
|
+
/* @__PURE__ */ jsxs19("label", { children: [
|
|
1587
1864
|
copy.expires,
|
|
1588
|
-
/* @__PURE__ */
|
|
1865
|
+
/* @__PURE__ */ jsx21("input", { type: "date", value: expires, onInput: (event) => setExpires(event.currentTarget.value) })
|
|
1589
1866
|
] }),
|
|
1590
|
-
/* @__PURE__ */
|
|
1867
|
+
/* @__PURE__ */ jsxs19("label", { style: { gridColumn: "1 / -1" }, children: [
|
|
1591
1868
|
copy.note,
|
|
1592
|
-
/* @__PURE__ */
|
|
1869
|
+
/* @__PURE__ */ jsx21("input", { value: note, maxLength: 2e3, onInput: (event) => setNote(event.currentTarget.value) })
|
|
1593
1870
|
] }),
|
|
1594
|
-
/* @__PURE__ */
|
|
1871
|
+
/* @__PURE__ */ jsx21("button", { className: "btn secondary", type: "submit", disabled: busy, children: copy.add })
|
|
1595
1872
|
] }),
|
|
1596
|
-
error ? /* @__PURE__ */
|
|
1873
|
+
error ? /* @__PURE__ */ jsx21("p", { role: "alert", children: error }) : null
|
|
1597
1874
|
] }),
|
|
1598
|
-
/* @__PURE__ */
|
|
1599
|
-
resource.loading ? /* @__PURE__ */
|
|
1600
|
-
/* @__PURE__ */
|
|
1875
|
+
/* @__PURE__ */ jsxs19(Panel, { title: copy.ledger, children: [
|
|
1876
|
+
resource.loading ? /* @__PURE__ */ jsx21("p", { className: "muted", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx21("p", { role: "alert", children: resource.error }) : null,
|
|
1877
|
+
/* @__PURE__ */ jsx21("div", { style: { display: "grid", gap: 10 }, children: (resource.data?.grants ?? []).map((grant) => {
|
|
1601
1878
|
const state = grant.consumedAt ? copy.consumed : grant.revokedAt || !grant.active ? copy.revoked : grant.expiresAt && grant.expiresAt <= Date.now() ? copy.expired : copy.active;
|
|
1602
|
-
return /* @__PURE__ */
|
|
1603
|
-
/* @__PURE__ */
|
|
1879
|
+
return /* @__PURE__ */ jsxs19("article", { className: "card", style: { padding: 14 }, children: [
|
|
1880
|
+
/* @__PURE__ */ jsx21("strong", { children: grant.email }),
|
|
1604
1881
|
" \xB7 ",
|
|
1605
1882
|
grant.decision === "waive_interview" ? copy.waive : copy.require,
|
|
1606
|
-
/* @__PURE__ */
|
|
1883
|
+
/* @__PURE__ */ jsxs19("p", { className: "muted", style: { margin: "6px 0" }, children: [
|
|
1607
1884
|
grant.sourceScope === "local" ? copy.local : copy.parent,
|
|
1608
1885
|
" \xB7 ",
|
|
1609
1886
|
state,
|
|
1610
1887
|
grant.note ? ` \xB7 ${grant.note}` : ""
|
|
1611
1888
|
] }),
|
|
1612
|
-
grant.sourceScope === "local" && state === copy.active ? /* @__PURE__ */
|
|
1889
|
+
grant.sourceScope === "local" && state === copy.active ? /* @__PURE__ */ jsx21("button", { className: "btn secondary mini", disabled: busy, onClick: () => void revoke(grant), children: copy.revoke }) : null
|
|
1613
1890
|
] }, grant.id);
|
|
1614
1891
|
}) })
|
|
1615
1892
|
] })
|
|
@@ -1620,28 +1897,56 @@ function admissionGrantsWorkspace(label = "Admission") {
|
|
|
1620
1897
|
id: "admission",
|
|
1621
1898
|
label,
|
|
1622
1899
|
defaultViewId: "grants",
|
|
1623
|
-
render: (ctx) => /* @__PURE__ */
|
|
1900
|
+
render: (ctx) => /* @__PURE__ */ jsx21(AdmissionGrantsWorkspace, { getToken: ctx.getToken })
|
|
1624
1901
|
};
|
|
1625
1902
|
}
|
|
1626
1903
|
function LeaderAdmissionAction(props) {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
const [
|
|
1904
|
+
return /* @__PURE__ */ jsx21(LeaderAdmissionForm, { ...props }, JSON.stringify([props.targetId, props.personId, props.sourceRecordId]));
|
|
1905
|
+
}
|
|
1906
|
+
function LeaderAdmissionForm(props) {
|
|
1907
|
+
const [decision, setDecision] = useState15("");
|
|
1908
|
+
const [note, setNote] = useState15("");
|
|
1909
|
+
const [loaded, setLoaded] = useState15(false);
|
|
1910
|
+
const [busy, setBusy] = useState15(false);
|
|
1911
|
+
const [error, setError] = useState15(null);
|
|
1912
|
+
const [status, setStatus] = useState15(null);
|
|
1913
|
+
const attempt = useRef2(null);
|
|
1914
|
+
useEffect8(() => {
|
|
1915
|
+
let live = true;
|
|
1916
|
+
const personId = props.sourceRecordId ? `chapter-person:${props.targetId}:${props.sourceRecordId}` : props.personId;
|
|
1917
|
+
void adminFetch(props.getToken, `/api/admin/network/admissions?targetId=${encodeURIComponent(props.targetId)}`).then((result) => {
|
|
1918
|
+
if (!live) return;
|
|
1919
|
+
const saved = result.assignments.find((row) => row.personId === personId && row.active && (!row.expiresAt || row.expiresAt > Date.now()));
|
|
1920
|
+
setDecision(saved?.decision ?? "");
|
|
1921
|
+
setNote(saved?.note ?? "");
|
|
1922
|
+
setLoaded(true);
|
|
1923
|
+
if (saved && result.delivery?.status !== "acknowledged") setStatus("Saved; delivery is pending. Retry to reconcile the same setting.");
|
|
1924
|
+
}).catch((cause) => {
|
|
1925
|
+
if (live) setError(cause instanceof Error ? cause.message : "Invitation setting could not be loaded.");
|
|
1926
|
+
});
|
|
1927
|
+
return () => {
|
|
1928
|
+
live = false;
|
|
1929
|
+
};
|
|
1930
|
+
}, [props.getToken, props.targetId, props.personId, props.sourceRecordId]);
|
|
1631
1931
|
const submit = async () => {
|
|
1932
|
+
if (!loaded || !decision || busy) return;
|
|
1632
1933
|
setBusy(true);
|
|
1633
1934
|
setError(null);
|
|
1634
1935
|
try {
|
|
1936
|
+
const fingerprint = JSON.stringify([props.targetId, props.personId, props.sourceRecordId, decision, note.trim()]);
|
|
1937
|
+
if (attempt.current?.fingerprint !== fingerprint) attempt.current = { fingerprint, id: crypto.randomUUID() };
|
|
1635
1938
|
const result = await adminFetch(props.getToken, "/api/admin/network/admissions", {
|
|
1636
1939
|
method: "POST",
|
|
1637
1940
|
body: JSON.stringify({
|
|
1638
1941
|
targetId: props.targetId,
|
|
1639
|
-
personId: props.personId,
|
|
1942
|
+
...props.sourceRecordId ? { sourceRecordId: props.sourceRecordId } : { personId: props.personId },
|
|
1640
1943
|
decision,
|
|
1641
1944
|
...note.trim() ? { note: note.trim() } : {},
|
|
1642
|
-
mutationId:
|
|
1945
|
+
mutationId: attempt.current.id
|
|
1643
1946
|
})
|
|
1644
1947
|
});
|
|
1948
|
+
setStatus(result.delivery.status === "acknowledged" ? "Invitation setting saved at the chapter." : "Saved; delivery is pending. Retry to reconcile the same setting.");
|
|
1949
|
+
if (result.delivery.status === "acknowledged") attempt.current = null;
|
|
1645
1950
|
props.onSaved?.(result);
|
|
1646
1951
|
} catch (cause) {
|
|
1647
1952
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -1649,20 +1954,22 @@ function LeaderAdmissionAction(props) {
|
|
|
1649
1954
|
setBusy(false);
|
|
1650
1955
|
}
|
|
1651
1956
|
};
|
|
1652
|
-
return /* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1957
|
+
return /* @__PURE__ */ jsxs19("div", { style: { display: "grid", gap: 8 }, children: [
|
|
1958
|
+
/* @__PURE__ */ jsxs19("label", { children: [
|
|
1654
1959
|
"Interview",
|
|
1655
|
-
/* @__PURE__ */
|
|
1656
|
-
/* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
1960
|
+
/* @__PURE__ */ jsxs19("select", { disabled: !loaded || busy, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
|
|
1961
|
+
/* @__PURE__ */ jsx21("option", { value: "", children: loaded ? "Choose an invitation setting" : "Loading saved setting\u2026" }),
|
|
1962
|
+
/* @__PURE__ */ jsx21("option", { value: "waive_interview", children: "Already completed" }),
|
|
1963
|
+
/* @__PURE__ */ jsx21("option", { value: "require_interview", children: "Required" })
|
|
1658
1964
|
] })
|
|
1659
1965
|
] }),
|
|
1660
|
-
/* @__PURE__ */
|
|
1966
|
+
/* @__PURE__ */ jsxs19("label", { children: [
|
|
1661
1967
|
"Internal note",
|
|
1662
|
-
/* @__PURE__ */
|
|
1968
|
+
/* @__PURE__ */ jsx21("input", { disabled: !loaded || busy, maxLength: 2e3, value: note, onInput: (event) => setNote(event.currentTarget.value) })
|
|
1663
1969
|
] }),
|
|
1664
|
-
/* @__PURE__ */
|
|
1665
|
-
error ? /* @__PURE__ */
|
|
1970
|
+
/* @__PURE__ */ jsx21("button", { className: "btn secondary", type: "button", disabled: busy || !loaded || !decision, onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save invitation setting" }),
|
|
1971
|
+
error ? /* @__PURE__ */ jsx21("p", { role: "alert", children: error }) : null,
|
|
1972
|
+
status ? /* @__PURE__ */ jsx21("p", { role: "status", children: status }) : null
|
|
1666
1973
|
] });
|
|
1667
1974
|
}
|
|
1668
1975
|
|
|
@@ -1670,19 +1977,19 @@ function LeaderAdmissionAction(props) {
|
|
|
1670
1977
|
import { Tabs as Tabs2 } from "@odla-ai/ui/components";
|
|
1671
1978
|
|
|
1672
1979
|
// src/ui/admin-availability.tsx
|
|
1673
|
-
import { useState as
|
|
1674
|
-
import { Button as
|
|
1675
|
-
import { jsx as
|
|
1980
|
+
import { useState as useState16 } from "preact/hooks";
|
|
1981
|
+
import { Button as Button9, Checkbox, Field as Field7, Input as Input2 } from "@odla-ai/ui/components";
|
|
1982
|
+
import { jsx as jsx22, jsxs as jsxs20 } from "preact/jsx-runtime";
|
|
1676
1983
|
function AvailabilityBody({ ctx }) {
|
|
1677
1984
|
const copy = useChapterCopy().admin.availability;
|
|
1678
1985
|
const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
|
|
1679
|
-
const [draft, setDraft] =
|
|
1680
|
-
const [busy, setBusy] =
|
|
1681
|
-
const [note, setNote] =
|
|
1682
|
-
const [errors, setErrors] =
|
|
1986
|
+
const [draft, setDraft] = useState16(null);
|
|
1987
|
+
const [busy, setBusy] = useState16(false);
|
|
1988
|
+
const [note, setNote] = useState16(null);
|
|
1989
|
+
const [errors, setErrors] = useState16({});
|
|
1683
1990
|
const cfg = draft ?? data?.scheduling ?? null;
|
|
1684
|
-
if (loading) return /* @__PURE__ */
|
|
1685
|
-
if (error || !cfg) return /* @__PURE__ */
|
|
1991
|
+
if (loading) return /* @__PURE__ */ jsx22(AdminNote, { children: copy.loading });
|
|
1992
|
+
if (error || !cfg) return /* @__PURE__ */ jsxs20(AdminNote, { children: [
|
|
1686
1993
|
copy.loadFailed,
|
|
1687
1994
|
error ? `: ${error}` : "",
|
|
1688
1995
|
"."
|
|
@@ -1712,29 +2019,29 @@ function AvailabilityBody({ ctx }) {
|
|
|
1712
2019
|
setBusy(false);
|
|
1713
2020
|
}
|
|
1714
2021
|
};
|
|
1715
|
-
const numField = (key, label) => /* @__PURE__ */
|
|
1716
|
-
return /* @__PURE__ */
|
|
1717
|
-
/* @__PURE__ */
|
|
1718
|
-
/* @__PURE__ */
|
|
2022
|
+
const numField = (key, label) => /* @__PURE__ */ jsx22(Field7, { label, error: errors[key], children: /* @__PURE__ */ jsx22(Input2, { type: "number", value: cfg[key], onInput: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
|
|
2023
|
+
return /* @__PURE__ */ jsx22(AdminPage, { children: /* @__PURE__ */ jsxs20(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx22("span", { className: "muted", children: note }) : void 0, children: [
|
|
2024
|
+
/* @__PURE__ */ jsx22(Field7, { label: copy.days, error: errors.days, children: /* @__PURE__ */ jsx22("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: copy.dayLabels.map((d, i) => /* @__PURE__ */ jsx22(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
|
|
2025
|
+
/* @__PURE__ */ jsxs20("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
|
|
1719
2026
|
numField("startHour", copy.startHour),
|
|
1720
2027
|
numField("endHour", copy.endHour),
|
|
1721
2028
|
numField("slotMinutes", copy.slotMinutes),
|
|
1722
2029
|
numField("minNoticeHours", copy.minNoticeHours),
|
|
1723
2030
|
numField("windowDays", copy.windowDays),
|
|
1724
|
-
/* @__PURE__ */
|
|
2031
|
+
/* @__PURE__ */ jsx22(Field7, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx22(Input2, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
|
|
1725
2032
|
] }),
|
|
1726
|
-
/* @__PURE__ */
|
|
1727
|
-
/* @__PURE__ */
|
|
2033
|
+
/* @__PURE__ */ jsx22(Field7, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx22(Input2, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
|
|
2034
|
+
/* @__PURE__ */ jsx22("div", { children: /* @__PURE__ */ jsx22(Button9, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
|
|
1728
2035
|
] }) });
|
|
1729
2036
|
}
|
|
1730
2037
|
function availabilitySection(options = {}) {
|
|
1731
2038
|
const { id = "availability", label = "Availability" } = options;
|
|
1732
|
-
return { id, label, render: (ctx) => /* @__PURE__ */
|
|
2039
|
+
return { id, label, render: (ctx) => /* @__PURE__ */ jsx22(AvailabilityBody, { ctx }) };
|
|
1733
2040
|
}
|
|
1734
2041
|
|
|
1735
2042
|
// src/ui/admin-builder-library.tsx
|
|
1736
|
-
import { useEffect as
|
|
1737
|
-
import { jsx as
|
|
2043
|
+
import { useEffect as useEffect9, useMemo as useMemo2, useState as useState17 } from "preact/hooks";
|
|
2044
|
+
import { jsx as jsx23, jsxs as jsxs21 } from "preact/jsx-runtime";
|
|
1738
2045
|
var KINDS = ["document", "note", "link", "inspiration", "image", "file"];
|
|
1739
2046
|
var KIND_LABEL = { document: "Document", note: "Note", link: "Link", inspiration: "Inspiration", image: "Image", file: "File" };
|
|
1740
2047
|
var STATUS_LABEL = { draft: "Draft", published: "Published", archived: "Archived" };
|
|
@@ -1766,13 +2073,13 @@ function localResource(kind, chapterId, mode) {
|
|
|
1766
2073
|
};
|
|
1767
2074
|
}
|
|
1768
2075
|
function ResourcePreview({ item }) {
|
|
1769
|
-
return /* @__PURE__ */
|
|
1770
|
-
/* @__PURE__ */
|
|
1771
|
-
/* @__PURE__ */
|
|
1772
|
-
item.summary ? /* @__PURE__ */
|
|
1773
|
-
item.bodyMarkdown ? /* @__PURE__ */
|
|
1774
|
-
item.url ? /* @__PURE__ */
|
|
1775
|
-
item.brandAsset ? /* @__PURE__ */
|
|
2076
|
+
return /* @__PURE__ */ jsxs21("div", { className: "card", style: { padding: 14 }, children: [
|
|
2077
|
+
/* @__PURE__ */ jsx23("div", { className: "card-label", children: "Preview" }),
|
|
2078
|
+
/* @__PURE__ */ jsx23("h3", { style: { margin: "6px 0" }, children: item.title || "Untitled" }),
|
|
2079
|
+
item.summary ? /* @__PURE__ */ jsx23("p", { style: { margin: "0 0 10px" }, children: item.summary }) : null,
|
|
2080
|
+
item.bodyMarkdown ? /* @__PURE__ */ jsx23("pre", { style: { margin: 0, whiteSpace: "pre-wrap", font: "inherit", lineHeight: 1.55 }, children: item.bodyMarkdown }) : null,
|
|
2081
|
+
item.url ? /* @__PURE__ */ jsx23("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url }) : null,
|
|
2082
|
+
item.brandAsset ? /* @__PURE__ */ jsxs21("div", { style: META, children: [
|
|
1776
2083
|
"Brand asset \xB7 ",
|
|
1777
2084
|
item.brandAsset.bookId,
|
|
1778
2085
|
" / ",
|
|
@@ -1784,15 +2091,15 @@ function ResourcePreview({ item }) {
|
|
|
1784
2091
|
function BuilderLibraryAdmin({ ctx, initialData }) {
|
|
1785
2092
|
const remote = useAdminResource(ctx.getToken, "/api/admin/builder-library");
|
|
1786
2093
|
const seed = initialData ?? remote.data;
|
|
1787
|
-
const [resources, setResources] =
|
|
1788
|
-
const [selectedKey, setSelectedKey] =
|
|
1789
|
-
const [query, setQuery] =
|
|
1790
|
-
const [kindFilter, setKindFilter] =
|
|
1791
|
-
const [statusFilter, setStatusFilter] =
|
|
1792
|
-
const [newKind, setNewKind] =
|
|
1793
|
-
const [saveError, setSaveError] =
|
|
1794
|
-
const [saving, setSaving] =
|
|
1795
|
-
|
|
2094
|
+
const [resources, setResources] = useState17(() => [...seed?.local?.document.resources ?? []]);
|
|
2095
|
+
const [selectedKey, setSelectedKey] = useState17(() => seed?.local?.document.resources[0]?.id ? localKey(seed.local.document.resources[0].id) : null);
|
|
2096
|
+
const [query, setQuery] = useState17("");
|
|
2097
|
+
const [kindFilter, setKindFilter] = useState17("all");
|
|
2098
|
+
const [statusFilter, setStatusFilter] = useState17("all");
|
|
2099
|
+
const [newKind, setNewKind] = useState17("document");
|
|
2100
|
+
const [saveError, setSaveError] = useState17(null);
|
|
2101
|
+
const [saving, setSaving] = useState17(false);
|
|
2102
|
+
useEffect9(() => {
|
|
1796
2103
|
if (!remote.data) return;
|
|
1797
2104
|
const next = [...remote.data.local?.document.resources ?? []];
|
|
1798
2105
|
setResources(next);
|
|
@@ -1808,8 +2115,8 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
|
|
|
1808
2115
|
return (!query.trim() || haystack.includes(query.toLowerCase().trim())) && (kindFilter === "all" || resource.kind === kindFilter) && (statusFilter === "all" || resource.status === statusFilter);
|
|
1809
2116
|
});
|
|
1810
2117
|
const selected = entries.find((entry) => entry.key === selectedKey) ?? visible[0] ?? null;
|
|
1811
|
-
if (!data && remote.loading) return /* @__PURE__ */
|
|
1812
|
-
if (remote.error || !data) return /* @__PURE__ */
|
|
2118
|
+
if (!data && remote.loading) return /* @__PURE__ */ jsx23(AdminPage, { children: /* @__PURE__ */ jsx23(AdminNote, { children: "Loading Builder Library\u2026" }) });
|
|
2119
|
+
if (remote.error || !data) return /* @__PURE__ */ jsx23(AdminPage, { children: /* @__PURE__ */ jsxs21(AdminNote, { children: [
|
|
1813
2120
|
"Builder Library could not be loaded: ",
|
|
1814
2121
|
remote.error ?? "unknown error"
|
|
1815
2122
|
] }) });
|
|
@@ -1845,179 +2152,232 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
|
|
|
1845
2152
|
}
|
|
1846
2153
|
};
|
|
1847
2154
|
const scopeValue = selected?.resource.scope.kind === "chapter" ? `chapter:${selected.resource.scope.chapterId}` : selected?.resource.scope.kind ?? "built_not_found";
|
|
1848
|
-
return /* @__PURE__ */
|
|
1849
|
-
/* @__PURE__ */
|
|
2155
|
+
return /* @__PURE__ */ jsxs21(AdminPage, { children: [
|
|
2156
|
+
/* @__PURE__ */ jsxs21(Panel, { title: /* @__PURE__ */ jsxs21("span", { children: [
|
|
1850
2157
|
"Builder Library ",
|
|
1851
|
-
/* @__PURE__ */
|
|
1852
|
-
] }), actions: /* @__PURE__ */
|
|
1853
|
-
/* @__PURE__ */
|
|
1854
|
-
/* @__PURE__ */
|
|
2158
|
+
/* @__PURE__ */ jsx23("span", { className: "muted", children: "\xB7 private" })
|
|
2159
|
+
] }), actions: /* @__PURE__ */ jsxs21("div", { style: ROW, children: [
|
|
2160
|
+
/* @__PURE__ */ jsx23("select", { "aria-label": "New resource type", value: newKind, onChange: (event) => setNewKind(event.currentTarget.value), children: KINDS.map((kind) => /* @__PURE__ */ jsx23("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }),
|
|
2161
|
+
/* @__PURE__ */ jsx23("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
|
|
1855
2162
|
] }), children: [
|
|
1856
|
-
/* @__PURE__ */
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
/* @__PURE__ */
|
|
1859
|
-
/* @__PURE__ */
|
|
1860
|
-
/* @__PURE__ */
|
|
1861
|
-
KINDS.map((kind) => /* @__PURE__ */
|
|
2163
|
+
/* @__PURE__ */ jsx23("p", { className: "muted", style: { margin: 0 }, children: "Documents, links, and Brand assets used by site-building agents. Nothing here appears on a member page." }),
|
|
2164
|
+
/* @__PURE__ */ jsxs21("div", { style: ROW, children: [
|
|
2165
|
+
/* @__PURE__ */ jsx23("input", { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
|
|
2166
|
+
/* @__PURE__ */ jsxs21("select", { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
|
|
2167
|
+
/* @__PURE__ */ jsx23("option", { value: "all", children: "All types" }),
|
|
2168
|
+
KINDS.map((kind) => /* @__PURE__ */ jsx23("option", { value: kind, children: KIND_LABEL[kind] }, kind))
|
|
1862
2169
|
] }),
|
|
1863
|
-
/* @__PURE__ */
|
|
1864
|
-
/* @__PURE__ */
|
|
1865
|
-
/* @__PURE__ */
|
|
1866
|
-
/* @__PURE__ */
|
|
1867
|
-
/* @__PURE__ */
|
|
2170
|
+
/* @__PURE__ */ jsxs21("select", { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
|
|
2171
|
+
/* @__PURE__ */ jsx23("option", { value: "all", children: "All statuses" }),
|
|
2172
|
+
/* @__PURE__ */ jsx23("option", { value: "draft", children: "Draft" }),
|
|
2173
|
+
/* @__PURE__ */ jsx23("option", { value: "published", children: "Published" }),
|
|
2174
|
+
/* @__PURE__ */ jsx23("option", { value: "archived", children: "Archived" })
|
|
1868
2175
|
] })
|
|
1869
2176
|
] })
|
|
1870
2177
|
] }),
|
|
1871
|
-
/* @__PURE__ */
|
|
1872
|
-
/* @__PURE__ */
|
|
1873
|
-
/* @__PURE__ */
|
|
1874
|
-
/* @__PURE__ */
|
|
1875
|
-
/* @__PURE__ */
|
|
2178
|
+
/* @__PURE__ */ jsxs21("div", { style: GRID, "data-builder-library-cms": true, children: [
|
|
2179
|
+
/* @__PURE__ */ jsx23(Panel, { title: `${visible.length} resource${visible.length === 1 ? "" : "s"}`, children: /* @__PURE__ */ jsx23("div", { style: { ...STACK, maxHeight: "70vh", overflow: "auto" }, children: visible.length === 0 ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "No resources match these filters." }) : visible.map((entry) => /* @__PURE__ */ jsxs21("button", { type: "button", style: { ...ITEM, ...selected?.key === entry.key ? { borderColor: "var(--ui-accent)", boxShadow: "0 0 0 1px var(--ui-accent)" } : {} }, onClick: () => setSelectedKey(entry.key), children: [
|
|
2180
|
+
/* @__PURE__ */ jsxs21("div", { style: { ...ROW, justifyContent: "space-between" }, children: [
|
|
2181
|
+
/* @__PURE__ */ jsx23("strong", { children: entry.resource.title }),
|
|
2182
|
+
/* @__PURE__ */ jsx23("span", { className: "badge", children: KIND_LABEL[entry.resource.kind] })
|
|
1876
2183
|
] }),
|
|
1877
|
-
/* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
1880
|
-
/* @__PURE__ */
|
|
1881
|
-
/* @__PURE__ */
|
|
1882
|
-
/* @__PURE__ */
|
|
1883
|
-
/* @__PURE__ */
|
|
2184
|
+
/* @__PURE__ */ jsx23("p", { className: "muted", style: { margin: "6px 0", lineHeight: 1.4 }, children: contentSummary(entry.resource) }),
|
|
2185
|
+
/* @__PURE__ */ jsxs21("div", { style: META, children: [
|
|
2186
|
+
/* @__PURE__ */ jsx23("span", { children: STATUS_LABEL[entry.resource.status] }),
|
|
2187
|
+
/* @__PURE__ */ jsx23("span", { children: "\xB7" }),
|
|
2188
|
+
/* @__PURE__ */ jsx23("span", { children: scopeLabel(entry.resource.scope) }),
|
|
2189
|
+
/* @__PURE__ */ jsx23("span", { children: "\xB7" }),
|
|
2190
|
+
/* @__PURE__ */ jsx23("span", { children: entry.sourceId })
|
|
1884
2191
|
] })
|
|
1885
2192
|
] }, entry.key)) }) }),
|
|
1886
|
-
/* @__PURE__ */
|
|
1887
|
-
/* @__PURE__ */
|
|
1888
|
-
/* @__PURE__ */
|
|
2193
|
+
/* @__PURE__ */ jsxs21(Panel, { title: selected ? selected.resource.title : "Select a resource", actions: selected?.local ? /* @__PURE__ */ jsxs21("div", { style: ROW, children: [
|
|
2194
|
+
/* @__PURE__ */ jsx23("button", { className: "btn secondary", type: "button", disabled: saving, onClick: () => update({ status: selected.resource.status === "archived" ? "draft" : "archived" }), children: selected.resource.status === "archived" ? "Restore draft" : "Archive" }),
|
|
2195
|
+
/* @__PURE__ */ jsx23("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save changes" })
|
|
1889
2196
|
] }) : null, children: [
|
|
1890
|
-
!selected ? /* @__PURE__ */
|
|
1891
|
-
/* @__PURE__ */
|
|
2197
|
+
!selected ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "Add a resource or choose one from the list." }) : selected.local ? /* @__PURE__ */ jsx23(LocalEditor, { selected: selected.resource, data, scopeValue, saving, update, setResources, setSelectedKey }) : /* @__PURE__ */ jsxs21("div", { style: STACK, children: [
|
|
2198
|
+
/* @__PURE__ */ jsxs21("div", { style: META, children: [
|
|
1892
2199
|
"Read only \xB7 supplied by ",
|
|
1893
2200
|
selected.sourceId,
|
|
1894
2201
|
" \xB7 source revision ",
|
|
1895
2202
|
selected.sourceRevision
|
|
1896
2203
|
] }),
|
|
1897
|
-
/* @__PURE__ */
|
|
1898
|
-
/* @__PURE__ */
|
|
2204
|
+
/* @__PURE__ */ jsxs21("div", { children: [
|
|
2205
|
+
/* @__PURE__ */ jsx23("span", { className: "badge", children: STATUS_LABEL[selected.resource.status] }),
|
|
1899
2206
|
" ",
|
|
1900
|
-
/* @__PURE__ */
|
|
2207
|
+
/* @__PURE__ */ jsx23("span", { className: "badge", children: scopeLabel(selected.resource.scope) })
|
|
1901
2208
|
] }),
|
|
1902
|
-
/* @__PURE__ */
|
|
1903
|
-
data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */
|
|
2209
|
+
/* @__PURE__ */ jsx23(ResourcePreview, { item: selected.resource }),
|
|
2210
|
+
data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */ jsx23("button", { className: "btn secondary", type: "button", onClick: () => void promote(selected), children: "Copy to all chapters" }) : null
|
|
1904
2211
|
] }),
|
|
1905
|
-
saveError ? /* @__PURE__ */
|
|
1906
|
-
/* @__PURE__ */
|
|
1907
|
-
/* @__PURE__ */
|
|
2212
|
+
saveError ? /* @__PURE__ */ jsxs21("div", { className: "notice danger", role: "alert", children: [
|
|
2213
|
+
/* @__PURE__ */ jsx23("strong", { children: "Fix these items before saving:" }),
|
|
2214
|
+
/* @__PURE__ */ jsx23("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx23("li", { children: error }, error)) })
|
|
1908
2215
|
] }) : null
|
|
1909
2216
|
] })
|
|
1910
2217
|
] })
|
|
1911
2218
|
] });
|
|
1912
2219
|
}
|
|
1913
2220
|
function LocalEditor({ selected, data, scopeValue, update, setResources, setSelectedKey }) {
|
|
1914
|
-
return /* @__PURE__ */
|
|
1915
|
-
/* @__PURE__ */
|
|
2221
|
+
return /* @__PURE__ */ jsxs21("div", { style: STACK, children: [
|
|
2222
|
+
/* @__PURE__ */ jsxs21("div", { style: META, children: [
|
|
1916
2223
|
"Local \xB7 resource revision ",
|
|
1917
2224
|
selected.revision,
|
|
1918
2225
|
" \xB7 library revision ",
|
|
1919
2226
|
data.local?.revision ?? 0
|
|
1920
2227
|
] }),
|
|
1921
|
-
/* @__PURE__ */
|
|
1922
|
-
/* @__PURE__ */
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
2228
|
+
/* @__PURE__ */ jsxs21("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
2229
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2230
|
+
/* @__PURE__ */ jsx23("span", { children: "Title" }),
|
|
2231
|
+
/* @__PURE__ */ jsx23("input", { value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) })
|
|
1925
2232
|
] }),
|
|
1926
|
-
/* @__PURE__ */
|
|
1927
|
-
/* @__PURE__ */
|
|
1928
|
-
/* @__PURE__ */
|
|
2233
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2234
|
+
/* @__PURE__ */ jsx23("span", { children: "ID" }),
|
|
2235
|
+
/* @__PURE__ */ jsx23("input", { value: selected.id, onInput: (event) => {
|
|
1929
2236
|
const id = cleanId(event.currentTarget.value);
|
|
1930
2237
|
setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
|
|
1931
2238
|
setSelectedKey(localKey(id));
|
|
1932
2239
|
} })
|
|
1933
2240
|
] }),
|
|
1934
|
-
/* @__PURE__ */
|
|
1935
|
-
/* @__PURE__ */
|
|
1936
|
-
/* @__PURE__ */
|
|
2241
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2242
|
+
/* @__PURE__ */ jsx23("span", { children: "Type" }),
|
|
2243
|
+
/* @__PURE__ */ jsx23("select", { value: selected.kind, onChange: (event) => {
|
|
1937
2244
|
const kind = event.currentTarget.value;
|
|
1938
2245
|
update({ kind, bodyMarkdown: kind === "document" || kind === "note" ? selected.bodyMarkdown ?? "Start writing\u2026" : void 0, url: kind === "link" || kind === "inspiration" ? selected.url ?? "https://example.com" : void 0, brandAsset: kind === "image" || kind === "file" ? selected.brandAsset ?? { bookId: "brand-book", assetId: "replace-me" } : void 0 });
|
|
1939
|
-
}, children: KINDS.map((kind) => /* @__PURE__ */
|
|
2246
|
+
}, children: KINDS.map((kind) => /* @__PURE__ */ jsx23("option", { value: kind, children: KIND_LABEL[kind] }, kind)) })
|
|
1940
2247
|
] }),
|
|
1941
|
-
/* @__PURE__ */
|
|
1942
|
-
/* @__PURE__ */
|
|
1943
|
-
/* @__PURE__ */
|
|
1944
|
-
/* @__PURE__ */
|
|
1945
|
-
/* @__PURE__ */
|
|
1946
|
-
/* @__PURE__ */
|
|
2248
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2249
|
+
/* @__PURE__ */ jsx23("span", { children: "Status" }),
|
|
2250
|
+
/* @__PURE__ */ jsxs21("select", { value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
|
|
2251
|
+
/* @__PURE__ */ jsx23("option", { value: "draft", children: "Draft" }),
|
|
2252
|
+
/* @__PURE__ */ jsx23("option", { value: "published", children: "Published" }),
|
|
2253
|
+
/* @__PURE__ */ jsx23("option", { value: "archived", children: "Archived" })
|
|
1947
2254
|
] })
|
|
1948
2255
|
] }),
|
|
1949
|
-
/* @__PURE__ */
|
|
1950
|
-
/* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
2256
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2257
|
+
/* @__PURE__ */ jsx23("span", { children: "Visible to" }),
|
|
2258
|
+
/* @__PURE__ */ jsxs21("select", { value: scopeValue, onChange: (event) => {
|
|
1952
2259
|
const value = event.currentTarget.value;
|
|
1953
2260
|
update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
|
|
1954
2261
|
}, children: [
|
|
1955
|
-
data.mode === "hub" ? /* @__PURE__ */
|
|
1956
|
-
data.mode === "hub" ? /* @__PURE__ */
|
|
1957
|
-
data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */
|
|
2262
|
+
data.mode === "hub" ? /* @__PURE__ */ jsx23("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
|
|
2263
|
+
data.mode === "hub" ? /* @__PURE__ */ jsx23("option", { value: "global", children: "All chapters" }) : null,
|
|
2264
|
+
data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs21("option", { value: `chapter:${chapterId}`, children: [
|
|
1958
2265
|
"Only ",
|
|
1959
2266
|
chapterId
|
|
1960
2267
|
] }, chapterId))
|
|
1961
2268
|
] })
|
|
1962
2269
|
] }),
|
|
1963
|
-
/* @__PURE__ */
|
|
1964
|
-
/* @__PURE__ */
|
|
1965
|
-
/* @__PURE__ */
|
|
2270
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2271
|
+
/* @__PURE__ */ jsx23("span", { children: "Tags" }),
|
|
2272
|
+
/* @__PURE__ */ jsx23("input", { value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) })
|
|
1966
2273
|
] })
|
|
1967
2274
|
] }),
|
|
1968
|
-
/* @__PURE__ */
|
|
1969
|
-
/* @__PURE__ */
|
|
1970
|
-
/* @__PURE__ */
|
|
2275
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2276
|
+
/* @__PURE__ */ jsx23("span", { children: "Summary" }),
|
|
2277
|
+
/* @__PURE__ */ jsx23("textarea", { rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) })
|
|
1971
2278
|
] }),
|
|
1972
|
-
selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */
|
|
1973
|
-
/* @__PURE__ */
|
|
1974
|
-
/* @__PURE__ */
|
|
2279
|
+
selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2280
|
+
/* @__PURE__ */ jsx23("span", { children: "Markdown" }),
|
|
2281
|
+
/* @__PURE__ */ jsx23("textarea", { rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) })
|
|
1975
2282
|
] }) : null,
|
|
1976
|
-
selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
1978
|
-
/* @__PURE__ */
|
|
2283
|
+
selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2284
|
+
/* @__PURE__ */ jsx23("span", { children: "HTTPS URL" }),
|
|
2285
|
+
/* @__PURE__ */ jsx23("input", { type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) })
|
|
1979
2286
|
] }) : null,
|
|
1980
|
-
selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */
|
|
1981
|
-
/* @__PURE__ */
|
|
1982
|
-
/* @__PURE__ */
|
|
1983
|
-
/* @__PURE__ */
|
|
2287
|
+
selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs21("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
2288
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2289
|
+
/* @__PURE__ */ jsx23("span", { children: "Brand book ID" }),
|
|
2290
|
+
/* @__PURE__ */ jsx23("input", { value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
|
|
1984
2291
|
] }),
|
|
1985
|
-
/* @__PURE__ */
|
|
1986
|
-
/* @__PURE__ */
|
|
1987
|
-
/* @__PURE__ */
|
|
2292
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2293
|
+
/* @__PURE__ */ jsx23("span", { children: "Asset ID" }),
|
|
2294
|
+
/* @__PURE__ */ jsx23("input", { value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
|
|
1988
2295
|
] }),
|
|
1989
|
-
/* @__PURE__ */
|
|
1990
|
-
/* @__PURE__ */
|
|
1991
|
-
/* @__PURE__ */
|
|
2296
|
+
/* @__PURE__ */ jsxs21("label", { className: "field", children: [
|
|
2297
|
+
/* @__PURE__ */ jsx23("span", { children: "Filename (optional)" }),
|
|
2298
|
+
/* @__PURE__ */ jsx23("input", { value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) })
|
|
1992
2299
|
] })
|
|
1993
2300
|
] }) : null,
|
|
1994
|
-
/* @__PURE__ */
|
|
2301
|
+
/* @__PURE__ */ jsx23(ResourcePreview, { item: selected })
|
|
1995
2302
|
] });
|
|
1996
2303
|
}
|
|
1997
2304
|
function builderLibrarySection() {
|
|
1998
|
-
return { id: "builder-library", label: "Builder Library", render: (ctx) => /* @__PURE__ */
|
|
2305
|
+
return { id: "builder-library", label: "Builder Library", render: (ctx) => /* @__PURE__ */ jsx23(BuilderLibraryAdmin, { ctx }) };
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/ui/admin-email.tsx
|
|
2309
|
+
import { useState as useState19 } from "preact/hooks";
|
|
2310
|
+
import { Button as Button11, Checkbox as Checkbox2, DataTable as DataTable3, Field as Field9, Input as Input4, Textarea as Textarea4 } from "@odla-ai/ui/components";
|
|
2311
|
+
|
|
2312
|
+
// src/ui/admin-email-resolution.tsx
|
|
2313
|
+
import { useState as useState18 } from "preact/hooks";
|
|
2314
|
+
import { Button as Button10, Field as Field8, Input as Input3, Textarea as Textarea3 } from "@odla-ai/ui/components";
|
|
2315
|
+
import { jsx as jsx24, jsxs as jsxs22 } from "preact/jsx-runtime";
|
|
2316
|
+
function EmailDeliveryResolution({ row, ctx, onResolved }) {
|
|
2317
|
+
const [open, setOpen] = useState18(false), [reason, setReason] = useState18(""), [messageId, setMessageId] = useState18("");
|
|
2318
|
+
const [busy, setBusy] = useState18(false), [note, setNote] = useState18("");
|
|
2319
|
+
if (!["sending", "uncertain"].includes(row.deliveryState ?? "") || !row.deliveryAttempt) return null;
|
|
2320
|
+
const resolve2 = async (decision) => {
|
|
2321
|
+
setBusy(true);
|
|
2322
|
+
setNote("");
|
|
2323
|
+
try {
|
|
2324
|
+
await adminFetch(ctx.getToken, `/api/admin/email/log/${encodeURIComponent(row.id)}/resolve`, {
|
|
2325
|
+
method: "POST",
|
|
2326
|
+
body: JSON.stringify({
|
|
2327
|
+
expectedAttempt: row.deliveryAttempt,
|
|
2328
|
+
expectedState: row.deliveryState,
|
|
2329
|
+
decision,
|
|
2330
|
+
reason,
|
|
2331
|
+
...messageId.trim() ? { messageId: messageId.trim() } : {}
|
|
2332
|
+
})
|
|
2333
|
+
});
|
|
2334
|
+
setOpen(false);
|
|
2335
|
+
onResolved();
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
setNote(error instanceof Error ? error.message : "Unable to record evidence");
|
|
2338
|
+
} finally {
|
|
2339
|
+
setBusy(false);
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
if (!open) return /* @__PURE__ */ jsx24(Button10, { mini: true, variant: "secondary", onClick: () => setOpen(true), children: "Review delivery" });
|
|
2343
|
+
return /* @__PURE__ */ jsxs22("div", { children: [
|
|
2344
|
+
/* @__PURE__ */ jsxs22("p", { children: [
|
|
2345
|
+
"Attempt ",
|
|
2346
|
+
row.deliveryAttempt,
|
|
2347
|
+
": ",
|
|
2348
|
+
row.subject,
|
|
2349
|
+
". Check provider evidence, not just whether the message is in an inbox. This action does not send mail."
|
|
2350
|
+
] }),
|
|
2351
|
+
/* @__PURE__ */ jsx24(Field8, { label: "Provider evidence", children: /* @__PURE__ */ jsx24(Textarea3, { value: reason, onInput: (e) => setReason(e.target.value) }) }),
|
|
2352
|
+
/* @__PURE__ */ jsx24(Field8, { label: "Provider message ID (optional)", children: /* @__PURE__ */ jsx24(Input3, { value: messageId, onInput: (e) => setMessageId(e.target.value) }) }),
|
|
2353
|
+
/* @__PURE__ */ jsx24(Button10, { mini: true, disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_delivered"), children: "Confirm delivered" }),
|
|
2354
|
+
/* @__PURE__ */ jsx24(Button10, { mini: true, variant: "secondary", disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_not_delivered"), children: "Confirm not delivered" }),
|
|
2355
|
+
/* @__PURE__ */ jsx24(Button10, { mini: true, variant: "secondary", disabled: busy, onClick: () => setOpen(false), children: "Cancel" }),
|
|
2356
|
+
note ? /* @__PURE__ */ jsx24("p", { role: "alert", children: note }) : null
|
|
2357
|
+
] });
|
|
1999
2358
|
}
|
|
2000
2359
|
|
|
2001
2360
|
// src/ui/admin-email.tsx
|
|
2002
|
-
import {
|
|
2003
|
-
|
|
2004
|
-
import { jsx as jsx19, jsxs as jsxs16 } from "preact/jsx-runtime";
|
|
2005
|
-
var logColumns = (copy) => [
|
|
2361
|
+
import { jsx as jsx25, jsxs as jsxs23 } from "preact/jsx-runtime";
|
|
2362
|
+
var logColumns = (copy, ctx, refresh) => [
|
|
2006
2363
|
{ key: "sentAt", header: copy.sentColumn, cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
|
|
2007
2364
|
{ key: "template", header: copy.templateColumn, cell: (r) => r.template },
|
|
2008
2365
|
{ key: "to", header: copy.toColumn, cell: (r) => r.to },
|
|
2009
|
-
{ key: "status", header: copy.statusColumn, cell: (r) =>
|
|
2366
|
+
{ key: "status", header: copy.statusColumn, cell: (r) => /* @__PURE__ */ jsxs23("div", { children: [
|
|
2367
|
+
r.deliveryState && r.deliveryState !== "sent" ? /* @__PURE__ */ jsx25("span", { className: "badge", children: r.deliveryState === "retry_ready" ? "Original workflow may retry" : r.deliveryState === "sending" ? "Sending \u2014 unconfirmed" : "Delivery uncertain" }) : r.error ? /* @__PURE__ */ jsx25("span", { className: "badge", children: copy.failed }) : r.transport === "log-only" ? "Logged only \u2014 not sent" : r.redirected ? /* @__PURE__ */ jsx25("span", { className: "badge", children: copy.redirected }) : copy.delivered,
|
|
2368
|
+
/* @__PURE__ */ jsx25(EmailDeliveryResolution, { row: r, ctx, onResolved: refresh })
|
|
2369
|
+
] }) }
|
|
2010
2370
|
];
|
|
2011
2371
|
function EmailBody({ ctx }) {
|
|
2012
2372
|
const copy = useChapterCopy().admin.email;
|
|
2013
2373
|
const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
|
|
2014
2374
|
const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
|
|
2015
|
-
const [draft, setDraft] =
|
|
2016
|
-
const [busy, setBusy] =
|
|
2017
|
-
const [note, setNote] =
|
|
2375
|
+
const [draft, setDraft] = useState19(null);
|
|
2376
|
+
const [busy, setBusy] = useState19(null);
|
|
2377
|
+
const [note, setNote] = useState19(null);
|
|
2018
2378
|
const data = draft ?? cfg.data;
|
|
2019
|
-
if (cfg.loading) return /* @__PURE__ */
|
|
2020
|
-
if (cfg.error || !data) return /* @__PURE__ */
|
|
2379
|
+
if (cfg.loading) return /* @__PURE__ */ jsx25(AdminNote, { children: copy.loading });
|
|
2380
|
+
if (cfg.error || !data) return /* @__PURE__ */ jsxs23(AdminNote, { children: [
|
|
2021
2381
|
copy.loadFailed,
|
|
2022
2382
|
cfg.error ? `: ${cfg.error}` : "",
|
|
2023
2383
|
"."
|
|
@@ -2050,44 +2410,44 @@ function EmailBody({ ctx }) {
|
|
|
2050
2410
|
}) : copy.testFailed);
|
|
2051
2411
|
log.refresh();
|
|
2052
2412
|
});
|
|
2053
|
-
return /* @__PURE__ */
|
|
2054
|
-
/* @__PURE__ */
|
|
2413
|
+
return /* @__PURE__ */ jsxs23(AdminPage, { children: [
|
|
2414
|
+
/* @__PURE__ */ jsxs23(
|
|
2055
2415
|
Panel,
|
|
2056
2416
|
{
|
|
2057
2417
|
title: copy.delivery,
|
|
2058
|
-
actions: /* @__PURE__ */
|
|
2418
|
+
actions: /* @__PURE__ */ jsxs23("span", { className: "muted", children: [
|
|
2059
2419
|
data.envName,
|
|
2060
2420
|
" \xB7 ",
|
|
2061
2421
|
data.transport,
|
|
2062
2422
|
data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
|
|
2063
2423
|
] }),
|
|
2064
2424
|
children: [
|
|
2065
|
-
/* @__PURE__ */
|
|
2066
|
-
/* @__PURE__ */
|
|
2067
|
-
/* @__PURE__ */
|
|
2425
|
+
/* @__PURE__ */ jsx25(Field9, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx25(Input4, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
|
|
2426
|
+
/* @__PURE__ */ jsx25(Field9, { label: copy.replyTo, children: /* @__PURE__ */ jsx25(Input4, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
|
|
2427
|
+
/* @__PURE__ */ jsx25(Field9, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx25(Input4, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
|
|
2068
2428
|
]
|
|
2069
2429
|
}
|
|
2070
2430
|
),
|
|
2071
|
-
Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */
|
|
2072
|
-
/* @__PURE__ */
|
|
2073
|
-
/* @__PURE__ */
|
|
2074
|
-
/* @__PURE__ */
|
|
2431
|
+
Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs23(Panel, { title: key, actions: /* @__PURE__ */ jsx25(Button11, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: copy.sendTest }), children: [
|
|
2432
|
+
/* @__PURE__ */ jsx25(Checkbox2, { label: copy.enabled, checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
|
|
2433
|
+
/* @__PURE__ */ jsx25(Field9, { label: copy.subject, children: /* @__PURE__ */ jsx25(Input4, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
|
|
2434
|
+
/* @__PURE__ */ jsx25(Field9, { label: copy.body, children: /* @__PURE__ */ jsx25(Textarea4, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) })
|
|
2075
2435
|
] }, key)),
|
|
2076
|
-
/* @__PURE__ */
|
|
2077
|
-
/* @__PURE__ */
|
|
2078
|
-
note ? /* @__PURE__ */
|
|
2436
|
+
/* @__PURE__ */ jsxs23("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
|
|
2437
|
+
/* @__PURE__ */ jsx25(Button11, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
|
|
2438
|
+
note ? /* @__PURE__ */ jsx25("span", { className: "muted", children: note }) : null
|
|
2079
2439
|
] }),
|
|
2080
|
-
/* @__PURE__ */
|
|
2440
|
+
/* @__PURE__ */ jsx25(Panel, { title: copy.sendLog, children: /* @__PURE__ */ jsx25(DataTable3, { rows: log.data?.sends ?? [], columns: logColumns(copy, ctx, log.refresh), rowKey: (r) => r.id }) })
|
|
2081
2441
|
] });
|
|
2082
2442
|
}
|
|
2083
2443
|
function emailSection(options = {}) {
|
|
2084
2444
|
const { id = "email", label = "Email" } = options;
|
|
2085
|
-
return { id, label, render: (ctx) => /* @__PURE__ */
|
|
2445
|
+
return { id, label, render: (ctx) => /* @__PURE__ */ jsx25(EmailBody, { ctx }) };
|
|
2086
2446
|
}
|
|
2087
2447
|
|
|
2088
2448
|
// src/ui/admin-member-content.tsx
|
|
2089
|
-
import { useEffect as
|
|
2090
|
-
import { jsx as
|
|
2449
|
+
import { useEffect as useEffect10, useState as useState20 } from "preact/hooks";
|
|
2450
|
+
import { jsx as jsx26, jsxs as jsxs24 } from "preact/jsx-runtime";
|
|
2091
2451
|
var emptyDraft = () => ({ pages: [], upsellOverrides: [] });
|
|
2092
2452
|
function accessLabel(page) {
|
|
2093
2453
|
return page.audience.kind === "all-members" ? "All members" : `Tiers: ${page.audience.tierIds.join(", ")}`;
|
|
@@ -2097,17 +2457,17 @@ function MemberContentAdmin({
|
|
|
2097
2457
|
initialData
|
|
2098
2458
|
}) {
|
|
2099
2459
|
const resource = useAdminResource(ctx.getToken, "/api/admin/member-content");
|
|
2100
|
-
const [draft, setDraft] =
|
|
2101
|
-
const [saveError, setSaveError] =
|
|
2102
|
-
const [saving, setSaving] =
|
|
2103
|
-
|
|
2460
|
+
const [draft, setDraft] = useState20(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
|
|
2461
|
+
const [saveError, setSaveError] = useState20(null);
|
|
2462
|
+
const [saving, setSaving] = useState20(false);
|
|
2463
|
+
useEffect10(() => {
|
|
2104
2464
|
if (!resource.data) return;
|
|
2105
2465
|
setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft(), null, 2));
|
|
2106
2466
|
}, [resource.data]);
|
|
2107
2467
|
const loaded = initialData ?? resource.data;
|
|
2108
|
-
if (!loaded && resource.loading) return /* @__PURE__ */
|
|
2468
|
+
if (!loaded && resource.loading) return /* @__PURE__ */ jsx26(AdminPage, { children: /* @__PURE__ */ jsx26(AdminNote, { children: "Loading member content\u2026" }) });
|
|
2109
2469
|
if (resource.error || !loaded) {
|
|
2110
|
-
return /* @__PURE__ */
|
|
2470
|
+
return /* @__PURE__ */ jsx26(AdminPage, { children: /* @__PURE__ */ jsxs24(AdminNote, { children: [
|
|
2111
2471
|
"Member content could not be loaded: ",
|
|
2112
2472
|
resource.error ?? "unknown error"
|
|
2113
2473
|
] }) });
|
|
@@ -2138,35 +2498,35 @@ function MemberContentAdmin({
|
|
|
2138
2498
|
setSaving(false);
|
|
2139
2499
|
}
|
|
2140
2500
|
};
|
|
2141
|
-
return /* @__PURE__ */
|
|
2142
|
-
/* @__PURE__ */
|
|
2143
|
-
/* @__PURE__ */
|
|
2144
|
-
/* @__PURE__ */
|
|
2145
|
-
/* @__PURE__ */
|
|
2146
|
-
/* @__PURE__ */
|
|
2147
|
-
/* @__PURE__ */
|
|
2148
|
-
/* @__PURE__ */
|
|
2149
|
-
/* @__PURE__ */
|
|
2501
|
+
return /* @__PURE__ */ jsxs24(AdminPage, { children: [
|
|
2502
|
+
/* @__PURE__ */ jsxs24(Panel, { children: [
|
|
2503
|
+
/* @__PURE__ */ jsx26("div", { className: "card-label", children: "Member content" }),
|
|
2504
|
+
/* @__PURE__ */ jsx26("h2", { children: "One catalog, two clear owners" }),
|
|
2505
|
+
/* @__PURE__ */ jsx26("p", { children: "Parent pages are inherited and read-only here. Chapter pages and delegated upsells are edited in the local document. Access uses exact tier IDs; no tier order is assumed." }),
|
|
2506
|
+
/* @__PURE__ */ jsxs24("div", { className: "stat-band", children: [
|
|
2507
|
+
/* @__PURE__ */ jsxs24("div", { children: [
|
|
2508
|
+
/* @__PURE__ */ jsx26("strong", { children: inherited.length }),
|
|
2509
|
+
/* @__PURE__ */ jsx26("span", { children: "Inherited pages" })
|
|
2150
2510
|
] }),
|
|
2151
|
-
/* @__PURE__ */
|
|
2152
|
-
/* @__PURE__ */
|
|
2153
|
-
/* @__PURE__ */
|
|
2511
|
+
/* @__PURE__ */ jsxs24("div", { children: [
|
|
2512
|
+
/* @__PURE__ */ jsx26("strong", { children: local.length }),
|
|
2513
|
+
/* @__PURE__ */ jsx26("span", { children: "Chapter pages" })
|
|
2154
2514
|
] }),
|
|
2155
|
-
/* @__PURE__ */
|
|
2156
|
-
/* @__PURE__ */
|
|
2157
|
-
/* @__PURE__ */
|
|
2515
|
+
/* @__PURE__ */ jsxs24("div", { children: [
|
|
2516
|
+
/* @__PURE__ */ jsx26("strong", { children: data.tiers.length }),
|
|
2517
|
+
/* @__PURE__ */ jsx26("span", { children: "Declared tiers" })
|
|
2158
2518
|
] })
|
|
2159
2519
|
] })
|
|
2160
2520
|
] }),
|
|
2161
|
-
/* @__PURE__ */
|
|
2162
|
-
/* @__PURE__ */
|
|
2163
|
-
/* @__PURE__ */
|
|
2521
|
+
/* @__PURE__ */ jsxs24(Panel, { children: [
|
|
2522
|
+
/* @__PURE__ */ jsx26("div", { className: "card-label", children: "Inherited \xB7 read only" }),
|
|
2523
|
+
/* @__PURE__ */ jsxs24("h2", { children: [
|
|
2164
2524
|
"Parent pages ",
|
|
2165
2525
|
data.parent ? `\xB7 revision ${data.parent.revision}` : ""
|
|
2166
2526
|
] }),
|
|
2167
|
-
inherited.length === 0 ? /* @__PURE__ */
|
|
2168
|
-
/* @__PURE__ */
|
|
2169
|
-
/* @__PURE__ */
|
|
2527
|
+
inherited.length === 0 ? /* @__PURE__ */ jsx26(AdminNote, { children: "No Parent pages have been delivered." }) : /* @__PURE__ */ jsx26("div", { className: "data-list", "data-member-content-inherited": true, children: inherited.map((page) => /* @__PURE__ */ jsxs24("article", { className: "card", children: [
|
|
2528
|
+
/* @__PURE__ */ jsx26("strong", { children: page.title }),
|
|
2529
|
+
/* @__PURE__ */ jsxs24("div", { className: "meeting-note", children: [
|
|
2170
2530
|
"/",
|
|
2171
2531
|
page.slug,
|
|
2172
2532
|
" \xB7 ",
|
|
@@ -2174,22 +2534,22 @@ function MemberContentAdmin({
|
|
|
2174
2534
|
" \xB7 ",
|
|
2175
2535
|
accessLabel(page)
|
|
2176
2536
|
] }),
|
|
2177
|
-
/* @__PURE__ */
|
|
2537
|
+
/* @__PURE__ */ jsxs24("div", { className: "meeting-note", children: [
|
|
2178
2538
|
"Upsell: ",
|
|
2179
2539
|
page.upsell ? `${page.upsell.owner} controlled` : "none"
|
|
2180
2540
|
] })
|
|
2181
2541
|
] }, page.id)) })
|
|
2182
2542
|
] }),
|
|
2183
|
-
/* @__PURE__ */
|
|
2184
|
-
/* @__PURE__ */
|
|
2185
|
-
/* @__PURE__ */
|
|
2543
|
+
/* @__PURE__ */ jsxs24(Panel, { children: [
|
|
2544
|
+
/* @__PURE__ */ jsx26("div", { className: "card-label", children: "Chapter \xB7 editable" }),
|
|
2545
|
+
/* @__PURE__ */ jsxs24("h2", { children: [
|
|
2186
2546
|
"Local document \xB7 revision ",
|
|
2187
2547
|
data.chapter?.revision ?? 0
|
|
2188
2548
|
] }),
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
/* @__PURE__ */
|
|
2191
|
-
/* @__PURE__ */
|
|
2192
|
-
/* @__PURE__ */
|
|
2549
|
+
/* @__PURE__ */ jsx26("p", { className: "meeting-note", children: "Use the published MemberContentDocument fields. The server supplies schemaVersion, revision, timestamp, and chapter source; unknown fields and undeclared tiers are rejected." }),
|
|
2550
|
+
/* @__PURE__ */ jsxs24("label", { className: "field", children: [
|
|
2551
|
+
/* @__PURE__ */ jsx26("span", { children: "Pages and delegated upsells (JSON)" }),
|
|
2552
|
+
/* @__PURE__ */ jsx26(
|
|
2193
2553
|
"textarea",
|
|
2194
2554
|
{
|
|
2195
2555
|
rows: 24,
|
|
@@ -2200,12 +2560,12 @@ function MemberContentAdmin({
|
|
|
2200
2560
|
}
|
|
2201
2561
|
)
|
|
2202
2562
|
] }),
|
|
2203
|
-
saveError ? /* @__PURE__ */
|
|
2204
|
-
/* @__PURE__ */
|
|
2205
|
-
/* @__PURE__ */
|
|
2563
|
+
saveError ? /* @__PURE__ */ jsxs24("div", { className: "notice danger", role: "alert", children: [
|
|
2564
|
+
/* @__PURE__ */ jsx26("strong", { children: "Fix these items before saving:" }),
|
|
2565
|
+
/* @__PURE__ */ jsx26("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx26("li", { children: error }, error)) })
|
|
2206
2566
|
] }) : null,
|
|
2207
|
-
/* @__PURE__ */
|
|
2208
|
-
!data.localAuthoring ? /* @__PURE__ */
|
|
2567
|
+
/* @__PURE__ */ jsx26("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save chapter content" }),
|
|
2568
|
+
!data.localAuthoring ? /* @__PURE__ */ jsx26(AdminNote, { children: "Local authoring is disabled in the checked-in Chapter config." }) : null
|
|
2209
2569
|
] })
|
|
2210
2570
|
] });
|
|
2211
2571
|
}
|
|
@@ -2213,12 +2573,12 @@ function memberContentSection() {
|
|
|
2213
2573
|
return {
|
|
2214
2574
|
id: "member-content",
|
|
2215
2575
|
label: "Member content",
|
|
2216
|
-
render: (ctx) => /* @__PURE__ */
|
|
2576
|
+
render: (ctx) => /* @__PURE__ */ jsx26(MemberContentAdmin, { ctx })
|
|
2217
2577
|
};
|
|
2218
2578
|
}
|
|
2219
2579
|
|
|
2220
2580
|
// src/ui/admin-settings-workspace.tsx
|
|
2221
|
-
import { jsx as
|
|
2581
|
+
import { jsx as jsx27 } from "preact/jsx-runtime";
|
|
2222
2582
|
var viewContext = (ctx, viewId, active) => {
|
|
2223
2583
|
const route = { workspaceId: ctx.route.workspaceId, viewId, ...active && ctx.route.recordId ? { recordId: ctx.route.recordId } : {}, ...active && ctx.route.detailTab ? { detailTab: ctx.route.detailTab } : {} };
|
|
2224
2584
|
const targetFromView = (target) => {
|
|
@@ -2236,7 +2596,7 @@ function SettingsWorkspace({ chapter, ctx }) {
|
|
|
2236
2596
|
const email = emailSection({ label: copy.workspaces.email });
|
|
2237
2597
|
const content = memberContentSection();
|
|
2238
2598
|
const library = builderLibrarySection();
|
|
2239
|
-
return /* @__PURE__ */
|
|
2599
|
+
return /* @__PURE__ */ jsx27(Tabs2, { ariaLabel: copy.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
|
|
2240
2600
|
{ value: "calendar", label: copy.workspaces.calendar, href: ctx.href({ viewId: "calendar", recordId: null, detailTab: null }), panel: calendar.render(viewContext(ctx, "calendar", active === "calendar")) },
|
|
2241
2601
|
{ value: "email", label: copy.workspaces.email, href: ctx.href({ viewId: "email", recordId: null, detailTab: null }), panel: email.render(viewContext(ctx, "email", active === "email")) },
|
|
2242
2602
|
...chapter.memberContent ? [{ value: "member-content", label: "Member content", href: ctx.href({ viewId: "member-content", recordId: null, detailTab: null }), panel: content.render(viewContext(ctx, "member-content", active === "member-content")) }] : [],
|
|
@@ -2247,14 +2607,14 @@ function LeaderSettingsWorkspace({ chapter, ctx }) {
|
|
|
2247
2607
|
const email = emailSection({ label: chapter.copy.admin.workspaces.email });
|
|
2248
2608
|
const library = builderLibrarySection();
|
|
2249
2609
|
const active = ctx.route.viewId === "builder-library" && chapter.builderLibrary ? "builder-library" : "email";
|
|
2250
|
-
return /* @__PURE__ */
|
|
2610
|
+
return /* @__PURE__ */ jsx27(Tabs2, { ariaLabel: chapter.copy.admin.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
|
|
2251
2611
|
{ value: "email", label: chapter.copy.admin.workspaces.email, href: ctx.href({ viewId: "email", recordId: null, detailTab: null }), panel: email.render(viewContext(ctx, "email", active === "email")) },
|
|
2252
2612
|
...chapter.builderLibrary ? [{ value: "builder-library", label: "Builder Library", href: ctx.href({ viewId: "builder-library", recordId: null, detailTab: null }), panel: library.render(viewContext(ctx, "builder-library", active === "builder-library")) }] : []
|
|
2253
2613
|
] });
|
|
2254
2614
|
}
|
|
2255
2615
|
|
|
2256
2616
|
// src/ui/admin-workspaces.tsx
|
|
2257
|
-
import { jsx as
|
|
2617
|
+
import { jsx as jsx28 } from "preact/jsx-runtime";
|
|
2258
2618
|
var adminViewContext = (ctx, viewId, active) => {
|
|
2259
2619
|
const route = {
|
|
2260
2620
|
workspaceId: ctx.route.workspaceId,
|
|
@@ -2280,7 +2640,7 @@ function DashboardWorkspace({ chapter, ctx }) {
|
|
|
2280
2640
|
const active = ctx.route.viewId === "billing" ? "billing" : "overview";
|
|
2281
2641
|
const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
|
|
2282
2642
|
const billing = billingSection({ label: copy.workspaces.billing });
|
|
2283
|
-
return /* @__PURE__ */
|
|
2643
|
+
return /* @__PURE__ */ jsx28(
|
|
2284
2644
|
Tabs3,
|
|
2285
2645
|
{
|
|
2286
2646
|
ariaLabel: copy.workspaces.dashboardViewsLabel,
|
|
@@ -2308,7 +2668,7 @@ function PeopleWorkspace({ chapter, ctx }) {
|
|
|
2308
2668
|
const fallback = types[0]?.[0] ?? "person";
|
|
2309
2669
|
const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
|
|
2310
2670
|
const networkSharing = chapter.network.targets.length > 0;
|
|
2311
|
-
return /* @__PURE__ */
|
|
2671
|
+
return /* @__PURE__ */ jsx28(
|
|
2312
2672
|
Tabs3,
|
|
2313
2673
|
{
|
|
2314
2674
|
ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
|
|
@@ -2338,8 +2698,8 @@ function PortfolioWorkspace({ chapter, ctx }) {
|
|
|
2338
2698
|
const types = Object.entries(chapter.crm.config.types).filter(([, def]) => def.workspace === "portfolio");
|
|
2339
2699
|
const fallback = types[0]?.[0] ?? "";
|
|
2340
2700
|
const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
|
|
2341
|
-
if (!active) return /* @__PURE__ */
|
|
2342
|
-
return /* @__PURE__ */
|
|
2701
|
+
if (!active) return /* @__PURE__ */ jsx28(LeaderNetworkOverview, { ctx });
|
|
2702
|
+
return /* @__PURE__ */ jsx28(
|
|
2343
2703
|
Tabs3,
|
|
2344
2704
|
{
|
|
2345
2705
|
ariaLabel: copy.workspaces.portfolioViewsLabel,
|
|
@@ -2363,7 +2723,7 @@ function PortfolioWorkspace({ chapter, ctx }) {
|
|
|
2363
2723
|
);
|
|
2364
2724
|
}
|
|
2365
2725
|
function LeaderDashboardWorkspace({ chapter, ctx }) {
|
|
2366
|
-
return /* @__PURE__ */
|
|
2726
|
+
return /* @__PURE__ */ jsx28(
|
|
2367
2727
|
Tabs3,
|
|
2368
2728
|
{
|
|
2369
2729
|
ariaLabel: chapter.copy.admin.workspaces.dashboardViewsLabel,
|
|
@@ -2373,7 +2733,7 @@ function LeaderDashboardWorkspace({ chapter, ctx }) {
|
|
|
2373
2733
|
value: "overview",
|
|
2374
2734
|
label: chapter.copy.admin.workspaces.overview,
|
|
2375
2735
|
href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
|
|
2376
|
-
panel: /* @__PURE__ */
|
|
2736
|
+
panel: /* @__PURE__ */ jsx28(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
|
|
2377
2737
|
}]
|
|
2378
2738
|
}
|
|
2379
2739
|
);
|
|
@@ -2384,7 +2744,7 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
2384
2744
|
id: "people",
|
|
2385
2745
|
label: copy.workspaces.people,
|
|
2386
2746
|
defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
|
|
2387
|
-
render: (ctx) => /* @__PURE__ */
|
|
2747
|
+
render: (ctx) => /* @__PURE__ */ jsx28(PeopleWorkspace, { chapter, ctx })
|
|
2388
2748
|
};
|
|
2389
2749
|
if (chapter.mode === "hub" && chapter.network.targets.length === 0) return [people];
|
|
2390
2750
|
if (chapter.mode === "hub") {
|
|
@@ -2394,7 +2754,7 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
2394
2754
|
id: "dashboard",
|
|
2395
2755
|
label: copy.workspaces.dashboard,
|
|
2396
2756
|
defaultViewId: "overview",
|
|
2397
|
-
render: (ctx) => /* @__PURE__ */
|
|
2757
|
+
render: (ctx) => /* @__PURE__ */ jsx28(LeaderDashboardWorkspace, { chapter, ctx })
|
|
2398
2758
|
},
|
|
2399
2759
|
networkRecordsWorkspace(chapter),
|
|
2400
2760
|
people,
|
|
@@ -2402,13 +2762,13 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
2402
2762
|
id: "portfolio",
|
|
2403
2763
|
label: copy.workspaces.portfolio,
|
|
2404
2764
|
defaultViewId: portfolioTypes[0]?.[0],
|
|
2405
|
-
render: (ctx) => /* @__PURE__ */
|
|
2765
|
+
render: (ctx) => /* @__PURE__ */ jsx28(PortfolioWorkspace, { chapter, ctx })
|
|
2406
2766
|
},
|
|
2407
2767
|
{
|
|
2408
2768
|
id: "settings",
|
|
2409
2769
|
label: copy.workspaces.settings,
|
|
2410
2770
|
defaultViewId: "email",
|
|
2411
|
-
render: (ctx) => /* @__PURE__ */
|
|
2771
|
+
render: (ctx) => /* @__PURE__ */ jsx28(LeaderSettingsWorkspace, { chapter, ctx })
|
|
2412
2772
|
}
|
|
2413
2773
|
];
|
|
2414
2774
|
}
|
|
@@ -2417,7 +2777,7 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
2417
2777
|
id: "dashboard",
|
|
2418
2778
|
label: copy.workspaces.dashboard,
|
|
2419
2779
|
defaultViewId: "overview",
|
|
2420
|
-
render: (ctx) => /* @__PURE__ */
|
|
2780
|
+
render: (ctx) => /* @__PURE__ */ jsx28(DashboardWorkspace, { chapter, ctx })
|
|
2421
2781
|
},
|
|
2422
2782
|
people,
|
|
2423
2783
|
admissionGrantsWorkspace(copy.workspaces.admission),
|
|
@@ -2425,17 +2785,17 @@ function defaultAdminWorkspaces(chapter) {
|
|
|
2425
2785
|
id: "settings",
|
|
2426
2786
|
label: copy.workspaces.settings,
|
|
2427
2787
|
defaultViewId: "calendar",
|
|
2428
|
-
render: (ctx) => /* @__PURE__ */
|
|
2788
|
+
render: (ctx) => /* @__PURE__ */ jsx28(SettingsWorkspace, { chapter, ctx })
|
|
2429
2789
|
}
|
|
2430
2790
|
];
|
|
2431
2791
|
}
|
|
2432
2792
|
|
|
2433
2793
|
// src/ui/admin-frame.tsx
|
|
2434
2794
|
import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
|
|
2435
|
-
import { jsx as
|
|
2795
|
+
import { jsx as jsx29, jsxs as jsxs25 } from "preact/jsx-runtime";
|
|
2436
2796
|
function AdminTheme(props) {
|
|
2437
2797
|
const brand = props.chapter?.brand;
|
|
2438
|
-
return /* @__PURE__ */
|
|
2798
|
+
return /* @__PURE__ */ jsx29(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs25(
|
|
2439
2799
|
ThemeScope2,
|
|
2440
2800
|
{
|
|
2441
2801
|
ref: props.scopeRef,
|
|
@@ -2444,7 +2804,7 @@ function AdminTheme(props) {
|
|
|
2444
2804
|
accent: brand?.accent,
|
|
2445
2805
|
colorScheme: brand?.colorScheme ?? "light",
|
|
2446
2806
|
children: [
|
|
2447
|
-
/* @__PURE__ */
|
|
2807
|
+
/* @__PURE__ */ jsx29(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
|
|
2448
2808
|
props.children
|
|
2449
2809
|
]
|
|
2450
2810
|
}
|
|
@@ -2464,11 +2824,11 @@ function signInRedirect(basePath, workspaces) {
|
|
|
2464
2824
|
}
|
|
2465
2825
|
|
|
2466
2826
|
// src/ui/admin-appearance.ts
|
|
2467
|
-
import { useEffect as
|
|
2827
|
+
import { useEffect as useEffect11, useState as useState21 } from "preact/hooks";
|
|
2468
2828
|
import { clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
|
|
2469
2829
|
function useScopedClerkAppearance(scope) {
|
|
2470
|
-
const [appearance, setAppearance] =
|
|
2471
|
-
|
|
2830
|
+
const [appearance, setAppearance] = useState21();
|
|
2831
|
+
useEffect11(() => {
|
|
2472
2832
|
setAppearance(clerkAppearanceFromTokens(scope.current ?? void 0));
|
|
2473
2833
|
}, [scope]);
|
|
2474
2834
|
return appearance;
|
|
@@ -2499,7 +2859,7 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
|
|
|
2499
2859
|
}
|
|
2500
2860
|
|
|
2501
2861
|
// src/ui/admin.tsx
|
|
2502
|
-
import { jsx as
|
|
2862
|
+
import { jsx as jsx30, jsxs as jsxs26 } from "preact/jsx-runtime";
|
|
2503
2863
|
function Authed(props) {
|
|
2504
2864
|
const copy = useChapterCopy().admin;
|
|
2505
2865
|
const {
|
|
@@ -2513,7 +2873,8 @@ function Authed(props) {
|
|
|
2513
2873
|
auth,
|
|
2514
2874
|
renderHeader,
|
|
2515
2875
|
renderAccountMenu,
|
|
2516
|
-
renderWorkspaceNav
|
|
2876
|
+
renderWorkspaceNav,
|
|
2877
|
+
renderWorkspaceFrame
|
|
2517
2878
|
} = props;
|
|
2518
2879
|
const { getToken, signOut } = useClerkAuth();
|
|
2519
2880
|
const client = useMemo3(
|
|
@@ -2526,8 +2887,8 @@ function Authed(props) {
|
|
|
2526
2887
|
}),
|
|
2527
2888
|
[getToken, crmBasePath]
|
|
2528
2889
|
);
|
|
2529
|
-
const [state, setState] =
|
|
2530
|
-
|
|
2890
|
+
const [state, setState] = useState22({ status: "checking" });
|
|
2891
|
+
useEffect12(() => {
|
|
2531
2892
|
let live = true;
|
|
2532
2893
|
void (async () => {
|
|
2533
2894
|
try {
|
|
@@ -2546,17 +2907,17 @@ function Authed(props) {
|
|
|
2546
2907
|
live = false;
|
|
2547
2908
|
};
|
|
2548
2909
|
}, [getToken, apiBase, auth]);
|
|
2549
|
-
if (state.status === "checking") return /* @__PURE__ */
|
|
2910
|
+
if (state.status === "checking") return /* @__PURE__ */ jsx30(Gate, { brand, children: /* @__PURE__ */ jsx30("p", { style: S.muted, children: copy.auth.checking }) });
|
|
2550
2911
|
if (state.status === "denied") {
|
|
2551
|
-
return /* @__PURE__ */
|
|
2552
|
-
/* @__PURE__ */
|
|
2553
|
-
/* @__PURE__ */
|
|
2912
|
+
return /* @__PURE__ */ jsx30(Gate, { brand, children: /* @__PURE__ */ jsxs26("div", { className: "card", style: S.card, children: [
|
|
2913
|
+
/* @__PURE__ */ jsx30("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
|
|
2914
|
+
/* @__PURE__ */ jsx30("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
|
|
2554
2915
|
account: state.email ?? copy.auth.thisAccount
|
|
2555
2916
|
}) }),
|
|
2556
|
-
/* @__PURE__ */
|
|
2917
|
+
/* @__PURE__ */ jsx30("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
|
|
2557
2918
|
] }) });
|
|
2558
2919
|
}
|
|
2559
|
-
return /* @__PURE__ */
|
|
2920
|
+
return /* @__PURE__ */ jsx30(
|
|
2560
2921
|
AdminShell,
|
|
2561
2922
|
{
|
|
2562
2923
|
workspaces,
|
|
@@ -2571,7 +2932,8 @@ function Authed(props) {
|
|
|
2571
2932
|
chrome,
|
|
2572
2933
|
renderHeader,
|
|
2573
2934
|
renderAccountMenu,
|
|
2574
|
-
renderWorkspaceNav
|
|
2935
|
+
renderWorkspaceNav,
|
|
2936
|
+
renderWorkspaceFrame
|
|
2575
2937
|
}
|
|
2576
2938
|
);
|
|
2577
2939
|
}
|
|
@@ -2597,10 +2959,10 @@ function ChapterAdmin(props) {
|
|
|
2597
2959
|
const chrome = props.chrome ?? "embedded";
|
|
2598
2960
|
const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
|
|
2599
2961
|
const redirect = signInRedirect(basePath, workspaces);
|
|
2600
|
-
const [publishableKey, setPublishableKey] =
|
|
2601
|
-
const scopeRef =
|
|
2962
|
+
const [publishableKey, setPublishableKey] = useState22(void 0);
|
|
2963
|
+
const scopeRef = useRef3(null);
|
|
2602
2964
|
const appearance = useScopedClerkAppearance(scopeRef);
|
|
2603
|
-
|
|
2965
|
+
useEffect12(() => {
|
|
2604
2966
|
let live = true;
|
|
2605
2967
|
void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
|
|
2606
2968
|
return () => {
|
|
@@ -2609,23 +2971,23 @@ function ChapterAdmin(props) {
|
|
|
2609
2971
|
}, [apiBase, auth]);
|
|
2610
2972
|
let content;
|
|
2611
2973
|
if (publishableKey === void 0 || publishableKey && !appearance) {
|
|
2612
|
-
content = /* @__PURE__ */
|
|
2974
|
+
content = /* @__PURE__ */ jsx30(Gate, { brand, children: /* @__PURE__ */ jsx30("p", { style: S.muted, children: copy.admin.auth.loading }) });
|
|
2613
2975
|
} else if (!publishableKey) {
|
|
2614
|
-
content = /* @__PURE__ */
|
|
2615
|
-
/* @__PURE__ */
|
|
2616
|
-
/* @__PURE__ */
|
|
2976
|
+
content = /* @__PURE__ */ jsx30(Gate, { brand, children: /* @__PURE__ */ jsxs26("div", { className: "card", style: S.card, children: [
|
|
2977
|
+
/* @__PURE__ */ jsx30("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
|
|
2978
|
+
/* @__PURE__ */ jsx30("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
|
|
2617
2979
|
] }) });
|
|
2618
2980
|
} else if (workspaces.length === 0) {
|
|
2619
|
-
content = /* @__PURE__ */
|
|
2981
|
+
content = /* @__PURE__ */ jsx30(Gate, { brand, children: /* @__PURE__ */ jsx30("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
|
|
2620
2982
|
} else {
|
|
2621
|
-
content = /* @__PURE__ */
|
|
2983
|
+
content = /* @__PURE__ */ jsxs26(
|
|
2622
2984
|
ClerkGate,
|
|
2623
2985
|
{
|
|
2624
2986
|
publishableKey,
|
|
2625
2987
|
appearance,
|
|
2626
2988
|
afterSignOutUrl: "/",
|
|
2627
2989
|
children: [
|
|
2628
|
-
/* @__PURE__ */
|
|
2990
|
+
/* @__PURE__ */ jsx30(SignedOut, { children: /* @__PURE__ */ jsx30(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx30(
|
|
2629
2991
|
SignIn,
|
|
2630
2992
|
{
|
|
2631
2993
|
routing: "hash",
|
|
@@ -2633,7 +2995,7 @@ function ChapterAdmin(props) {
|
|
|
2633
2995
|
signUpForceRedirectUrl: redirect
|
|
2634
2996
|
}
|
|
2635
2997
|
) }) }),
|
|
2636
|
-
/* @__PURE__ */
|
|
2998
|
+
/* @__PURE__ */ jsx30(SignedIn, { children: /* @__PURE__ */ jsx30(
|
|
2637
2999
|
Authed,
|
|
2638
3000
|
{
|
|
2639
3001
|
workspaces,
|
|
@@ -2646,14 +3008,15 @@ function ChapterAdmin(props) {
|
|
|
2646
3008
|
auth,
|
|
2647
3009
|
renderHeader: props.renderHeader,
|
|
2648
3010
|
renderAccountMenu: props.renderAccountMenu,
|
|
2649
|
-
renderWorkspaceNav: props.renderWorkspaceNav
|
|
3011
|
+
renderWorkspaceNav: props.renderWorkspaceNav,
|
|
3012
|
+
renderWorkspaceFrame: props.renderWorkspaceFrame
|
|
2650
3013
|
}
|
|
2651
3014
|
) })
|
|
2652
3015
|
]
|
|
2653
3016
|
}
|
|
2654
3017
|
);
|
|
2655
3018
|
}
|
|
2656
|
-
return /* @__PURE__ */
|
|
3019
|
+
return /* @__PURE__ */ jsx30(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
|
|
2657
3020
|
}
|
|
2658
3021
|
|
|
2659
3022
|
// src/ui/admin-defaults.ts
|
|
@@ -2662,17 +3025,17 @@ function defaultAdminSections(chapter) {
|
|
|
2662
3025
|
}
|
|
2663
3026
|
|
|
2664
3027
|
// src/ui/admin-meetings.tsx
|
|
2665
|
-
import { useState as
|
|
2666
|
-
import { Button as
|
|
2667
|
-
import { Fragment as
|
|
3028
|
+
import { useState as useState23 } from "preact/hooks";
|
|
3029
|
+
import { Button as Button12 } from "@odla-ai/ui/components";
|
|
3030
|
+
import { Fragment as Fragment8, jsx as jsx31, jsxs as jsxs27 } from "preact/jsx-runtime";
|
|
2668
3031
|
var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
|
|
2669
3032
|
function MeetingsBody({ ctx }) {
|
|
2670
3033
|
const copy = useChapterCopy().admin.meetings;
|
|
2671
3034
|
const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
|
|
2672
|
-
const [busy, setBusy] =
|
|
2673
|
-
const [note, setNote] =
|
|
2674
|
-
if (loading) return /* @__PURE__ */
|
|
2675
|
-
if (error || !data) return /* @__PURE__ */
|
|
3035
|
+
const [busy, setBusy] = useState23(null);
|
|
3036
|
+
const [note, setNote] = useState23(null);
|
|
3037
|
+
if (loading) return /* @__PURE__ */ jsx31(AdminNote, { children: copy.loading });
|
|
3038
|
+
if (error || !data) return /* @__PURE__ */ jsxs27(AdminNote, { children: [
|
|
2676
3039
|
copy.loadFailed,
|
|
2677
3040
|
error ? `: ${error}` : "",
|
|
2678
3041
|
"."
|
|
@@ -2703,32 +3066,121 @@ function MeetingsBody({ ctx }) {
|
|
|
2703
3066
|
}
|
|
2704
3067
|
void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
|
|
2705
3068
|
};
|
|
2706
|
-
return /* @__PURE__ */
|
|
3069
|
+
return /* @__PURE__ */ jsx31(AdminPage, { children: /* @__PURE__ */ jsx31(Panel, { title: /* @__PURE__ */ jsxs27(Fragment8, { children: [
|
|
2707
3070
|
copy.agenda,
|
|
2708
3071
|
" ",
|
|
2709
|
-
/* @__PURE__ */
|
|
3072
|
+
/* @__PURE__ */ jsxs27("span", { className: "muted", style: { fontWeight: 400 }, children: [
|
|
2710
3073
|
"\xB7 ",
|
|
2711
3074
|
data.timezone
|
|
2712
3075
|
] })
|
|
2713
|
-
] }), actions: note ? /* @__PURE__ */
|
|
2714
|
-
/* @__PURE__ */
|
|
2715
|
-
/* @__PURE__ */
|
|
2716
|
-
/* @__PURE__ */
|
|
2717
|
-
m.drift && m.drift !== "none" ? /* @__PURE__ */
|
|
3076
|
+
] }), actions: note ? /* @__PURE__ */ jsx31("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx31("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx31("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs27("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
|
|
3077
|
+
/* @__PURE__ */ jsx31("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
|
|
3078
|
+
/* @__PURE__ */ jsx31("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
|
|
3079
|
+
/* @__PURE__ */ jsx31("span", { className: "badge", children: m.status }),
|
|
3080
|
+
m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs27("span", { className: "badge", children: [
|
|
2718
3081
|
copy.drift,
|
|
2719
3082
|
": ",
|
|
2720
3083
|
m.drift
|
|
2721
3084
|
] }) : null,
|
|
2722
|
-
m.meetUrl ? /* @__PURE__ */
|
|
2723
|
-
m.status === "scheduled" ? /* @__PURE__ */
|
|
2724
|
-
/* @__PURE__ */
|
|
2725
|
-
/* @__PURE__ */
|
|
3085
|
+
m.meetUrl ? /* @__PURE__ */ jsx31("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
|
|
3086
|
+
m.status === "scheduled" ? /* @__PURE__ */ jsxs27(Fragment8, { children: [
|
|
3087
|
+
/* @__PURE__ */ jsx31(Button12, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
|
|
3088
|
+
/* @__PURE__ */ jsx31(Button12, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
|
|
2726
3089
|
] }) : null
|
|
2727
3090
|
] }, m.id)) }) }) });
|
|
2728
3091
|
}
|
|
2729
3092
|
function meetingsSection(options = {}) {
|
|
2730
3093
|
const { id = "meetings", label = "Meetings" } = options;
|
|
2731
|
-
return { id, label, render: (ctx) => /* @__PURE__ */
|
|
3094
|
+
return { id, label, render: (ctx) => /* @__PURE__ */ jsx31(MeetingsBody, { ctx }) };
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// src/ui/admin-network-record-editor.tsx
|
|
3098
|
+
import { Button as Button13, Field as Field10, Select as Select6 } from "@odla-ai/ui/components";
|
|
3099
|
+
import { useRef as useRef4, useState as useState24 } from "preact/hooks";
|
|
3100
|
+
import { Fragment as Fragment9, jsx as jsx32, jsxs as jsxs28 } from "preact/jsx-runtime";
|
|
3101
|
+
function NetworkRecordEditor(props) {
|
|
3102
|
+
const { ctx, record } = props;
|
|
3103
|
+
const [stage, setStage] = useState24("");
|
|
3104
|
+
const [busy, setBusy] = useState24(false);
|
|
3105
|
+
const [error, setError] = useState24(null);
|
|
3106
|
+
const attempt = useRef4(null);
|
|
3107
|
+
const pending = record.edit?.pendingAction;
|
|
3108
|
+
const superAdmin = ctx.currentUser?.superAdmin === true;
|
|
3109
|
+
const ownPending = !pending || pending.actorId === ctx.currentUser?.userId || pending.actorId === ctx.currentUser?.id || Boolean(pending.actorEmail && pending.actorEmail === ctx.currentUser?.email);
|
|
3110
|
+
const mutate = async (change, mutationId, retry = false) => {
|
|
3111
|
+
await adminFetch(ctx.getToken, "/api/admin/network/records", { method: "PATCH", body: JSON.stringify({
|
|
3112
|
+
targetId: props.targetId,
|
|
3113
|
+
type: record.type,
|
|
3114
|
+
recordId: record.id,
|
|
3115
|
+
expectedRevision: retry && pending ? pending.expectedRevision : record.revision,
|
|
3116
|
+
expectedStage: retry && pending ? pending.expectedStage : record.stage ?? null,
|
|
3117
|
+
mutationId,
|
|
3118
|
+
...change
|
|
3119
|
+
}) });
|
|
3120
|
+
props.onChanged();
|
|
3121
|
+
};
|
|
3122
|
+
const move = async () => {
|
|
3123
|
+
setBusy(true);
|
|
3124
|
+
setError(null);
|
|
3125
|
+
try {
|
|
3126
|
+
const destination = pending?.stage ?? stage;
|
|
3127
|
+
if (attempt.current?.stage !== destination) attempt.current = { stage: destination, id: crypto.randomUUID() };
|
|
3128
|
+
await mutate({ stage: destination }, pending?.mutationId ?? attempt.current.id, Boolean(pending));
|
|
3129
|
+
attempt.current = null;
|
|
3130
|
+
setStage("");
|
|
3131
|
+
} catch (cause) {
|
|
3132
|
+
setError(cause instanceof Error ? cause.message : "Action could not be completed.");
|
|
3133
|
+
props.onChanged();
|
|
3134
|
+
} finally {
|
|
3135
|
+
setBusy(false);
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
return /* @__PURE__ */ jsxs28("div", { style: { display: "grid", gap: 24 }, children: [
|
|
3139
|
+
superAdmin && record.edit ? /* @__PURE__ */ jsxs28(Fragment9, { children: [
|
|
3140
|
+
/* @__PURE__ */ jsxs28("section", { children: [
|
|
3141
|
+
/* @__PURE__ */ jsx32("h3", { children: "Chapter profile" }),
|
|
3142
|
+
/* @__PURE__ */ jsx32(
|
|
3143
|
+
OperationalProfileForm,
|
|
3144
|
+
{
|
|
3145
|
+
fields: record.edit.fields,
|
|
3146
|
+
values: record.fields,
|
|
3147
|
+
onSave: (fields, id) => mutate({ fields }, id)
|
|
3148
|
+
},
|
|
3149
|
+
`${record.id}:${record.revision}`
|
|
3150
|
+
)
|
|
3151
|
+
] }),
|
|
3152
|
+
/* @__PURE__ */ jsxs28("section", { style: { display: "grid", gap: 12 }, children: [
|
|
3153
|
+
/* @__PURE__ */ jsx32("h3", { children: "Membership actions" }),
|
|
3154
|
+
pending ? /* @__PURE__ */ jsxs28("p", { children: [
|
|
3155
|
+
"An application action is pending: ",
|
|
3156
|
+
pending.stage.replaceAll("_", " "),
|
|
3157
|
+
". ",
|
|
3158
|
+
ownPending ? "Retry reconciles the same operation." : "The original operator must reconcile it."
|
|
3159
|
+
] }) : record.edit.allowedStages.length ? /* @__PURE__ */ jsx32(Field10, { label: "Action", children: /* @__PURE__ */ jsxs28(Select6, { "aria-label": "Action", value: stage, disabled: busy, onChange: (event) => setStage(event.currentTarget.value), children: [
|
|
3160
|
+
/* @__PURE__ */ jsx32("option", { value: "", children: "Choose an action" }),
|
|
3161
|
+
record.edit.allowedStages.map((action) => /* @__PURE__ */ jsx32("option", { value: action.id, children: action.label }, action.id))
|
|
3162
|
+
] }) }) : /* @__PURE__ */ jsx32("p", { children: "No membership action is currently allowed." }),
|
|
3163
|
+
pending || record.edit.allowedStages.length ? /* @__PURE__ */ jsx32("div", { children: /* @__PURE__ */ jsx32(Button13, { disabled: busy || !ownPending || !pending && !stage, onClick: () => void move(), children: busy ? "Applying\u2026" : pending ? "Retry pending action" : "Apply action" }) }) : null,
|
|
3164
|
+
error ? /* @__PURE__ */ jsx32("p", { role: "alert", children: error }) : null
|
|
3165
|
+
] })
|
|
3166
|
+
] }) : null,
|
|
3167
|
+
superAdmin && props.admissionGrants ? /* @__PURE__ */ jsxs28("section", { children: [
|
|
3168
|
+
/* @__PURE__ */ jsx32("h3", { children: "Next invitation" }),
|
|
3169
|
+
/* @__PURE__ */ jsx32("p", { children: "This setting applies to the next eligible signup; it does not replace approval of an existing application." }),
|
|
3170
|
+
/* @__PURE__ */ jsx32(LeaderAdmissionAction, { getToken: ctx.getToken, targetId: props.targetId, sourceRecordId: record.id })
|
|
3171
|
+
] }) : null,
|
|
3172
|
+
/* @__PURE__ */ jsx32(
|
|
3173
|
+
NetworkRecordDetail,
|
|
3174
|
+
{
|
|
3175
|
+
ctx,
|
|
3176
|
+
targetId: props.targetId,
|
|
3177
|
+
targetName: props.targetName,
|
|
3178
|
+
leaderName: props.leaderName,
|
|
3179
|
+
record,
|
|
3180
|
+
sharedEnabled: superAdmin && props.sharedNotes === true
|
|
3181
|
+
}
|
|
3182
|
+
)
|
|
3183
|
+
] });
|
|
2732
3184
|
}
|
|
2733
3185
|
|
|
2734
3186
|
export {
|
|
@@ -2766,6 +3218,7 @@ export {
|
|
|
2766
3218
|
loadChapterAdminUser,
|
|
2767
3219
|
ChapterAdmin,
|
|
2768
3220
|
defaultAdminSections,
|
|
2769
|
-
meetingsSection
|
|
3221
|
+
meetingsSection,
|
|
3222
|
+
NetworkRecordEditor
|
|
2770
3223
|
};
|
|
2771
|
-
//# sourceMappingURL=chunk-
|
|
3224
|
+
//# sourceMappingURL=chunk-MQ4OPZCS.js.map
|