@odla-ai/chapter 0.61.0 → 0.62.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.
@@ -4,10 +4,10 @@ import {
4
4
  DEFAULT_CHAPTER_COPY,
5
5
  formatChapterCopy,
6
6
  useChapterCopy
7
- } from "./chunk-PKVHXKGJ.js";
7
+ } from "./chunk-7CVP6VSO.js";
8
8
 
9
9
  // src/ui/admin.tsx
10
- import { useEffect as useEffect12, useMemo as useMemo3, useRef as useRef3, useState as useState27 } from "preact/hooks";
10
+ import { useEffect as useEffect13, useMemo as useMemo3, useRef as useRef3, useState as useState29 } 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
 
@@ -710,6 +710,74 @@ function dashboardSection(options = {}) {
710
710
  // src/ui/admin-people-workspace.tsx
711
711
  import { BillingCard, CrmWorkspace } from "@odla-ai/crm/ui";
712
712
 
713
+ // src/ui/admin-member-tier.tsx
714
+ import { useEffect as useEffect4, useState as useState6 } from "preact/hooks";
715
+ import { Select } from "@odla-ai/ui/components";
716
+ import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
717
+ function MemberTierCard(props) {
718
+ const path = `/api/admin/applications/${encodeURIComponent(props.applicationId)}/entitlement-tier`;
719
+ const view = useAdminResource(props.getToken, path);
720
+ const [tierId, setTierId] = useState6("");
721
+ const [reason, setReason] = useState6("");
722
+ const [paymentReference, setPaymentReference] = useState6("");
723
+ const [status, setStatus] = useState6(null);
724
+ useEffect4(() => {
725
+ if (view.data && !tierId) setTierId(view.data.entitledTierId ?? view.data.tiers[0]?.id ?? "");
726
+ }, [view.data]);
727
+ if (!view.data) return null;
728
+ const send = async (method) => {
729
+ try {
730
+ const result = await adminFetch(props.getToken, path, { method, body: JSON.stringify({ tierId, reason, paymentReference }) });
731
+ setStatus(`Portal tier is now ${result.toTierId ?? "the purchased tier"}.`);
732
+ setReason("");
733
+ view.refresh();
734
+ } catch (error) {
735
+ setStatus(error instanceof Error ? error.message : String(error));
736
+ }
737
+ };
738
+ const changed = view.data.entitledTierId !== view.data.purchasedTierId;
739
+ return /* @__PURE__ */ jsxs7("div", { className: "card", "data-member-tier": true, children: [
740
+ /* @__PURE__ */ jsx7("div", { className: "card-label", children: "Portal tier" }),
741
+ /* @__PURE__ */ jsxs7("p", { className: "meeting-note", children: [
742
+ "Purchased: ",
743
+ view.data.purchasedTierId ?? "none",
744
+ " \xB7 Portal: ",
745
+ view.data.entitledTierId ?? "none",
746
+ view.data.entitlementTierEndsAt ? ` until ${new Date(view.data.entitlementTierEndsAt).toLocaleDateString("en-US")}` : "",
747
+ ". Use after a payment taken outside checkout; the membership purchase is unchanged."
748
+ ] }),
749
+ /* @__PURE__ */ jsxs7("label", { className: "field", children: [
750
+ /* @__PURE__ */ jsx7("span", { children: "Tier" }),
751
+ /* @__PURE__ */ jsx7(Select, { value: tierId, onChange: (event) => setTierId(event.currentTarget.value), children: view.data.tiers.map((tier) => /* @__PURE__ */ jsx7("option", { value: tier.id, children: tier.name }, tier.id)) })
752
+ ] }),
753
+ /* @__PURE__ */ jsxs7("label", { className: "field", children: [
754
+ /* @__PURE__ */ jsx7("span", { children: "Reason" }),
755
+ /* @__PURE__ */ jsx7("input", { required: true, value: reason, onInput: (event) => setReason(event.currentTarget.value) })
756
+ ] }),
757
+ /* @__PURE__ */ jsxs7("label", { className: "field", children: [
758
+ /* @__PURE__ */ jsx7("span", { children: "Payment reference (optional)" }),
759
+ /* @__PURE__ */ jsx7("input", { value: paymentReference, onInput: (event) => setPaymentReference(event.currentTarget.value) })
760
+ ] }),
761
+ /* @__PURE__ */ jsxs7("div", { className: "account-actions", children: [
762
+ /* @__PURE__ */ jsx7("button", { className: "btn primary mini", type: "button", disabled: !reason.trim(), onClick: () => void send("POST"), children: "Change portal tier" }),
763
+ changed ? /* @__PURE__ */ jsx7("button", { className: "btn secondary mini", type: "button", disabled: !reason.trim(), onClick: () => void send("DELETE"), children: "Revert to purchased tier" }) : null
764
+ ] }),
765
+ status ? /* @__PURE__ */ jsx7("p", { role: "status", children: status }) : null,
766
+ view.data.history.length ? /* @__PURE__ */ jsx7("ul", { className: "meeting-note", children: view.data.history.map((row) => /* @__PURE__ */ jsxs7("li", { children: [
767
+ new Date(Number(row.at)).toLocaleDateString("en-US"),
768
+ ": ",
769
+ String(row.fromTierId ?? "none"),
770
+ " \u2192 ",
771
+ String(row.toTierId ?? "purchased"),
772
+ " by ",
773
+ String(row.actor),
774
+ " (",
775
+ String(row.reason),
776
+ ")"
777
+ ] }, String(row.id))) }) : null
778
+ ] });
779
+ }
780
+
713
781
  // src/ui/admin-lifecycle-adapter.ts
714
782
  function applicationLifecycleAdapter(ctx, copy = DEFAULT_CHAPTER_COPY.admin.records) {
715
783
  return {
@@ -739,18 +807,18 @@ function applicationLifecycleAdapter(ctx, copy = DEFAULT_CHAPTER_COPY.admin.reco
739
807
  }
740
808
 
741
809
  // src/ui/admin-named-seat.tsx
742
- import { useState as useState6 } from "preact/hooks";
810
+ import { useState as useState7 } from "preact/hooks";
743
811
  import { Button as Button3 } from "@odla-ai/ui/components";
744
- import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
812
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
745
813
  function NamedSeatRecordPanel(props) {
746
- return props.superAdmin ? /* @__PURE__ */ jsx7(SeatPanel, { ...props }) : null;
814
+ return props.superAdmin ? /* @__PURE__ */ jsx8(SeatPanel, { ...props }) : null;
747
815
  }
748
816
  function SeatPanel(props) {
749
817
  const resource = useAdminResource(props.getToken, `/api/admin/applications/${encodeURIComponent(props.applicationId)}/named-seat`);
750
- const [busy, setBusy] = useState6(false);
751
- const [note, setNote] = useState6(null);
752
- const [renaming, setRenaming] = useState6(false), [name, setName] = useState6(""), [email, setEmail] = useState6("");
753
- if (resource.loading) return /* @__PURE__ */ jsx7("p", { role: "status", children: "Loading additional seat\u2026" });
818
+ const [busy, setBusy] = useState7(false);
819
+ const [note, setNote] = useState7(null);
820
+ const [renaming, setRenaming] = useState7(false), [name, setName] = useState7(""), [email, setEmail] = useState7("");
821
+ if (resource.loading) return /* @__PURE__ */ jsx8("p", { role: "status", children: "Loading additional seat\u2026" });
754
822
  if (resource.error || !resource.data) return null;
755
823
  const { data } = resource, seat = data.seat;
756
824
  const regift = async (event) => {
@@ -795,10 +863,10 @@ function SeatPanel(props) {
795
863
  setBusy(false);
796
864
  }
797
865
  };
798
- return /* @__PURE__ */ jsxs7("section", { className: "panel", "aria-label": "Additional seat", style: { display: "grid", gap: 8, marginTop: 16 }, children: [
799
- /* @__PURE__ */ jsx7("h3", { style: { margin: 0, fontSize: 16 }, children: "Additional seat" }),
800
- !seat ? /* @__PURE__ */ jsx7("p", { className: "muted", style: { margin: 0 }, children: "No additional seat has been purchased." }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
801
- /* @__PURE__ */ jsxs7("p", { style: { margin: 0 }, children: [
866
+ return /* @__PURE__ */ jsxs8("section", { className: "panel", "aria-label": "Additional seat", style: { display: "grid", gap: 8, marginTop: 16 }, children: [
867
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: 16 }, children: "Additional seat" }),
868
+ !seat ? /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: "No additional seat has been purchased." }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
869
+ /* @__PURE__ */ jsxs8("p", { style: { margin: 0 }, children: [
802
870
  seat.recipientName,
803
871
  " \xB7 ",
804
872
  seat.recipientEmail,
@@ -806,41 +874,41 @@ function SeatPanel(props) {
806
874
  (seat.recipientState ?? seat.status).replaceAll("_", " "),
807
875
  seat.invitationHeld ? " (invitation waits for the purchaser's approval)" : ""
808
876
  ] }),
809
- /* @__PURE__ */ jsxs7("p", { className: "muted", style: { margin: 0 }, children: [
877
+ /* @__PURE__ */ jsxs8("p", { className: "muted", style: { margin: 0 }, children: [
810
878
  data.amountCents === 0 ? "Included with the membership; nothing was charged." : `${money6} paid in full.`,
811
879
  " The seat waits for its recipient and is never refunded automatically."
812
880
  ] }),
813
- data.refundable || data.regiftable ? /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 8 }, children: [
814
- data.refundable ? /* @__PURE__ */ jsx7(Button3, { variant: "danger", mini: true, disabled: busy, onClick: () => void refund(), children: busy ? "Refunding\u2026" : "Refund seat" }) : null,
815
- data.regiftable && !renaming ? /* @__PURE__ */ jsx7(Button3, { variant: "secondary", mini: true, disabled: busy, onClick: () => {
881
+ data.refundable || data.regiftable ? /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8 }, children: [
882
+ data.refundable ? /* @__PURE__ */ jsx8(Button3, { variant: "danger", mini: true, disabled: busy, onClick: () => void refund(), children: busy ? "Refunding\u2026" : "Refund seat" }) : null,
883
+ data.regiftable && !renaming ? /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, disabled: busy, onClick: () => {
816
884
  setRenaming(true);
817
885
  setName("");
818
886
  setEmail("");
819
887
  }, children: "Name someone else" }) : null
820
888
  ] }) : null,
821
- renaming ? /* @__PURE__ */ jsxs7("form", { "aria-label": "Name a different recipient", onSubmit: (event) => void regift(event), style: { display: "grid", gap: 8 }, children: [
822
- /* @__PURE__ */ jsxs7("label", { children: [
889
+ renaming ? /* @__PURE__ */ jsxs8("form", { "aria-label": "Name a different recipient", onSubmit: (event) => void regift(event), style: { display: "grid", gap: 8 }, children: [
890
+ /* @__PURE__ */ jsxs8("label", { children: [
823
891
  "Recipient name",
824
- /* @__PURE__ */ jsx7("input", { required: true, maxLength: 160, value: name, onInput: (event) => setName(event.currentTarget.value) })
892
+ /* @__PURE__ */ jsx8("input", { required: true, maxLength: 160, value: name, onInput: (event) => setName(event.currentTarget.value) })
825
893
  ] }),
826
- /* @__PURE__ */ jsxs7("label", { children: [
894
+ /* @__PURE__ */ jsxs8("label", { children: [
827
895
  "Recipient email",
828
- /* @__PURE__ */ jsx7("input", { required: true, type: "email", maxLength: 254, value: email, onInput: (event) => setEmail(event.currentTarget.value) })
896
+ /* @__PURE__ */ jsx8("input", { required: true, type: "email", maxLength: 254, value: email, onInput: (event) => setEmail(event.currentTarget.value) })
829
897
  ] }),
830
- /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 8 }, children: [
831
- /* @__PURE__ */ jsx7(Button3, { mini: true, type: "submit", disabled: busy, children: busy ? "Changing\u2026" : "Give the seat to this person" }),
832
- /* @__PURE__ */ jsx7(Button3, { variant: "secondary", mini: true, type: "button", disabled: busy, onClick: () => setRenaming(false), children: "Cancel" })
898
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8 }, children: [
899
+ /* @__PURE__ */ jsx8(Button3, { mini: true, type: "submit", disabled: busy, children: busy ? "Changing\u2026" : "Give the seat to this person" }),
900
+ /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, type: "button", disabled: busy, onClick: () => setRenaming(false), children: "Cancel" })
833
901
  ] })
834
902
  ] }) : null
835
903
  ] }),
836
- note ? /* @__PURE__ */ jsx7("p", { className: "muted", role: "status", style: { margin: 0 }, children: note }) : null
904
+ note ? /* @__PURE__ */ jsx8("p", { className: "muted", role: "status", style: { margin: 0 }, children: note }) : null
837
905
  ] });
838
906
  }
839
907
 
840
908
  // src/ui/admin-candidate-stage.tsx
841
- import { useState as useState7 } from "preact/hooks";
842
- import { Button as Button4, Field, Select } from "@odla-ai/ui/components";
843
- import { Fragment as Fragment6, jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
909
+ import { useState as useState8 } from "preact/hooks";
910
+ import { Button as Button4, Field, Select as Select2 } from "@odla-ai/ui/components";
911
+ import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
844
912
  var APPLICATION_STAGES = [
845
913
  "submitted",
846
914
  "paid_pending_vetting",
@@ -851,20 +919,20 @@ var APPLICATION_STAGES = [
851
919
  "refunded"
852
920
  ];
853
921
  function CandidateStagePanel(props) {
854
- const [selected, setSelected] = useState7("");
855
- const [busy, setBusy] = useState7(false);
856
- const [error, setError] = useState7(null);
922
+ const [selected, setSelected] = useState8("");
923
+ const [busy, setBusy] = useState8(false);
924
+ const [error, setError] = useState8(null);
857
925
  const options = props.stages.filter((stage) => !props.applicationStages.includes(stage.id) && stage.id !== props.current);
858
926
  const label = (id) => props.stages.find((stage) => stage.id === id)?.label ?? id.replaceAll("_", " ");
859
927
  if (!options.length && props.current == null) {
860
- return /* @__PURE__ */ jsx8("p", { children: "Journey stage: Not applicable. No membership application is linked. Administrative access is separate." });
928
+ return /* @__PURE__ */ jsx9("p", { children: "Journey stage: Not applicable. No membership application is linked. Administrative access is separate." });
861
929
  }
862
930
  const target = options.some((stage) => stage.id === selected) ? selected : "";
863
- return /* @__PURE__ */ jsxs8(Field, { label: "Stage", error, children: [
864
- /* @__PURE__ */ jsx8("p", { children: props.current == null ? "No stage yet. Membership statuses begin when this person applies." : label(props.current) }),
865
- options.length ? /* @__PURE__ */ jsxs8(Fragment6, { children: [
866
- /* @__PURE__ */ jsx8(
867
- Select,
931
+ return /* @__PURE__ */ jsxs9(Field, { label: "Stage", error, children: [
932
+ /* @__PURE__ */ jsx9("p", { children: props.current == null ? "No stage yet. Membership statuses begin when this person applies." : label(props.current) }),
933
+ options.length ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
934
+ /* @__PURE__ */ jsx9(
935
+ Select2,
868
936
  {
869
937
  "aria-label": "Move to stage",
870
938
  value: target,
@@ -873,7 +941,7 @@ function CandidateStagePanel(props) {
873
941
  options: [{ value: "", label: "Move to\u2026" }, ...options.map((stage) => ({ value: stage.id, label: label(stage.id) }))]
874
942
  }
875
943
  ),
876
- /* @__PURE__ */ jsx8(Button4, { mini: true, disabled: busy || !target, onClick: async () => {
944
+ /* @__PURE__ */ jsx9(Button4, { mini: true, disabled: busy || !target, onClick: async () => {
877
945
  setBusy(true);
878
946
  setError(null);
879
947
  try {
@@ -885,22 +953,22 @@ function CandidateStagePanel(props) {
885
953
  setBusy(false);
886
954
  }
887
955
  }, children: "Move" })
888
- ] }) : /* @__PURE__ */ jsx8("p", { children: "No pre-application stages are configured for this chapter\u2019s CRM." })
956
+ ] }) : /* @__PURE__ */ jsx9("p", { children: "No pre-application stages are configured for this chapter\u2019s CRM." })
889
957
  ] });
890
958
  }
891
959
 
892
960
  // src/ui/admin-record-admission.tsx
893
- import { useState as useState8 } from "preact/hooks";
894
- import { Button as Button5, Field as Field2, Select as Select2 } from "@odla-ai/ui/components";
895
- import { Fragment as Fragment7, jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
961
+ import { useState as useState9 } from "preact/hooks";
962
+ import { Button as Button5, Field as Field2, Select as Select3 } from "@odla-ai/ui/components";
963
+ import { Fragment as Fragment7, jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
896
964
  function RecordAdmissionPanel(props) {
897
965
  const copy = useChapterCopy().admin.admission;
898
966
  const resource = useAdminResource(props.getToken, "/api/admin/admission-grants");
899
- const [decision, setDecision] = useState8("waive_interview");
900
- const [note, setNote] = useState8("");
901
- const [busy, setBusy] = useState8(false);
902
- const [error, setError] = useState8(null);
903
- if (!props.email) return /* @__PURE__ */ jsx9("p", { className: "muted", children: "This record has no email yet. Add one before setting an invitation." });
967
+ const [decision, setDecision] = useState9("waive_interview");
968
+ const [note, setNote] = useState9("");
969
+ const [busy, setBusy] = useState9(false);
970
+ const [error, setError] = useState9(null);
971
+ if (!props.email) return /* @__PURE__ */ jsx10("p", { className: "muted", children: "This record has no email yet. Add one before setting an invitation." });
904
972
  const email = props.email.trim().toLowerCase();
905
973
  const grants = (resource.data?.grants ?? []).filter((grant) => grant.email.trim().toLowerCase() === email);
906
974
  const effective = grants.find((grant) => grant.active && !grant.consumedAt && !grant.revokedAt && (!grant.expiresAt || grant.expiresAt > Date.now()));
@@ -923,23 +991,23 @@ function RecordAdmissionPanel(props) {
923
991
  body: JSON.stringify({ email: props.email, decision, ...note.trim() ? { note: note.trim() } : {} })
924
992
  }));
925
993
  const revoke = (grant) => run(() => adminFetch(props.getToken, `/api/admin/admission-grants/${encodeURIComponent(grant.id)}`, { method: "DELETE" }));
926
- return /* @__PURE__ */ jsxs9("section", { "aria-label": "Invitation", style: { display: "grid", gap: 12 }, children: [
927
- resource.loading ? /* @__PURE__ */ jsx9("p", { role: "status", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: resource.error }) : effective ? /* @__PURE__ */ jsxs9("p", { style: { margin: 0 }, "data-testid": "effective-invitation", children: [
928
- /* @__PURE__ */ jsx9("strong", { children: effective.decision === "waive_interview" ? copy.waive : copy.require }),
994
+ return /* @__PURE__ */ jsxs10("section", { "aria-label": "Invitation", style: { display: "grid", gap: 12 }, children: [
995
+ resource.loading ? /* @__PURE__ */ jsx10("p", { role: "status", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx10("p", { role: "alert", children: resource.error }) : effective ? /* @__PURE__ */ jsxs10("p", { style: { margin: 0 }, "data-testid": "effective-invitation", children: [
996
+ /* @__PURE__ */ jsx10("strong", { children: effective.decision === "waive_interview" ? copy.waive : copy.require }),
929
997
  " \xB7 ",
930
998
  effective.sourceScope === "local" ? copy.local : copy.parent,
931
999
  effective.note ? ` \xB7 ${effective.note}` : "",
932
- effective.sourceScope === "local" ? /* @__PURE__ */ jsxs9(Fragment7, { children: [
1000
+ effective.sourceScope === "local" ? /* @__PURE__ */ jsxs10(Fragment7, { children: [
933
1001
  " ",
934
- /* @__PURE__ */ jsx9(Button5, { variant: "secondary", mini: true, disabled: busy, onClick: () => void revoke(effective), children: copy.revoke })
1002
+ /* @__PURE__ */ jsx10(Button5, { variant: "secondary", mini: true, disabled: busy, onClick: () => void revoke(effective), children: copy.revoke })
935
1003
  ] }) : null
936
- ] }) : /* @__PURE__ */ jsxs9("p", { className: "muted", style: { margin: 0 }, children: [
1004
+ ] }) : /* @__PURE__ */ jsxs10("p", { className: "muted", style: { margin: 0 }, children: [
937
1005
  "No invitation setting for ",
938
1006
  email,
939
1007
  ". The next signup follows the chapter\u2019s ordinary interview rule."
940
1008
  ] }),
941
- /* @__PURE__ */ jsx9(Field2, { label: copy.decision, children: /* @__PURE__ */ jsx9(
942
- Select2,
1009
+ /* @__PURE__ */ jsx10(Field2, { label: copy.decision, children: /* @__PURE__ */ jsx10(
1010
+ Select3,
943
1011
  {
944
1012
  value: decision,
945
1013
  disabled: busy,
@@ -947,22 +1015,22 @@ function RecordAdmissionPanel(props) {
947
1015
  options: [{ value: "waive_interview", label: copy.waive }, { value: "require_interview", label: copy.require }]
948
1016
  }
949
1017
  ) }),
950
- /* @__PURE__ */ jsx9(Field2, { label: copy.note, children: /* @__PURE__ */ jsx9("input", { value: note, maxLength: 2e3, disabled: busy, onInput: (event) => setNote(event.currentTarget.value) }) }),
951
- /* @__PURE__ */ jsx9("div", { children: /* @__PURE__ */ jsx9(Button5, { variant: "secondary", mini: true, disabled: busy, onClick: () => void save(), children: copy.add }) }),
952
- error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: error }) : null
1018
+ /* @__PURE__ */ jsx10(Field2, { label: copy.note, children: /* @__PURE__ */ jsx10("input", { value: note, maxLength: 2e3, disabled: busy, onInput: (event) => setNote(event.currentTarget.value) }) }),
1019
+ /* @__PURE__ */ jsx10("div", { children: /* @__PURE__ */ jsx10(Button5, { variant: "secondary", mini: true, disabled: busy, onClick: () => void save(), children: copy.add }) }),
1020
+ error ? /* @__PURE__ */ jsx10("p", { role: "alert", children: error }) : null
953
1021
  ] });
954
1022
  }
955
1023
 
956
1024
  // src/ui/admin-application-repair.tsx
957
- import { useState as useState9 } from "preact/hooks";
1025
+ import { useState as useState10 } from "preact/hooks";
958
1026
  import { Button as Button6 } from "@odla-ai/ui/components";
959
- import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
1027
+ import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
960
1028
  function ApplicationLinkRepair({ recordId, getToken, onChanged }) {
961
- const [busy, setBusy] = useState9(false);
962
- const [error, setError] = useState9(null);
963
- return /* @__PURE__ */ jsxs10("section", { className: "panel", children: [
964
- /* @__PURE__ */ jsx10("p", { children: "This person is missing an application link. Repair checks for one matching chapter application; it does not change membership or profile details." }),
965
- /* @__PURE__ */ jsx10(Button6, { variant: "secondary", disabled: busy, onClick: async () => {
1029
+ const [busy, setBusy] = useState10(false);
1030
+ const [error, setError] = useState10(null);
1031
+ return /* @__PURE__ */ jsxs11("section", { className: "panel", children: [
1032
+ /* @__PURE__ */ jsx11("p", { children: "This person is missing an application link. Repair checks for one matching chapter application; it does not change membership or profile details." }),
1033
+ /* @__PURE__ */ jsx11(Button6, { variant: "secondary", disabled: busy, onClick: async () => {
966
1034
  setBusy(true);
967
1035
  setError(null);
968
1036
  try {
@@ -974,34 +1042,34 @@ function ApplicationLinkRepair({ recordId, getToken, onChanged }) {
974
1042
  setBusy(false);
975
1043
  }
976
1044
  }, children: busy ? "Checking application link\u2026" : "Repair application link" }),
977
- error ? /* @__PURE__ */ jsx10("p", { role: "alert", children: error }) : null
1045
+ error ? /* @__PURE__ */ jsx11("p", { role: "alert", children: error }) : null
978
1046
  ] });
979
1047
  }
980
1048
 
981
1049
  // src/ui/admin-application-stage.tsx
982
- import { useState as useState10 } from "preact/hooks";
983
- import { Button as Button7, Field as Field3, Select as Select3 } from "@odla-ai/ui/components";
984
- import { Fragment as Fragment8, jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
1050
+ import { useState as useState11 } from "preact/hooks";
1051
+ import { Button as Button7, Field as Field3, Select as Select4 } from "@odla-ai/ui/components";
1052
+ import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
985
1053
  function ApplicationStagePanel(props) {
986
1054
  const actions = useAdminResource(
987
1055
  props.getToken,
988
1056
  `/api/admin/applications/${encodeURIComponent(props.applicationId)}/actions`
989
1057
  );
990
- const [selected, setSelected] = useState10("");
991
- const [busy, setBusy] = useState10(false);
992
- const [error, setError] = useState10(null);
993
- if (actions.loading) return /* @__PURE__ */ jsx11("p", { role: "status", children: "Loading available actions\u2026" });
994
- if (actions.error) return /* @__PURE__ */ jsxs11("p", { role: "alert", children: [
1058
+ const [selected, setSelected] = useState11("");
1059
+ const [busy, setBusy] = useState11(false);
1060
+ const [error, setError] = useState11(null);
1061
+ if (actions.loading) return /* @__PURE__ */ jsx12("p", { role: "status", children: "Loading available actions\u2026" });
1062
+ if (actions.error) return /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
995
1063
  "Could not load application actions: ",
996
1064
  actions.error
997
1065
  ] });
998
1066
  const allowed = actions.data?.allowedStages ?? [];
999
1067
  const target = allowed.includes(selected) ? selected : "";
1000
- return /* @__PURE__ */ jsxs11(Field3, { label: "Stage", error, children: [
1001
- /* @__PURE__ */ jsx11("p", { children: actions.data?.status?.replaceAll("_", " ") }),
1002
- allowed.length ? /* @__PURE__ */ jsxs11(Fragment8, { children: [
1003
- /* @__PURE__ */ jsx11(
1004
- Select3,
1068
+ return /* @__PURE__ */ jsxs12(Field3, { label: "Stage", error, children: [
1069
+ /* @__PURE__ */ jsx12("p", { children: actions.data?.status?.replaceAll("_", " ") }),
1070
+ allowed.length ? /* @__PURE__ */ jsxs12(Fragment8, { children: [
1071
+ /* @__PURE__ */ jsx12(
1072
+ Select4,
1005
1073
  {
1006
1074
  "aria-label": "Move to stage",
1007
1075
  value: target,
@@ -1010,7 +1078,7 @@ function ApplicationStagePanel(props) {
1010
1078
  options: [{ value: "", label: "Move to\u2026" }, ...allowed.map((value) => ({ value, label: value.replaceAll("_", " ") }))]
1011
1079
  }
1012
1080
  ),
1013
- /* @__PURE__ */ jsx11(Button7, { mini: true, disabled: busy || !target, onClick: async () => {
1081
+ /* @__PURE__ */ jsx12(Button7, { mini: true, disabled: busy || !target, onClick: async () => {
1014
1082
  setBusy(true);
1015
1083
  setError(null);
1016
1084
  try {
@@ -1023,19 +1091,19 @@ function ApplicationStagePanel(props) {
1023
1091
  actions.refresh();
1024
1092
  }
1025
1093
  }, children: "Move" })
1026
- ] }) : /* @__PURE__ */ jsx11("p", { children: "No stage changes are available under this chapter\u2019s current membership policy." })
1094
+ ] }) : /* @__PURE__ */ jsx12("p", { children: "No stage changes are available under this chapter\u2019s current membership policy." })
1027
1095
  ] });
1028
1096
  }
1029
1097
 
1030
1098
  // src/ui/operational-profile-form.tsx
1031
- import { Button as Button8, Field as Field4, Input, Select as Select4, Textarea } from "@odla-ai/ui/components";
1032
- import { useRef, useState as useState11 } from "preact/hooks";
1033
- import { jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
1099
+ import { Button as Button8, Field as Field4, Input, Select as Select5, Textarea } from "@odla-ai/ui/components";
1100
+ import { useRef, useState as useState12 } from "preact/hooks";
1101
+ import { jsx as jsx13, jsxs as jsxs13 } from "preact/jsx-runtime";
1034
1102
  var valueText = (value) => value == null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
1035
1103
  function OperationalProfileForm(props) {
1036
- const [draft, setDraft] = useState11(() => Object.fromEntries(props.fields.map((field) => [field.id, valueText(props.values[field.id])])));
1037
- const [busy, setBusy] = useState11(false);
1038
- const [error, setError] = useState11(null);
1104
+ const [draft, setDraft] = useState12(() => Object.fromEntries(props.fields.map((field) => [field.id, valueText(props.values[field.id])])));
1105
+ const [busy, setBusy] = useState12(false);
1106
+ const [error, setError] = useState12(null);
1039
1107
  const pending = useRef(null);
1040
1108
  const changed = props.fields.filter((field) => draft[field.id] !== valueText(props.values[field.id]));
1041
1109
  const save = async () => {
@@ -1063,20 +1131,20 @@ function OperationalProfileForm(props) {
1063
1131
  setBusy(false);
1064
1132
  }
1065
1133
  };
1066
- return /* @__PURE__ */ jsxs12("section", { style: { display: "grid", gap: 16 }, children: [
1067
- props.fields.map((field) => /* @__PURE__ */ jsx12(Field4, { label: field.label, children: field.options || field.type === "boolean" ? /* @__PURE__ */ jsxs12(
1068
- Select4,
1134
+ return /* @__PURE__ */ jsxs13("section", { style: { display: "grid", gap: 16 }, children: [
1135
+ props.fields.map((field) => /* @__PURE__ */ jsx13(Field4, { label: field.label, children: field.options || field.type === "boolean" ? /* @__PURE__ */ jsxs13(
1136
+ Select5,
1069
1137
  {
1070
1138
  "aria-label": field.label,
1071
1139
  value: draft[field.id],
1072
1140
  disabled: busy,
1073
1141
  onChange: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value }),
1074
1142
  children: [
1075
- /* @__PURE__ */ jsx12("option", { value: "", children: "Not set" }),
1076
- (field.options ?? ["true", "false"]).map((option) => /* @__PURE__ */ jsx12("option", { value: option, children: option }, option))
1143
+ /* @__PURE__ */ jsx13("option", { value: "", children: "Not set" }),
1144
+ (field.options ?? ["true", "false"]).map((option) => /* @__PURE__ */ jsx13("option", { value: option, children: option }, option))
1077
1145
  ]
1078
1146
  }
1079
- ) : field.type === "json" ? /* @__PURE__ */ jsx12(
1147
+ ) : field.type === "json" ? /* @__PURE__ */ jsx13(
1080
1148
  Textarea,
1081
1149
  {
1082
1150
  "aria-label": field.label,
@@ -1084,7 +1152,7 @@ function OperationalProfileForm(props) {
1084
1152
  disabled: busy,
1085
1153
  onInput: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value })
1086
1154
  }
1087
- ) : /* @__PURE__ */ jsx12(
1155
+ ) : /* @__PURE__ */ jsx13(
1088
1156
  Input,
1089
1157
  {
1090
1158
  "aria-label": field.label,
@@ -1094,26 +1162,26 @@ function OperationalProfileForm(props) {
1094
1162
  onInput: (event) => setDraft({ ...draft, [field.id]: event.currentTarget.value })
1095
1163
  }
1096
1164
  ) }, field.id)),
1097
- error ? /* @__PURE__ */ jsx12("p", { role: "alert", children: error }) : null,
1098
- /* @__PURE__ */ jsx12("div", { children: /* @__PURE__ */ jsx12(Button8, { disabled: busy || !changed.length, onClick: () => void save(), children: busy ? "Saving\u2026" : "Save profile" }) })
1165
+ error ? /* @__PURE__ */ jsx13("p", { role: "alert", children: error }) : null,
1166
+ /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(Button8, { disabled: busy || !changed.length, onClick: () => void save(), children: busy ? "Saving\u2026" : "Save profile" }) })
1099
1167
  ] });
1100
1168
  }
1101
1169
 
1102
1170
  // src/ui/admin-person-profile.tsx
1103
- import { Fragment as Fragment9, jsx as jsx13, jsxs as jsxs13 } from "preact/jsx-runtime";
1171
+ import { Fragment as Fragment9, jsx as jsx14, jsxs as jsxs14 } from "preact/jsx-runtime";
1104
1172
  function ChapterPersonProfile(props) {
1105
1173
  const path = `/api/admin/people/profile?${new URLSearchParams({ recordId: props.recordId })}`;
1106
1174
  const resource = useAdminResource(props.getToken, path);
1107
- if (resource.loading) return /* @__PURE__ */ jsx13("p", { children: "Loading profile\u2026" });
1108
- if (resource.error) return /* @__PURE__ */ jsx13("p", { role: "alert", children: resource.error });
1175
+ if (resource.loading) return /* @__PURE__ */ jsx14("p", { children: "Loading profile\u2026" });
1176
+ if (resource.error) return /* @__PURE__ */ jsx14("p", { role: "alert", children: resource.error });
1109
1177
  const record = resource.data?.record;
1110
- if (!record?.edit) return /* @__PURE__ */ jsx13("p", { children: "Profile editing is unavailable." });
1111
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1112
- /* @__PURE__ */ jsxs13("p", { children: [
1178
+ if (!record?.edit) return /* @__PURE__ */ jsx14("p", { children: "Profile editing is unavailable." });
1179
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1180
+ /* @__PURE__ */ jsxs14("p", { children: [
1113
1181
  String(record.fields.email ?? ""),
1114
1182
  " \u2014 login email changes use the verified account workflow."
1115
1183
  ] }),
1116
- /* @__PURE__ */ jsx13(
1184
+ /* @__PURE__ */ jsx14(
1117
1185
  OperationalProfileForm,
1118
1186
  {
1119
1187
  fields: record.edit.fields,
@@ -1130,14 +1198,14 @@ function ChapterPersonProfile(props) {
1130
1198
  }
1131
1199
 
1132
1200
  // src/ui/admin-account-sync.tsx
1133
- import { useState as useState12 } from "preact/hooks";
1201
+ import { useState as useState13 } from "preact/hooks";
1134
1202
  import { Button as Button9 } from "@odla-ai/ui/components";
1135
- import { jsx as jsx14, jsxs as jsxs14 } from "preact/jsx-runtime";
1203
+ import { jsx as jsx15, jsxs as jsxs15 } from "preact/jsx-runtime";
1136
1204
  function AccountPeopleSync({ getToken, refresh }) {
1137
- const [busy, setBusy] = useState12(false);
1138
- const [message, setMessage] = useState12("");
1139
- return /* @__PURE__ */ jsxs14("div", { children: [
1140
- /* @__PURE__ */ jsx14(Button9, { mini: true, variant: "secondary", disabled: busy, onClick: async () => {
1205
+ const [busy, setBusy] = useState13(false);
1206
+ const [message, setMessage] = useState13("");
1207
+ return /* @__PURE__ */ jsxs15("div", { children: [
1208
+ /* @__PURE__ */ jsx15(Button9, { mini: true, variant: "secondary", disabled: busy, onClick: async () => {
1141
1209
  setBusy(true);
1142
1210
  setMessage("");
1143
1211
  let after = null, count = 0, errors = 0;
@@ -1161,22 +1229,22 @@ function AccountPeopleSync({ getToken, refresh }) {
1161
1229
  setBusy(false);
1162
1230
  }
1163
1231
  }, children: busy ? "Syncing accounts\u2026" : "Sync accounts" }),
1164
- message ? /* @__PURE__ */ jsx14("p", { role: "status", children: message }) : null
1232
+ message ? /* @__PURE__ */ jsx15("p", { role: "status", children: message }) : null
1165
1233
  ] });
1166
1234
  }
1167
1235
 
1168
1236
  // src/ui/admin-record-actions.tsx
1169
- import { useEffect as useEffect4, useState as useState13 } from "preact/hooks";
1170
- import { Button as Button10, Field as Field5, Select as Select5 } from "@odla-ai/ui/components";
1171
- import { jsx as jsx15, jsxs as jsxs15 } from "preact/jsx-runtime";
1237
+ import { useEffect as useEffect5, useState as useState14 } from "preact/hooks";
1238
+ import { Button as Button10, Field as Field5, Select as Select6 } from "@odla-ai/ui/components";
1239
+ import { jsx as jsx16, jsxs as jsxs16 } from "preact/jsx-runtime";
1172
1240
  function RecordActions({ getToken, record, roles, onChanged }) {
1173
1241
  const copy = useChapterCopy().admin.records;
1174
1242
  const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
1175
1243
  const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
1176
- const [access, setAccess] = useState13(null);
1177
- const [busy, setBusy] = useState13(null);
1178
- const [note, setNote] = useState13(null);
1179
- useEffect4(() => {
1244
+ const [access, setAccess] = useState14(null);
1245
+ const [busy, setBusy] = useState14(null);
1246
+ const [note, setNote] = useState14(null);
1247
+ useEffect5(() => {
1180
1248
  if (!userId) {
1181
1249
  setAccess(null);
1182
1250
  return;
@@ -1205,35 +1273,35 @@ function RecordActions({ getToken, record, roles, onChanged }) {
1205
1273
  const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), copy.refunded);
1206
1274
  const decline = () => act("decline", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/decline`, { method: "POST" }), copy.declined);
1207
1275
  if (!userId && !applicationId) return null;
1208
- return /* @__PURE__ */ jsxs15("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
1209
- /* @__PURE__ */ jsx15("h3", { style: { margin: 0, fontSize: 16 }, children: copy.lifecycle }),
1210
- userId ? /* @__PURE__ */ jsx15(Field5, { label: copy.role, hint: access?.superAdmin ? copy.superAdminHint : void 0, children: /* @__PURE__ */ jsx15(
1211
- Select5,
1276
+ return /* @__PURE__ */ jsxs16("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
1277
+ /* @__PURE__ */ jsx16("h3", { style: { margin: 0, fontSize: 16 }, children: copy.lifecycle }),
1278
+ userId ? /* @__PURE__ */ jsx16(Field5, { label: copy.role, hint: access?.superAdmin ? copy.superAdminHint : void 0, children: /* @__PURE__ */ jsx16(
1279
+ Select6,
1212
1280
  {
1213
1281
  value: access?.role ?? "",
1214
1282
  disabled: busy === "role" || access?.superAdmin,
1215
1283
  options: roles.map((r) => ({ value: r, label: r })),
1216
1284
  onChange: (e) => void setRole(e.target.value)
1217
1285
  }
1218
- ) }) : /* @__PURE__ */ jsx15("p", { className: "muted", style: { margin: 0 }, children: copy.noAccount }),
1219
- applicationId ? /* @__PURE__ */ jsxs15("div", { style: { display: "flex", gap: 8 }, children: [
1220
- /* @__PURE__ */ jsx15(Button10, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: copy.approve }),
1221
- /* @__PURE__ */ jsx15(Button10, { variant: "secondary", mini: true, disabled: busy === "decline", onClick: () => void decline(), children: copy.decline }),
1222
- /* @__PURE__ */ jsx15(Button10, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: copy.refund })
1286
+ ) }) : /* @__PURE__ */ jsx16("p", { className: "muted", style: { margin: 0 }, children: copy.noAccount }),
1287
+ applicationId ? /* @__PURE__ */ jsxs16("div", { style: { display: "flex", gap: 8 }, children: [
1288
+ /* @__PURE__ */ jsx16(Button10, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: copy.approve }),
1289
+ /* @__PURE__ */ jsx16(Button10, { variant: "secondary", mini: true, disabled: busy === "decline", onClick: () => void decline(), children: copy.decline }),
1290
+ /* @__PURE__ */ jsx16(Button10, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: copy.refund })
1223
1291
  ] }) : null,
1224
- note ? /* @__PURE__ */ jsx15("p", { className: "muted", style: { margin: 0 }, children: note }) : null
1292
+ note ? /* @__PURE__ */ jsx16("p", { className: "muted", style: { margin: 0 }, children: note }) : null
1225
1293
  ] });
1226
1294
  }
1227
1295
 
1228
1296
  // src/ui/admin-network.tsx
1229
- import { useState as useState14 } from "preact/hooks";
1230
- import { jsx as jsx16, jsxs as jsxs16 } from "preact/jsx-runtime";
1297
+ import { useState as useState15 } from "preact/hooks";
1298
+ import { jsx as jsx17, jsxs as jsxs17 } from "preact/jsx-runtime";
1231
1299
  function NetworkShareActions(props) {
1232
1300
  const copy = useChapterCopy().admin.network;
1233
1301
  const { recordId, recordType, getToken } = props;
1234
1302
  const targets = useAdminResource(getToken, "/api/admin/network/targets");
1235
- const [busy, setBusy] = useState14(null);
1236
- const [message, setMessage] = useState14(null);
1303
+ const [busy, setBusy] = useState15(null);
1304
+ const [message, setMessage] = useState15(null);
1237
1305
  const compatible = (targets.data?.targets ?? []).filter((target) => target.types.includes(recordType));
1238
1306
  if (targets.loading || targets.error || compatible.length === 0) return null;
1239
1307
  const push = async (target) => {
@@ -1252,26 +1320,26 @@ function NetworkShareActions(props) {
1252
1320
  setBusy(null);
1253
1321
  }
1254
1322
  };
1255
- return /* @__PURE__ */ jsxs16("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
1256
- /* @__PURE__ */ jsxs16("div", { children: [
1257
- /* @__PURE__ */ jsx16("h3", { style: { margin: 0, fontSize: 16 }, children: copy.title }),
1258
- /* @__PURE__ */ jsx16("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: copy.allowlistDescription })
1323
+ return /* @__PURE__ */ jsxs17("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
1324
+ /* @__PURE__ */ jsxs17("div", { children: [
1325
+ /* @__PURE__ */ jsx17("h3", { style: { margin: 0, fontSize: 16 }, children: copy.title }),
1326
+ /* @__PURE__ */ jsx17("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: copy.allowlistDescription })
1259
1327
  ] }),
1260
- /* @__PURE__ */ jsx16("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: compatible.map((target) => /* @__PURE__ */ jsx16("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)) }),
1261
- message ? /* @__PURE__ */ jsx16("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
1328
+ /* @__PURE__ */ jsx17("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: compatible.map((target) => /* @__PURE__ */ jsx17("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)) }),
1329
+ message ? /* @__PURE__ */ jsx17("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
1262
1330
  ] });
1263
1331
  }
1264
1332
 
1265
1333
  // src/ui/admin-record-activity.tsx
1266
- import { useCallback as useCallback3, useEffect as useEffect5, useState as useState15 } from "preact/hooks";
1334
+ import { useCallback as useCallback3, useEffect as useEffect6, useState as useState16 } from "preact/hooks";
1267
1335
  import { ActivityFeed } from "@odla-ai/crm/ui";
1268
1336
  import { parseCrmDiscussionReference as parseCrmDiscussionReference2 } from "@odla-ai/crm";
1269
- import { jsx as jsx17 } from "preact/jsx-runtime";
1337
+ import { jsx as jsx18 } from "preact/jsx-runtime";
1270
1338
  function RecordActivityPanel(props) {
1271
1339
  const { client, recordId } = props;
1272
- const [activities, setActivities] = useState15([]);
1273
- const [busy, setBusy] = useState15(false);
1274
- const [error, setError] = useState15(null);
1340
+ const [activities, setActivities] = useState16([]);
1341
+ const [busy, setBusy] = useState16(false);
1342
+ const [error, setError] = useState16(null);
1275
1343
  const reference = typeof window === "undefined" ? null : parseCrmDiscussionReference2(new URL(window.location.href));
1276
1344
  const focusedActivityId = reference?.kind === "crm:activity" && reference.recordId === recordId ? reference.activityId : void 0;
1277
1345
  const load = useCallback3(async () => {
@@ -1283,7 +1351,7 @@ function RecordActivityPanel(props) {
1283
1351
  setError(failure instanceof Error ? failure.message : String(failure));
1284
1352
  }
1285
1353
  }, [client, recordId]);
1286
- useEffect5(() => void load(), [load]);
1354
+ useEffect6(() => void load(), [load]);
1287
1355
  const run = async (action) => {
1288
1356
  setBusy(true);
1289
1357
  try {
@@ -1293,8 +1361,8 @@ function RecordActivityPanel(props) {
1293
1361
  setBusy(false);
1294
1362
  }
1295
1363
  };
1296
- if (error) return /* @__PURE__ */ jsx17("p", { role: "alert", className: "muted", children: error });
1297
- return /* @__PURE__ */ jsx17(
1364
+ if (error) return /* @__PURE__ */ jsx18("p", { role: "alert", className: "muted", children: error });
1365
+ return /* @__PURE__ */ jsx18(
1298
1366
  ActivityFeed,
1299
1367
  {
1300
1368
  activities,
@@ -1308,17 +1376,17 @@ function RecordActivityPanel(props) {
1308
1376
  }
1309
1377
 
1310
1378
  // src/ui/admin-record-comms.tsx
1311
- import { useCallback as useCallback4, useEffect as useEffect6, useState as useState16 } from "preact/hooks";
1312
- import { Button as Button11, Field as Field6, Select as Select6 } from "@odla-ai/ui/components";
1313
- import { jsx as jsx18, jsxs as jsxs17 } from "preact/jsx-runtime";
1379
+ import { useCallback as useCallback4, useEffect as useEffect7, useState as useState17 } from "preact/hooks";
1380
+ import { Button as Button11, Field as Field6, Select as Select7 } from "@odla-ai/ui/components";
1381
+ import { jsx as jsx19, jsxs as jsxs18 } from "preact/jsx-runtime";
1314
1382
  function RecordCommsPanel(props) {
1315
1383
  const copy = useChapterCopy().admin.records;
1316
1384
  const { client, recordId, getToken } = props;
1317
1385
  const config = useAdminResource(getToken, "/api/admin/group/email");
1318
1386
  const templates = Object.entries(config.data?.emailTemplates ?? {}).filter(([, value]) => value.enabled);
1319
- const [template, setTemplate] = useState16("");
1320
- const [busy, setBusy] = useState16(false);
1321
- const [note, setNote] = useState16(null);
1387
+ const [template, setTemplate] = useState17("");
1388
+ const [busy, setBusy] = useState17(false);
1389
+ const [note, setNote] = useState17(null);
1322
1390
  const send = async () => {
1323
1391
  if (!template) return;
1324
1392
  setBusy(true);
@@ -1332,11 +1400,11 @@ function RecordCommsPanel(props) {
1332
1400
  setBusy(false);
1333
1401
  }
1334
1402
  };
1335
- if (config.loading) return /* @__PURE__ */ jsx18("p", { className: "muted", children: copy.loadingTemplates });
1336
- if (config.error) return /* @__PURE__ */ jsx18("p", { role: "alert", className: "muted", children: config.error });
1337
- return /* @__PURE__ */ jsxs17("div", { style: { display: "grid", gap: 14, maxWidth: 560 }, children: [
1338
- /* @__PURE__ */ jsx18(Field6, { label: copy.template, children: /* @__PURE__ */ jsx18(
1339
- Select6,
1403
+ if (config.loading) return /* @__PURE__ */ jsx19("p", { className: "muted", children: copy.loadingTemplates });
1404
+ if (config.error) return /* @__PURE__ */ jsx19("p", { role: "alert", className: "muted", children: config.error });
1405
+ return /* @__PURE__ */ jsxs18("div", { style: { display: "grid", gap: 14, maxWidth: 560 }, children: [
1406
+ /* @__PURE__ */ jsx19(Field6, { label: copy.template, children: /* @__PURE__ */ jsx19(
1407
+ Select7,
1340
1408
  {
1341
1409
  value: template,
1342
1410
  onChange: (event) => setTemplate(event.currentTarget.value),
@@ -1346,15 +1414,15 @@ function RecordCommsPanel(props) {
1346
1414
  ]
1347
1415
  }
1348
1416
  ) }),
1349
- /* @__PURE__ */ jsx18("div", { children: /* @__PURE__ */ jsx18(Button11, { disabled: !template || busy, onClick: () => void send(), children: busy ? copy.sending : copy.send }) }),
1350
- note ? /* @__PURE__ */ jsx18("p", { role: "status", className: "muted", children: note }) : null
1417
+ /* @__PURE__ */ jsx19("div", { children: /* @__PURE__ */ jsx19(Button11, { disabled: !template || busy, onClick: () => void send(), children: busy ? copy.sending : copy.send }) }),
1418
+ note ? /* @__PURE__ */ jsx19("p", { role: "status", className: "muted", children: note }) : null
1351
1419
  ] });
1352
1420
  }
1353
1421
  function RecordCommsHistory(props) {
1354
1422
  const copy = useChapterCopy().admin.records;
1355
1423
  const { client, recordId } = props;
1356
- const [rows, setRows] = useState16([]);
1357
- const [error, setError] = useState16(null);
1424
+ const [rows, setRows] = useState17([]);
1425
+ const [error, setError] = useState17(null);
1358
1426
  const load = useCallback4(async () => {
1359
1427
  try {
1360
1428
  const result = await client.emailLog({ recordId, limit: 100 });
@@ -1364,12 +1432,12 @@ function RecordCommsHistory(props) {
1364
1432
  setError(failure instanceof Error ? failure.message : String(failure));
1365
1433
  }
1366
1434
  }, [client, recordId]);
1367
- useEffect6(() => void load(), [load]);
1368
- if (error) return /* @__PURE__ */ jsx18("p", { role: "alert", className: "muted", children: error });
1369
- if (rows.length === 0) return /* @__PURE__ */ jsx18("p", { className: "muted", children: copy.noMessages });
1370
- return /* @__PURE__ */ jsx18("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 10 }, children: rows.map((row, index) => /* @__PURE__ */ jsxs17("li", { className: "panel", children: [
1371
- /* @__PURE__ */ jsx18("strong", { children: String(row["templateId"] ?? row["template"] ?? copy.message) }),
1372
- /* @__PURE__ */ jsxs17("div", { className: "muted", children: [
1435
+ useEffect7(() => void load(), [load]);
1436
+ if (error) return /* @__PURE__ */ jsx19("p", { role: "alert", className: "muted", children: error });
1437
+ if (rows.length === 0) return /* @__PURE__ */ jsx19("p", { className: "muted", children: copy.noMessages });
1438
+ return /* @__PURE__ */ jsx19("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 10 }, children: rows.map((row, index) => /* @__PURE__ */ jsxs18("li", { className: "panel", children: [
1439
+ /* @__PURE__ */ jsx19("strong", { children: String(row["templateId"] ?? row["template"] ?? copy.message) }),
1440
+ /* @__PURE__ */ jsxs18("div", { className: "muted", children: [
1373
1441
  row["to"] ? String(row["to"]) : "",
1374
1442
  row["sentAt"] ? ` \xB7 ${new Date(Number(row["sentAt"])).toLocaleString()}` : ""
1375
1443
  ] })
@@ -1377,9 +1445,9 @@ function RecordCommsHistory(props) {
1377
1445
  }
1378
1446
 
1379
1447
  // src/ui/admin-record-scheduling.tsx
1380
- import { useState as useState17 } from "preact/hooks";
1448
+ import { useState as useState18 } from "preact/hooks";
1381
1449
  import { Button as Button12 } from "@odla-ai/ui/components";
1382
- import { Fragment as Fragment10, jsx as jsx19, jsxs as jsxs18 } from "preact/jsx-runtime";
1450
+ import { Fragment as Fragment10, jsx as jsx20, jsxs as jsxs19 } from "preact/jsx-runtime";
1383
1451
  function RecordSchedulingPanel(props) {
1384
1452
  const copy = useChapterCopy().admin.records;
1385
1453
  const { getToken, applicationId } = props;
@@ -1387,8 +1455,8 @@ function RecordSchedulingPanel(props) {
1387
1455
  getToken,
1388
1456
  "/api/admin/meetings?all=1"
1389
1457
  );
1390
- const [busy, setBusy] = useState17(null);
1391
- const [note, setNote] = useState17(null);
1458
+ const [busy, setBusy] = useState18(null);
1459
+ const [note, setNote] = useState18(null);
1392
1460
  const meetings = (resource.data?.meetings ?? []).filter((meeting) => meeting.applicationId === applicationId);
1393
1461
  const run = async (meeting, action) => {
1394
1462
  let body;
@@ -1415,33 +1483,33 @@ function RecordSchedulingPanel(props) {
1415
1483
  setBusy(null);
1416
1484
  }
1417
1485
  };
1418
- if (!applicationId) return /* @__PURE__ */ jsx19("p", { className: "muted", children: copy.noApplication });
1419
- if (resource.loading) return /* @__PURE__ */ jsx19("p", { className: "muted", children: copy.loadingMeetings });
1420
- if (resource.error) return /* @__PURE__ */ jsx19("p", { role: "alert", className: "muted", children: resource.error });
1421
- if (meetings.length === 0) return /* @__PURE__ */ jsx19("p", { className: "muted", children: copy.noMeetings });
1422
- return /* @__PURE__ */ jsxs18("div", { style: { display: "grid", gap: 12 }, children: [
1423
- meetings.map((meeting) => /* @__PURE__ */ jsxs18("section", { className: "panel", children: [
1424
- /* @__PURE__ */ jsx19("strong", { children: new Date(meeting.startAt).toLocaleString() }),
1425
- /* @__PURE__ */ jsxs18("p", { className: "muted", children: [
1486
+ if (!applicationId) return /* @__PURE__ */ jsx20("p", { className: "muted", children: copy.noApplication });
1487
+ if (resource.loading) return /* @__PURE__ */ jsx20("p", { className: "muted", children: copy.loadingMeetings });
1488
+ if (resource.error) return /* @__PURE__ */ jsx20("p", { role: "alert", className: "muted", children: resource.error });
1489
+ if (meetings.length === 0) return /* @__PURE__ */ jsx20("p", { className: "muted", children: copy.noMeetings });
1490
+ return /* @__PURE__ */ jsxs19("div", { style: { display: "grid", gap: 12 }, children: [
1491
+ meetings.map((meeting) => /* @__PURE__ */ jsxs19("section", { className: "panel", children: [
1492
+ /* @__PURE__ */ jsx20("strong", { children: new Date(meeting.startAt).toLocaleString() }),
1493
+ /* @__PURE__ */ jsxs19("p", { className: "muted", children: [
1426
1494
  meeting.status,
1427
1495
  " \xB7 ",
1428
1496
  resource.data?.timezone
1429
1497
  ] }),
1430
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
1431
- meeting.meetUrl ? /* @__PURE__ */ jsx19("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.joinMeeting }) : null,
1432
- meeting.htmlLink ? /* @__PURE__ */ jsx19("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.openCalendar }) : null,
1433
- meeting.status === "scheduled" ? /* @__PURE__ */ jsxs18(Fragment10, { children: [
1434
- /* @__PURE__ */ jsx19(Button12, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: copy.reschedule }),
1435
- /* @__PURE__ */ jsx19(Button12, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: copy.cancel })
1498
+ /* @__PURE__ */ jsxs19("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
1499
+ meeting.meetUrl ? /* @__PURE__ */ jsx20("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.joinMeeting }) : null,
1500
+ meeting.htmlLink ? /* @__PURE__ */ jsx20("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.openCalendar }) : null,
1501
+ meeting.status === "scheduled" ? /* @__PURE__ */ jsxs19(Fragment10, { children: [
1502
+ /* @__PURE__ */ jsx20(Button12, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: copy.reschedule }),
1503
+ /* @__PURE__ */ jsx20(Button12, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: copy.cancel })
1436
1504
  ] }) : null
1437
1505
  ] })
1438
1506
  ] }, meeting.id)),
1439
- note ? /* @__PURE__ */ jsx19("p", { role: "status", className: "muted", children: note }) : null
1507
+ note ? /* @__PURE__ */ jsx20("p", { role: "status", className: "muted", children: note }) : null
1440
1508
  ] });
1441
1509
  }
1442
1510
 
1443
1511
  // src/ui/admin-people-workspace.tsx
1444
- import { Fragment as Fragment11, jsx as jsx20, jsxs as jsxs19 } from "preact/jsx-runtime";
1512
+ import { Fragment as Fragment11, jsx as jsx21, jsxs as jsxs20 } from "preact/jsx-runtime";
1445
1513
  function PeopleBody(props) {
1446
1514
  const copy = useChapterCopy().admin.records;
1447
1515
  const {
@@ -1470,7 +1538,7 @@ function PeopleBody(props) {
1470
1538
  const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx, copy) : void 0;
1471
1539
  const resolvedLifecycle = typeof lifecycleAdapter === "function" ? lifecycleAdapter(ctx) : lifecycleAdapter ?? builtInLifecycle;
1472
1540
  const workspaceClient = client;
1473
- return /* @__PURE__ */ jsx20(AdminPage, { children: /* @__PURE__ */ jsx20(
1541
+ return /* @__PURE__ */ jsx21(AdminPage, { children: /* @__PURE__ */ jsx21(
1474
1542
  CrmWorkspace,
1475
1543
  {
1476
1544
  crm,
@@ -1486,9 +1554,9 @@ function PeopleBody(props) {
1486
1554
  collapseClosedDetail,
1487
1555
  lifecycle: resolvedLifecycle,
1488
1556
  requireLifecycleAdapter: requireLifecycleAdapter ?? lifecycle,
1489
- renderSummary: (context) => /* @__PURE__ */ jsxs19(Fragment11, { children: [
1557
+ renderSummary: (context) => /* @__PURE__ */ jsxs20(Fragment11, { children: [
1490
1558
  renderSummary?.(context, ctx),
1491
- type === "person" ? /* @__PURE__ */ jsx20(AccountPeopleSync, { getToken: ctx.getToken, refresh: context.query.refresh }) : null
1559
+ type === "person" ? /* @__PURE__ */ jsx21(AccountPeopleSync, { getToken: ctx.getToken, refresh: context.query.refresh }) : null
1492
1560
  ] }),
1493
1561
  renderMaster: renderMaster ? (context) => renderMaster(context, ctx) : void 0,
1494
1562
  renderDetailHeader: renderDetailHeader ? (context) => renderDetailHeader(context, ctx) : void 0,
@@ -1502,7 +1570,7 @@ function PeopleBody(props) {
1502
1570
  const add = (id) => {
1503
1571
  const tab = byId.get(id);
1504
1572
  if (tab && lifecycle && type === "person" && id === "profile" && !lifecycleAdapter) {
1505
- tabs.push({ ...tab, panel: /* @__PURE__ */ jsx20(
1573
+ tabs.push({ ...tab, panel: /* @__PURE__ */ jsx21(
1506
1574
  ChapterPersonProfile,
1507
1575
  {
1508
1576
  recordId: record.id,
@@ -1517,7 +1585,7 @@ function PeopleBody(props) {
1517
1585
  return;
1518
1586
  }
1519
1587
  if (tab && lifecycle && applicationId && id === "stage" && !lifecycleAdapter) {
1520
- tabs.push({ ...tab, panel: /* @__PURE__ */ jsx20(
1588
+ tabs.push({ ...tab, panel: /* @__PURE__ */ jsx21(
1521
1589
  ApplicationStagePanel,
1522
1590
  {
1523
1591
  applicationId,
@@ -1530,7 +1598,7 @@ function PeopleBody(props) {
1530
1598
  }
1531
1599
  if (tab) tabs.push(lifecycle && !applicationId && id === "stage" ? {
1532
1600
  ...tab,
1533
- panel: record.stage == null || !applicationStages.includes(record.stage) ? /* @__PURE__ */ jsx20(
1601
+ panel: record.stage == null || !applicationStages.includes(record.stage) ? /* @__PURE__ */ jsx21(
1534
1602
  CandidateStagePanel,
1535
1603
  {
1536
1604
  stages: def.pipeline?.stages ?? [],
@@ -1539,8 +1607,8 @@ function PeopleBody(props) {
1539
1607
  onMove: context.moveStage
1540
1608
  },
1541
1609
  record.id + record.updatedAt
1542
- ) : /* @__PURE__ */ jsxs19(Fragment11, { children: [
1543
- /* @__PURE__ */ jsx20(ApplicationLinkRepair, { recordId: record.id, getToken: ctx.getToken, onChanged: () => {
1610
+ ) : /* @__PURE__ */ jsxs20(Fragment11, { children: [
1611
+ /* @__PURE__ */ jsx21(ApplicationLinkRepair, { recordId: record.id, getToken: ctx.getToken, onChanged: () => {
1544
1612
  context.refreshRecord();
1545
1613
  context.refreshList();
1546
1614
  } }, record.id),
@@ -1554,21 +1622,21 @@ function PeopleBody(props) {
1554
1622
  tabs.push({
1555
1623
  id: "comms",
1556
1624
  label: copy.comms,
1557
- panel: /* @__PURE__ */ jsx20(RecordCommsPanel, { client, recordId: record.id, getToken: ctx.getToken })
1625
+ panel: /* @__PURE__ */ jsx21(RecordCommsPanel, { client, recordId: record.id, getToken: ctx.getToken })
1558
1626
  });
1559
1627
  tabs.push({
1560
1628
  id: "history",
1561
1629
  label: copy.commsHistory,
1562
- panel: /* @__PURE__ */ jsx20(RecordCommsHistory, { client, recordId: record.id })
1630
+ panel: /* @__PURE__ */ jsx21(RecordCommsHistory, { client, recordId: record.id })
1563
1631
  });
1564
1632
  }
1565
1633
  if (lifecycle) {
1566
1634
  tabs.push({
1567
1635
  id: "scheduling",
1568
1636
  label: copy.scheduling,
1569
- panel: /* @__PURE__ */ jsx20(RecordSchedulingPanel, { getToken: ctx.getToken, applicationId })
1637
+ panel: /* @__PURE__ */ jsx21(RecordSchedulingPanel, { getToken: ctx.getToken, applicationId })
1570
1638
  });
1571
- tabs.push({ id: "invitation", label: copy.invitation, panel: /* @__PURE__ */ jsx20(
1639
+ tabs.push({ id: "invitation", label: copy.invitation, panel: /* @__PURE__ */ jsx21(
1572
1640
  RecordAdmissionPanel,
1573
1641
  {
1574
1642
  getToken: ctx.getToken,
@@ -1579,9 +1647,10 @@ function PeopleBody(props) {
1579
1647
  }
1580
1648
  }
1581
1649
  ) });
1582
- tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */ jsxs19(Fragment11, { children: [
1583
- /* @__PURE__ */ jsx20(BillingCard, { record }),
1584
- applicationId ? /* @__PURE__ */ jsx20(
1650
+ tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */ jsxs20(Fragment11, { children: [
1651
+ /* @__PURE__ */ jsx21(BillingCard, { record }),
1652
+ applicationId ? /* @__PURE__ */ jsx21(MemberTierCard, { getToken: ctx.getToken, applicationId }) : null,
1653
+ applicationId ? /* @__PURE__ */ jsx21(
1585
1654
  NamedSeatRecordPanel,
1586
1655
  {
1587
1656
  getToken: ctx.getToken,
@@ -1600,14 +1669,14 @@ function PeopleBody(props) {
1600
1669
  tabs.push({
1601
1670
  id: "notes",
1602
1671
  label: copy.notes,
1603
- panel: /* @__PURE__ */ jsx20(RecordActivityPanel, { client, recordId: record.id })
1672
+ panel: /* @__PURE__ */ jsx21(RecordActivityPanel, { client, recordId: record.id })
1604
1673
  });
1605
1674
  add("connections");
1606
1675
  if (lifecycle) {
1607
1676
  tabs.push({
1608
1677
  id: "access",
1609
1678
  label: copy.access,
1610
- panel: /* @__PURE__ */ jsx20(
1679
+ panel: /* @__PURE__ */ jsx21(
1611
1680
  RecordActions,
1612
1681
  {
1613
1682
  getToken: ctx.getToken,
@@ -1626,7 +1695,7 @@ function PeopleBody(props) {
1626
1695
  tabs.push({
1627
1696
  id: "sharing",
1628
1697
  label: copy.sharing,
1629
- panel: /* @__PURE__ */ jsx20(NetworkShareActions, { recordId: record.id, recordType: record.type, getToken: ctx.getToken })
1698
+ panel: /* @__PURE__ */ jsx21(NetworkShareActions, { recordId: record.id, recordType: record.type, getToken: ctx.getToken })
1630
1699
  });
1631
1700
  }
1632
1701
  return extendRecordTabs?.(tabs, context) ?? tabs;
@@ -1636,7 +1705,7 @@ function PeopleBody(props) {
1636
1705
  }
1637
1706
 
1638
1707
  // src/ui/admin-people.tsx
1639
- import { jsx as jsx21 } from "preact/jsx-runtime";
1708
+ import { jsx as jsx22 } from "preact/jsx-runtime";
1640
1709
  var DEFAULT_ROLES = ["provisional", "member", "admin"];
1641
1710
  function collectionSection(options) {
1642
1711
  const { crm, type } = options;
@@ -1667,7 +1736,7 @@ function collectionSection(options) {
1667
1736
  return {
1668
1737
  id,
1669
1738
  label,
1670
- render: (ctx) => /* @__PURE__ */ jsx21(
1739
+ render: (ctx) => /* @__PURE__ */ jsx22(
1671
1740
  PeopleBody,
1672
1741
  {
1673
1742
  crm,
@@ -1740,14 +1809,14 @@ import {
1740
1809
  Field as Field8,
1741
1810
  MasterDetail,
1742
1811
  Pagination,
1743
- Select as Select7
1812
+ Select as Select8
1744
1813
  } from "@odla-ai/ui/components";
1745
- import { useEffect as useEffect7, useMemo, useState as useState19 } from "preact/hooks";
1814
+ import { useEffect as useEffect8, useMemo, useState as useState20 } from "preact/hooks";
1746
1815
 
1747
1816
  // src/ui/admin-network-notes.tsx
1748
1817
  import { Button as Button13, Field as Field7, Textarea as Textarea2 } from "@odla-ai/ui/components";
1749
- import { useState as useState18 } from "preact/hooks";
1750
- import { jsx as jsx22, jsxs as jsxs20 } from "preact/jsx-runtime";
1818
+ import { useState as useState19 } from "preact/hooks";
1819
+ import { jsx as jsx23, jsxs as jsxs21 } from "preact/jsx-runtime";
1751
1820
  function displayValue(value) {
1752
1821
  if (Array.isArray(value)) return value.join(", ");
1753
1822
  if (value && typeof value === "object") return JSON.stringify(value);
@@ -1762,9 +1831,9 @@ function PrivateNotes(props) {
1762
1831
  });
1763
1832
  const path = `/api/admin/network/notes?${query}`;
1764
1833
  const notes = useAdminResource(ctx.getToken, path);
1765
- const [body, setBody] = useState18("");
1766
- const [busy, setBusy] = useState18(false);
1767
- const [error, setError] = useState18(null);
1834
+ const [body, setBody] = useState19("");
1835
+ const [busy, setBusy] = useState19(false);
1836
+ const [error, setError] = useState19(null);
1768
1837
  const submit = async () => {
1769
1838
  const trimmed = body.trim();
1770
1839
  if (!trimmed) return;
@@ -1783,18 +1852,18 @@ function PrivateNotes(props) {
1783
1852
  setBusy(false);
1784
1853
  }
1785
1854
  };
1786
- return /* @__PURE__ */ jsxs20("section", { style: { display: "grid", gap: 12 }, children: [
1787
- /* @__PURE__ */ jsxs20("h3", { style: { margin: "8px 0 0" }, children: [
1855
+ return /* @__PURE__ */ jsxs21("section", { style: { display: "grid", gap: 12 }, children: [
1856
+ /* @__PURE__ */ jsxs21("h3", { style: { margin: "8px 0 0" }, children: [
1788
1857
  "Private to ",
1789
1858
  leaderName
1790
1859
  ] }),
1791
- /* @__PURE__ */ jsx22(
1860
+ /* @__PURE__ */ jsx23(
1792
1861
  Field7,
1793
1862
  {
1794
1863
  label: "Add a note",
1795
1864
  hint: `Visible only to ${leaderName} admins. Nothing is written back to the follower.`,
1796
1865
  error,
1797
- children: /* @__PURE__ */ jsx22(
1866
+ children: /* @__PURE__ */ jsx23(
1798
1867
  Textarea2,
1799
1868
  {
1800
1869
  rows: 4,
@@ -1805,13 +1874,13 @@ function PrivateNotes(props) {
1805
1874
  )
1806
1875
  }
1807
1876
  ),
1808
- /* @__PURE__ */ jsx22("div", { children: /* @__PURE__ */ jsx22(Button13, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save note" }) }),
1809
- notes.loading ? /* @__PURE__ */ jsx22("p", { className: "muted", children: "Loading notes\u2026" }) : null,
1810
- notes.error ? /* @__PURE__ */ jsx22("p", { role: "alert", children: notes.error }) : null,
1811
- !notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx22("p", { className: "muted", children: "No notes yet." }) : null,
1812
- (notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs20("article", { className: "panel", style: { padding: 14 }, children: [
1813
- /* @__PURE__ */ jsx22("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
1814
- /* @__PURE__ */ jsxs20("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
1877
+ /* @__PURE__ */ jsx23("div", { children: /* @__PURE__ */ jsx23(Button13, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save note" }) }),
1878
+ notes.loading ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "Loading notes\u2026" }) : null,
1879
+ notes.error ? /* @__PURE__ */ jsx23("p", { role: "alert", children: notes.error }) : null,
1880
+ !notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "No notes yet." }) : null,
1881
+ (notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs21("article", { className: "panel", style: { padding: 14 }, children: [
1882
+ /* @__PURE__ */ jsx23("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
1883
+ /* @__PURE__ */ jsxs21("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
1815
1884
  note.authorEmail ?? note.authorId,
1816
1885
  " \xB7 ",
1817
1886
  new Date(note.createdAt).toLocaleString()
@@ -1828,9 +1897,9 @@ function SharedNotes(props) {
1828
1897
  });
1829
1898
  const path = `/api/admin/network/shared-notes?${query}`;
1830
1899
  const notes = useAdminResource(ctx.getToken, path);
1831
- const [body, setBody] = useState18("");
1832
- const [busy, setBusy] = useState18(false);
1833
- const [error, setError] = useState18(null);
1900
+ const [body, setBody] = useState19("");
1901
+ const [busy, setBusy] = useState19(false);
1902
+ const [error, setError] = useState19(null);
1834
1903
  const submit = async () => {
1835
1904
  const trimmed = body.trim();
1836
1905
  if (!trimmed) return;
@@ -1849,18 +1918,18 @@ function SharedNotes(props) {
1849
1918
  setBusy(false);
1850
1919
  }
1851
1920
  };
1852
- return /* @__PURE__ */ jsxs20("section", { style: { display: "grid", gap: 12 }, children: [
1853
- /* @__PURE__ */ jsxs20("h3", { style: { margin: "8px 0 0" }, children: [
1921
+ return /* @__PURE__ */ jsxs21("section", { style: { display: "grid", gap: 12 }, children: [
1922
+ /* @__PURE__ */ jsxs21("h3", { style: { margin: "8px 0 0" }, children: [
1854
1923
  "Shared with ",
1855
1924
  targetName
1856
1925
  ] }),
1857
- /* @__PURE__ */ jsx22(
1926
+ /* @__PURE__ */ jsx23(
1858
1927
  Field7,
1859
1928
  {
1860
1929
  label: "Add a shared note",
1861
1930
  hint: `This is written to the record's Notes feed for ${targetName} admins.`,
1862
1931
  error,
1863
- children: /* @__PURE__ */ jsx22(
1932
+ children: /* @__PURE__ */ jsx23(
1864
1933
  Textarea2,
1865
1934
  {
1866
1935
  rows: 4,
@@ -1871,13 +1940,13 @@ function SharedNotes(props) {
1871
1940
  )
1872
1941
  }
1873
1942
  ),
1874
- /* @__PURE__ */ jsx22("div", { children: /* @__PURE__ */ jsx22(Button13, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Sharing\u2026" : `Share with ${targetName}` }) }),
1875
- notes.loading ? /* @__PURE__ */ jsx22("p", { className: "muted", children: "Loading shared notes\u2026" }) : null,
1876
- notes.error ? /* @__PURE__ */ jsx22("p", { role: "alert", children: notes.error }) : null,
1877
- !notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx22("p", { className: "muted", children: "No shared notes yet." }) : null,
1878
- (notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs20("article", { className: "panel", style: { padding: 14 }, children: [
1879
- /* @__PURE__ */ jsx22("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
1880
- /* @__PURE__ */ jsxs20("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
1943
+ /* @__PURE__ */ jsx23("div", { children: /* @__PURE__ */ jsx23(Button13, { disabled: busy || !body.trim(), onClick: () => void submit(), children: busy ? "Sharing\u2026" : `Share with ${targetName}` }) }),
1944
+ notes.loading ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "Loading shared notes\u2026" }) : null,
1945
+ notes.error ? /* @__PURE__ */ jsx23("p", { role: "alert", children: notes.error }) : null,
1946
+ !notes.loading && !notes.error && (notes.data?.notes.length ?? 0) === 0 ? /* @__PURE__ */ jsx23("p", { className: "muted", children: "No shared notes yet." }) : null,
1947
+ (notes.data?.notes ?? []).map((note) => /* @__PURE__ */ jsxs21("article", { className: "panel", style: { padding: 14 }, children: [
1948
+ /* @__PURE__ */ jsx23("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: note.body }),
1949
+ /* @__PURE__ */ jsxs21("p", { className: "muted", style: { margin: "8px 0 0", fontSize: 12 }, children: [
1881
1950
  "Shared with ",
1882
1951
  targetName,
1883
1952
  " \xB7 ",
@@ -1888,23 +1957,23 @@ function SharedNotes(props) {
1888
1957
  }
1889
1958
  function NetworkRecordDetail(props) {
1890
1959
  const { ctx, targetId, targetName, leaderName, sharedEnabled, record } = props;
1891
- return /* @__PURE__ */ jsxs20("div", { style: { display: "grid", gap: 14 }, children: [
1892
- /* @__PURE__ */ jsxs20("header", { children: [
1893
- /* @__PURE__ */ jsx22("h2", { style: { margin: 0 }, children: record.name }),
1894
- record.stage ? /* @__PURE__ */ jsx22("span", { className: "badge", style: { marginTop: 8 }, children: record.stage.replaceAll("_", " ") }) : null
1960
+ return /* @__PURE__ */ jsxs21("div", { style: { display: "grid", gap: 14 }, children: [
1961
+ /* @__PURE__ */ jsxs21("header", { children: [
1962
+ /* @__PURE__ */ jsx23("h2", { style: { margin: 0 }, children: record.name }),
1963
+ record.stage ? /* @__PURE__ */ jsx23("span", { className: "badge", style: { marginTop: 8 }, children: record.stage.replaceAll("_", " ") }) : null
1895
1964
  ] }),
1896
- /* @__PURE__ */ jsxs20("dl", { style: { display: "grid", gridTemplateColumns: "minmax(8rem, auto) 1fr", gap: "8px 16px", margin: 0 }, children: [
1897
- Object.entries(record.fields).map(([name, value]) => /* @__PURE__ */ jsxs20("div", { style: { display: "contents" }, children: [
1898
- /* @__PURE__ */ jsx22("dt", { className: "muted", children: name.replaceAll("_", " ") }),
1899
- /* @__PURE__ */ jsx22("dd", { style: { margin: 0, overflowWrap: "anywhere" }, children: displayValue(value) })
1965
+ /* @__PURE__ */ jsxs21("dl", { style: { display: "grid", gridTemplateColumns: "minmax(8rem, auto) 1fr", gap: "8px 16px", margin: 0 }, children: [
1966
+ Object.entries(record.fields).map(([name, value]) => /* @__PURE__ */ jsxs21("div", { style: { display: "contents" }, children: [
1967
+ /* @__PURE__ */ jsx23("dt", { className: "muted", children: name.replaceAll("_", " ") }),
1968
+ /* @__PURE__ */ jsx23("dd", { style: { margin: 0, overflowWrap: "anywhere" }, children: displayValue(value) })
1900
1969
  ] }, name)),
1901
- /* @__PURE__ */ jsxs20("div", { style: { display: "contents" }, children: [
1902
- /* @__PURE__ */ jsx22("dt", { className: "muted", children: "Updated" }),
1903
- /* @__PURE__ */ jsx22("dd", { style: { margin: 0 }, children: new Date(record.updatedAt).toLocaleString() })
1970
+ /* @__PURE__ */ jsxs21("div", { style: { display: "contents" }, children: [
1971
+ /* @__PURE__ */ jsx23("dt", { className: "muted", children: "Updated" }),
1972
+ /* @__PURE__ */ jsx23("dd", { style: { margin: 0 }, children: new Date(record.updatedAt).toLocaleString() })
1904
1973
  ] })
1905
1974
  ] }),
1906
- /* @__PURE__ */ jsxs20("div", { style: { display: "grid", gap: 22 }, children: [
1907
- /* @__PURE__ */ jsx22(
1975
+ /* @__PURE__ */ jsxs21("div", { style: { display: "grid", gap: 22 }, children: [
1976
+ /* @__PURE__ */ jsx23(
1908
1977
  PrivateNotes,
1909
1978
  {
1910
1979
  ctx,
@@ -1913,7 +1982,7 @@ function NetworkRecordDetail(props) {
1913
1982
  leaderName
1914
1983
  }
1915
1984
  ),
1916
- sharedEnabled ? /* @__PURE__ */ jsx22(
1985
+ sharedEnabled ? /* @__PURE__ */ jsx23(
1917
1986
  SharedNotes,
1918
1987
  {
1919
1988
  ctx,
@@ -1927,11 +1996,11 @@ function NetworkRecordDetail(props) {
1927
1996
  }
1928
1997
 
1929
1998
  // src/ui/admin-network-records.tsx
1930
- import { jsx as jsx23, jsxs as jsxs21 } from "preact/jsx-runtime";
1999
+ import { jsx as jsx24, jsxs as jsxs22 } from "preact/jsx-runtime";
1931
2000
  function RecordsExplorer(props) {
1932
2001
  const { ctx, target, type, leaderName } = props;
1933
- const [offset, setOffset] = useState19(0);
1934
- const [search, setSearch] = useState19("");
2002
+ const [offset, setOffset] = useState20(0);
2003
+ const [search, setSearch] = useState20("");
1935
2004
  const query = new URLSearchParams({
1936
2005
  targetId: target.id,
1937
2006
  type,
@@ -1948,12 +2017,12 @@ function RecordsExplorer(props) {
1948
2017
  {
1949
2018
  key: "name",
1950
2019
  header: "Name",
1951
- cell: (record) => /* @__PURE__ */ jsx23("a", { href: ctx.href({ recordId: record.id, detailTab: null }), children: record.name })
2020
+ cell: (record) => /* @__PURE__ */ jsx24("a", { href: ctx.href({ recordId: record.id, detailTab: null }), children: record.name })
1952
2021
  },
1953
2022
  {
1954
2023
  key: "stage",
1955
2024
  header: "Stage",
1956
- cell: (record) => record.stage ? /* @__PURE__ */ jsx23("span", { className: "badge", children: record.stage.replaceAll("_", " ") }) : "\u2014"
2025
+ cell: (record) => record.stage ? /* @__PURE__ */ jsx24("span", { className: "badge", children: record.stage.replaceAll("_", " ") }) : "\u2014"
1957
2026
  },
1958
2027
  {
1959
2028
  key: "updatedAt",
@@ -1961,7 +2030,7 @@ function RecordsExplorer(props) {
1961
2030
  cell: (record) => new Date(record.updatedAt).toLocaleDateString()
1962
2031
  }
1963
2032
  ], [ctx]);
1964
- return /* @__PURE__ */ jsx23(
2033
+ return /* @__PURE__ */ jsx24(
1965
2034
  MasterDetail,
1966
2035
  {
1967
2036
  collapseClosedDetail: true,
@@ -1969,13 +2038,13 @@ function RecordsExplorer(props) {
1969
2038
  detailLabel: "Follower record details, private notes, and shared notes",
1970
2039
  detailOpen: Boolean(selected),
1971
2040
  closeHref: ctx.href({ recordId: null, detailTab: null }),
1972
- master: /* @__PURE__ */ jsxs21("div", { style: { display: "grid", gap: 14, padding: "var(--ui-space-4)" }, children: [
1973
- /* @__PURE__ */ jsxs21("h2", { style: { margin: 0, fontSize: 18 }, children: [
2041
+ master: /* @__PURE__ */ jsxs22("div", { style: { display: "grid", gap: 14, padding: "var(--ui-space-4)" }, children: [
2042
+ /* @__PURE__ */ jsxs22("h2", { style: { margin: 0, fontSize: 18 }, children: [
1974
2043
  target.name,
1975
2044
  " \xB7 ",
1976
2045
  type.replaceAll("_", " ")
1977
2046
  ] }),
1978
- /* @__PURE__ */ jsx23(
2047
+ /* @__PURE__ */ jsx24(
1979
2048
  DataTable2,
1980
2049
  {
1981
2050
  manual: true,
@@ -1995,7 +2064,7 @@ function RecordsExplorer(props) {
1995
2064
  ariaLabel: `${target.name} ${type} records`
1996
2065
  }
1997
2066
  ),
1998
- /* @__PURE__ */ jsx23(
2067
+ /* @__PURE__ */ jsx24(
1999
2068
  Pagination,
2000
2069
  {
2001
2070
  offset: page.data?.offset ?? offset,
@@ -2010,7 +2079,7 @@ function RecordsExplorer(props) {
2010
2079
  }
2011
2080
  )
2012
2081
  ] }),
2013
- detail: selected ? /* @__PURE__ */ jsx23(
2082
+ detail: selected ? /* @__PURE__ */ jsx24(
2014
2083
  NetworkRecordDetail,
2015
2084
  {
2016
2085
  ctx,
@@ -2027,27 +2096,27 @@ function RecordsExplorer(props) {
2027
2096
  function NetworkRecordsWorkspace(props) {
2028
2097
  const { chapter, ctx } = props;
2029
2098
  const targets = useAdminResource(ctx.getToken, "/api/admin/network/targets");
2030
- const [targetId, setTargetId] = useState19("");
2031
- const [type, setType] = useState19("");
2099
+ const [targetId, setTargetId] = useState20("");
2100
+ const [type, setType] = useState20("");
2032
2101
  const target = targets.data?.targets.find((candidate) => candidate.id === targetId) ?? targets.data?.targets[0];
2033
- useEffect7(() => {
2102
+ useEffect8(() => {
2034
2103
  if (target && target.id !== targetId) setTargetId(target.id);
2035
2104
  }, [target, targetId]);
2036
- useEffect7(() => {
2105
+ useEffect8(() => {
2037
2106
  const first = target?.types[0] ?? "";
2038
2107
  if (target && !target.types.includes(type)) setType(first);
2039
2108
  }, [target, type]);
2040
- if (targets.loading) return /* @__PURE__ */ jsx23(AdminNote, { children: "Loading follower records\u2026" });
2041
- if (targets.error || !targets.data) return /* @__PURE__ */ jsxs21(AdminNote, { children: [
2109
+ if (targets.loading) return /* @__PURE__ */ jsx24(AdminNote, { children: "Loading follower records\u2026" });
2110
+ if (targets.error || !targets.data) return /* @__PURE__ */ jsxs22(AdminNote, { children: [
2042
2111
  "Could not load followers",
2043
2112
  targets.error ? `: ${targets.error}` : "",
2044
2113
  "."
2045
2114
  ] });
2046
- if (!target) return /* @__PURE__ */ jsx23(AdminPage, { children: /* @__PURE__ */ jsx23(Panel, { title: "Follower records", children: /* @__PURE__ */ jsx23("p", { className: "muted", children: "No followers are configured." }) }) });
2047
- return /* @__PURE__ */ jsxs21(AdminPage, { children: [
2048
- /* @__PURE__ */ jsxs21("div", { style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "end" }, children: [
2049
- /* @__PURE__ */ jsx23(Field8, { label: "Chapter", style: { minWidth: 240 }, children: /* @__PURE__ */ jsx23(
2050
- Select7,
2115
+ if (!target) return /* @__PURE__ */ jsx24(AdminPage, { children: /* @__PURE__ */ jsx24(Panel, { title: "Follower records", children: /* @__PURE__ */ jsx24("p", { className: "muted", children: "No followers are configured." }) }) });
2116
+ return /* @__PURE__ */ jsxs22(AdminPage, { children: [
2117
+ /* @__PURE__ */ jsxs22("div", { style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "end" }, children: [
2118
+ /* @__PURE__ */ jsx24(Field8, { label: "Chapter", style: { minWidth: 240 }, children: /* @__PURE__ */ jsx24(
2119
+ Select8,
2051
2120
  {
2052
2121
  value: target.id,
2053
2122
  options: targets.data.targets.map((item) => ({ value: item.id, label: item.name })),
@@ -2058,8 +2127,8 @@ function NetworkRecordsWorkspace(props) {
2058
2127
  }
2059
2128
  }
2060
2129
  ) }),
2061
- /* @__PURE__ */ jsx23(Field8, { label: "Record type", style: { minWidth: 180 }, children: /* @__PURE__ */ jsx23(
2062
- Select7,
2130
+ /* @__PURE__ */ jsx24(Field8, { label: "Record type", style: { minWidth: 180 }, children: /* @__PURE__ */ jsx24(
2131
+ Select8,
2063
2132
  {
2064
2133
  value: type,
2065
2134
  options: target.types.map((item) => ({ value: item, label: item.replaceAll("_", " ") })),
@@ -2070,7 +2139,7 @@ function NetworkRecordsWorkspace(props) {
2070
2139
  }
2071
2140
  ) })
2072
2141
  ] }),
2073
- type ? /* @__PURE__ */ jsx23(
2142
+ type ? /* @__PURE__ */ jsx24(
2074
2143
  RecordsExplorer,
2075
2144
  {
2076
2145
  ctx,
@@ -2079,7 +2148,7 @@ function NetworkRecordsWorkspace(props) {
2079
2148
  leaderName: chapter.name
2080
2149
  },
2081
2150
  `${target.id}:${type}`
2082
- ) : /* @__PURE__ */ jsx23(Panel, { children: /* @__PURE__ */ jsx23("p", { className: "muted", children: "This follower has no browsable record types configured." }) })
2151
+ ) : /* @__PURE__ */ jsx24(Panel, { children: /* @__PURE__ */ jsx24("p", { className: "muted", children: "This follower has no browsable record types configured." }) })
2083
2152
  ] });
2084
2153
  }
2085
2154
  function networkRecordsWorkspace(chapter) {
@@ -2087,7 +2156,7 @@ function networkRecordsWorkspace(chapter) {
2087
2156
  id: "network",
2088
2157
  label: chapter.copy.admin.workspaces.network,
2089
2158
  defaultViewId: "records",
2090
- render: (ctx) => /* @__PURE__ */ jsx23(NetworkRecordsWorkspace, { chapter, ctx })
2159
+ render: (ctx) => /* @__PURE__ */ jsx24(NetworkRecordsWorkspace, { chapter, ctx })
2091
2160
  };
2092
2161
  }
2093
2162
 
@@ -2098,20 +2167,20 @@ import { StatBand as StatBand2, StepPipeline as StepPipeline2 } from "@odla-ai/u
2098
2167
  var EDGE_UNPAIRED = "edge_unpaired";
2099
2168
 
2100
2169
  // src/ui/admin-portfolio.tsx
2101
- import { jsx as jsx24, jsxs as jsxs22 } from "preact/jsx-runtime";
2170
+ import { jsx as jsx25, jsxs as jsxs23 } from "preact/jsx-runtime";
2102
2171
  function LeaderNetworkOverview({ ctx }) {
2103
2172
  const copy = useChapterCopy().admin.network;
2104
2173
  const { data, loading, error } = useAdminResource(
2105
2174
  ctx.getToken,
2106
2175
  "/api/admin/network/rollup"
2107
2176
  );
2108
- if (loading) return /* @__PURE__ */ jsx24(AdminNote, { children: copy.loadingRollup });
2109
- if (error || !data) return /* @__PURE__ */ jsxs22(AdminNote, { children: [
2177
+ if (loading) return /* @__PURE__ */ jsx25(AdminNote, { children: copy.loadingRollup });
2178
+ if (error || !data) return /* @__PURE__ */ jsxs23(AdminNote, { children: [
2110
2179
  copy.loadRollupFailed,
2111
2180
  error ? `: ${error}` : "",
2112
2181
  "."
2113
2182
  ] });
2114
- return /* @__PURE__ */ jsx24(LeaderNetworkOverviewView, { data });
2183
+ return /* @__PURE__ */ jsx25(LeaderNetworkOverviewView, { data });
2115
2184
  }
2116
2185
  function unpairedEdge(target) {
2117
2186
  const error = target.transport.error;
@@ -2119,63 +2188,63 @@ function unpairedEdge(target) {
2119
2188
  }
2120
2189
  function LeaderNetworkOverviewView({ data }) {
2121
2190
  const copy = useChapterCopy().admin.network;
2122
- if (data.configured === 0) return /* @__PURE__ */ jsx24(AdminPage, { children: /* @__PURE__ */ jsx24(Panel, { title: copy.overview, children: /* @__PURE__ */ jsx24("p", { className: "muted", children: copy.noFollowers }) }) });
2191
+ if (data.configured === 0) return /* @__PURE__ */ jsx25(AdminPage, { children: /* @__PURE__ */ jsx25(Panel, { title: copy.overview, children: /* @__PURE__ */ jsx25("p", { className: "muted", children: copy.noFollowers }) }) });
2123
2192
  const stats = [
2124
2193
  { value: data.configured.toLocaleString(), label: copy.configuredFollowers },
2125
2194
  { value: data.available.toLocaleString(), label: copy.reachableFollowers },
2126
2195
  { value: data.totalRecords.toLocaleString(), label: copy.recordsAcrossFollowers }
2127
2196
  ];
2128
2197
  const steps = data.types.map((type) => ({
2129
- icon: /* @__PURE__ */ jsx24("span", { style: { fontSize: 22, fontWeight: 700 }, children: type.total }),
2198
+ icon: /* @__PURE__ */ jsx25("span", { style: { fontSize: 22, fontWeight: 700 }, children: type.total }),
2130
2199
  title: type.type.replaceAll("_", " "),
2131
2200
  description: Object.entries(type.stages).filter(([, count]) => count > 0).map(([stage, count]) => `${stage.replaceAll("_", " ")} ${count}`).join(" \xB7 ") || copy.noRecordTotals
2132
2201
  }));
2133
- return /* @__PURE__ */ jsxs22(AdminPage, { children: [
2134
- /* @__PURE__ */ jsx24(StatBand2, { stats }),
2135
- /* @__PURE__ */ jsx24(Panel, { title: copy.followers, children: /* @__PURE__ */ jsx24("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }, children: data.targets.map((target) => /* @__PURE__ */ jsx24(FollowerRow, { target }, target.id)) }) }),
2136
- /* @__PURE__ */ jsx24(Panel, { title: copy.recordTotals, children: steps.length ? /* @__PURE__ */ jsx24(StepPipeline2, { ariaLabel: copy.recordTotals, steps }) : /* @__PURE__ */ jsx24("p", { className: "muted", children: copy.noRecordTotals }) })
2202
+ return /* @__PURE__ */ jsxs23(AdminPage, { children: [
2203
+ /* @__PURE__ */ jsx25(StatBand2, { stats }),
2204
+ /* @__PURE__ */ jsx25(Panel, { title: copy.followers, children: /* @__PURE__ */ jsx25("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }, children: data.targets.map((target) => /* @__PURE__ */ jsx25(FollowerRow, { target }, target.id)) }) }),
2205
+ /* @__PURE__ */ jsx25(Panel, { title: copy.recordTotals, children: steps.length ? /* @__PURE__ */ jsx25(StepPipeline2, { ariaLabel: copy.recordTotals, steps }) : /* @__PURE__ */ jsx25("p", { className: "muted", children: copy.noRecordTotals }) })
2137
2206
  ] });
2138
2207
  }
2139
2208
  function FollowerRow({ target }) {
2140
2209
  const copy = useChapterCopy().admin.network;
2141
2210
  const unpaired = unpairedEdge(target);
2142
- return /* @__PURE__ */ jsxs22(
2211
+ return /* @__PURE__ */ jsxs23(
2143
2212
  "li",
2144
2213
  {
2145
2214
  "data-network-target": target.id,
2146
2215
  "data-edge-state": unpaired ? "unpaired" : "paired",
2147
2216
  style: { display: "flex", gap: 12, alignItems: "baseline", flexWrap: "wrap" },
2148
2217
  children: [
2149
- /* @__PURE__ */ jsx24("strong", { style: { minWidth: 180 }, children: target.name }),
2150
- /* @__PURE__ */ jsx24("span", { className: "badge", children: unpaired ? copy.edgeUnpaired : target.transport.reachable === null ? copy.transportNotChecked : target.transport.reachable ? copy.transportReachable : copy.transportUnavailable }),
2151
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.transport.kind === "binding" ? formatChapterCopy(copy.bindingTransport, { binding: target.transport.binding ?? "" }) : copy.urlTransport }),
2152
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.configuration.activeRevision === null ? copy.noActiveConfiguration : formatChapterCopy(copy.activeConfiguration, { revision: target.configuration.activeRevision }) }),
2153
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.publicSite.available === null ? copy.publicSiteNotChecked : target.publicSite.available ? copy.publicSiteAvailable : copy.publicSiteUnavailable }),
2154
- unpaired ? /* @__PURE__ */ jsxs22("span", { className: "muted", style: { flexBasis: "100%" }, children: [
2218
+ /* @__PURE__ */ jsx25("strong", { style: { minWidth: 180 }, children: target.name }),
2219
+ /* @__PURE__ */ jsx25("span", { className: "badge", children: unpaired ? copy.edgeUnpaired : target.transport.reachable === null ? copy.transportNotChecked : target.transport.reachable ? copy.transportReachable : copy.transportUnavailable }),
2220
+ /* @__PURE__ */ jsx25("span", { className: "muted", children: target.transport.kind === "binding" ? formatChapterCopy(copy.bindingTransport, { binding: target.transport.binding ?? "" }) : copy.urlTransport }),
2221
+ /* @__PURE__ */ jsx25("span", { className: "muted", children: target.configuration.activeRevision === null ? copy.noActiveConfiguration : formatChapterCopy(copy.activeConfiguration, { revision: target.configuration.activeRevision }) }),
2222
+ /* @__PURE__ */ jsx25("span", { className: "muted", children: target.publicSite.available === null ? copy.publicSiteNotChecked : target.publicSite.available ? copy.publicSiteAvailable : copy.publicSiteUnavailable }),
2223
+ unpaired ? /* @__PURE__ */ jsxs23("span", { className: "muted", style: { flexBasis: "100%" }, children: [
2155
2224
  copy.edgeUnpairedHint,
2156
2225
  " ",
2157
- /* @__PURE__ */ jsx24("code", { children: unpaired.hint })
2158
- ] }) : target.error ? /* @__PURE__ */ jsx24("span", { className: "muted", children: target.error }) : null,
2159
- /* @__PURE__ */ jsx24("a", { href: target.url, target: "_blank", rel: "noreferrer", style: { marginLeft: "auto" }, children: target.url })
2226
+ /* @__PURE__ */ jsx25("code", { children: unpaired.hint })
2227
+ ] }) : target.error ? /* @__PURE__ */ jsx25("span", { className: "muted", children: target.error }) : null,
2228
+ /* @__PURE__ */ jsx25("a", { href: target.url, target: "_blank", rel: "noreferrer", style: { marginLeft: "auto" }, children: target.url })
2160
2229
  ]
2161
2230
  }
2162
2231
  );
2163
2232
  }
2164
2233
 
2165
2234
  // src/ui/admin-admission.tsx
2166
- import { useEffect as useEffect8, useId, useRef as useRef2, useState as useState20 } from "preact/hooks";
2167
- import { Field as Field9, Input as Input2, Select as Select8 } from "@odla-ai/ui/components";
2168
- import { jsx as jsx25, jsxs as jsxs23 } from "preact/jsx-runtime";
2235
+ import { useEffect as useEffect9, useId, useRef as useRef2, useState as useState21 } from "preact/hooks";
2236
+ import { Field as Field9, Input as Input2, Select as Select9 } from "@odla-ai/ui/components";
2237
+ import { jsx as jsx26, jsxs as jsxs24 } from "preact/jsx-runtime";
2169
2238
  function AdmissionGrantsWorkspace(props) {
2170
2239
  const copy = useChapterCopy().admin.admission;
2171
2240
  const uid = useId();
2172
2241
  const resource = useAdminResource(props.getToken, "/api/admin/admission-grants");
2173
- const [email, setEmail] = useState20("");
2174
- const [decision, setDecision] = useState20("waive_interview");
2175
- const [note, setNote] = useState20("");
2176
- const [expires, setExpires] = useState20("");
2177
- const [busy, setBusy] = useState20(false);
2178
- const [error, setError] = useState20(null);
2242
+ const [email, setEmail] = useState21("");
2243
+ const [decision, setDecision] = useState21("waive_interview");
2244
+ const [note, setNote] = useState21("");
2245
+ const [expires, setExpires] = useState21("");
2246
+ const [busy, setBusy] = useState21(false);
2247
+ const [error, setError] = useState21(null);
2179
2248
  const submit = async (event) => {
2180
2249
  event.preventDefault();
2181
2250
  setBusy(true);
@@ -2212,36 +2281,36 @@ function AdmissionGrantsWorkspace(props) {
2212
2281
  setBusy(false);
2213
2282
  }
2214
2283
  };
2215
- return /* @__PURE__ */ jsxs23(AdminPage, { children: [
2216
- /* @__PURE__ */ jsxs23(Panel, { title: copy.title, children: [
2217
- /* @__PURE__ */ jsx25("p", { className: "muted", style: { margin: 0 }, children: copy.description }),
2218
- /* @__PURE__ */ jsxs23("form", { onSubmit: (event) => void submit(event), style: { display: "grid", gap: 12, gridTemplateColumns: "minmax(220px,2fr) minmax(180px,1fr) minmax(160px,1fr)" }, children: [
2219
- /* @__PURE__ */ jsx25(Field9, { label: copy.email, htmlFor: `${uid}-email`, children: /* @__PURE__ */ jsx25(Input2, { id: `${uid}-email`, required: true, type: "email", value: email, onInput: (event) => setEmail(event.currentTarget.value) }) }),
2220
- /* @__PURE__ */ jsx25(Field9, { label: copy.decision, htmlFor: `${uid}-decision`, children: /* @__PURE__ */ jsxs23(Select8, { id: `${uid}-decision`, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2221
- /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: copy.waive }),
2222
- /* @__PURE__ */ jsx25("option", { value: "require_interview", children: copy.require })
2284
+ return /* @__PURE__ */ jsxs24(AdminPage, { children: [
2285
+ /* @__PURE__ */ jsxs24(Panel, { title: copy.title, children: [
2286
+ /* @__PURE__ */ jsx26("p", { className: "muted", style: { margin: 0 }, children: copy.description }),
2287
+ /* @__PURE__ */ jsxs24("form", { onSubmit: (event) => void submit(event), style: { display: "grid", gap: 12, gridTemplateColumns: "minmax(220px,2fr) minmax(180px,1fr) minmax(160px,1fr)" }, children: [
2288
+ /* @__PURE__ */ jsx26(Field9, { label: copy.email, htmlFor: `${uid}-email`, children: /* @__PURE__ */ jsx26(Input2, { id: `${uid}-email`, required: true, type: "email", value: email, onInput: (event) => setEmail(event.currentTarget.value) }) }),
2289
+ /* @__PURE__ */ jsx26(Field9, { label: copy.decision, htmlFor: `${uid}-decision`, children: /* @__PURE__ */ jsxs24(Select9, { id: `${uid}-decision`, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2290
+ /* @__PURE__ */ jsx26("option", { value: "waive_interview", children: copy.waive }),
2291
+ /* @__PURE__ */ jsx26("option", { value: "require_interview", children: copy.require })
2223
2292
  ] }) }),
2224
- /* @__PURE__ */ jsx25(Field9, { label: copy.expires, htmlFor: `${uid}-expires`, children: /* @__PURE__ */ jsx25(Input2, { id: `${uid}-expires`, type: "date", value: expires, onInput: (event) => setExpires(event.currentTarget.value) }) }),
2225
- /* @__PURE__ */ jsx25(Field9, { label: copy.note, htmlFor: `${uid}-note`, style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsx25(Input2, { id: `${uid}-note`, value: note, maxLength: 2e3, onInput: (event) => setNote(event.currentTarget.value) }) }),
2226
- /* @__PURE__ */ jsx25("button", { className: "btn secondary", type: "submit", disabled: busy, children: copy.add })
2293
+ /* @__PURE__ */ jsx26(Field9, { label: copy.expires, htmlFor: `${uid}-expires`, children: /* @__PURE__ */ jsx26(Input2, { id: `${uid}-expires`, type: "date", value: expires, onInput: (event) => setExpires(event.currentTarget.value) }) }),
2294
+ /* @__PURE__ */ jsx26(Field9, { label: copy.note, htmlFor: `${uid}-note`, style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsx26(Input2, { id: `${uid}-note`, value: note, maxLength: 2e3, onInput: (event) => setNote(event.currentTarget.value) }) }),
2295
+ /* @__PURE__ */ jsx26("button", { className: "btn secondary", type: "submit", disabled: busy, children: copy.add })
2227
2296
  ] }),
2228
- error ? /* @__PURE__ */ jsx25("p", { role: "alert", children: error }) : null
2297
+ error ? /* @__PURE__ */ jsx26("p", { role: "alert", children: error }) : null
2229
2298
  ] }),
2230
- /* @__PURE__ */ jsxs23(Panel, { title: copy.ledger, children: [
2231
- resource.loading ? /* @__PURE__ */ jsx25("p", { className: "muted", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx25("p", { role: "alert", children: resource.error }) : null,
2232
- /* @__PURE__ */ jsx25("div", { style: { display: "grid", gap: 10 }, children: (resource.data?.grants ?? []).map((grant) => {
2299
+ /* @__PURE__ */ jsxs24(Panel, { title: copy.ledger, children: [
2300
+ resource.loading ? /* @__PURE__ */ jsx26("p", { className: "muted", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx26("p", { role: "alert", children: resource.error }) : null,
2301
+ /* @__PURE__ */ jsx26("div", { style: { display: "grid", gap: 10 }, children: (resource.data?.grants ?? []).map((grant) => {
2233
2302
  const state = grant.consumedAt ? copy.consumed : grant.revokedAt || !grant.active ? copy.revoked : grant.expiresAt && grant.expiresAt <= Date.now() ? copy.expired : copy.active;
2234
- return /* @__PURE__ */ jsxs23("article", { className: "card", style: { padding: 14 }, children: [
2235
- /* @__PURE__ */ jsx25("strong", { children: grant.email }),
2303
+ return /* @__PURE__ */ jsxs24("article", { className: "card", style: { padding: 14 }, children: [
2304
+ /* @__PURE__ */ jsx26("strong", { children: grant.email }),
2236
2305
  " \xB7 ",
2237
2306
  grant.decision === "waive_interview" ? copy.waive : copy.require,
2238
- /* @__PURE__ */ jsxs23("p", { className: "muted", style: { margin: "6px 0" }, children: [
2307
+ /* @__PURE__ */ jsxs24("p", { className: "muted", style: { margin: "6px 0" }, children: [
2239
2308
  grant.sourceScope === "local" ? copy.local : copy.parent,
2240
2309
  " \xB7 ",
2241
2310
  state,
2242
2311
  grant.note ? ` \xB7 ${grant.note}` : ""
2243
2312
  ] }),
2244
- grant.sourceScope === "local" && state === copy.active ? /* @__PURE__ */ jsx25("button", { className: "btn secondary mini", disabled: busy, onClick: () => void revoke(grant), children: copy.revoke }) : null
2313
+ grant.sourceScope === "local" && state === copy.active ? /* @__PURE__ */ jsx26("button", { className: "btn secondary mini", disabled: busy, onClick: () => void revoke(grant), children: copy.revoke }) : null
2245
2314
  ] }, grant.id);
2246
2315
  }) })
2247
2316
  ] })
@@ -2252,22 +2321,22 @@ function admissionGrantsWorkspace(label = "Admission") {
2252
2321
  id: "admission",
2253
2322
  label,
2254
2323
  defaultViewId: "grants",
2255
- render: (ctx) => /* @__PURE__ */ jsx25(AdmissionGrantsWorkspace, { getToken: ctx.getToken })
2324
+ render: (ctx) => /* @__PURE__ */ jsx26(AdmissionGrantsWorkspace, { getToken: ctx.getToken })
2256
2325
  };
2257
2326
  }
2258
2327
  function LeaderAdmissionAction(props) {
2259
- return /* @__PURE__ */ jsx25(LeaderAdmissionForm, { ...props }, JSON.stringify([props.targetId, props.personId, props.sourceRecordId]));
2328
+ return /* @__PURE__ */ jsx26(LeaderAdmissionForm, { ...props }, JSON.stringify([props.targetId, props.personId, props.sourceRecordId]));
2260
2329
  }
2261
2330
  function LeaderAdmissionForm(props) {
2262
2331
  const uid = useId();
2263
- const [decision, setDecision] = useState20("");
2264
- const [note, setNote] = useState20("");
2265
- const [loaded, setLoaded] = useState20(false);
2266
- const [busy, setBusy] = useState20(false);
2267
- const [error, setError] = useState20(null);
2268
- const [status, setStatus] = useState20(null);
2332
+ const [decision, setDecision] = useState21("");
2333
+ const [note, setNote] = useState21("");
2334
+ const [loaded, setLoaded] = useState21(false);
2335
+ const [busy, setBusy] = useState21(false);
2336
+ const [error, setError] = useState21(null);
2337
+ const [status, setStatus] = useState21(null);
2269
2338
  const attempt = useRef2(null);
2270
- useEffect8(() => {
2339
+ useEffect9(() => {
2271
2340
  let live = true;
2272
2341
  const personId = props.sourceRecordId ? `chapter-person:${props.targetId}:${props.sourceRecordId}` : props.personId;
2273
2342
  void adminFetch(props.getToken, `/api/admin/network/admissions?targetId=${encodeURIComponent(props.targetId)}`).then((result) => {
@@ -2310,16 +2379,16 @@ function LeaderAdmissionForm(props) {
2310
2379
  setBusy(false);
2311
2380
  }
2312
2381
  };
2313
- return /* @__PURE__ */ jsxs23("div", { style: { display: "grid", gap: 8 }, children: [
2314
- /* @__PURE__ */ jsx25(Field9, { label: "Interview", htmlFor: `${uid}-decision`, children: /* @__PURE__ */ jsxs23(Select8, { id: `${uid}-decision`, disabled: !loaded || busy, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2315
- /* @__PURE__ */ jsx25("option", { value: "", children: loaded ? "Choose an invitation setting" : "Loading saved setting\u2026" }),
2316
- /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: "Already completed" }),
2317
- /* @__PURE__ */ jsx25("option", { value: "require_interview", children: "Required" })
2382
+ return /* @__PURE__ */ jsxs24("div", { style: { display: "grid", gap: 8 }, children: [
2383
+ /* @__PURE__ */ jsx26(Field9, { label: "Interview", htmlFor: `${uid}-decision`, children: /* @__PURE__ */ jsxs24(Select9, { id: `${uid}-decision`, disabled: !loaded || busy, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2384
+ /* @__PURE__ */ jsx26("option", { value: "", children: loaded ? "Choose an invitation setting" : "Loading saved setting\u2026" }),
2385
+ /* @__PURE__ */ jsx26("option", { value: "waive_interview", children: "Already completed" }),
2386
+ /* @__PURE__ */ jsx26("option", { value: "require_interview", children: "Required" })
2318
2387
  ] }) }),
2319
- /* @__PURE__ */ jsx25(Field9, { label: "Internal note", htmlFor: `${uid}-note`, children: /* @__PURE__ */ jsx25(Input2, { id: `${uid}-note`, disabled: !loaded || busy, maxLength: 2e3, value: note, onInput: (event) => setNote(event.currentTarget.value) }) }),
2320
- /* @__PURE__ */ jsx25("button", { className: "btn secondary", type: "button", disabled: busy || !loaded || !decision, onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save invitation setting" }),
2321
- error ? /* @__PURE__ */ jsx25("p", { role: "alert", children: error }) : null,
2322
- status ? /* @__PURE__ */ jsx25("p", { role: "status", children: status }) : null
2388
+ /* @__PURE__ */ jsx26(Field9, { label: "Internal note", htmlFor: `${uid}-note`, children: /* @__PURE__ */ jsx26(Input2, { id: `${uid}-note`, disabled: !loaded || busy, maxLength: 2e3, value: note, onInput: (event) => setNote(event.currentTarget.value) }) }),
2389
+ /* @__PURE__ */ jsx26("button", { className: "btn secondary", type: "button", disabled: busy || !loaded || !decision, onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save invitation setting" }),
2390
+ error ? /* @__PURE__ */ jsx26("p", { role: "alert", children: error }) : null,
2391
+ status ? /* @__PURE__ */ jsx26("p", { role: "status", children: status }) : null
2323
2392
  ] });
2324
2393
  }
2325
2394
 
@@ -2327,19 +2396,19 @@ function LeaderAdmissionForm(props) {
2327
2396
  import { Tabs as Tabs2 } from "@odla-ai/ui/components";
2328
2397
 
2329
2398
  // src/ui/admin-availability.tsx
2330
- import { useState as useState21 } from "preact/hooks";
2399
+ import { useState as useState22 } from "preact/hooks";
2331
2400
  import { Button as Button14, Checkbox, Field as Field10, Input as Input3 } from "@odla-ai/ui/components";
2332
- import { jsx as jsx26, jsxs as jsxs24 } from "preact/jsx-runtime";
2401
+ import { jsx as jsx27, jsxs as jsxs25 } from "preact/jsx-runtime";
2333
2402
  function AvailabilityBody({ ctx }) {
2334
2403
  const copy = useChapterCopy().admin.availability;
2335
2404
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
2336
- const [draft, setDraft] = useState21(null);
2337
- const [busy, setBusy] = useState21(false);
2338
- const [note, setNote] = useState21(null);
2339
- const [errors, setErrors] = useState21({});
2405
+ const [draft, setDraft] = useState22(null);
2406
+ const [busy, setBusy] = useState22(false);
2407
+ const [note, setNote] = useState22(null);
2408
+ const [errors, setErrors] = useState22({});
2340
2409
  const cfg = draft ?? data?.scheduling ?? null;
2341
- if (loading) return /* @__PURE__ */ jsx26(AdminNote, { children: copy.loading });
2342
- if (error || !cfg) return /* @__PURE__ */ jsxs24(AdminNote, { children: [
2410
+ if (loading) return /* @__PURE__ */ jsx27(AdminNote, { children: copy.loading });
2411
+ if (error || !cfg) return /* @__PURE__ */ jsxs25(AdminNote, { children: [
2343
2412
  copy.loadFailed,
2344
2413
  error ? `: ${error}` : "",
2345
2414
  "."
@@ -2369,30 +2438,30 @@ function AvailabilityBody({ ctx }) {
2369
2438
  setBusy(false);
2370
2439
  }
2371
2440
  };
2372
- const numField = (key, label) => /* @__PURE__ */ jsx26(Field10, { label, error: errors[key], children: /* @__PURE__ */ jsx26(Input3, { type: "number", value: cfg[key], onInput: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
2373
- return /* @__PURE__ */ jsx26(AdminPage, { children: /* @__PURE__ */ jsxs24(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx26("span", { className: "muted", children: note }) : void 0, children: [
2374
- /* @__PURE__ */ jsx26(Field10, { label: copy.days, error: errors.days, children: /* @__PURE__ */ jsx26("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: copy.dayLabels.map((d, i) => /* @__PURE__ */ jsx26(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
2375
- /* @__PURE__ */ jsxs24("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
2441
+ const numField = (key, label) => /* @__PURE__ */ jsx27(Field10, { label, error: errors[key], children: /* @__PURE__ */ jsx27(Input3, { type: "number", value: cfg[key], onInput: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
2442
+ return /* @__PURE__ */ jsx27(AdminPage, { children: /* @__PURE__ */ jsxs25(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx27("span", { className: "muted", children: note }) : void 0, children: [
2443
+ /* @__PURE__ */ jsx27(Field10, { label: copy.days, error: errors.days, children: /* @__PURE__ */ jsx27("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: copy.dayLabels.map((d, i) => /* @__PURE__ */ jsx27(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
2444
+ /* @__PURE__ */ jsxs25("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
2376
2445
  numField("startHour", copy.startHour),
2377
2446
  numField("endHour", copy.endHour),
2378
2447
  numField("slotMinutes", copy.slotMinutes),
2379
2448
  numField("minNoticeHours", copy.minNoticeHours),
2380
2449
  numField("windowDays", copy.windowDays),
2381
- /* @__PURE__ */ jsx26(Field10, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx26(Input3, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
2450
+ /* @__PURE__ */ jsx27(Field10, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx27(Input3, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
2382
2451
  ] }),
2383
- /* @__PURE__ */ jsx26(Field10, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx26(Input3, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
2384
- /* @__PURE__ */ jsx26("div", { children: /* @__PURE__ */ jsx26(Button14, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
2452
+ /* @__PURE__ */ jsx27(Field10, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx27(Input3, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
2453
+ /* @__PURE__ */ jsx27("div", { children: /* @__PURE__ */ jsx27(Button14, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
2385
2454
  ] }) });
2386
2455
  }
2387
2456
  function availabilitySection(options = {}) {
2388
2457
  const { id = "availability", label = "Availability" } = options;
2389
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx26(AvailabilityBody, { ctx }) };
2458
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx27(AvailabilityBody, { ctx }) };
2390
2459
  }
2391
2460
 
2392
2461
  // src/ui/admin-builder-library.tsx
2393
- import { useEffect as useEffect9, useId as useId2, useMemo as useMemo2, useState as useState22 } from "preact/hooks";
2394
- import { Field as Field11, Input as Input4, Select as Select9, Textarea as Textarea3 } from "@odla-ai/ui/components";
2395
- import { jsx as jsx27, jsxs as jsxs25 } from "preact/jsx-runtime";
2462
+ import { useEffect as useEffect10, useId as useId2, useMemo as useMemo2, useState as useState23 } from "preact/hooks";
2463
+ import { Field as Field11, Input as Input4, Select as Select10, Textarea as Textarea3 } from "@odla-ai/ui/components";
2464
+ import { jsx as jsx28, jsxs as jsxs26 } from "preact/jsx-runtime";
2396
2465
  var KINDS = ["document", "note", "link", "inspiration", "image", "file"];
2397
2466
  var KIND_LABEL = { document: "Document", note: "Note", link: "Link", inspiration: "Inspiration", image: "Image", file: "File" };
2398
2467
  var STATUS_LABEL = { draft: "Draft", published: "Published", archived: "Archived" };
@@ -2424,13 +2493,13 @@ function localResource(kind, chapterId, mode) {
2424
2493
  };
2425
2494
  }
2426
2495
  function ResourcePreview({ item }) {
2427
- return /* @__PURE__ */ jsxs25("div", { className: "card", style: { padding: 14 }, children: [
2428
- /* @__PURE__ */ jsx27("div", { className: "card-label", children: "Preview" }),
2429
- /* @__PURE__ */ jsx27("h3", { style: { margin: "6px 0" }, children: item.title || "Untitled" }),
2430
- item.summary ? /* @__PURE__ */ jsx27("p", { style: { margin: "0 0 10px" }, children: item.summary }) : null,
2431
- item.bodyMarkdown ? /* @__PURE__ */ jsx27("pre", { style: { margin: 0, whiteSpace: "pre-wrap", font: "inherit", lineHeight: 1.55 }, children: item.bodyMarkdown }) : null,
2432
- item.url ? /* @__PURE__ */ jsx27("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url }) : null,
2433
- item.brandAsset ? /* @__PURE__ */ jsxs25("div", { style: META, children: [
2496
+ return /* @__PURE__ */ jsxs26("div", { className: "card", style: { padding: 14 }, children: [
2497
+ /* @__PURE__ */ jsx28("div", { className: "card-label", children: "Preview" }),
2498
+ /* @__PURE__ */ jsx28("h3", { style: { margin: "6px 0" }, children: item.title || "Untitled" }),
2499
+ item.summary ? /* @__PURE__ */ jsx28("p", { style: { margin: "0 0 10px" }, children: item.summary }) : null,
2500
+ item.bodyMarkdown ? /* @__PURE__ */ jsx28("pre", { style: { margin: 0, whiteSpace: "pre-wrap", font: "inherit", lineHeight: 1.55 }, children: item.bodyMarkdown }) : null,
2501
+ item.url ? /* @__PURE__ */ jsx28("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url }) : null,
2502
+ item.brandAsset ? /* @__PURE__ */ jsxs26("div", { style: META, children: [
2434
2503
  "Brand asset \xB7 ",
2435
2504
  item.brandAsset.bookId,
2436
2505
  " / ",
@@ -2442,15 +2511,15 @@ function ResourcePreview({ item }) {
2442
2511
  function BuilderLibraryAdmin({ ctx, initialData }) {
2443
2512
  const remote = useAdminResource(ctx.getToken, "/api/admin/builder-library");
2444
2513
  const seed = initialData ?? remote.data;
2445
- const [resources, setResources] = useState22(() => [...seed?.local?.document.resources ?? []]);
2446
- const [selectedKey, setSelectedKey] = useState22(() => seed?.local?.document.resources[0]?.id ? localKey(seed.local.document.resources[0].id) : null);
2447
- const [query, setQuery] = useState22("");
2448
- const [kindFilter, setKindFilter] = useState22("all");
2449
- const [statusFilter, setStatusFilter] = useState22("all");
2450
- const [newKind, setNewKind] = useState22("document");
2451
- const [saveError, setSaveError] = useState22(null);
2452
- const [saving, setSaving] = useState22(false);
2453
- useEffect9(() => {
2514
+ const [resources, setResources] = useState23(() => [...seed?.local?.document.resources ?? []]);
2515
+ const [selectedKey, setSelectedKey] = useState23(() => seed?.local?.document.resources[0]?.id ? localKey(seed.local.document.resources[0].id) : null);
2516
+ const [query, setQuery] = useState23("");
2517
+ const [kindFilter, setKindFilter] = useState23("all");
2518
+ const [statusFilter, setStatusFilter] = useState23("all");
2519
+ const [newKind, setNewKind] = useState23("document");
2520
+ const [saveError, setSaveError] = useState23(null);
2521
+ const [saving, setSaving] = useState23(false);
2522
+ useEffect10(() => {
2454
2523
  if (!remote.data) return;
2455
2524
  const next = [...remote.data.local?.document.resources ?? []];
2456
2525
  setResources(next);
@@ -2466,8 +2535,8 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
2466
2535
  return (!query.trim() || haystack.includes(query.toLowerCase().trim())) && (kindFilter === "all" || resource.kind === kindFilter) && (statusFilter === "all" || resource.status === statusFilter);
2467
2536
  });
2468
2537
  const selected = entries.find((entry) => entry.key === selectedKey) ?? visible[0] ?? null;
2469
- if (!data && remote.loading) return /* @__PURE__ */ jsx27(AdminPage, { children: /* @__PURE__ */ jsx27(AdminNote, { children: "Loading Builder Library\u2026" }) });
2470
- if (remote.error || !data) return /* @__PURE__ */ jsx27(AdminPage, { children: /* @__PURE__ */ jsxs25(AdminNote, { children: [
2538
+ if (!data && remote.loading) return /* @__PURE__ */ jsx28(AdminPage, { children: /* @__PURE__ */ jsx28(AdminNote, { children: "Loading Builder Library\u2026" }) });
2539
+ if (remote.error || !data) return /* @__PURE__ */ jsx28(AdminPage, { children: /* @__PURE__ */ jsxs26(AdminNote, { children: [
2471
2540
  "Builder Library could not be loaded: ",
2472
2541
  remote.error ?? "unknown error"
2473
2542
  ] }) });
@@ -2503,66 +2572,66 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
2503
2572
  }
2504
2573
  };
2505
2574
  const scopeValue = selected?.resource.scope.kind === "chapter" ? `chapter:${selected.resource.scope.chapterId}` : selected?.resource.scope.kind ?? "built_not_found";
2506
- return /* @__PURE__ */ jsxs25(AdminPage, { children: [
2507
- /* @__PURE__ */ jsxs25(Panel, { title: /* @__PURE__ */ jsxs25("span", { children: [
2575
+ return /* @__PURE__ */ jsxs26(AdminPage, { children: [
2576
+ /* @__PURE__ */ jsxs26(Panel, { title: /* @__PURE__ */ jsxs26("span", { children: [
2508
2577
  "Builder Library ",
2509
- /* @__PURE__ */ jsx27("span", { className: "muted", children: "\xB7 private" })
2510
- ] }), actions: /* @__PURE__ */ jsxs25("div", { style: ROW, children: [
2511
- /* @__PURE__ */ jsx27(Select9, { "aria-label": "New resource type", value: newKind, onChange: (event) => setNewKind(event.currentTarget.value), children: KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }),
2512
- /* @__PURE__ */ jsx27("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
2578
+ /* @__PURE__ */ jsx28("span", { className: "muted", children: "\xB7 private" })
2579
+ ] }), actions: /* @__PURE__ */ jsxs26("div", { style: ROW, children: [
2580
+ /* @__PURE__ */ jsx28(Select10, { "aria-label": "New resource type", value: newKind, onChange: (event) => setNewKind(event.currentTarget.value), children: KINDS.map((kind) => /* @__PURE__ */ jsx28("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }),
2581
+ /* @__PURE__ */ jsx28("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
2513
2582
  ] }), children: [
2514
- /* @__PURE__ */ jsx27("p", { className: "muted", style: { margin: 0 }, children: "Documents, links, and Brand assets used by site-building agents. Nothing here appears on a member page." }),
2515
- /* @__PURE__ */ jsxs25("div", { style: ROW, children: [
2516
- /* @__PURE__ */ jsx27(Input4, { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
2517
- /* @__PURE__ */ jsxs25(Select9, { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
2518
- /* @__PURE__ */ jsx27("option", { value: "all", children: "All types" }),
2519
- KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind))
2583
+ /* @__PURE__ */ jsx28("p", { className: "muted", style: { margin: 0 }, children: "Documents, links, and Brand assets used by site-building agents. Nothing here appears on a member page." }),
2584
+ /* @__PURE__ */ jsxs26("div", { style: ROW, children: [
2585
+ /* @__PURE__ */ jsx28(Input4, { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
2586
+ /* @__PURE__ */ jsxs26(Select10, { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
2587
+ /* @__PURE__ */ jsx28("option", { value: "all", children: "All types" }),
2588
+ KINDS.map((kind) => /* @__PURE__ */ jsx28("option", { value: kind, children: KIND_LABEL[kind] }, kind))
2520
2589
  ] }),
2521
- /* @__PURE__ */ jsxs25(Select9, { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
2522
- /* @__PURE__ */ jsx27("option", { value: "all", children: "All statuses" }),
2523
- /* @__PURE__ */ jsx27("option", { value: "draft", children: "Draft" }),
2524
- /* @__PURE__ */ jsx27("option", { value: "published", children: "Published" }),
2525
- /* @__PURE__ */ jsx27("option", { value: "archived", children: "Archived" })
2590
+ /* @__PURE__ */ jsxs26(Select10, { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
2591
+ /* @__PURE__ */ jsx28("option", { value: "all", children: "All statuses" }),
2592
+ /* @__PURE__ */ jsx28("option", { value: "draft", children: "Draft" }),
2593
+ /* @__PURE__ */ jsx28("option", { value: "published", children: "Published" }),
2594
+ /* @__PURE__ */ jsx28("option", { value: "archived", children: "Archived" })
2526
2595
  ] })
2527
2596
  ] })
2528
2597
  ] }),
2529
- /* @__PURE__ */ jsxs25("div", { style: GRID, "data-builder-library-cms": true, children: [
2530
- /* @__PURE__ */ jsx27(Panel, { title: `${visible.length} resource${visible.length === 1 ? "" : "s"}`, children: /* @__PURE__ */ jsx27("div", { style: { ...STACK, maxHeight: "70vh", overflow: "auto" }, children: visible.length === 0 ? /* @__PURE__ */ jsx27("p", { className: "muted", children: "No resources match these filters." }) : visible.map((entry) => /* @__PURE__ */ jsxs25("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: [
2531
- /* @__PURE__ */ jsxs25("div", { style: { ...ROW, justifyContent: "space-between" }, children: [
2532
- /* @__PURE__ */ jsx27("strong", { children: entry.resource.title }),
2533
- /* @__PURE__ */ jsx27("span", { className: "badge", children: KIND_LABEL[entry.resource.kind] })
2598
+ /* @__PURE__ */ jsxs26("div", { style: GRID, "data-builder-library-cms": true, children: [
2599
+ /* @__PURE__ */ jsx28(Panel, { title: `${visible.length} resource${visible.length === 1 ? "" : "s"}`, children: /* @__PURE__ */ jsx28("div", { style: { ...STACK, maxHeight: "70vh", overflow: "auto" }, children: visible.length === 0 ? /* @__PURE__ */ jsx28("p", { className: "muted", children: "No resources match these filters." }) : visible.map((entry) => /* @__PURE__ */ jsxs26("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: [
2600
+ /* @__PURE__ */ jsxs26("div", { style: { ...ROW, justifyContent: "space-between" }, children: [
2601
+ /* @__PURE__ */ jsx28("strong", { children: entry.resource.title }),
2602
+ /* @__PURE__ */ jsx28("span", { className: "badge", children: KIND_LABEL[entry.resource.kind] })
2534
2603
  ] }),
2535
- /* @__PURE__ */ jsx27("p", { className: "muted", style: { margin: "6px 0", lineHeight: 1.4 }, children: contentSummary(entry.resource) }),
2536
- /* @__PURE__ */ jsxs25("div", { style: META, children: [
2537
- /* @__PURE__ */ jsx27("span", { children: STATUS_LABEL[entry.resource.status] }),
2538
- /* @__PURE__ */ jsx27("span", { children: "\xB7" }),
2539
- /* @__PURE__ */ jsx27("span", { children: scopeLabel(entry.resource.scope) }),
2540
- /* @__PURE__ */ jsx27("span", { children: "\xB7" }),
2541
- /* @__PURE__ */ jsx27("span", { children: entry.sourceId })
2604
+ /* @__PURE__ */ jsx28("p", { className: "muted", style: { margin: "6px 0", lineHeight: 1.4 }, children: contentSummary(entry.resource) }),
2605
+ /* @__PURE__ */ jsxs26("div", { style: META, children: [
2606
+ /* @__PURE__ */ jsx28("span", { children: STATUS_LABEL[entry.resource.status] }),
2607
+ /* @__PURE__ */ jsx28("span", { children: "\xB7" }),
2608
+ /* @__PURE__ */ jsx28("span", { children: scopeLabel(entry.resource.scope) }),
2609
+ /* @__PURE__ */ jsx28("span", { children: "\xB7" }),
2610
+ /* @__PURE__ */ jsx28("span", { children: entry.sourceId })
2542
2611
  ] })
2543
2612
  ] }, entry.key)) }) }),
2544
- /* @__PURE__ */ jsxs25(Panel, { title: selected ? selected.resource.title : "Select a resource", actions: selected?.local ? /* @__PURE__ */ jsxs25("div", { style: ROW, children: [
2545
- /* @__PURE__ */ jsx27("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" }),
2546
- /* @__PURE__ */ jsx27("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save changes" })
2613
+ /* @__PURE__ */ jsxs26(Panel, { title: selected ? selected.resource.title : "Select a resource", actions: selected?.local ? /* @__PURE__ */ jsxs26("div", { style: ROW, children: [
2614
+ /* @__PURE__ */ jsx28("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" }),
2615
+ /* @__PURE__ */ jsx28("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save changes" })
2547
2616
  ] }) : null, children: [
2548
- !selected ? /* @__PURE__ */ jsx27("p", { className: "muted", children: "Add a resource or choose one from the list." }) : selected.local ? /* @__PURE__ */ jsx27(LocalEditor, { selected: selected.resource, data, scopeValue, saving, update, setResources, setSelectedKey }) : /* @__PURE__ */ jsxs25("div", { style: STACK, children: [
2549
- /* @__PURE__ */ jsxs25("div", { style: META, children: [
2617
+ !selected ? /* @__PURE__ */ jsx28("p", { className: "muted", children: "Add a resource or choose one from the list." }) : selected.local ? /* @__PURE__ */ jsx28(LocalEditor, { selected: selected.resource, data, scopeValue, saving, update, setResources, setSelectedKey }) : /* @__PURE__ */ jsxs26("div", { style: STACK, children: [
2618
+ /* @__PURE__ */ jsxs26("div", { style: META, children: [
2550
2619
  "Read only \xB7 supplied by ",
2551
2620
  selected.sourceId,
2552
2621
  " \xB7 source revision ",
2553
2622
  selected.sourceRevision
2554
2623
  ] }),
2555
- /* @__PURE__ */ jsxs25("div", { children: [
2556
- /* @__PURE__ */ jsx27("span", { className: "badge", children: STATUS_LABEL[selected.resource.status] }),
2624
+ /* @__PURE__ */ jsxs26("div", { children: [
2625
+ /* @__PURE__ */ jsx28("span", { className: "badge", children: STATUS_LABEL[selected.resource.status] }),
2557
2626
  " ",
2558
- /* @__PURE__ */ jsx27("span", { className: "badge", children: scopeLabel(selected.resource.scope) })
2627
+ /* @__PURE__ */ jsx28("span", { className: "badge", children: scopeLabel(selected.resource.scope) })
2559
2628
  ] }),
2560
- /* @__PURE__ */ jsx27(ResourcePreview, { item: selected.resource }),
2561
- data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */ jsx27("button", { className: "btn secondary", type: "button", onClick: () => void promote(selected), children: "Copy to all chapters" }) : null
2629
+ /* @__PURE__ */ jsx28(ResourcePreview, { item: selected.resource }),
2630
+ data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */ jsx28("button", { className: "btn secondary", type: "button", onClick: () => void promote(selected), children: "Copy to all chapters" }) : null
2562
2631
  ] }),
2563
- saveError ? /* @__PURE__ */ jsxs25("div", { className: "notice danger", role: "alert", children: [
2564
- /* @__PURE__ */ jsx27("strong", { children: "Fix these items before saving:" }),
2565
- /* @__PURE__ */ jsx27("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx27("li", { children: error }, error)) })
2632
+ saveError ? /* @__PURE__ */ jsxs26("div", { className: "notice danger", role: "alert", children: [
2633
+ /* @__PURE__ */ jsx28("strong", { children: "Fix these items before saving:" }),
2634
+ /* @__PURE__ */ jsx28("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx28("li", { children: error }, error)) })
2566
2635
  ] }) : null
2567
2636
  ] })
2568
2637
  ] })
@@ -2570,68 +2639,68 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
2570
2639
  }
2571
2640
  function LocalEditor({ selected, data, scopeValue, update, setResources, setSelectedKey }) {
2572
2641
  const uid = useId2();
2573
- return /* @__PURE__ */ jsxs25("div", { style: STACK, children: [
2574
- /* @__PURE__ */ jsxs25("div", { style: META, children: [
2642
+ return /* @__PURE__ */ jsxs26("div", { style: STACK, children: [
2643
+ /* @__PURE__ */ jsxs26("div", { style: META, children: [
2575
2644
  "Local \xB7 resource revision ",
2576
2645
  selected.revision,
2577
2646
  " \xB7 library revision ",
2578
2647
  data.local?.revision ?? 0
2579
2648
  ] }),
2580
- /* @__PURE__ */ jsxs25("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2581
- /* @__PURE__ */ jsx27(Field11, { label: "Title", htmlFor: `${uid}-title`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-title`, value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) }) }),
2582
- /* @__PURE__ */ jsx27(Field11, { label: "ID", htmlFor: `${uid}-id`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-id`, value: selected.id, onInput: (event) => {
2649
+ /* @__PURE__ */ jsxs26("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2650
+ /* @__PURE__ */ jsx28(Field11, { label: "Title", htmlFor: `${uid}-title`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-title`, value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) }) }),
2651
+ /* @__PURE__ */ jsx28(Field11, { label: "ID", htmlFor: `${uid}-id`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-id`, value: selected.id, onInput: (event) => {
2583
2652
  const id = cleanId(event.currentTarget.value);
2584
2653
  setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
2585
2654
  setSelectedKey(localKey(id));
2586
2655
  } }) }),
2587
- /* @__PURE__ */ jsx27(Field11, { label: "Type", htmlFor: `${uid}-kind`, children: /* @__PURE__ */ jsx27(Select9, { id: `${uid}-kind`, value: selected.kind, onChange: (event) => {
2656
+ /* @__PURE__ */ jsx28(Field11, { label: "Type", htmlFor: `${uid}-kind`, children: /* @__PURE__ */ jsx28(Select10, { id: `${uid}-kind`, value: selected.kind, onChange: (event) => {
2588
2657
  const kind = event.currentTarget.value;
2589
2658
  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 });
2590
- }, children: KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }) }),
2591
- /* @__PURE__ */ jsx27(Field11, { label: "Status", htmlFor: `${uid}-status`, children: /* @__PURE__ */ jsxs25(Select9, { id: `${uid}-status`, value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
2592
- /* @__PURE__ */ jsx27("option", { value: "draft", children: "Draft" }),
2593
- /* @__PURE__ */ jsx27("option", { value: "published", children: "Published" }),
2594
- /* @__PURE__ */ jsx27("option", { value: "archived", children: "Archived" })
2659
+ }, children: KINDS.map((kind) => /* @__PURE__ */ jsx28("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }) }),
2660
+ /* @__PURE__ */ jsx28(Field11, { label: "Status", htmlFor: `${uid}-status`, children: /* @__PURE__ */ jsxs26(Select10, { id: `${uid}-status`, value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
2661
+ /* @__PURE__ */ jsx28("option", { value: "draft", children: "Draft" }),
2662
+ /* @__PURE__ */ jsx28("option", { value: "published", children: "Published" }),
2663
+ /* @__PURE__ */ jsx28("option", { value: "archived", children: "Archived" })
2595
2664
  ] }) }),
2596
- /* @__PURE__ */ jsx27(Field11, { label: "Visible to", htmlFor: `${uid}-scope`, children: /* @__PURE__ */ jsxs25(Select9, { id: `${uid}-scope`, value: scopeValue, onChange: (event) => {
2665
+ /* @__PURE__ */ jsx28(Field11, { label: "Visible to", htmlFor: `${uid}-scope`, children: /* @__PURE__ */ jsxs26(Select10, { id: `${uid}-scope`, value: scopeValue, onChange: (event) => {
2597
2666
  const value = event.currentTarget.value;
2598
2667
  update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
2599
2668
  }, children: [
2600
- data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
2601
- data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "global", children: "All chapters" }) : null,
2602
- data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs25("option", { value: `chapter:${chapterId}`, children: [
2669
+ data.mode === "hub" ? /* @__PURE__ */ jsx28("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
2670
+ data.mode === "hub" ? /* @__PURE__ */ jsx28("option", { value: "global", children: "All chapters" }) : null,
2671
+ data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs26("option", { value: `chapter:${chapterId}`, children: [
2603
2672
  "Only ",
2604
2673
  chapterId
2605
2674
  ] }, chapterId))
2606
2675
  ] }) }),
2607
- /* @__PURE__ */ jsx27(Field11, { label: "Tags", htmlFor: `${uid}-tags`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-tags`, value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) }) })
2676
+ /* @__PURE__ */ jsx28(Field11, { label: "Tags", htmlFor: `${uid}-tags`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-tags`, value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) }) })
2608
2677
  ] }),
2609
- /* @__PURE__ */ jsx27(Field11, { label: "Summary", htmlFor: `${uid}-summary`, children: /* @__PURE__ */ jsx27(Textarea3, { id: `${uid}-summary`, rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) }) }),
2610
- selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsx27(Field11, { label: "Markdown", htmlFor: `${uid}-markdown`, children: /* @__PURE__ */ jsx27(Textarea3, { id: `${uid}-markdown`, rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) }) }) : null,
2611
- selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsx27(Field11, { label: "HTTPS URL", htmlFor: `${uid}-url`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-url`, type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) }) }) : null,
2612
- selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs25("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2613
- /* @__PURE__ */ jsx27(Field11, { label: "Brand book ID", htmlFor: `${uid}-book`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-book`, value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) }) }),
2614
- /* @__PURE__ */ jsx27(Field11, { label: "Asset ID", htmlFor: `${uid}-asset`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-asset`, value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) }) }),
2615
- /* @__PURE__ */ jsx27(Field11, { label: "Filename (optional)", htmlFor: `${uid}-filename`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-filename`, value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) }) })
2678
+ /* @__PURE__ */ jsx28(Field11, { label: "Summary", htmlFor: `${uid}-summary`, children: /* @__PURE__ */ jsx28(Textarea3, { id: `${uid}-summary`, rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) }) }),
2679
+ selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsx28(Field11, { label: "Markdown", htmlFor: `${uid}-markdown`, children: /* @__PURE__ */ jsx28(Textarea3, { id: `${uid}-markdown`, rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) }) }) : null,
2680
+ selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsx28(Field11, { label: "HTTPS URL", htmlFor: `${uid}-url`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-url`, type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) }) }) : null,
2681
+ selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs26("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2682
+ /* @__PURE__ */ jsx28(Field11, { label: "Brand book ID", htmlFor: `${uid}-book`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-book`, value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) }) }),
2683
+ /* @__PURE__ */ jsx28(Field11, { label: "Asset ID", htmlFor: `${uid}-asset`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-asset`, value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) }) }),
2684
+ /* @__PURE__ */ jsx28(Field11, { label: "Filename (optional)", htmlFor: `${uid}-filename`, children: /* @__PURE__ */ jsx28(Input4, { id: `${uid}-filename`, value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) }) })
2616
2685
  ] }) : null,
2617
- /* @__PURE__ */ jsx27(ResourcePreview, { item: selected })
2686
+ /* @__PURE__ */ jsx28(ResourcePreview, { item: selected })
2618
2687
  ] });
2619
2688
  }
2620
2689
  function builderLibrarySection() {
2621
- return { id: "builder-library", label: "Builder Library", render: (ctx) => /* @__PURE__ */ jsx27(BuilderLibraryAdmin, { ctx }) };
2690
+ return { id: "builder-library", label: "Builder Library", render: (ctx) => /* @__PURE__ */ jsx28(BuilderLibraryAdmin, { ctx }) };
2622
2691
  }
2623
2692
 
2624
2693
  // src/ui/admin-email.tsx
2625
- import { useState as useState24 } from "preact/hooks";
2694
+ import { useState as useState25 } from "preact/hooks";
2626
2695
  import { Button as Button16, Checkbox as Checkbox2, DataTable as DataTable3, Field as Field13, Input as Input6, Textarea as Textarea5 } from "@odla-ai/ui/components";
2627
2696
 
2628
2697
  // src/ui/admin-email-resolution.tsx
2629
- import { useState as useState23 } from "preact/hooks";
2698
+ import { useState as useState24 } from "preact/hooks";
2630
2699
  import { Button as Button15, Field as Field12, Input as Input5, Textarea as Textarea4 } from "@odla-ai/ui/components";
2631
- import { jsx as jsx28, jsxs as jsxs26 } from "preact/jsx-runtime";
2700
+ import { jsx as jsx29, jsxs as jsxs27 } from "preact/jsx-runtime";
2632
2701
  function EmailDeliveryResolution({ row, ctx, onResolved }) {
2633
- const [open, setOpen] = useState23(false), [reason, setReason] = useState23(""), [messageId, setMessageId] = useState23("");
2634
- const [busy, setBusy] = useState23(false), [note, setNote] = useState23("");
2702
+ const [open, setOpen] = useState24(false), [reason, setReason] = useState24(""), [messageId, setMessageId] = useState24("");
2703
+ const [busy, setBusy] = useState24(false), [note, setNote] = useState24("");
2635
2704
  if (!["sending", "uncertain"].includes(row.deliveryState ?? "") || !row.deliveryAttempt) return null;
2636
2705
  const resolve2 = async (decision) => {
2637
2706
  setBusy(true);
@@ -2655,45 +2724,45 @@ function EmailDeliveryResolution({ row, ctx, onResolved }) {
2655
2724
  setBusy(false);
2656
2725
  }
2657
2726
  };
2658
- if (!open) return /* @__PURE__ */ jsx28(Button15, { mini: true, variant: "secondary", onClick: () => setOpen(true), children: "Review delivery" });
2659
- return /* @__PURE__ */ jsxs26("div", { children: [
2660
- /* @__PURE__ */ jsxs26("p", { children: [
2727
+ if (!open) return /* @__PURE__ */ jsx29(Button15, { mini: true, variant: "secondary", onClick: () => setOpen(true), children: "Review delivery" });
2728
+ return /* @__PURE__ */ jsxs27("div", { children: [
2729
+ /* @__PURE__ */ jsxs27("p", { children: [
2661
2730
  "Attempt ",
2662
2731
  row.deliveryAttempt,
2663
2732
  ": ",
2664
2733
  row.subject,
2665
2734
  ". Check provider evidence, not just whether the message is in an inbox. This action does not send mail."
2666
2735
  ] }),
2667
- /* @__PURE__ */ jsx28(Field12, { label: "Provider evidence", children: /* @__PURE__ */ jsx28(Textarea4, { value: reason, onInput: (e) => setReason(e.target.value) }) }),
2668
- /* @__PURE__ */ jsx28(Field12, { label: "Provider message ID (optional)", children: /* @__PURE__ */ jsx28(Input5, { value: messageId, onInput: (e) => setMessageId(e.target.value) }) }),
2669
- /* @__PURE__ */ jsx28(Button15, { mini: true, disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_delivered"), children: "Confirm delivered" }),
2670
- /* @__PURE__ */ jsx28(Button15, { mini: true, variant: "secondary", disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_not_delivered"), children: "Confirm not delivered" }),
2671
- /* @__PURE__ */ jsx28(Button15, { mini: true, variant: "secondary", disabled: busy, onClick: () => setOpen(false), children: "Cancel" }),
2672
- note ? /* @__PURE__ */ jsx28("p", { role: "alert", children: note }) : null
2736
+ /* @__PURE__ */ jsx29(Field12, { label: "Provider evidence", children: /* @__PURE__ */ jsx29(Textarea4, { value: reason, onInput: (e) => setReason(e.target.value) }) }),
2737
+ /* @__PURE__ */ jsx29(Field12, { label: "Provider message ID (optional)", children: /* @__PURE__ */ jsx29(Input5, { value: messageId, onInput: (e) => setMessageId(e.target.value) }) }),
2738
+ /* @__PURE__ */ jsx29(Button15, { mini: true, disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_delivered"), children: "Confirm delivered" }),
2739
+ /* @__PURE__ */ jsx29(Button15, { mini: true, variant: "secondary", disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_not_delivered"), children: "Confirm not delivered" }),
2740
+ /* @__PURE__ */ jsx29(Button15, { mini: true, variant: "secondary", disabled: busy, onClick: () => setOpen(false), children: "Cancel" }),
2741
+ note ? /* @__PURE__ */ jsx29("p", { role: "alert", children: note }) : null
2673
2742
  ] });
2674
2743
  }
2675
2744
 
2676
2745
  // src/ui/admin-email.tsx
2677
- import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs27 } from "preact/jsx-runtime";
2746
+ import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs28 } from "preact/jsx-runtime";
2678
2747
  var logColumns = (copy, ctx, refresh) => [
2679
2748
  { key: "sentAt", header: copy.sentColumn, cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
2680
2749
  { key: "template", header: copy.templateColumn, cell: (r) => r.template },
2681
2750
  { key: "to", header: copy.toColumn, cell: (r) => r.to },
2682
- { key: "status", header: copy.statusColumn, cell: (r) => /* @__PURE__ */ jsxs27("div", { children: [
2683
- r.deliveryState && r.deliveryState !== "sent" ? /* @__PURE__ */ jsx29("span", { className: "badge", children: r.deliveryState === "retry_ready" ? "Original workflow may retry" : r.deliveryState === "sending" ? "Sending \u2014 unconfirmed" : "Delivery uncertain" }) : r.error ? /* @__PURE__ */ jsx29("span", { className: "badge", children: copy.failed }) : r.transport === "log-only" ? "Logged only \u2014 not sent" : r.redirected ? /* @__PURE__ */ jsx29("span", { className: "badge", children: copy.redirected }) : copy.delivered,
2684
- /* @__PURE__ */ jsx29(EmailDeliveryResolution, { row: r, ctx, onResolved: refresh })
2751
+ { key: "status", header: copy.statusColumn, cell: (r) => /* @__PURE__ */ jsxs28("div", { children: [
2752
+ r.deliveryState && r.deliveryState !== "sent" ? /* @__PURE__ */ jsx30("span", { className: "badge", children: r.deliveryState === "retry_ready" ? "Original workflow may retry" : r.deliveryState === "sending" ? "Sending \u2014 unconfirmed" : "Delivery uncertain" }) : r.error ? /* @__PURE__ */ jsx30("span", { className: "badge", children: copy.failed }) : r.transport === "log-only" ? "Logged only \u2014 not sent" : r.redirected ? /* @__PURE__ */ jsx30("span", { className: "badge", children: copy.redirected }) : copy.delivered,
2753
+ /* @__PURE__ */ jsx30(EmailDeliveryResolution, { row: r, ctx, onResolved: refresh })
2685
2754
  ] }) }
2686
2755
  ];
2687
2756
  function EmailBody({ ctx }) {
2688
2757
  const copy = useChapterCopy().admin.email;
2689
2758
  const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
2690
2759
  const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
2691
- const [draft, setDraft] = useState24(null);
2692
- const [busy, setBusy] = useState24(null);
2693
- const [note, setNote] = useState24(null);
2760
+ const [draft, setDraft] = useState25(null);
2761
+ const [busy, setBusy] = useState25(null);
2762
+ const [note, setNote] = useState25(null);
2694
2763
  const data = draft ?? cfg.data;
2695
- if (cfg.loading) return /* @__PURE__ */ jsx29(AdminNote, { children: copy.loading });
2696
- if (cfg.error || !data) return /* @__PURE__ */ jsxs27(AdminNote, { children: [
2764
+ if (cfg.loading) return /* @__PURE__ */ jsx30(AdminNote, { children: copy.loading });
2765
+ if (cfg.error || !data) return /* @__PURE__ */ jsxs28(AdminNote, { children: [
2697
2766
  copy.loadFailed,
2698
2767
  cfg.error ? `: ${cfg.error}` : "",
2699
2768
  "."
@@ -2726,11 +2795,11 @@ function EmailBody({ ctx }) {
2726
2795
  }) : copy.testFailed);
2727
2796
  log.refresh();
2728
2797
  });
2729
- return /* @__PURE__ */ jsxs27(AdminPage, { children: [
2730
- /* @__PURE__ */ jsxs27(Panel, { title: "Email readiness", actions: /* @__PURE__ */ jsx29(Button16, { variant: "secondary", mini: true, disabled: !!draft, onClick: () => cfg.refresh(), children: "Check again" }), children: [
2731
- /* @__PURE__ */ jsx29("p", { children: "Each event is checked against its saved email and current tier names when this page opens. Missing copy or required values blocks delivery." }),
2732
- data.validation ? /* @__PURE__ */ jsxs27(Fragment12, { children: [
2733
- /* @__PURE__ */ jsxs27("p", { children: [
2798
+ return /* @__PURE__ */ jsxs28(AdminPage, { children: [
2799
+ /* @__PURE__ */ jsxs28(Panel, { title: "Email readiness", actions: /* @__PURE__ */ jsx30(Button16, { variant: "secondary", mini: true, disabled: !!draft, onClick: () => cfg.refresh(), children: "Check again" }), children: [
2800
+ /* @__PURE__ */ jsx30("p", { children: "Each event is checked against its saved email and current tier names when this page opens. Missing copy or required values blocks delivery." }),
2801
+ data.validation ? /* @__PURE__ */ jsxs28(Fragment12, { children: [
2802
+ /* @__PURE__ */ jsxs28("p", { children: [
2734
2803
  data.validation.events.filter((event) => event.status === "blocked").length,
2735
2804
  " blocked \xB7 ",
2736
2805
  data.validation.events.filter((event) => event.status === "disabled").length,
@@ -2738,78 +2807,78 @@ function EmailBody({ ctx }) {
2738
2807
  data.validation.events.filter((event) => event.status === "ready").length,
2739
2808
  " ready"
2740
2809
  ] }),
2741
- data.validation.issues.length ? /* @__PURE__ */ jsx29("ul", { role: "alert", children: data.validation.issues.map((issue, i) => /* @__PURE__ */ jsx29("li", { children: issue.message }, i)) }) : null,
2742
- data.validation.warnings?.length ? /* @__PURE__ */ jsx29("ul", { children: data.validation.warnings.map((warning, i) => /* @__PURE__ */ jsxs27("li", { children: [
2810
+ data.validation.issues.length ? /* @__PURE__ */ jsx30("ul", { role: "alert", children: data.validation.issues.map((issue, i) => /* @__PURE__ */ jsx30("li", { children: issue.message }, i)) }) : null,
2811
+ data.validation.warnings?.length ? /* @__PURE__ */ jsx30("ul", { children: data.validation.warnings.map((warning, i) => /* @__PURE__ */ jsxs28("li", { children: [
2743
2812
  copy.spamRisk,
2744
2813
  ": ",
2745
2814
  warning.message
2746
2815
  ] }, i)) }) : null,
2747
- draft ? /* @__PURE__ */ jsx29("p", { role: "status", children: "Save your changes to refresh these checks." }) : /* @__PURE__ */ jsxs27("p", { className: "muted", children: [
2816
+ draft ? /* @__PURE__ */ jsx30("p", { role: "status", children: "Save your changes to refresh these checks." }) : /* @__PURE__ */ jsxs28("p", { className: "muted", children: [
2748
2817
  "Checked ",
2749
2818
  new Date(data.validation.checkedAt).toLocaleString()
2750
2819
  ] })
2751
- ] }) : /* @__PURE__ */ jsx29("p", { role: "alert", children: "Email validation is unavailable. Refresh after the server has been updated." })
2820
+ ] }) : /* @__PURE__ */ jsx30("p", { role: "alert", children: "Email validation is unavailable. Refresh after the server has been updated." })
2752
2821
  ] }),
2753
- /* @__PURE__ */ jsxs27(
2822
+ /* @__PURE__ */ jsxs28(
2754
2823
  Panel,
2755
2824
  {
2756
2825
  title: copy.delivery,
2757
- actions: /* @__PURE__ */ jsxs27("span", { className: "muted", children: [
2826
+ actions: /* @__PURE__ */ jsxs28("span", { className: "muted", children: [
2758
2827
  data.envName,
2759
2828
  " \xB7 ",
2760
2829
  data.transport,
2761
2830
  data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
2762
2831
  ] }),
2763
2832
  children: [
2764
- /* @__PURE__ */ jsx29(Field13, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx29(Input6, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
2765
- /* @__PURE__ */ jsx29(Field13, { label: copy.replyTo, children: /* @__PURE__ */ jsx29(Input6, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
2766
- data.testAvailable ? /* @__PURE__ */ jsx29(Field13, { label: copy.fromName, hint: copy.fromNameHint, children: /* @__PURE__ */ jsx29(Input6, { value: data.fromName ?? "", onInput: (e) => edit({ fromName: e.target.value }) }) }) : null,
2767
- /* @__PURE__ */ jsx29(Field13, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx29(Input6, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
2833
+ /* @__PURE__ */ jsx30(Field13, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx30(Input6, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
2834
+ /* @__PURE__ */ jsx30(Field13, { label: copy.replyTo, children: /* @__PURE__ */ jsx30(Input6, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
2835
+ data.testAvailable ? /* @__PURE__ */ jsx30(Field13, { label: copy.fromName, hint: copy.fromNameHint, children: /* @__PURE__ */ jsx30(Input6, { value: data.fromName ?? "", onInput: (e) => edit({ fromName: e.target.value }) }) }) : null,
2836
+ /* @__PURE__ */ jsx30(Field13, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx30(Input6, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
2768
2837
  ]
2769
2838
  }
2770
2839
  ),
2771
2840
  (data.validation?.events ?? []).map((event) => {
2772
2841
  const key = event.key, t = data.emailTemplates[key] ?? { subject: "", text: "", enabled: false };
2773
- return /* @__PURE__ */ jsxs27(Panel, { title: event.label, actions: /* @__PURE__ */ jsx29("span", { children: event.status === "ready" ? "Ready" : event.status === "disabled" ? "Switched off" : "Blocked" }), children: [
2774
- /* @__PURE__ */ jsxs27("p", { children: [
2842
+ return /* @__PURE__ */ jsxs28(Panel, { title: event.label, actions: /* @__PURE__ */ jsx30("span", { children: event.status === "ready" ? "Ready" : event.status === "disabled" ? "Switched off" : "Blocked" }), children: [
2843
+ /* @__PURE__ */ jsxs28("p", { children: [
2775
2844
  event.trigger,
2776
2845
  ". Recipient: ",
2777
2846
  event.audience,
2778
2847
  "."
2779
2848
  ] }),
2780
- event.issues.length ? /* @__PURE__ */ jsx29("ul", { children: event.issues.map((issue, i) => /* @__PURE__ */ jsx29("li", { children: issue.message }, i)) }) : null,
2781
- event.warnings?.length ? /* @__PURE__ */ jsx29("ul", { className: "muted", children: event.warnings.map((warning, i) => /* @__PURE__ */ jsxs27("li", { children: [
2849
+ event.issues.length ? /* @__PURE__ */ jsx30("ul", { children: event.issues.map((issue, i) => /* @__PURE__ */ jsx30("li", { children: issue.message }, i)) }) : null,
2850
+ event.warnings?.length ? /* @__PURE__ */ jsx30("ul", { className: "muted", children: event.warnings.map((warning, i) => /* @__PURE__ */ jsxs28("li", { children: [
2782
2851
  copy.spamRisk,
2783
2852
  ": ",
2784
2853
  warning.message
2785
2854
  ] }, i)) }) : null,
2786
- /* @__PURE__ */ jsx29(Checkbox2, { label: copy.enabled, checked: t.enabled === true, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
2787
- /* @__PURE__ */ jsx29(Field13, { label: copy.subject, children: /* @__PURE__ */ jsx29(Input6, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
2788
- /* @__PURE__ */ jsx29(Field13, { label: copy.body, children: /* @__PURE__ */ jsx29(Textarea5, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) }),
2789
- /* @__PURE__ */ jsx29(Field13, { label: copy.htmlBody, hint: copy.htmlBodyHint, children: /* @__PURE__ */ jsx29(Textarea5, { rows: 6, value: t.html ?? "", onInput: (e) => editTemplate(key, { html: e.target.value }) }) }),
2790
- /* @__PURE__ */ jsxs27("p", { className: "muted", children: [
2855
+ /* @__PURE__ */ jsx30(Checkbox2, { label: copy.enabled, checked: t.enabled === true, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
2856
+ /* @__PURE__ */ jsx30(Field13, { label: copy.subject, children: /* @__PURE__ */ jsx30(Input6, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
2857
+ /* @__PURE__ */ jsx30(Field13, { label: copy.body, children: /* @__PURE__ */ jsx30(Textarea5, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) }),
2858
+ /* @__PURE__ */ jsx30(Field13, { label: copy.htmlBody, hint: copy.htmlBodyHint, children: /* @__PURE__ */ jsx30(Textarea5, { rows: 6, value: t.html ?? "", onInput: (e) => editTemplate(key, { html: e.target.value }) }) }),
2859
+ /* @__PURE__ */ jsxs28("p", { className: "muted", children: [
2791
2860
  "Available values: ",
2792
2861
  event.variables.map((name) => `{{${name}}}`).join(", ")
2793
2862
  ] }),
2794
- data.testAvailable ? /* @__PURE__ */ jsx29(Button16, { variant: "secondary", mini: true, disabled: !!draft || event.issues.length > 0 || busy === `test:${key}`, onClick: () => void test(key), children: copy.sendTest }) : null
2863
+ data.testAvailable ? /* @__PURE__ */ jsx30(Button16, { variant: "secondary", mini: true, disabled: !!draft || event.issues.length > 0 || busy === `test:${key}`, onClick: () => void test(key), children: copy.sendTest }) : null
2795
2864
  ] }, key);
2796
2865
  }),
2797
- /* @__PURE__ */ jsxs27("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
2798
- /* @__PURE__ */ jsx29(Button16, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
2799
- note ? /* @__PURE__ */ jsx29("span", { className: "muted", children: note }) : null
2866
+ /* @__PURE__ */ jsxs28("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
2867
+ /* @__PURE__ */ jsx30(Button16, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
2868
+ note ? /* @__PURE__ */ jsx30("span", { className: "muted", children: note }) : null
2800
2869
  ] }),
2801
- /* @__PURE__ */ jsx29(Panel, { title: copy.sendLog, children: /* @__PURE__ */ jsx29(DataTable3, { rows: log.data?.sends ?? [], columns: logColumns(copy, ctx, log.refresh), rowKey: (r) => r.id }) })
2870
+ /* @__PURE__ */ jsx30(Panel, { title: copy.sendLog, children: /* @__PURE__ */ jsx30(DataTable3, { rows: log.data?.sends ?? [], columns: logColumns(copy, ctx, log.refresh), rowKey: (r) => r.id }) })
2802
2871
  ] });
2803
2872
  }
2804
2873
  function emailSection(options = {}) {
2805
2874
  const { id = "email", label = "Email" } = options;
2806
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx29(EmailBody, { ctx }) };
2875
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx30(EmailBody, { ctx }) };
2807
2876
  }
2808
2877
 
2809
2878
  // src/ui/admin-member-content.tsx
2810
- import { useEffect as useEffect10, useState as useState25 } from "preact/hooks";
2811
- import { jsx as jsx30, jsxs as jsxs28 } from "preact/jsx-runtime";
2812
- var emptyDraft = () => ({ pages: [], upsellOverrides: [] });
2879
+ import { useEffect as useEffect11, useState as useState26 } from "preact/hooks";
2880
+ import { jsx as jsx31, jsxs as jsxs29 } from "preact/jsx-runtime";
2881
+ var emptyDraft = () => ({ schemaVersion: 2, sections: [], pages: [], upsellOverrides: [] });
2813
2882
  function accessLabel(page) {
2814
2883
  return page.audience.kind === "all-members" ? "All members" : `Tiers: ${page.audience.tierIds.join(", ")}`;
2815
2884
  }
@@ -2818,17 +2887,17 @@ function MemberContentAdmin({
2818
2887
  initialData
2819
2888
  }) {
2820
2889
  const resource = useAdminResource(ctx.getToken, "/api/admin/member-content");
2821
- const [draft, setDraft] = useState25(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
2822
- const [saveError, setSaveError] = useState25(null);
2823
- const [saving, setSaving] = useState25(false);
2824
- useEffect10(() => {
2890
+ const [draft, setDraft] = useState26(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
2891
+ const [saveError, setSaveError] = useState26(null);
2892
+ const [saving, setSaving] = useState26(false);
2893
+ useEffect11(() => {
2825
2894
  if (!resource.data) return;
2826
2895
  setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft(), null, 2));
2827
2896
  }, [resource.data]);
2828
2897
  const loaded = initialData ?? resource.data;
2829
- if (!loaded && resource.loading) return /* @__PURE__ */ jsx30(AdminPage, { children: /* @__PURE__ */ jsx30(AdminNote, { children: "Loading member content\u2026" }) });
2898
+ if (!loaded && resource.loading) return /* @__PURE__ */ jsx31(AdminPage, { children: /* @__PURE__ */ jsx31(AdminNote, { children: "Loading member content\u2026" }) });
2830
2899
  if (resource.error || !loaded) {
2831
- return /* @__PURE__ */ jsx30(AdminPage, { children: /* @__PURE__ */ jsxs28(AdminNote, { children: [
2900
+ return /* @__PURE__ */ jsx31(AdminPage, { children: /* @__PURE__ */ jsxs29(AdminNote, { children: [
2832
2901
  "Member content could not be loaded: ",
2833
2902
  resource.error ?? "unknown error"
2834
2903
  ] }) });
@@ -2859,35 +2928,35 @@ function MemberContentAdmin({
2859
2928
  setSaving(false);
2860
2929
  }
2861
2930
  };
2862
- return /* @__PURE__ */ jsxs28(AdminPage, { children: [
2863
- /* @__PURE__ */ jsxs28(Panel, { children: [
2864
- /* @__PURE__ */ jsx30("div", { className: "card-label", children: "Member content" }),
2865
- /* @__PURE__ */ jsx30("h2", { children: "One catalog, two clear owners" }),
2866
- /* @__PURE__ */ jsx30("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." }),
2867
- /* @__PURE__ */ jsxs28("div", { className: "stat-band", children: [
2868
- /* @__PURE__ */ jsxs28("div", { children: [
2869
- /* @__PURE__ */ jsx30("strong", { children: inherited.length }),
2870
- /* @__PURE__ */ jsx30("span", { children: "Inherited pages" })
2931
+ return /* @__PURE__ */ jsxs29(AdminPage, { children: [
2932
+ /* @__PURE__ */ jsxs29(Panel, { children: [
2933
+ /* @__PURE__ */ jsx31("div", { className: "card-label", children: "Member content" }),
2934
+ /* @__PURE__ */ jsx31("h2", { children: "One catalog, two clear owners" }),
2935
+ /* @__PURE__ */ jsx31("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." }),
2936
+ /* @__PURE__ */ jsxs29("div", { className: "stat-band", children: [
2937
+ /* @__PURE__ */ jsxs29("div", { children: [
2938
+ /* @__PURE__ */ jsx31("strong", { children: inherited.length }),
2939
+ /* @__PURE__ */ jsx31("span", { children: "Inherited pages" })
2871
2940
  ] }),
2872
- /* @__PURE__ */ jsxs28("div", { children: [
2873
- /* @__PURE__ */ jsx30("strong", { children: local.length }),
2874
- /* @__PURE__ */ jsx30("span", { children: "Chapter pages" })
2941
+ /* @__PURE__ */ jsxs29("div", { children: [
2942
+ /* @__PURE__ */ jsx31("strong", { children: local.length }),
2943
+ /* @__PURE__ */ jsx31("span", { children: "Chapter pages" })
2875
2944
  ] }),
2876
- /* @__PURE__ */ jsxs28("div", { children: [
2877
- /* @__PURE__ */ jsx30("strong", { children: data.tiers.length }),
2878
- /* @__PURE__ */ jsx30("span", { children: "Declared tiers" })
2945
+ /* @__PURE__ */ jsxs29("div", { children: [
2946
+ /* @__PURE__ */ jsx31("strong", { children: data.tiers.length }),
2947
+ /* @__PURE__ */ jsx31("span", { children: "Declared tiers" })
2879
2948
  ] })
2880
2949
  ] })
2881
2950
  ] }),
2882
- /* @__PURE__ */ jsxs28(Panel, { children: [
2883
- /* @__PURE__ */ jsx30("div", { className: "card-label", children: "Inherited \xB7 read only" }),
2884
- /* @__PURE__ */ jsxs28("h2", { children: [
2951
+ /* @__PURE__ */ jsxs29(Panel, { children: [
2952
+ /* @__PURE__ */ jsx31("div", { className: "card-label", children: "Inherited \xB7 read only" }),
2953
+ /* @__PURE__ */ jsxs29("h2", { children: [
2885
2954
  "Parent pages ",
2886
2955
  data.parent ? `\xB7 revision ${data.parent.revision}` : ""
2887
2956
  ] }),
2888
- inherited.length === 0 ? /* @__PURE__ */ jsx30(AdminNote, { children: "No Parent pages have been delivered." }) : /* @__PURE__ */ jsx30("div", { className: "data-list", "data-member-content-inherited": true, children: inherited.map((page) => /* @__PURE__ */ jsxs28("article", { className: "card", children: [
2889
- /* @__PURE__ */ jsx30("strong", { children: page.title }),
2890
- /* @__PURE__ */ jsxs28("div", { className: "meeting-note", children: [
2957
+ inherited.length === 0 ? /* @__PURE__ */ jsx31(AdminNote, { children: "No Parent pages have been delivered." }) : /* @__PURE__ */ jsx31("div", { className: "data-list", "data-member-content-inherited": true, children: inherited.map((page) => /* @__PURE__ */ jsxs29("article", { className: "card", children: [
2958
+ /* @__PURE__ */ jsx31("strong", { children: page.title }),
2959
+ /* @__PURE__ */ jsxs29("div", { className: "meeting-note", children: [
2891
2960
  "/",
2892
2961
  page.slug,
2893
2962
  " \xB7 ",
@@ -2895,22 +2964,22 @@ function MemberContentAdmin({
2895
2964
  " \xB7 ",
2896
2965
  accessLabel(page)
2897
2966
  ] }),
2898
- /* @__PURE__ */ jsxs28("div", { className: "meeting-note", children: [
2967
+ /* @__PURE__ */ jsxs29("div", { className: "meeting-note", children: [
2899
2968
  "Upsell: ",
2900
2969
  page.upsell ? `${page.upsell.owner} controlled` : "none"
2901
2970
  ] })
2902
2971
  ] }, page.id)) })
2903
2972
  ] }),
2904
- /* @__PURE__ */ jsxs28(Panel, { children: [
2905
- /* @__PURE__ */ jsx30("div", { className: "card-label", children: "Chapter \xB7 editable" }),
2906
- /* @__PURE__ */ jsxs28("h2", { children: [
2973
+ /* @__PURE__ */ jsxs29(Panel, { children: [
2974
+ /* @__PURE__ */ jsx31("div", { className: "card-label", children: "Chapter \xB7 editable" }),
2975
+ /* @__PURE__ */ jsxs29("h2", { children: [
2907
2976
  "Local document \xB7 revision ",
2908
2977
  data.chapter?.revision ?? 0
2909
2978
  ] }),
2910
- /* @__PURE__ */ jsx30("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." }),
2911
- /* @__PURE__ */ jsxs28("label", { className: "field", children: [
2912
- /* @__PURE__ */ jsx30("span", { children: "Pages and delegated upsells (JSON)" }),
2913
- /* @__PURE__ */ jsx30(
2979
+ /* @__PURE__ */ jsx31("p", { className: "meeting-note", children: "Use the published MemberContentDocument fields. schemaVersion 2 adds ordered sections, page kinds (markdown, placeholder, feature), tags and icons; omit it for version 1. The server supplies revision, timestamp, and chapter source; unknown fields and undeclared tiers are rejected." }),
2980
+ /* @__PURE__ */ jsxs29("label", { className: "field", children: [
2981
+ /* @__PURE__ */ jsx31("span", { children: "Pages and delegated upsells (JSON)" }),
2982
+ /* @__PURE__ */ jsx31(
2914
2983
  "textarea",
2915
2984
  {
2916
2985
  rows: 24,
@@ -2921,12 +2990,12 @@ function MemberContentAdmin({
2921
2990
  }
2922
2991
  )
2923
2992
  ] }),
2924
- saveError ? /* @__PURE__ */ jsxs28("div", { className: "notice danger", role: "alert", children: [
2925
- /* @__PURE__ */ jsx30("strong", { children: "Fix these items before saving:" }),
2926
- /* @__PURE__ */ jsx30("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx30("li", { children: error }, error)) })
2993
+ saveError ? /* @__PURE__ */ jsxs29("div", { className: "notice danger", role: "alert", children: [
2994
+ /* @__PURE__ */ jsx31("strong", { children: "Fix these items before saving:" }),
2995
+ /* @__PURE__ */ jsx31("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx31("li", { children: error }, error)) })
2927
2996
  ] }) : null,
2928
- /* @__PURE__ */ jsx30("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save chapter content" }),
2929
- !data.localAuthoring ? /* @__PURE__ */ jsx30(AdminNote, { children: "Local authoring is disabled in the checked-in Chapter config." }) : null
2997
+ /* @__PURE__ */ jsx31("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save chapter content" }),
2998
+ !data.localAuthoring ? /* @__PURE__ */ jsx31(AdminNote, { children: "Local authoring is disabled in the checked-in Chapter config." }) : null
2930
2999
  ] })
2931
3000
  ] });
2932
3001
  }
@@ -2934,12 +3003,124 @@ function memberContentSection() {
2934
3003
  return {
2935
3004
  id: "member-content",
2936
3005
  label: "Member content",
2937
- render: (ctx) => /* @__PURE__ */ jsx30(MemberContentAdmin, { ctx })
3006
+ render: (ctx) => /* @__PURE__ */ jsx31(MemberContentAdmin, { ctx })
3007
+ };
3008
+ }
3009
+
3010
+ // src/ui/admin-member-portal.tsx
3011
+ import { useState as useState27 } from "preact/hooks";
3012
+ import { jsx as jsx32, jsxs as jsxs30 } from "preact/jsx-runtime";
3013
+ var when2 = (at) => typeof at === "number" ? new Date(at).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }) : "";
3014
+ function HubRosterPanel({ ctx }) {
3015
+ const roster = useAdminResource(ctx.getToken, "/api/admin/hub-members");
3016
+ const [email, setEmail] = useState27("");
3017
+ const [status, setStatus] = useState27(null);
3018
+ const act = async (path, init, done) => {
3019
+ try {
3020
+ await adminFetch(ctx.getToken, path, init);
3021
+ setStatus(done);
3022
+ roster.refresh();
3023
+ } catch (error) {
3024
+ setStatus(error instanceof Error ? error.message : String(error));
3025
+ }
3026
+ };
3027
+ return /* @__PURE__ */ jsxs30(Panel, { children: [
3028
+ /* @__PURE__ */ jsx32("div", { className: "card-label", children: "Hub members" }),
3029
+ /* @__PURE__ */ jsx32("p", { className: "meeting-note", children: "Members see the portal and its all-members pages. Granting membership never grants admin access." }),
3030
+ /* @__PURE__ */ jsxs30("form", { onSubmit: (event) => {
3031
+ event.preventDefault();
3032
+ void act("/api/admin/hub-members", { method: "POST", body: JSON.stringify({ email }) }, `Granted ${email}`).then(() => setEmail(""));
3033
+ }, children: [
3034
+ /* @__PURE__ */ jsxs30("label", { className: "field", children: [
3035
+ /* @__PURE__ */ jsx32("span", { children: "Email" }),
3036
+ /* @__PURE__ */ jsx32("input", { type: "email", required: true, value: email, onInput: (event) => setEmail(event.currentTarget.value) })
3037
+ ] }),
3038
+ /* @__PURE__ */ jsx32("button", { className: "btn primary", type: "submit", children: "Grant member access" })
3039
+ ] }),
3040
+ status ? /* @__PURE__ */ jsx32("p", { role: "status", children: status }) : null,
3041
+ /* @__PURE__ */ jsxs30("table", { children: [
3042
+ /* @__PURE__ */ jsx32("thead", { children: /* @__PURE__ */ jsxs30("tr", { children: [
3043
+ /* @__PURE__ */ jsx32("th", { children: "Email" }),
3044
+ /* @__PURE__ */ jsx32("th", { children: "Status" }),
3045
+ /* @__PURE__ */ jsx32("th", { children: "Granted" }),
3046
+ /* @__PURE__ */ jsx32("th", {})
3047
+ ] }) }),
3048
+ /* @__PURE__ */ jsx32("tbody", { children: (roster.data?.members ?? []).map((member) => /* @__PURE__ */ jsxs30("tr", { children: [
3049
+ /* @__PURE__ */ jsx32("td", { children: String(member.email) }),
3050
+ /* @__PURE__ */ jsxs30("td", { children: [
3051
+ String(member.status),
3052
+ member.revokedBy ? ` by ${String(member.revokedBy)}` : ""
3053
+ ] }),
3054
+ /* @__PURE__ */ jsxs30("td", { children: [
3055
+ when2(member.grantedAt),
3056
+ " by ",
3057
+ String(member.grantedBy)
3058
+ ] }),
3059
+ /* @__PURE__ */ jsx32("td", { children: member.status === "active" ? /* @__PURE__ */ jsx32("button", { className: "btn secondary mini", type: "button", onClick: () => void act(`/api/admin/hub-members/${encodeURIComponent(String(member.id))}`, { method: "DELETE" }, `Revoked ${String(member.email)}`), children: "Revoke" }) : /* @__PURE__ */ jsx32("button", { className: "btn secondary mini", type: "button", onClick: () => void act("/api/admin/hub-members", { method: "POST", body: JSON.stringify({ email: member.email }) }, `Granted ${String(member.email)}`), children: "Grant again" }) })
3060
+ ] }, String(member.id))) })
3061
+ ] })
3062
+ ] });
3063
+ }
3064
+ var KIND_LABELS = {
3065
+ introduction_unmatched: "Introduced by matches no member, or several",
3066
+ referral_self: "Self-referral",
3067
+ referral_mutual: "Two members referring each other this season",
3068
+ referral_lapsed_referrer: "Referrer's membership has lapsed"
3069
+ };
3070
+ function PortalReviewPanel({ ctx }) {
3071
+ const queue = useAdminResource(ctx.getToken, "/api/admin/portal-review");
3072
+ const [decisions, setDecisions] = useState27({});
3073
+ const [status, setStatus] = useState27(null);
3074
+ const resolve2 = async (id) => {
3075
+ try {
3076
+ await adminFetch(ctx.getToken, `/api/admin/portal-review/${encodeURIComponent(id)}`, { method: "POST", body: JSON.stringify({ decision: decisions[id] ?? "" }) });
3077
+ setStatus("Resolved");
3078
+ queue.refresh();
3079
+ } catch (error) {
3080
+ setStatus(error instanceof Error ? error.message : String(error));
3081
+ }
3082
+ };
3083
+ const items = queue.data?.items ?? [];
3084
+ return /* @__PURE__ */ jsxs30(Panel, { children: [
3085
+ /* @__PURE__ */ jsxs30("div", { className: "card-label", children: [
3086
+ "Review queue \xB7 ",
3087
+ items.length,
3088
+ " open"
3089
+ ] }),
3090
+ status ? /* @__PURE__ */ jsx32("p", { role: "status", children: status }) : null,
3091
+ items.length === 0 ? /* @__PURE__ */ jsx32("p", { className: "meeting-note", children: "Nothing to review." }) : items.map((item) => {
3092
+ const id = String(item.id);
3093
+ return /* @__PURE__ */ jsxs30("div", { className: "card", "data-review-item": id, children: [
3094
+ /* @__PURE__ */ jsx32("strong", { children: KIND_LABELS[String(item.kind)] ?? String(item.kind) }),
3095
+ /* @__PURE__ */ jsx32("pre", { style: { whiteSpace: "pre-wrap" }, children: JSON.stringify(item.detail) }),
3096
+ /* @__PURE__ */ jsxs30("div", { className: "meeting-note", children: [
3097
+ "Member ",
3098
+ String(item.memberId ?? ""),
3099
+ " \xB7 ",
3100
+ when2(item.createdAt)
3101
+ ] }),
3102
+ /* @__PURE__ */ jsxs30("label", { className: "field", children: [
3103
+ /* @__PURE__ */ jsx32("span", { children: "Decision" }),
3104
+ /* @__PURE__ */ jsx32("input", { value: decisions[id] ?? "", onInput: (event) => setDecisions({ ...decisions, [id]: event.currentTarget.value }) })
3105
+ ] }),
3106
+ /* @__PURE__ */ jsx32("button", { className: "btn primary mini", type: "button", disabled: !(decisions[id] ?? "").trim(), onClick: () => void resolve2(id), children: "Resolve" })
3107
+ ] }, id);
3108
+ })
3109
+ ] });
3110
+ }
3111
+ function memberPortalSection(chapter) {
3112
+ return {
3113
+ id: "member-portal",
3114
+ label: "Member portal",
3115
+ render: (ctx) => /* @__PURE__ */ jsxs30("div", { className: "chapter-admin-stack", children: [
3116
+ chapter.mode === "hub" ? /* @__PURE__ */ jsx32(HubRosterPanel, { ctx }) : null,
3117
+ chapter.memberPortal ? /* @__PURE__ */ jsx32(PortalReviewPanel, { ctx }) : null
3118
+ ] })
2938
3119
  };
2939
3120
  }
2940
3121
 
2941
3122
  // src/ui/admin-settings-workspace.tsx
2942
- import { jsx as jsx31 } from "preact/jsx-runtime";
3123
+ import { jsx as jsx33 } from "preact/jsx-runtime";
2943
3124
  var viewContext = (ctx, viewId, active) => {
2944
3125
  const route = { workspaceId: ctx.route.workspaceId, viewId, ...active && ctx.route.recordId ? { recordId: ctx.route.recordId } : {}, ...active && ctx.route.detailTab ? { detailTab: ctx.route.detailTab } : {} };
2945
3126
  const targetFromView = (target) => {
@@ -2952,30 +3133,40 @@ var viewContext = (ctx, viewId, active) => {
2952
3133
  };
2953
3134
  function SettingsWorkspace({ chapter, ctx }) {
2954
3135
  const copy = chapter.copy.admin;
2955
- const active = ctx.route.viewId === "email" ? "email" : ctx.route.viewId === "builder-library" && chapter.builderLibrary ? "builder-library" : ctx.route.viewId === "member-content" && chapter.memberContent ? "member-content" : "calendar";
3136
+ const active = ctx.route.viewId === "email" ? "email" : ctx.route.viewId === "builder-library" && chapter.builderLibrary ? "builder-library" : ctx.route.viewId === "member-content" && chapter.memberContent ? "member-content" : ctx.route.viewId === "member-portal" && chapter.memberPortal ? "member-portal" : "calendar";
2956
3137
  const calendar = availabilitySection({ id: "calendar", label: copy.workspaces.calendar });
2957
3138
  const email = emailSection({ label: copy.workspaces.email });
2958
3139
  const content = memberContentSection();
2959
3140
  const library = builderLibrarySection();
2960
- return /* @__PURE__ */ jsx31(Tabs2, { ariaLabel: copy.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
3141
+ return /* @__PURE__ */ jsx33(Tabs2, { ariaLabel: copy.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
2961
3142
  { value: "calendar", label: copy.workspaces.calendar, href: ctx.href({ viewId: "calendar", recordId: null, detailTab: null }), panel: calendar.render(viewContext(ctx, "calendar", active === "calendar")) },
2962
3143
  { value: "email", label: copy.workspaces.email, href: ctx.href({ viewId: "email", recordId: null, detailTab: null }), panel: email.render(viewContext(ctx, "email", active === "email")) },
2963
3144
  ...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")) }] : [],
3145
+ ...chapter.memberPortal ? [portalTab(chapter, ctx, active)] : [],
2964
3146
  ...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")) }] : []
2965
3147
  ] });
2966
3148
  }
3149
+ var portalTab = (chapter, ctx, active) => ({
3150
+ value: "member-portal",
3151
+ label: "Member portal",
3152
+ href: ctx.href({ viewId: "member-portal", recordId: null, detailTab: null }),
3153
+ panel: memberPortalSection(chapter).render(viewContext(ctx, "member-portal", active === "member-portal"))
3154
+ });
2967
3155
  function LeaderSettingsWorkspace({ chapter, ctx }) {
2968
3156
  const email = emailSection({ label: chapter.copy.admin.workspaces.email });
2969
3157
  const library = builderLibrarySection();
2970
- const active = ctx.route.viewId === "builder-library" && chapter.builderLibrary ? "builder-library" : "email";
2971
- return /* @__PURE__ */ jsx31(Tabs2, { ariaLabel: chapter.copy.admin.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
3158
+ const members = Boolean(chapter.memberPortal || chapter.memberContent);
3159
+ const active = ctx.route.viewId === "builder-library" && chapter.builderLibrary ? "builder-library" : ctx.route.viewId === "member-content" && chapter.memberContent ? "member-content" : ctx.route.viewId === "member-portal" && members ? "member-portal" : "email";
3160
+ return /* @__PURE__ */ jsx33(Tabs2, { ariaLabel: chapter.copy.admin.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
2972
3161
  { 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")) },
3162
+ ...chapter.memberContent ? [{ value: "member-content", label: "Member content", href: ctx.href({ viewId: "member-content", recordId: null, detailTab: null }), panel: memberContentSection().render(viewContext(ctx, "member-content", active === "member-content")) }] : [],
3163
+ ...members ? [portalTab(chapter, ctx, active)] : [],
2973
3164
  ...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")) }] : []
2974
3165
  ] });
2975
3166
  }
2976
3167
 
2977
3168
  // src/ui/admin-workspaces.tsx
2978
- import { jsx as jsx32 } from "preact/jsx-runtime";
3169
+ import { jsx as jsx34 } from "preact/jsx-runtime";
2979
3170
  var adminViewContext = (ctx, viewId, active) => {
2980
3171
  const route = {
2981
3172
  workspaceId: ctx.route.workspaceId,
@@ -3001,7 +3192,7 @@ function DashboardWorkspace({ chapter, ctx }) {
3001
3192
  const active = ctx.route.viewId === "billing" ? "billing" : "overview";
3002
3193
  const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
3003
3194
  const billing = billingSection({ label: copy.workspaces.billing });
3004
- return /* @__PURE__ */ jsx32(
3195
+ return /* @__PURE__ */ jsx34(
3005
3196
  Tabs3,
3006
3197
  {
3007
3198
  ariaLabel: copy.workspaces.dashboardViewsLabel,
@@ -3029,7 +3220,7 @@ function PeopleWorkspace({ chapter, ctx }) {
3029
3220
  const fallback = types[0]?.[0] ?? "person";
3030
3221
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
3031
3222
  const networkSharing = chapter.network.targets.length > 0;
3032
- return /* @__PURE__ */ jsx32(
3223
+ return /* @__PURE__ */ jsx34(
3033
3224
  Tabs3,
3034
3225
  {
3035
3226
  ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
@@ -3060,8 +3251,8 @@ function PortfolioWorkspace({ chapter, ctx }) {
3060
3251
  const types = Object.entries(chapter.crm.config.types).filter(([, def]) => def.workspace === "portfolio");
3061
3252
  const fallback = types[0]?.[0] ?? "";
3062
3253
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
3063
- if (!active) return /* @__PURE__ */ jsx32(LeaderNetworkOverview, { ctx });
3064
- return /* @__PURE__ */ jsx32(
3254
+ if (!active) return /* @__PURE__ */ jsx34(LeaderNetworkOverview, { ctx });
3255
+ return /* @__PURE__ */ jsx34(
3065
3256
  Tabs3,
3066
3257
  {
3067
3258
  ariaLabel: copy.workspaces.portfolioViewsLabel,
@@ -3085,7 +3276,7 @@ function PortfolioWorkspace({ chapter, ctx }) {
3085
3276
  );
3086
3277
  }
3087
3278
  function LeaderDashboardWorkspace({ chapter, ctx }) {
3088
- return /* @__PURE__ */ jsx32(
3279
+ return /* @__PURE__ */ jsx34(
3089
3280
  Tabs3,
3090
3281
  {
3091
3282
  ariaLabel: chapter.copy.admin.workspaces.dashboardViewsLabel,
@@ -3095,7 +3286,7 @@ function LeaderDashboardWorkspace({ chapter, ctx }) {
3095
3286
  value: "overview",
3096
3287
  label: chapter.copy.admin.workspaces.overview,
3097
3288
  href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
3098
- panel: /* @__PURE__ */ jsx32(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
3289
+ panel: /* @__PURE__ */ jsx34(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
3099
3290
  }]
3100
3291
  }
3101
3292
  );
@@ -3106,7 +3297,7 @@ function defaultAdminWorkspaces(chapter) {
3106
3297
  id: "people",
3107
3298
  label: copy.workspaces.people,
3108
3299
  defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
3109
- render: (ctx) => /* @__PURE__ */ jsx32(PeopleWorkspace, { chapter, ctx })
3300
+ render: (ctx) => /* @__PURE__ */ jsx34(PeopleWorkspace, { chapter, ctx })
3110
3301
  };
3111
3302
  if (chapter.mode === "hub" && chapter.network.targets.length === 0) return [people];
3112
3303
  if (chapter.mode === "hub") {
@@ -3116,7 +3307,7 @@ function defaultAdminWorkspaces(chapter) {
3116
3307
  id: "dashboard",
3117
3308
  label: copy.workspaces.dashboard,
3118
3309
  defaultViewId: "overview",
3119
- render: (ctx) => /* @__PURE__ */ jsx32(LeaderDashboardWorkspace, { chapter, ctx })
3310
+ render: (ctx) => /* @__PURE__ */ jsx34(LeaderDashboardWorkspace, { chapter, ctx })
3120
3311
  },
3121
3312
  networkRecordsWorkspace(chapter),
3122
3313
  people,
@@ -3124,13 +3315,13 @@ function defaultAdminWorkspaces(chapter) {
3124
3315
  id: "portfolio",
3125
3316
  label: copy.workspaces.portfolio,
3126
3317
  defaultViewId: portfolioTypes[0]?.[0],
3127
- render: (ctx) => /* @__PURE__ */ jsx32(PortfolioWorkspace, { chapter, ctx })
3318
+ render: (ctx) => /* @__PURE__ */ jsx34(PortfolioWorkspace, { chapter, ctx })
3128
3319
  },
3129
3320
  {
3130
3321
  id: "settings",
3131
3322
  label: copy.workspaces.settings,
3132
3323
  defaultViewId: "email",
3133
- render: (ctx) => /* @__PURE__ */ jsx32(LeaderSettingsWorkspace, { chapter, ctx })
3324
+ render: (ctx) => /* @__PURE__ */ jsx34(LeaderSettingsWorkspace, { chapter, ctx })
3134
3325
  }
3135
3326
  ];
3136
3327
  }
@@ -3139,7 +3330,7 @@ function defaultAdminWorkspaces(chapter) {
3139
3330
  id: "dashboard",
3140
3331
  label: copy.workspaces.dashboard,
3141
3332
  defaultViewId: "overview",
3142
- render: (ctx) => /* @__PURE__ */ jsx32(DashboardWorkspace, { chapter, ctx })
3333
+ render: (ctx) => /* @__PURE__ */ jsx34(DashboardWorkspace, { chapter, ctx })
3143
3334
  },
3144
3335
  people,
3145
3336
  admissionGrantsWorkspace(copy.workspaces.admission),
@@ -3147,17 +3338,17 @@ function defaultAdminWorkspaces(chapter) {
3147
3338
  id: "settings",
3148
3339
  label: copy.workspaces.settings,
3149
3340
  defaultViewId: "calendar",
3150
- render: (ctx) => /* @__PURE__ */ jsx32(SettingsWorkspace, { chapter, ctx })
3341
+ render: (ctx) => /* @__PURE__ */ jsx34(SettingsWorkspace, { chapter, ctx })
3151
3342
  }
3152
3343
  ];
3153
3344
  }
3154
3345
 
3155
3346
  // src/ui/admin-frame.tsx
3156
3347
  import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
3157
- import { jsx as jsx33, jsxs as jsxs29 } from "preact/jsx-runtime";
3348
+ import { jsx as jsx35, jsxs as jsxs31 } from "preact/jsx-runtime";
3158
3349
  function AdminTheme(props) {
3159
3350
  const brand = props.chapter?.brand;
3160
- return /* @__PURE__ */ jsx33(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs29(
3351
+ return /* @__PURE__ */ jsx35(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs31(
3161
3352
  ThemeScope2,
3162
3353
  {
3163
3354
  ref: props.scopeRef,
@@ -3166,7 +3357,7 @@ function AdminTheme(props) {
3166
3357
  accent: brand?.accent,
3167
3358
  colorScheme: brand?.colorScheme ?? "light",
3168
3359
  children: [
3169
- /* @__PURE__ */ jsx33(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
3360
+ /* @__PURE__ */ jsx35(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
3170
3361
  props.children
3171
3362
  ]
3172
3363
  }
@@ -3186,11 +3377,11 @@ function signInRedirect(basePath, workspaces) {
3186
3377
  }
3187
3378
 
3188
3379
  // src/ui/admin-appearance.ts
3189
- import { useEffect as useEffect11, useState as useState26 } from "preact/hooks";
3380
+ import { useEffect as useEffect12, useState as useState28 } from "preact/hooks";
3190
3381
  import { clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
3191
3382
  function useScopedClerkAppearance(scope) {
3192
- const [appearance, setAppearance] = useState26();
3193
- useEffect11(() => {
3383
+ const [appearance, setAppearance] = useState28();
3384
+ useEffect12(() => {
3194
3385
  setAppearance(clerkAppearanceFromTokens(scope.current ?? void 0));
3195
3386
  }, [scope]);
3196
3387
  return appearance;
@@ -3221,7 +3412,7 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
3221
3412
  }
3222
3413
 
3223
3414
  // src/ui/admin.tsx
3224
- import { jsx as jsx34, jsxs as jsxs30 } from "preact/jsx-runtime";
3415
+ import { jsx as jsx36, jsxs as jsxs32 } from "preact/jsx-runtime";
3225
3416
  function Authed(props) {
3226
3417
  const copy = useChapterCopy().admin;
3227
3418
  const {
@@ -3249,8 +3440,8 @@ function Authed(props) {
3249
3440
  }),
3250
3441
  [getToken, crmBasePath]
3251
3442
  );
3252
- const [state, setState] = useState27({ status: "checking" });
3253
- useEffect12(() => {
3443
+ const [state, setState] = useState29({ status: "checking" });
3444
+ useEffect13(() => {
3254
3445
  let live = true;
3255
3446
  void (async () => {
3256
3447
  try {
@@ -3269,17 +3460,17 @@ function Authed(props) {
3269
3460
  live = false;
3270
3461
  };
3271
3462
  }, [getToken, apiBase, auth]);
3272
- if (state.status === "checking") return /* @__PURE__ */ jsx34(Gate, { brand, children: /* @__PURE__ */ jsx34("p", { style: S.muted, children: copy.auth.checking }) });
3463
+ if (state.status === "checking") return /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.auth.checking }) });
3273
3464
  if (state.status === "denied") {
3274
- return /* @__PURE__ */ jsx34(Gate, { brand, children: /* @__PURE__ */ jsxs30("div", { className: "card", style: S.card, children: [
3275
- /* @__PURE__ */ jsx34("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
3276
- /* @__PURE__ */ jsx34("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
3465
+ return /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsxs32("div", { className: "card", style: S.card, children: [
3466
+ /* @__PURE__ */ jsx36("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
3467
+ /* @__PURE__ */ jsx36("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
3277
3468
  account: state.email ?? copy.auth.thisAccount
3278
3469
  }) }),
3279
- /* @__PURE__ */ jsx34("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
3470
+ /* @__PURE__ */ jsx36("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
3280
3471
  ] }) });
3281
3472
  }
3282
- return /* @__PURE__ */ jsx34(
3473
+ return /* @__PURE__ */ jsx36(
3283
3474
  AdminShell,
3284
3475
  {
3285
3476
  workspaces,
@@ -3321,10 +3512,10 @@ function ChapterAdmin(props) {
3321
3512
  const chrome = props.chrome ?? "embedded";
3322
3513
  const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
3323
3514
  const redirect = signInRedirect(basePath, workspaces);
3324
- const [publishableKey, setPublishableKey] = useState27(void 0);
3515
+ const [publishableKey, setPublishableKey] = useState29(void 0);
3325
3516
  const scopeRef = useRef3(null);
3326
3517
  const appearance = useScopedClerkAppearance(scopeRef);
3327
- useEffect12(() => {
3518
+ useEffect13(() => {
3328
3519
  let live = true;
3329
3520
  void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
3330
3521
  return () => {
@@ -3333,23 +3524,23 @@ function ChapterAdmin(props) {
3333
3524
  }, [apiBase, auth]);
3334
3525
  let content;
3335
3526
  if (publishableKey === void 0 || publishableKey && !appearance) {
3336
- content = /* @__PURE__ */ jsx34(Gate, { brand, children: /* @__PURE__ */ jsx34("p", { style: S.muted, children: copy.admin.auth.loading }) });
3527
+ content = /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.admin.auth.loading }) });
3337
3528
  } else if (!publishableKey) {
3338
- content = /* @__PURE__ */ jsx34(Gate, { brand, children: /* @__PURE__ */ jsxs30("div", { className: "card", style: S.card, children: [
3339
- /* @__PURE__ */ jsx34("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
3340
- /* @__PURE__ */ jsx34("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
3529
+ content = /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsxs32("div", { className: "card", style: S.card, children: [
3530
+ /* @__PURE__ */ jsx36("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
3531
+ /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
3341
3532
  ] }) });
3342
3533
  } else if (workspaces.length === 0) {
3343
- content = /* @__PURE__ */ jsx34(Gate, { brand, children: /* @__PURE__ */ jsx34("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
3534
+ content = /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
3344
3535
  } else {
3345
- content = /* @__PURE__ */ jsxs30(
3536
+ content = /* @__PURE__ */ jsxs32(
3346
3537
  ClerkGate,
3347
3538
  {
3348
3539
  publishableKey,
3349
3540
  appearance,
3350
3541
  afterSignOutUrl: "/",
3351
3542
  children: [
3352
- /* @__PURE__ */ jsx34(SignedOut, { children: /* @__PURE__ */ jsx34(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx34(
3543
+ /* @__PURE__ */ jsx36(SignedOut, { children: /* @__PURE__ */ jsx36(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx36(
3353
3544
  SignIn,
3354
3545
  {
3355
3546
  routing: "hash",
@@ -3357,7 +3548,7 @@ function ChapterAdmin(props) {
3357
3548
  signUpForceRedirectUrl: redirect
3358
3549
  }
3359
3550
  ) }) }),
3360
- /* @__PURE__ */ jsx34(SignedIn, { children: /* @__PURE__ */ jsx34(
3551
+ /* @__PURE__ */ jsx36(SignedIn, { children: /* @__PURE__ */ jsx36(
3361
3552
  Authed,
3362
3553
  {
3363
3554
  workspaces,
@@ -3378,7 +3569,7 @@ function ChapterAdmin(props) {
3378
3569
  }
3379
3570
  );
3380
3571
  }
3381
- return /* @__PURE__ */ jsx34(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
3572
+ return /* @__PURE__ */ jsx36(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
3382
3573
  }
3383
3574
 
3384
3575
  // src/ui/admin-defaults.ts
@@ -3387,17 +3578,17 @@ function defaultAdminSections(chapter) {
3387
3578
  }
3388
3579
 
3389
3580
  // src/ui/admin-meetings.tsx
3390
- import { useState as useState28 } from "preact/hooks";
3581
+ import { useState as useState30 } from "preact/hooks";
3391
3582
  import { Button as Button17 } from "@odla-ai/ui/components";
3392
- import { Fragment as Fragment13, jsx as jsx35, jsxs as jsxs31 } from "preact/jsx-runtime";
3393
- var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
3583
+ import { Fragment as Fragment13, jsx as jsx37, jsxs as jsxs33 } from "preact/jsx-runtime";
3584
+ var when3 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
3394
3585
  function MeetingsBody({ ctx }) {
3395
3586
  const copy = useChapterCopy().admin.meetings;
3396
3587
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
3397
- const [busy, setBusy] = useState28(null);
3398
- const [note, setNote] = useState28(null);
3399
- if (loading) return /* @__PURE__ */ jsx35(AdminNote, { children: copy.loading });
3400
- if (error || !data) return /* @__PURE__ */ jsxs31(AdminNote, { children: [
3588
+ const [busy, setBusy] = useState30(null);
3589
+ const [note, setNote] = useState30(null);
3590
+ if (loading) return /* @__PURE__ */ jsx37(AdminNote, { children: copy.loading });
3591
+ if (error || !data) return /* @__PURE__ */ jsxs33(AdminNote, { children: [
3401
3592
  copy.loadFailed,
3402
3593
  error ? `: ${error}` : "",
3403
3594
  "."
@@ -3428,43 +3619,43 @@ function MeetingsBody({ ctx }) {
3428
3619
  }
3429
3620
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
3430
3621
  };
3431
- return /* @__PURE__ */ jsx35(AdminPage, { children: /* @__PURE__ */ jsx35(Panel, { title: /* @__PURE__ */ jsxs31(Fragment13, { children: [
3622
+ return /* @__PURE__ */ jsx37(AdminPage, { children: /* @__PURE__ */ jsx37(Panel, { title: /* @__PURE__ */ jsxs33(Fragment13, { children: [
3432
3623
  copy.agenda,
3433
3624
  " ",
3434
- /* @__PURE__ */ jsxs31("span", { className: "muted", style: { fontWeight: 400 }, children: [
3625
+ /* @__PURE__ */ jsxs33("span", { className: "muted", style: { fontWeight: 400 }, children: [
3435
3626
  "\xB7 ",
3436
3627
  data.timezone
3437
3628
  ] })
3438
- ] }), actions: note ? /* @__PURE__ */ jsx35("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx35("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx35("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs31("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
3439
- /* @__PURE__ */ jsx35("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
3440
- /* @__PURE__ */ jsx35("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
3441
- /* @__PURE__ */ jsx35("span", { className: "badge", children: m.status }),
3442
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs31("span", { className: "badge", children: [
3629
+ ] }), actions: note ? /* @__PURE__ */ jsx37("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx37("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx37("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs33("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
3630
+ /* @__PURE__ */ jsx37("strong", { style: { minWidth: 170 }, children: when3(m.startAt) }),
3631
+ /* @__PURE__ */ jsx37("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
3632
+ /* @__PURE__ */ jsx37("span", { className: "badge", children: m.status }),
3633
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs33("span", { className: "badge", children: [
3443
3634
  copy.drift,
3444
3635
  ": ",
3445
3636
  m.drift
3446
3637
  ] }) : null,
3447
- m.meetUrl ? /* @__PURE__ */ jsx35("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
3448
- m.status === "scheduled" ? /* @__PURE__ */ jsxs31(Fragment13, { children: [
3449
- /* @__PURE__ */ jsx35(Button17, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
3450
- /* @__PURE__ */ jsx35(Button17, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
3638
+ m.meetUrl ? /* @__PURE__ */ jsx37("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
3639
+ m.status === "scheduled" ? /* @__PURE__ */ jsxs33(Fragment13, { children: [
3640
+ /* @__PURE__ */ jsx37(Button17, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
3641
+ /* @__PURE__ */ jsx37(Button17, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
3451
3642
  ] }) : null
3452
3643
  ] }, m.id)) }) }) });
3453
3644
  }
3454
3645
  function meetingsSection(options = {}) {
3455
3646
  const { id = "meetings", label = "Meetings" } = options;
3456
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx35(MeetingsBody, { ctx }) };
3647
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx37(MeetingsBody, { ctx }) };
3457
3648
  }
3458
3649
 
3459
3650
  // src/ui/admin-network-record-editor.tsx
3460
- import { Button as Button18, Field as Field14, Select as Select10 } from "@odla-ai/ui/components";
3461
- import { useRef as useRef4, useState as useState29 } from "preact/hooks";
3462
- import { Fragment as Fragment14, jsx as jsx36, jsxs as jsxs32 } from "preact/jsx-runtime";
3651
+ import { Button as Button18, Field as Field14, Select as Select11 } from "@odla-ai/ui/components";
3652
+ import { useRef as useRef4, useState as useState31 } from "preact/hooks";
3653
+ import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs34 } from "preact/jsx-runtime";
3463
3654
  function NetworkRecordEditor(props) {
3464
3655
  const { ctx, record } = props;
3465
- const [stage, setStage] = useState29("");
3466
- const [busy, setBusy] = useState29(false);
3467
- const [error, setError] = useState29(null);
3656
+ const [stage, setStage] = useState31("");
3657
+ const [busy, setBusy] = useState31(false);
3658
+ const [error, setError] = useState31(null);
3468
3659
  const attempt = useRef4(null);
3469
3660
  const pending = record.edit?.pendingAction;
3470
3661
  const superAdmin = ctx.currentUser?.superAdmin === true;
@@ -3497,11 +3688,11 @@ function NetworkRecordEditor(props) {
3497
3688
  setBusy(false);
3498
3689
  }
3499
3690
  };
3500
- return /* @__PURE__ */ jsxs32("div", { style: { display: "grid", gap: 24 }, children: [
3501
- superAdmin && record.edit ? /* @__PURE__ */ jsxs32(Fragment14, { children: [
3502
- /* @__PURE__ */ jsxs32("section", { children: [
3503
- /* @__PURE__ */ jsx36("h3", { children: "Chapter profile" }),
3504
- /* @__PURE__ */ jsx36(
3691
+ return /* @__PURE__ */ jsxs34("div", { style: { display: "grid", gap: 24 }, children: [
3692
+ superAdmin && record.edit ? /* @__PURE__ */ jsxs34(Fragment14, { children: [
3693
+ /* @__PURE__ */ jsxs34("section", { children: [
3694
+ /* @__PURE__ */ jsx38("h3", { children: "Chapter profile" }),
3695
+ /* @__PURE__ */ jsx38(
3505
3696
  OperationalProfileForm,
3506
3697
  {
3507
3698
  fields: record.edit.fields,
@@ -3511,27 +3702,27 @@ function NetworkRecordEditor(props) {
3511
3702
  `${record.id}:${record.revision}`
3512
3703
  )
3513
3704
  ] }),
3514
- /* @__PURE__ */ jsxs32("section", { style: { display: "grid", gap: 12 }, children: [
3515
- /* @__PURE__ */ jsx36("h3", { children: "Membership actions" }),
3516
- pending ? /* @__PURE__ */ jsxs32("p", { children: [
3705
+ /* @__PURE__ */ jsxs34("section", { style: { display: "grid", gap: 12 }, children: [
3706
+ /* @__PURE__ */ jsx38("h3", { children: "Membership actions" }),
3707
+ pending ? /* @__PURE__ */ jsxs34("p", { children: [
3517
3708
  "An application action is pending: ",
3518
3709
  pending.stage.replaceAll("_", " "),
3519
3710
  ". ",
3520
3711
  ownPending ? "Retry reconciles the same operation." : "The original operator must reconcile it."
3521
- ] }) : record.edit.allowedStages.length ? /* @__PURE__ */ jsx36(Field14, { label: "Action", children: /* @__PURE__ */ jsxs32(Select10, { "aria-label": "Action", value: stage, disabled: busy, onChange: (event) => setStage(event.currentTarget.value), children: [
3522
- /* @__PURE__ */ jsx36("option", { value: "", children: "Choose an action" }),
3523
- record.edit.allowedStages.map((action) => /* @__PURE__ */ jsx36("option", { value: action.id, children: action.label }, action.id))
3524
- ] }) }) : /* @__PURE__ */ jsx36("p", { children: "No membership action is currently allowed." }),
3525
- pending || record.edit.allowedStages.length ? /* @__PURE__ */ jsx36("div", { children: /* @__PURE__ */ jsx36(Button18, { disabled: busy || !ownPending || !pending && !stage, onClick: () => void move(), children: busy ? "Applying\u2026" : pending ? "Retry pending action" : "Apply action" }) }) : null,
3526
- error ? /* @__PURE__ */ jsx36("p", { role: "alert", children: error }) : null
3712
+ ] }) : record.edit.allowedStages.length ? /* @__PURE__ */ jsx38(Field14, { label: "Action", children: /* @__PURE__ */ jsxs34(Select11, { "aria-label": "Action", value: stage, disabled: busy, onChange: (event) => setStage(event.currentTarget.value), children: [
3713
+ /* @__PURE__ */ jsx38("option", { value: "", children: "Choose an action" }),
3714
+ record.edit.allowedStages.map((action) => /* @__PURE__ */ jsx38("option", { value: action.id, children: action.label }, action.id))
3715
+ ] }) }) : /* @__PURE__ */ jsx38("p", { children: "No membership action is currently allowed." }),
3716
+ pending || record.edit.allowedStages.length ? /* @__PURE__ */ jsx38("div", { children: /* @__PURE__ */ jsx38(Button18, { disabled: busy || !ownPending || !pending && !stage, onClick: () => void move(), children: busy ? "Applying\u2026" : pending ? "Retry pending action" : "Apply action" }) }) : null,
3717
+ error ? /* @__PURE__ */ jsx38("p", { role: "alert", children: error }) : null
3527
3718
  ] })
3528
3719
  ] }) : null,
3529
- superAdmin && props.admissionGrants ? /* @__PURE__ */ jsxs32("section", { children: [
3530
- /* @__PURE__ */ jsx36("h3", { children: "Next invitation" }),
3531
- /* @__PURE__ */ jsx36("p", { children: "This setting applies to the next eligible signup; it does not replace approval of an existing application." }),
3532
- /* @__PURE__ */ jsx36(LeaderAdmissionAction, { getToken: ctx.getToken, targetId: props.targetId, sourceRecordId: record.id })
3720
+ superAdmin && props.admissionGrants ? /* @__PURE__ */ jsxs34("section", { children: [
3721
+ /* @__PURE__ */ jsx38("h3", { children: "Next invitation" }),
3722
+ /* @__PURE__ */ jsx38("p", { children: "This setting applies to the next eligible signup; it does not replace approval of an existing application." }),
3723
+ /* @__PURE__ */ jsx38(LeaderAdmissionAction, { getToken: ctx.getToken, targetId: props.targetId, sourceRecordId: record.id })
3533
3724
  ] }) : null,
3534
- /* @__PURE__ */ jsx36(
3725
+ /* @__PURE__ */ jsx38(
3535
3726
  NetworkRecordDetail,
3536
3727
  {
3537
3728
  ctx,
@@ -3546,29 +3737,29 @@ function NetworkRecordEditor(props) {
3546
3737
  }
3547
3738
 
3548
3739
  // src/ui/network-seat-billing.tsx
3549
- import { useState as useState30 } from "preact/hooks";
3740
+ import { useState as useState32 } from "preact/hooks";
3550
3741
  import { Button as Button19, DataTable as DataTable4 } from "@odla-ai/ui/components";
3551
- import { jsx as jsx37, jsxs as jsxs33 } from "preact/jsx-runtime";
3742
+ import { jsx as jsx39, jsxs as jsxs35 } from "preact/jsx-runtime";
3552
3743
  var money3 = (row, cents) => row.currency ? new Intl.NumberFormat("en-US", {
3553
3744
  style: "currency",
3554
3745
  currency: row.currency
3555
3746
  }).format(cents / 100) : "Unknown";
3556
3747
  var columns2 = (refund, busy) => [
3557
- { key: "seatId", header: "Seat", cell: (row) => /* @__PURE__ */ jsx37("code", { children: row.seatId }) },
3748
+ { key: "seatId", header: "Seat", cell: (row) => /* @__PURE__ */ jsx39("code", { children: row.seatId }) },
3558
3749
  { key: "sourceKey", header: "Chapter / runtime", cell: (row) => row.sourceKey },
3559
3750
  { key: "status", header: "Payment", cell: (row) => row.status ?? "Unknown" },
3560
3751
  { key: "amountReceivedCents", header: "Collected", cell: (row) => money3(row, row.amountReceivedCents) },
3561
3752
  { key: "refundedCents", header: "Refunded", cell: (row) => money3(row, row.refundedCents) },
3562
- { key: "membershipId", header: "Primary membership", cell: (row) => /* @__PURE__ */ jsx37("code", { children: row.membershipId }) },
3563
- { key: "paymentIntentId", header: "Stripe payment", cell: (row) => /* @__PURE__ */ jsx37("code", { children: row.paymentIntentId }) },
3564
- { key: "subscriptionItemId", header: "Renewal item", cell: (row) => row.subscriptionItemId ? /* @__PURE__ */ jsx37("code", { children: row.subscriptionItemId }) : "Awaiting provider linkage" },
3753
+ { key: "membershipId", header: "Primary membership", cell: (row) => /* @__PURE__ */ jsx39("code", { children: row.membershipId }) },
3754
+ { key: "paymentIntentId", header: "Stripe payment", cell: (row) => /* @__PURE__ */ jsx39("code", { children: row.paymentIntentId }) },
3755
+ { key: "subscriptionItemId", header: "Renewal item", cell: (row) => row.subscriptionItemId ? /* @__PURE__ */ jsx39("code", { children: row.subscriptionItemId }) : "Awaiting provider linkage" },
3565
3756
  { key: "currentPeriodEnd", header: "Paid through", cell: (row) => row.currentPeriodEnd ? new Date(row.currentPeriodEnd * 1e3).toLocaleDateString() : "Awaiting provider linkage" },
3566
- { key: "refund", header: "Refund", cell: (row) => row.refundedCents < row.amountReceivedCents ? /* @__PURE__ */ jsx37(Button19, { variant: "danger", mini: true, disabled: busy !== null, onClick: () => refund(row), children: busy === row.seatId ? "Refunding\u2026" : "Refund seat" }) : "Refunded" }
3757
+ { key: "refund", header: "Refund", cell: (row) => row.refundedCents < row.amountReceivedCents ? /* @__PURE__ */ jsx39(Button19, { variant: "danger", mini: true, disabled: busy !== null, onClick: () => refund(row), children: busy === row.seatId ? "Refunding\u2026" : "Refund seat" }) : "Refunded" }
3567
3758
  ];
3568
3759
  function NetworkSeatBilling(props) {
3569
3760
  const { data, loading, error, refresh } = useAdminResource(props.getToken, "/api/admin/network-commercial-read-model");
3570
- const [busy, setBusy] = useState30(null);
3571
- const [note, setNote] = useState30(null);
3761
+ const [busy, setBusy] = useState32(null);
3762
+ const [note, setNote] = useState32(null);
3572
3763
  const refund = async (row) => {
3573
3764
  setBusy(row.seatId);
3574
3765
  setNote(null);
@@ -3586,20 +3777,20 @@ function NetworkSeatBilling(props) {
3586
3777
  setBusy(null);
3587
3778
  }
3588
3779
  };
3589
- return /* @__PURE__ */ jsxs33(Panel, { title: "Additional membership seats", children: [
3590
- /* @__PURE__ */ jsx37("p", { children: "Seat payments are included in the network totals. Renewal items share the primary subscription\u2019s term. An unaccepted seat is never refunded automatically." }),
3591
- loading ? /* @__PURE__ */ jsx37("p", { children: "Loading seats\u2026" }) : error ? /* @__PURE__ */ jsxs33("p", { role: "alert", children: [
3780
+ return /* @__PURE__ */ jsxs35(Panel, { title: "Additional membership seats", children: [
3781
+ /* @__PURE__ */ jsx39("p", { children: "Seat payments are included in the network totals. Renewal items share the primary subscription\u2019s term. An unaccepted seat is never refunded automatically." }),
3782
+ loading ? /* @__PURE__ */ jsx39("p", { children: "Loading seats\u2026" }) : error ? /* @__PURE__ */ jsxs35("p", { role: "alert", children: [
3592
3783
  "Seats could not be loaded: ",
3593
3784
  error
3594
- ] }) : /* @__PURE__ */ jsx37(DataTable4, { rows: data?.seats ?? [], columns: columns2((row) => void refund(row), busy), rowKey: (row) => `${row.sourceKey}:${row.seatId}` }),
3595
- note ? /* @__PURE__ */ jsx37("p", { role: "status", children: note }) : null
3785
+ ] }) : /* @__PURE__ */ jsx39(DataTable4, { rows: data?.seats ?? [], columns: columns2((row) => void refund(row), busy), rowKey: (row) => `${row.sourceKey}:${row.seatId}` }),
3786
+ note ? /* @__PURE__ */ jsx39("p", { role: "status", children: note }) : null
3596
3787
  ] });
3597
3788
  }
3598
3789
 
3599
3790
  // src/ui/membership-disputes.tsx
3600
- import { useState as useState31 } from "preact/hooks";
3791
+ import { useState as useState33 } from "preact/hooks";
3601
3792
  import { Button as Button20, DataTable as DataTable5 } from "@odla-ai/ui/components";
3602
- import { jsx as jsx38, jsxs as jsxs34 } from "preact/jsx-runtime";
3793
+ import { jsx as jsx40, jsxs as jsxs36 } from "preact/jsx-runtime";
3603
3794
  var money4 = (row, cents) => new Intl.NumberFormat("en-US", { style: "currency", currency: row.currency }).format(cents / 100);
3604
3795
  var day = (at) => at ? new Date(at).toLocaleDateString() : "\u2014";
3605
3796
  var DISPUTE_STATUS_WORDS = {
@@ -3613,35 +3804,35 @@ var DISPUTE_STATUS_WORDS = {
3613
3804
  recharged: "re-charged"
3614
3805
  };
3615
3806
  var columns3 = (begin, busy) => [
3616
- { key: "member", header: "Member", cell: (row) => row.kind === "seat" ? /* @__PURE__ */ jsxs34("span", { children: [
3807
+ { key: "member", header: "Member", cell: (row) => row.kind === "seat" ? /* @__PURE__ */ jsxs36("span", { children: [
3617
3808
  "Seat for ",
3618
3809
  row.recipientName ?? "the named recipient",
3619
3810
  " \xB7 ",
3620
- /* @__PURE__ */ jsx38("code", { children: row.applicationId })
3621
- ] }) : /* @__PURE__ */ jsxs34("span", { children: [
3811
+ /* @__PURE__ */ jsx40("code", { children: row.applicationId })
3812
+ ] }) : /* @__PURE__ */ jsxs36("span", { children: [
3622
3813
  row.memberNumber ? `#${row.memberNumber} \xB7 ` : "",
3623
- /* @__PURE__ */ jsx38("code", { children: row.applicationId })
3814
+ /* @__PURE__ */ jsx40("code", { children: row.applicationId })
3624
3815
  ] }) },
3625
3816
  { key: "chapter", header: "Chapter / runtime", cell: (row) => `${row.chapterId} / ${row.runtime}` },
3626
3817
  { key: "status", header: "Record", cell: (row) => row.status === "payment_disputed" ? `${row.kind === "seat" ? "seat" : "membership"} suspended by dispute` : row.status },
3627
- { key: "dispute", header: "Dispute", cell: (row) => /* @__PURE__ */ jsxs34("span", { children: [
3818
+ { key: "dispute", header: "Dispute", cell: (row) => /* @__PURE__ */ jsxs36("span", { children: [
3628
3819
  DISPUTE_STATUS_WORDS[row.disputeStatus] ?? row.disputeStatus,
3629
3820
  row.disputeReason ? ` \xB7 ${row.disputeReason}` : ""
3630
3821
  ] }) },
3631
3822
  { key: "amount", header: "Amount", cell: (row) => money4(row, row.disputeAmountCents ?? row.amountCents) },
3632
3823
  { key: "opened", header: "Opened", cell: (row) => day(row.disputedAt) },
3633
3824
  { key: "closed", header: "Closed", cell: (row) => day(row.disputeClosedAt) },
3634
- { key: "recharge", header: "Re-charge", cell: (row) => row.rechargePaymentIntentId ? /* @__PURE__ */ jsxs34("span", { children: [
3825
+ { key: "recharge", header: "Re-charge", cell: (row) => row.rechargePaymentIntentId ? /* @__PURE__ */ jsxs36("span", { children: [
3635
3826
  day(row.rechargedAt),
3636
3827
  " \xB7 ",
3637
- /* @__PURE__ */ jsx38("code", { children: row.rechargePaymentIntentId })
3638
- ] }) : row.status === "payment_disputed" && row.disputeStatus === "lost" ? /* @__PURE__ */ jsx38(Button20, { mini: true, disabled: busy !== null, onClick: () => begin(row), children: busy === row.id ? "Charging\u2026" : "Charge again" }) : row.status === "payment_disputed" ? "Respond to the dispute in Stripe" : "\u2014" }
3828
+ /* @__PURE__ */ jsx40("code", { children: row.rechargePaymentIntentId })
3829
+ ] }) : row.status === "payment_disputed" && row.disputeStatus === "lost" ? /* @__PURE__ */ jsx40(Button20, { mini: true, disabled: busy !== null, onClick: () => begin(row), children: busy === row.id ? "Charging\u2026" : "Charge again" }) : row.status === "payment_disputed" ? "Respond to the dispute in Stripe" : "\u2014" }
3639
3830
  ];
3640
3831
  function MembershipDisputes(props) {
3641
3832
  const { data, loading, error, refresh } = useAdminResource(props.getToken, "/api/admin/membership-disputes");
3642
- const [busy, setBusy] = useState31(null);
3643
- const [confirming, setConfirming] = useState31(null);
3644
- const [note, setNote] = useState31(null);
3833
+ const [busy, setBusy] = useState33(null);
3834
+ const [confirming, setConfirming] = useState33(null);
3835
+ const [note, setNote] = useState33(null);
3645
3836
  const recharge = async (row) => {
3646
3837
  setBusy(row.id);
3647
3838
  setNote(null);
@@ -3662,33 +3853,33 @@ function MembershipDisputes(props) {
3662
3853
  }
3663
3854
  };
3664
3855
  const rows = data?.disputes ?? [];
3665
- return /* @__PURE__ */ jsxs34(Panel, { title: "Card disputes", children: [
3666
- /* @__PURE__ */ jsx38("p", { children: "A chargeback suspends the membership until the dispute is won or the member clears it with their bank. Re-charging collects the disputed amount again on the saved card, only after a lost dispute and only when you ask." }),
3667
- loading ? /* @__PURE__ */ jsx38("p", { children: "Loading disputes\u2026" }) : error ? /* @__PURE__ */ jsxs34("p", { role: "alert", children: [
3856
+ return /* @__PURE__ */ jsxs36(Panel, { title: "Card disputes", children: [
3857
+ /* @__PURE__ */ jsx40("p", { children: "A chargeback suspends the membership until the dispute is won or the member clears it with their bank. Re-charging collects the disputed amount again on the saved card, only after a lost dispute and only when you ask." }),
3858
+ loading ? /* @__PURE__ */ jsx40("p", { children: "Loading disputes\u2026" }) : error ? /* @__PURE__ */ jsxs36("p", { role: "alert", children: [
3668
3859
  "Disputes could not be loaded: ",
3669
3860
  error
3670
- ] }) : rows.length ? /* @__PURE__ */ jsx38(DataTable5, { rows, columns: columns3(setConfirming, busy), rowKey: (row) => row.id }) : /* @__PURE__ */ jsx38("p", { children: "No membership carries a dispute." }),
3671
- data?.truncated ? /* @__PURE__ */ jsx38("p", { role: "alert", children: "Only the most recent memberships were checked; older disputes may exist." }) : null,
3672
- confirming ? /* @__PURE__ */ jsxs34("div", { role: "group", "aria-label": "Confirm re-charge", children: [
3673
- /* @__PURE__ */ jsxs34("p", { children: [
3861
+ ] }) : rows.length ? /* @__PURE__ */ jsx40(DataTable5, { rows, columns: columns3(setConfirming, busy), rowKey: (row) => row.id }) : /* @__PURE__ */ jsx40("p", { children: "No membership carries a dispute." }),
3862
+ data?.truncated ? /* @__PURE__ */ jsx40("p", { role: "alert", children: "Only the most recent memberships were checked; older disputes may exist." }) : null,
3863
+ confirming ? /* @__PURE__ */ jsxs36("div", { role: "group", "aria-label": "Confirm re-charge", children: [
3864
+ /* @__PURE__ */ jsxs36("p", { children: [
3674
3865
  "Charge ",
3675
3866
  money4(confirming, confirming.disputeAmountCents ?? confirming.amountCents),
3676
3867
  " again to the card on file for ",
3677
3868
  confirming.kind === "seat" ? `the seat for ${confirming.recipientName ?? "the recipient"}` : confirming.applicationId,
3678
3869
  "?"
3679
3870
  ] }),
3680
- /* @__PURE__ */ jsx38(Button20, { mini: true, onClick: () => void recharge(confirming), children: "Yes, charge the card" }),
3871
+ /* @__PURE__ */ jsx40(Button20, { mini: true, onClick: () => void recharge(confirming), children: "Yes, charge the card" }),
3681
3872
  " ",
3682
- /* @__PURE__ */ jsx38(Button20, { mini: true, onClick: () => setConfirming(null), children: "Cancel" })
3873
+ /* @__PURE__ */ jsx40(Button20, { mini: true, onClick: () => setConfirming(null), children: "Cancel" })
3683
3874
  ] }) : null,
3684
- note ? /* @__PURE__ */ jsx38("p", { role: "status", children: note }) : null
3875
+ note ? /* @__PURE__ */ jsx40("p", { role: "status", children: note }) : null
3685
3876
  ] });
3686
3877
  }
3687
3878
 
3688
3879
  // src/ui/membership-policy-versions.tsx
3689
3880
  import { Button as Button21, DataTable as DataTable6 } from "@odla-ai/ui/components";
3690
- import { useState as useState32 } from "preact/hooks";
3691
- import { Fragment as Fragment15, jsx as jsx39, jsxs as jsxs35 } from "preact/jsx-runtime";
3881
+ import { useState as useState34 } from "preact/hooks";
3882
+ import { Fragment as Fragment15, jsx as jsx41, jsxs as jsxs37 } from "preact/jsx-runtime";
3692
3883
  var PATH = "/api/admin/membership-policy-versions";
3693
3884
  var RETERM = "/api/admin/membership-discount/reterm";
3694
3885
  var day2 = (ms) => new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
@@ -3700,50 +3891,50 @@ var MECHANISM = {
3700
3891
  stripe_coupon_forever: "Stripe coupon, applied while active",
3701
3892
  stripe_coupon_term_bound: "Stripe coupon, applied once; issuance refuses unless the offering's fixed term ends on the same date"
3702
3893
  };
3703
- var State = ({ row }) => /* @__PURE__ */ jsx39("span", { className: row.state === "active" ? "badge" : "muted", children: STATE[row.state] });
3894
+ var State = ({ row }) => /* @__PURE__ */ jsx41("span", { className: row.state === "active" ? "badge" : "muted", children: STATE[row.state] });
3704
3895
  var superseded = (row) => row.supersededAt === void 0 ? "\u2014" : day2(row.supersededAt);
3705
- var Source = ({ row }) => /* @__PURE__ */ jsxs35("span", { children: [
3706
- row.source ?? /* @__PURE__ */ jsx39("span", { className: "muted", children: "Unrecorded" }),
3707
- row.actor ? /* @__PURE__ */ jsxs35(Fragment15, { children: [
3896
+ var Source = ({ row }) => /* @__PURE__ */ jsxs37("span", { children: [
3897
+ row.source ?? /* @__PURE__ */ jsx41("span", { className: "muted", children: "Unrecorded" }),
3898
+ row.actor ? /* @__PURE__ */ jsxs37(Fragment15, { children: [
3708
3899
  " \xB7 ",
3709
- /* @__PURE__ */ jsx39("code", { children: row.actor })
3900
+ /* @__PURE__ */ jsx41("code", { children: row.actor })
3710
3901
  ] }) : null,
3711
- row.recordedAt !== void 0 ? /* @__PURE__ */ jsxs35(Fragment15, { children: [
3902
+ row.recordedAt !== void 0 ? /* @__PURE__ */ jsxs37(Fragment15, { children: [
3712
3903
  " \xB7 ",
3713
3904
  day2(row.recordedAt)
3714
3905
  ] }) : null
3715
3906
  ] });
3716
3907
  var offeringColumns = [
3717
- { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs35(Fragment15, { children: [
3718
- /* @__PURE__ */ jsxs35("code", { children: [
3908
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
3909
+ /* @__PURE__ */ jsxs37("code", { children: [
3719
3910
  "v",
3720
3911
  row.version
3721
3912
  ] }),
3722
3913
  " ",
3723
- /* @__PURE__ */ jsx39(State, { row })
3914
+ /* @__PURE__ */ jsx41(State, { row })
3724
3915
  ] }), sortAs: "number", sortValue: (row) => row.version },
3725
- { key: "sku", header: "SKU", cell: (row) => /* @__PURE__ */ jsx39("code", { children: row.baseSkuId }) },
3916
+ { key: "sku", header: "SKU", cell: (row) => /* @__PURE__ */ jsx41("code", { children: row.baseSkuId }) },
3726
3917
  { key: "list", header: "List price", cell: (row) => `${money5(row.basePriceCents, row.currency)} / ${row.interval}` },
3727
3918
  { key: "term", header: "Term", cell: (row) => row.term.kind === "fixed_end" ? `Ends ${day2(row.term.endsAt)}` : `Rolling ${row.term.months} months` },
3728
3919
  { key: "seat", header: "Seat", cell: (row) => `${row.seatPriceCents === 0 ? "Included" : money5(row.seatPriceCents, row.currency)} \xB7 ${row.seatPurchaseWindowCalendarMonths === void 0 ? "no window" : `${row.seatPurchaseWindowCalendarMonths}-month window`}` },
3729
3920
  { key: "effectiveFrom", header: "Effective from", cell: (row) => day2(row.effectiveFrom), sortAs: "number", sortValue: (row) => row.effectiveFrom },
3730
3921
  { key: "effectiveUntil", header: "Effective until", cell: (row) => row.effectiveUntil === void 0 ? "Open" : day2(row.effectiveUntil) },
3731
3922
  { key: "supersededAt", header: "Superseded", cell: superseded },
3732
- { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx39(Source, { row }) }
3923
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx41(Source, { row }) }
3733
3924
  ];
3734
3925
  var policyColumns = (getToken) => [
3735
- { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs35(Fragment15, { children: [
3736
- /* @__PURE__ */ jsxs35("code", { children: [
3926
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
3927
+ /* @__PURE__ */ jsxs37("code", { children: [
3737
3928
  "v",
3738
3929
  row.version
3739
3930
  ] }),
3740
3931
  " ",
3741
- /* @__PURE__ */ jsx39(State, { row })
3932
+ /* @__PURE__ */ jsx41(State, { row })
3742
3933
  ] }), sortAs: "number", sortValue: (row) => row.version },
3743
- { key: "cohort", header: "Cohort", cell: (row) => /* @__PURE__ */ jsxs35(Fragment15, { children: [
3934
+ { key: "cohort", header: "Cohort", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
3744
3935
  row.cohortLabel,
3745
3936
  " ",
3746
- /* @__PURE__ */ jsxs35("span", { className: "muted", children: [
3937
+ /* @__PURE__ */ jsxs37("span", { className: "muted", children: [
3747
3938
  "\xB7 soft target ",
3748
3939
  row.softTarget
3749
3940
  ] })
@@ -3754,8 +3945,8 @@ var policyColumns = (getToken) => [
3754
3945
  { key: "benefit", header: "Benefit duration", cell: (row) => row.benefitDuration.kind === "fixed_end" ? `Until ${day2(row.benefitDuration.endsAt)}` : row.benefitDuration.kind === "first_term" ? "First term" : "While active" },
3755
3946
  { key: "mechanism", header: "Provider mechanism", cell: (row) => MECHANISM[row.mechanism] },
3756
3947
  { key: "supersededAt", header: "Superseded", cell: superseded },
3757
- { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx39(Source, { row }) },
3758
- { key: "members", header: "Existing members", cell: (row) => /* @__PURE__ */ jsx39(RetermAction, { row, getToken }) }
3948
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx41(Source, { row }) },
3949
+ { key: "members", header: "Existing members", cell: (row) => /* @__PURE__ */ jsx41(RetermAction, { row, getToken }) }
3759
3950
  ];
3760
3951
  var summary = (receipt) => {
3761
3952
  const skipped = receipt.skipped.reduce((acc, item) => ({ ...acc, [item.reason]: (acc[item.reason] ?? 0) + 1 }), {});
@@ -3767,9 +3958,9 @@ var summary = (receipt) => {
3767
3958
  return `${parts.join(" \xB7 ")}${receipt.more ? " \xB7 more remain, run again" : ""}`;
3768
3959
  };
3769
3960
  function RetermAction({ row, getToken }) {
3770
- const [busy, setBusy] = useState32(false);
3771
- const [result, setResult] = useState32(null);
3772
- if (row.state !== "active" || row.mechanism !== "stripe_coupon_forever") return /* @__PURE__ */ jsx39("span", { className: "muted", children: row.state !== "active" ? "Not the active version" : "Benefit is not while-active" });
3961
+ const [busy, setBusy] = useState34(false);
3962
+ const [result, setResult] = useState34(null);
3963
+ if (row.state !== "active" || row.mechanism !== "stripe_coupon_forever") return /* @__PURE__ */ jsx41("span", { className: "muted", children: row.state !== "active" ? "Not the active version" : "Benefit is not while-active" });
3773
3964
  const run = async () => {
3774
3965
  if (!globalThis.confirm?.(`Move every paid ${row.cohortLabel} member of ${row.chapterId} onto v${row.version}? Their next renewal invoice carries ${row.percentOffBps / 100}% off while they stay active. A member whose renewal this would raise is left alone.`)) return;
3775
3966
  setBusy(true);
@@ -3783,28 +3974,28 @@ function RetermAction({ row, getToken }) {
3783
3974
  setBusy(false);
3784
3975
  }
3785
3976
  };
3786
- return /* @__PURE__ */ jsxs35("span", { style: { display: "inline-flex", flexDirection: "column", gap: 4 }, children: [
3787
- /* @__PURE__ */ jsx39(Button21, { mini: true, disabled: busy, onClick: run, children: busy ? "Moving\u2026" : "Move members to this benefit" }),
3788
- result ? /* @__PURE__ */ jsx39("span", { role: result.tone === "good" ? "status" : "alert", className: result.tone === "danger" ? "danger" : "muted", children: result.text }) : null
3977
+ return /* @__PURE__ */ jsxs37("span", { style: { display: "inline-flex", flexDirection: "column", gap: 4 }, children: [
3978
+ /* @__PURE__ */ jsx41(Button21, { mini: true, disabled: busy, onClick: run, children: busy ? "Moving\u2026" : "Move members to this benefit" }),
3979
+ result ? /* @__PURE__ */ jsx41("span", { role: result.tone === "good" ? "status" : "alert", className: result.tone === "danger" ? "danger" : "muted", children: result.text }) : null
3789
3980
  ] });
3790
3981
  }
3791
3982
  function MembershipPolicyVersions(props) {
3792
3983
  const path = props.chapterId ? `${PATH}?chapterId=${encodeURIComponent(props.chapterId)}` : PATH;
3793
3984
  const { data, loading, error } = useAdminResource(props.getToken, path);
3794
3985
  const scope = props.chapterId ? `chapter ${props.chapterId}` : "any chapter";
3795
- if (loading) return /* @__PURE__ */ jsx39(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsx39("p", { role: "status", children: "Loading offering versions\u2026" }) });
3796
- if (error || !data) return /* @__PURE__ */ jsx39(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsxs35("p", { role: "alert", children: [
3986
+ if (loading) return /* @__PURE__ */ jsx41(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsx41("p", { role: "status", children: "Loading offering versions\u2026" }) });
3987
+ if (error || !data) return /* @__PURE__ */ jsx41(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsxs37("p", { role: "alert", children: [
3797
3988
  "Offering versions could not be loaded: ",
3798
3989
  error ?? "empty response"
3799
3990
  ] }) });
3800
- return /* @__PURE__ */ jsxs35(Fragment15, { children: [
3801
- /* @__PURE__ */ jsxs35(Panel, { title: "Membership offering versions", actions: data.truncated ? /* @__PURE__ */ jsx39("span", { className: "badge", children: "Truncated" }) : void 0, children: [
3802
- /* @__PURE__ */ jsxs35("p", { className: "muted", children: [
3991
+ return /* @__PURE__ */ jsxs37(Fragment15, { children: [
3992
+ /* @__PURE__ */ jsxs37(Panel, { title: "Membership offering versions", actions: data.truncated ? /* @__PURE__ */ jsx41("span", { className: "badge", children: "Truncated" }) : void 0, children: [
3993
+ /* @__PURE__ */ jsxs37("p", { className: "muted", children: [
3803
3994
  "The list price a card shows comes from the active offering; the payable price applies the active discount policy. State is judged as of ",
3804
3995
  day2(data.now),
3805
3996
  ", exactly as checkout issuance judges it."
3806
3997
  ] }),
3807
- /* @__PURE__ */ jsx39(
3998
+ /* @__PURE__ */ jsx41(
3808
3999
  DataTable6,
3809
4000
  {
3810
4001
  rows: data.offerings,
@@ -3815,8 +4006,8 @@ function MembershipPolicyVersions(props) {
3815
4006
  }
3816
4007
  )
3817
4008
  ] }),
3818
- /* @__PURE__ */ jsxs35(Panel, { title: "Discount policy versions", children: [
3819
- /* @__PURE__ */ jsx39(
4009
+ /* @__PURE__ */ jsxs37(Panel, { title: "Discount policy versions", children: [
4010
+ /* @__PURE__ */ jsx41(
3820
4011
  DataTable6,
3821
4012
  {
3822
4013
  rows: data.discountPolicies,
@@ -3826,7 +4017,7 @@ function MembershipPolicyVersions(props) {
3826
4017
  emptyState: `No discount policy versions are recorded for ${scope}.`
3827
4018
  }
3828
4019
  ),
3829
- data.invalid.length ? /* @__PURE__ */ jsxs35("p", { role: "alert", children: [
4020
+ data.invalid.length ? /* @__PURE__ */ jsxs37("p", { role: "alert", children: [
3830
4021
  data.invalid.length,
3831
4022
  " recorded version",
3832
4023
  data.invalid.length === 1 ? " no longer normalizes and blocks" : "s no longer normalize and block",
@@ -3886,4 +4077,4 @@ export {
3886
4077
  MembershipDisputes,
3887
4078
  MembershipPolicyVersions
3888
4079
  };
3889
- //# sourceMappingURL=chunk-24L7FANC.js.map
4080
+ //# sourceMappingURL=chunk-MUUBATZJ.js.map