@odla-ai/chapter 0.51.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import {
4
4
  DEFAULT_CHAPTER_COPY,
5
5
  formatChapterCopy,
6
6
  useChapterCopy
7
- } from "./chunk-JXX4BFDP.js";
7
+ } from "./chunk-4M2PN5K3.js";
8
8
 
9
9
  // src/ui/admin.tsx
10
10
  import { useEffect as useEffect12, useMemo as useMemo3, useRef as useRef3, useState as useState27 } from "preact/hooks";
@@ -750,7 +750,7 @@ function SeatPanel(props) {
750
750
  if (resource.loading) return /* @__PURE__ */ jsx7("p", { role: "status", children: "Loading additional seat\u2026" });
751
751
  if (resource.error || !resource.data) return null;
752
752
  const { data } = resource, seat = data.seat;
753
- const money4 = new Intl.NumberFormat("en-US", { style: "currency", currency: data.currency }).format(data.amountCents / 100);
753
+ const money5 = new Intl.NumberFormat("en-US", { style: "currency", currency: data.currency }).format(data.amountCents / 100);
754
754
  const refund = async () => {
755
755
  setBusy(true);
756
756
  setNote(null);
@@ -783,7 +783,7 @@ function SeatPanel(props) {
783
783
  seat.status.replaceAll("_", " ")
784
784
  ] }),
785
785
  /* @__PURE__ */ jsxs7("p", { className: "muted", style: { margin: 0 }, children: [
786
- money4,
786
+ money5,
787
787
  " paid in full. The seat waits for its recipient and is never refunded automatically."
788
788
  ] }),
789
789
  data.refundable ? /* @__PURE__ */ jsx7("div", { children: /* @__PURE__ */ jsx7(Button3, { variant: "danger", mini: true, disabled: busy, onClick: () => void refund(), children: busy ? "Refunding\u2026" : "Refund seat" }) }) : null
@@ -2046,6 +2046,11 @@ function networkRecordsWorkspace(chapter) {
2046
2046
 
2047
2047
  // src/ui/admin-portfolio.tsx
2048
2048
  import { StatBand as StatBand2, StepPipeline as StepPipeline2 } from "@odla-ai/ui/components";
2049
+
2050
+ // src/network-edge.ts
2051
+ var EDGE_UNPAIRED = "edge_unpaired";
2052
+
2053
+ // src/ui/admin-portfolio.tsx
2049
2054
  import { jsx as jsx24, jsxs as jsxs22 } from "preact/jsx-runtime";
2050
2055
  function LeaderNetworkOverview({ ctx }) {
2051
2056
  const copy = useChapterCopy().admin.network;
@@ -2059,6 +2064,14 @@ function LeaderNetworkOverview({ ctx }) {
2059
2064
  error ? `: ${error}` : "",
2060
2065
  "."
2061
2066
  ] });
2067
+ return /* @__PURE__ */ jsx24(LeaderNetworkOverviewView, { data });
2068
+ }
2069
+ function unpairedEdge(target) {
2070
+ const error = target.transport.error;
2071
+ return error && error.code === EDGE_UNPAIRED ? error : null;
2072
+ }
2073
+ function LeaderNetworkOverviewView({ data }) {
2074
+ const copy = useChapterCopy().admin.network;
2062
2075
  if (data.configured === 0) return /* @__PURE__ */ jsx24(AdminPage, { children: /* @__PURE__ */ jsx24(Panel, { title: copy.overview, children: /* @__PURE__ */ jsx24("p", { className: "muted", children: copy.noFollowers }) }) });
2063
2076
  const stats = [
2064
2077
  { value: data.configured.toLocaleString(), label: copy.configuredFollowers },
@@ -2072,24 +2085,43 @@ function LeaderNetworkOverview({ ctx }) {
2072
2085
  }));
2073
2086
  return /* @__PURE__ */ jsxs22(AdminPage, { children: [
2074
2087
  /* @__PURE__ */ jsx24(StatBand2, { stats }),
2075
- /* @__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__ */ jsxs22("li", { style: { display: "flex", gap: 12, alignItems: "baseline", flexWrap: "wrap" }, children: [
2076
- /* @__PURE__ */ jsx24("strong", { style: { minWidth: 180 }, children: target.name }),
2077
- /* @__PURE__ */ jsx24("span", { className: "badge", children: target.transport.reachable === null ? copy.transportNotChecked : target.transport.reachable ? copy.transportReachable : copy.transportUnavailable }),
2078
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.transport.kind === "binding" ? formatChapterCopy(copy.bindingTransport, { binding: target.transport.binding ?? "" }) : copy.urlTransport }),
2079
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.configuration.activeRevision === null ? copy.noActiveConfiguration : formatChapterCopy(copy.activeConfiguration, { revision: target.configuration.activeRevision }) }),
2080
- /* @__PURE__ */ jsx24("span", { className: "muted", children: target.publicSite.available === null ? copy.publicSiteNotChecked : target.publicSite.available ? copy.publicSiteAvailable : copy.publicSiteUnavailable }),
2081
- target.error ? /* @__PURE__ */ jsx24("span", { className: "muted", children: target.error }) : null,
2082
- /* @__PURE__ */ jsx24("a", { href: target.url, target: "_blank", rel: "noreferrer", style: { marginLeft: "auto" }, children: target.url })
2083
- ] }, target.id)) }) }),
2088
+ /* @__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)) }) }),
2084
2089
  /* @__PURE__ */ jsx24(Panel, { title: copy.recordTotals, children: steps.length ? /* @__PURE__ */ jsx24(StepPipeline2, { ariaLabel: copy.recordTotals, steps }) : /* @__PURE__ */ jsx24("p", { className: "muted", children: copy.noRecordTotals }) })
2085
2090
  ] });
2086
2091
  }
2092
+ function FollowerRow({ target }) {
2093
+ const copy = useChapterCopy().admin.network;
2094
+ const unpaired = unpairedEdge(target);
2095
+ return /* @__PURE__ */ jsxs22(
2096
+ "li",
2097
+ {
2098
+ "data-network-target": target.id,
2099
+ "data-edge-state": unpaired ? "unpaired" : "paired",
2100
+ style: { display: "flex", gap: 12, alignItems: "baseline", flexWrap: "wrap" },
2101
+ children: [
2102
+ /* @__PURE__ */ jsx24("strong", { style: { minWidth: 180 }, children: target.name }),
2103
+ /* @__PURE__ */ jsx24("span", { className: "badge", children: unpaired ? copy.edgeUnpaired : target.transport.reachable === null ? copy.transportNotChecked : target.transport.reachable ? copy.transportReachable : copy.transportUnavailable }),
2104
+ /* @__PURE__ */ jsx24("span", { className: "muted", children: target.transport.kind === "binding" ? formatChapterCopy(copy.bindingTransport, { binding: target.transport.binding ?? "" }) : copy.urlTransport }),
2105
+ /* @__PURE__ */ jsx24("span", { className: "muted", children: target.configuration.activeRevision === null ? copy.noActiveConfiguration : formatChapterCopy(copy.activeConfiguration, { revision: target.configuration.activeRevision }) }),
2106
+ /* @__PURE__ */ jsx24("span", { className: "muted", children: target.publicSite.available === null ? copy.publicSiteNotChecked : target.publicSite.available ? copy.publicSiteAvailable : copy.publicSiteUnavailable }),
2107
+ unpaired ? /* @__PURE__ */ jsxs22("span", { className: "muted", style: { flexBasis: "100%" }, children: [
2108
+ copy.edgeUnpairedHint,
2109
+ " ",
2110
+ /* @__PURE__ */ jsx24("code", { children: unpaired.hint })
2111
+ ] }) : target.error ? /* @__PURE__ */ jsx24("span", { className: "muted", children: target.error }) : null,
2112
+ /* @__PURE__ */ jsx24("a", { href: target.url, target: "_blank", rel: "noreferrer", style: { marginLeft: "auto" }, children: target.url })
2113
+ ]
2114
+ }
2115
+ );
2116
+ }
2087
2117
 
2088
2118
  // src/ui/admin-admission.tsx
2089
- import { useEffect as useEffect8, useRef as useRef2, useState as useState20 } from "preact/hooks";
2119
+ import { useEffect as useEffect8, useId, useRef as useRef2, useState as useState20 } from "preact/hooks";
2120
+ import { Field as Field9, Input as Input2, Select as Select8 } from "@odla-ai/ui/components";
2090
2121
  import { jsx as jsx25, jsxs as jsxs23 } from "preact/jsx-runtime";
2091
2122
  function AdmissionGrantsWorkspace(props) {
2092
2123
  const copy = useChapterCopy().admin.admission;
2124
+ const uid = useId();
2093
2125
  const resource = useAdminResource(props.getToken, "/api/admin/admission-grants");
2094
2126
  const [email, setEmail] = useState20("");
2095
2127
  const [decision, setDecision] = useState20("waive_interview");
@@ -2137,25 +2169,13 @@ function AdmissionGrantsWorkspace(props) {
2137
2169
  /* @__PURE__ */ jsxs23(Panel, { title: copy.title, children: [
2138
2170
  /* @__PURE__ */ jsx25("p", { className: "muted", style: { margin: 0 }, children: copy.description }),
2139
2171
  /* @__PURE__ */ jsxs23("form", { onSubmit: (event) => void submit(event), style: { display: "grid", gap: 12, gridTemplateColumns: "minmax(220px,2fr) minmax(180px,1fr) minmax(160px,1fr)" }, children: [
2140
- /* @__PURE__ */ jsxs23("label", { children: [
2141
- copy.email,
2142
- /* @__PURE__ */ jsx25("input", { required: true, type: "email", value: email, onInput: (event) => setEmail(event.currentTarget.value) })
2143
- ] }),
2144
- /* @__PURE__ */ jsxs23("label", { children: [
2145
- copy.decision,
2146
- /* @__PURE__ */ jsxs23("select", { value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2147
- /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: copy.waive }),
2148
- /* @__PURE__ */ jsx25("option", { value: "require_interview", children: copy.require })
2149
- ] })
2150
- ] }),
2151
- /* @__PURE__ */ jsxs23("label", { children: [
2152
- copy.expires,
2153
- /* @__PURE__ */ jsx25("input", { type: "date", value: expires, onInput: (event) => setExpires(event.currentTarget.value) })
2154
- ] }),
2155
- /* @__PURE__ */ jsxs23("label", { style: { gridColumn: "1 / -1" }, children: [
2156
- copy.note,
2157
- /* @__PURE__ */ jsx25("input", { value: note, maxLength: 2e3, onInput: (event) => setNote(event.currentTarget.value) })
2158
- ] }),
2172
+ /* @__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) }) }),
2173
+ /* @__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: [
2174
+ /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: copy.waive }),
2175
+ /* @__PURE__ */ jsx25("option", { value: "require_interview", children: copy.require })
2176
+ ] }) }),
2177
+ /* @__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) }) }),
2178
+ /* @__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) }) }),
2159
2179
  /* @__PURE__ */ jsx25("button", { className: "btn secondary", type: "submit", disabled: busy, children: copy.add })
2160
2180
  ] }),
2161
2181
  error ? /* @__PURE__ */ jsx25("p", { role: "alert", children: error }) : null
@@ -2192,6 +2212,7 @@ function LeaderAdmissionAction(props) {
2192
2212
  return /* @__PURE__ */ jsx25(LeaderAdmissionForm, { ...props }, JSON.stringify([props.targetId, props.personId, props.sourceRecordId]));
2193
2213
  }
2194
2214
  function LeaderAdmissionForm(props) {
2215
+ const uid = useId();
2195
2216
  const [decision, setDecision] = useState20("");
2196
2217
  const [note, setNote] = useState20("");
2197
2218
  const [loaded, setLoaded] = useState20(false);
@@ -2243,18 +2264,12 @@ function LeaderAdmissionForm(props) {
2243
2264
  }
2244
2265
  };
2245
2266
  return /* @__PURE__ */ jsxs23("div", { style: { display: "grid", gap: 8 }, children: [
2246
- /* @__PURE__ */ jsxs23("label", { children: [
2247
- "Interview",
2248
- /* @__PURE__ */ jsxs23("select", { disabled: !loaded || busy, value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
2249
- /* @__PURE__ */ jsx25("option", { value: "", children: loaded ? "Choose an invitation setting" : "Loading saved setting\u2026" }),
2250
- /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: "Already completed" }),
2251
- /* @__PURE__ */ jsx25("option", { value: "require_interview", children: "Required" })
2252
- ] })
2253
- ] }),
2254
- /* @__PURE__ */ jsxs23("label", { children: [
2255
- "Internal note",
2256
- /* @__PURE__ */ jsx25("input", { disabled: !loaded || busy, maxLength: 2e3, value: note, onInput: (event) => setNote(event.currentTarget.value) })
2257
- ] }),
2267
+ /* @__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: [
2268
+ /* @__PURE__ */ jsx25("option", { value: "", children: loaded ? "Choose an invitation setting" : "Loading saved setting\u2026" }),
2269
+ /* @__PURE__ */ jsx25("option", { value: "waive_interview", children: "Already completed" }),
2270
+ /* @__PURE__ */ jsx25("option", { value: "require_interview", children: "Required" })
2271
+ ] }) }),
2272
+ /* @__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) }) }),
2258
2273
  /* @__PURE__ */ jsx25("button", { className: "btn secondary", type: "button", disabled: busy || !loaded || !decision, onClick: () => void submit(), children: busy ? "Saving\u2026" : "Save invitation setting" }),
2259
2274
  error ? /* @__PURE__ */ jsx25("p", { role: "alert", children: error }) : null,
2260
2275
  status ? /* @__PURE__ */ jsx25("p", { role: "status", children: status }) : null
@@ -2266,7 +2281,7 @@ import { Tabs as Tabs2 } from "@odla-ai/ui/components";
2266
2281
 
2267
2282
  // src/ui/admin-availability.tsx
2268
2283
  import { useState as useState21 } from "preact/hooks";
2269
- import { Button as Button14, Checkbox, Field as Field9, Input as Input2 } from "@odla-ai/ui/components";
2284
+ import { Button as Button14, Checkbox, Field as Field10, Input as Input3 } from "@odla-ai/ui/components";
2270
2285
  import { jsx as jsx26, jsxs as jsxs24 } from "preact/jsx-runtime";
2271
2286
  function AvailabilityBody({ ctx }) {
2272
2287
  const copy = useChapterCopy().admin.availability;
@@ -2307,18 +2322,18 @@ function AvailabilityBody({ ctx }) {
2307
2322
  setBusy(false);
2308
2323
  }
2309
2324
  };
2310
- const numField = (key, label) => /* @__PURE__ */ jsx26(Field9, { label, error: errors[key], children: /* @__PURE__ */ jsx26(Input2, { type: "number", value: cfg[key], onInput: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
2325
+ 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]) }) }) });
2311
2326
  return /* @__PURE__ */ jsx26(AdminPage, { children: /* @__PURE__ */ jsxs24(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx26("span", { className: "muted", children: note }) : void 0, children: [
2312
- /* @__PURE__ */ jsx26(Field9, { 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)) }) }),
2327
+ /* @__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)) }) }),
2313
2328
  /* @__PURE__ */ jsxs24("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
2314
2329
  numField("startHour", copy.startHour),
2315
2330
  numField("endHour", copy.endHour),
2316
2331
  numField("slotMinutes", copy.slotMinutes),
2317
2332
  numField("minNoticeHours", copy.minNoticeHours),
2318
2333
  numField("windowDays", copy.windowDays),
2319
- /* @__PURE__ */ jsx26(Field9, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx26(Input2, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
2334
+ /* @__PURE__ */ jsx26(Field10, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx26(Input3, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
2320
2335
  ] }),
2321
- /* @__PURE__ */ jsx26(Field9, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx26(Input2, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
2336
+ /* @__PURE__ */ jsx26(Field10, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx26(Input3, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
2322
2337
  /* @__PURE__ */ jsx26("div", { children: /* @__PURE__ */ jsx26(Button14, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
2323
2338
  ] }) });
2324
2339
  }
@@ -2328,7 +2343,8 @@ function availabilitySection(options = {}) {
2328
2343
  }
2329
2344
 
2330
2345
  // src/ui/admin-builder-library.tsx
2331
- import { useEffect as useEffect9, useMemo as useMemo2, useState as useState22 } from "preact/hooks";
2346
+ import { useEffect as useEffect9, useId as useId2, useMemo as useMemo2, useState as useState22 } from "preact/hooks";
2347
+ import { Field as Field11, Input as Input4, Select as Select9, Textarea as Textarea3 } from "@odla-ai/ui/components";
2332
2348
  import { jsx as jsx27, jsxs as jsxs25 } from "preact/jsx-runtime";
2333
2349
  var KINDS = ["document", "note", "link", "inspiration", "image", "file"];
2334
2350
  var KIND_LABEL = { document: "Document", note: "Note", link: "Link", inspiration: "Inspiration", image: "Image", file: "File" };
@@ -2445,17 +2461,17 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
2445
2461
  "Builder Library ",
2446
2462
  /* @__PURE__ */ jsx27("span", { className: "muted", children: "\xB7 private" })
2447
2463
  ] }), actions: /* @__PURE__ */ jsxs25("div", { style: ROW, children: [
2448
- /* @__PURE__ */ jsx27("select", { "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)) }),
2464
+ /* @__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)) }),
2449
2465
  /* @__PURE__ */ jsx27("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
2450
2466
  ] }), children: [
2451
2467
  /* @__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." }),
2452
2468
  /* @__PURE__ */ jsxs25("div", { style: ROW, children: [
2453
- /* @__PURE__ */ jsx27("input", { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
2454
- /* @__PURE__ */ jsxs25("select", { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
2469
+ /* @__PURE__ */ jsx27(Input4, { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
2470
+ /* @__PURE__ */ jsxs25(Select9, { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
2455
2471
  /* @__PURE__ */ jsx27("option", { value: "all", children: "All types" }),
2456
2472
  KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind))
2457
2473
  ] }),
2458
- /* @__PURE__ */ jsxs25("select", { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
2474
+ /* @__PURE__ */ jsxs25(Select9, { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
2459
2475
  /* @__PURE__ */ jsx27("option", { value: "all", children: "All statuses" }),
2460
2476
  /* @__PURE__ */ jsx27("option", { value: "draft", children: "Draft" }),
2461
2477
  /* @__PURE__ */ jsx27("option", { value: "published", children: "Published" }),
@@ -2506,6 +2522,7 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
2506
2522
  ] });
2507
2523
  }
2508
2524
  function LocalEditor({ selected, data, scopeValue, update, setResources, setSelectedKey }) {
2525
+ const uid = useId2();
2509
2526
  return /* @__PURE__ */ jsxs25("div", { style: STACK, children: [
2510
2527
  /* @__PURE__ */ jsxs25("div", { style: META, children: [
2511
2528
  "Local \xB7 resource revision ",
@@ -2514,77 +2531,41 @@ function LocalEditor({ selected, data, scopeValue, update, setResources, setSele
2514
2531
  data.local?.revision ?? 0
2515
2532
  ] }),
2516
2533
  /* @__PURE__ */ jsxs25("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2517
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2518
- /* @__PURE__ */ jsx27("span", { children: "Title" }),
2519
- /* @__PURE__ */ jsx27("input", { value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) })
2520
- ] }),
2521
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2522
- /* @__PURE__ */ jsx27("span", { children: "ID" }),
2523
- /* @__PURE__ */ jsx27("input", { value: selected.id, onInput: (event) => {
2524
- const id = cleanId(event.currentTarget.value);
2525
- setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
2526
- setSelectedKey(localKey(id));
2527
- } })
2528
- ] }),
2529
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2530
- /* @__PURE__ */ jsx27("span", { children: "Type" }),
2531
- /* @__PURE__ */ jsx27("select", { value: selected.kind, onChange: (event) => {
2532
- const kind = event.currentTarget.value;
2533
- 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 });
2534
- }, children: KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind)) })
2535
- ] }),
2536
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2537
- /* @__PURE__ */ jsx27("span", { children: "Status" }),
2538
- /* @__PURE__ */ jsxs25("select", { value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
2539
- /* @__PURE__ */ jsx27("option", { value: "draft", children: "Draft" }),
2540
- /* @__PURE__ */ jsx27("option", { value: "published", children: "Published" }),
2541
- /* @__PURE__ */ jsx27("option", { value: "archived", children: "Archived" })
2542
- ] })
2543
- ] }),
2544
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2545
- /* @__PURE__ */ jsx27("span", { children: "Visible to" }),
2546
- /* @__PURE__ */ jsxs25("select", { value: scopeValue, onChange: (event) => {
2547
- const value = event.currentTarget.value;
2548
- update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
2549
- }, children: [
2550
- data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
2551
- data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "global", children: "All chapters" }) : null,
2552
- data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs25("option", { value: `chapter:${chapterId}`, children: [
2553
- "Only ",
2554
- chapterId
2555
- ] }, chapterId))
2556
- ] })
2557
- ] }),
2558
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2559
- /* @__PURE__ */ jsx27("span", { children: "Tags" }),
2560
- /* @__PURE__ */ jsx27("input", { value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) })
2561
- ] })
2562
- ] }),
2563
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2564
- /* @__PURE__ */ jsx27("span", { children: "Summary" }),
2565
- /* @__PURE__ */ jsx27("textarea", { rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) })
2534
+ /* @__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 }) }) }),
2535
+ /* @__PURE__ */ jsx27(Field11, { label: "ID", htmlFor: `${uid}-id`, children: /* @__PURE__ */ jsx27(Input4, { id: `${uid}-id`, value: selected.id, onInput: (event) => {
2536
+ const id = cleanId(event.currentTarget.value);
2537
+ setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
2538
+ setSelectedKey(localKey(id));
2539
+ } }) }),
2540
+ /* @__PURE__ */ jsx27(Field11, { label: "Type", htmlFor: `${uid}-kind`, children: /* @__PURE__ */ jsx27(Select9, { id: `${uid}-kind`, value: selected.kind, onChange: (event) => {
2541
+ const kind = event.currentTarget.value;
2542
+ 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 });
2543
+ }, children: KINDS.map((kind) => /* @__PURE__ */ jsx27("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }) }),
2544
+ /* @__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: [
2545
+ /* @__PURE__ */ jsx27("option", { value: "draft", children: "Draft" }),
2546
+ /* @__PURE__ */ jsx27("option", { value: "published", children: "Published" }),
2547
+ /* @__PURE__ */ jsx27("option", { value: "archived", children: "Archived" })
2548
+ ] }) }),
2549
+ /* @__PURE__ */ jsx27(Field11, { label: "Visible to", htmlFor: `${uid}-scope`, children: /* @__PURE__ */ jsxs25(Select9, { id: `${uid}-scope`, value: scopeValue, onChange: (event) => {
2550
+ const value = event.currentTarget.value;
2551
+ update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
2552
+ }, children: [
2553
+ data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
2554
+ data.mode === "hub" ? /* @__PURE__ */ jsx27("option", { value: "global", children: "All chapters" }) : null,
2555
+ data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs25("option", { value: `chapter:${chapterId}`, children: [
2556
+ "Only ",
2557
+ chapterId
2558
+ ] }, chapterId))
2559
+ ] }) }),
2560
+ /* @__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) }) }) })
2566
2561
  ] }),
2567
- selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2568
- /* @__PURE__ */ jsx27("span", { children: "Markdown" }),
2569
- /* @__PURE__ */ jsx27("textarea", { rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) })
2570
- ] }) : null,
2571
- selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2572
- /* @__PURE__ */ jsx27("span", { children: "HTTPS URL" }),
2573
- /* @__PURE__ */ jsx27("input", { type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) })
2574
- ] }) : null,
2562
+ /* @__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 }) }) }),
2563
+ 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,
2564
+ 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,
2575
2565
  selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs25("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
2576
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2577
- /* @__PURE__ */ jsx27("span", { children: "Brand book ID" }),
2578
- /* @__PURE__ */ jsx27("input", { value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
2579
- ] }),
2580
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2581
- /* @__PURE__ */ jsx27("span", { children: "Asset ID" }),
2582
- /* @__PURE__ */ jsx27("input", { value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
2583
- ] }),
2584
- /* @__PURE__ */ jsxs25("label", { className: "field", children: [
2585
- /* @__PURE__ */ jsx27("span", { children: "Filename (optional)" }),
2586
- /* @__PURE__ */ jsx27("input", { value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) })
2587
- ] })
2566
+ /* @__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 } : {} } }) }) }),
2567
+ /* @__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 } : {} } }) }) }),
2568
+ /* @__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 } : {} } }) }) })
2588
2569
  ] }) : null,
2589
2570
  /* @__PURE__ */ jsx27(ResourcePreview, { item: selected })
2590
2571
  ] });
@@ -2595,11 +2576,11 @@ function builderLibrarySection() {
2595
2576
 
2596
2577
  // src/ui/admin-email.tsx
2597
2578
  import { useState as useState24 } from "preact/hooks";
2598
- import { Button as Button16, Checkbox as Checkbox2, DataTable as DataTable3, Field as Field11, Input as Input4, Textarea as Textarea4 } from "@odla-ai/ui/components";
2579
+ import { Button as Button16, Checkbox as Checkbox2, DataTable as DataTable3, Field as Field13, Input as Input6, Textarea as Textarea5 } from "@odla-ai/ui/components";
2599
2580
 
2600
2581
  // src/ui/admin-email-resolution.tsx
2601
2582
  import { useState as useState23 } from "preact/hooks";
2602
- import { Button as Button15, Field as Field10, Input as Input3, Textarea as Textarea3 } from "@odla-ai/ui/components";
2583
+ import { Button as Button15, Field as Field12, Input as Input5, Textarea as Textarea4 } from "@odla-ai/ui/components";
2603
2584
  import { jsx as jsx28, jsxs as jsxs26 } from "preact/jsx-runtime";
2604
2585
  function EmailDeliveryResolution({ row, ctx, onResolved }) {
2605
2586
  const [open, setOpen] = useState23(false), [reason, setReason] = useState23(""), [messageId, setMessageId] = useState23("");
@@ -2636,8 +2617,8 @@ function EmailDeliveryResolution({ row, ctx, onResolved }) {
2636
2617
  row.subject,
2637
2618
  ". Check provider evidence, not just whether the message is in an inbox. This action does not send mail."
2638
2619
  ] }),
2639
- /* @__PURE__ */ jsx28(Field10, { label: "Provider evidence", children: /* @__PURE__ */ jsx28(Textarea3, { value: reason, onInput: (e) => setReason(e.target.value) }) }),
2640
- /* @__PURE__ */ jsx28(Field10, { label: "Provider message ID (optional)", children: /* @__PURE__ */ jsx28(Input3, { value: messageId, onInput: (e) => setMessageId(e.target.value) }) }),
2620
+ /* @__PURE__ */ jsx28(Field12, { label: "Provider evidence", children: /* @__PURE__ */ jsx28(Textarea4, { value: reason, onInput: (e) => setReason(e.target.value) }) }),
2621
+ /* @__PURE__ */ jsx28(Field12, { label: "Provider message ID (optional)", children: /* @__PURE__ */ jsx28(Input5, { value: messageId, onInput: (e) => setMessageId(e.target.value) }) }),
2641
2622
  /* @__PURE__ */ jsx28(Button15, { mini: true, disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_delivered"), children: "Confirm delivered" }),
2642
2623
  /* @__PURE__ */ jsx28(Button15, { mini: true, variant: "secondary", disabled: busy || reason.trim().length < 10, onClick: () => void resolve2("confirmed_not_delivered"), children: "Confirm not delivered" }),
2643
2624
  /* @__PURE__ */ jsx28(Button15, { mini: true, variant: "secondary", disabled: busy, onClick: () => setOpen(false), children: "Cancel" }),
@@ -2710,16 +2691,17 @@ function EmailBody({ ctx }) {
2710
2691
  data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
2711
2692
  ] }),
2712
2693
  children: [
2713
- /* @__PURE__ */ jsx29(Field11, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx29(Input4, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
2714
- /* @__PURE__ */ jsx29(Field11, { label: copy.replyTo, children: /* @__PURE__ */ jsx29(Input4, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
2715
- /* @__PURE__ */ jsx29(Field11, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx29(Input4, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
2694
+ /* @__PURE__ */ jsx29(Field13, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx29(Input6, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
2695
+ /* @__PURE__ */ jsx29(Field13, { label: copy.replyTo, children: /* @__PURE__ */ jsx29(Input6, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
2696
+ /* @__PURE__ */ jsx29(Field13, { label: copy.fromName, hint: copy.fromNameHint, children: /* @__PURE__ */ jsx29(Input6, { value: data.fromName ?? "", onInput: (e) => edit({ fromName: e.target.value }) }) }),
2697
+ /* @__PURE__ */ jsx29(Field13, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx29(Input6, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
2716
2698
  ]
2717
2699
  }
2718
2700
  ),
2719
2701
  Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs27(Panel, { title: key, actions: /* @__PURE__ */ jsx29(Button16, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: copy.sendTest }), children: [
2720
2702
  /* @__PURE__ */ jsx29(Checkbox2, { label: copy.enabled, checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
2721
- /* @__PURE__ */ jsx29(Field11, { label: copy.subject, children: /* @__PURE__ */ jsx29(Input4, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
2722
- /* @__PURE__ */ jsx29(Field11, { label: copy.body, children: /* @__PURE__ */ jsx29(Textarea4, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) })
2703
+ /* @__PURE__ */ jsx29(Field13, { label: copy.subject, children: /* @__PURE__ */ jsx29(Input6, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
2704
+ /* @__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 }) }) })
2723
2705
  ] }, key)),
2724
2706
  /* @__PURE__ */ jsxs27("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
2725
2707
  /* @__PURE__ */ jsx29(Button16, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
@@ -3384,7 +3366,7 @@ function meetingsSection(options = {}) {
3384
3366
  }
3385
3367
 
3386
3368
  // src/ui/admin-network-record-editor.tsx
3387
- import { Button as Button18, Field as Field12, Select as Select8 } from "@odla-ai/ui/components";
3369
+ import { Button as Button18, Field as Field14, Select as Select10 } from "@odla-ai/ui/components";
3388
3370
  import { useRef as useRef4, useState as useState29 } from "preact/hooks";
3389
3371
  import { Fragment as Fragment13, jsx as jsx36, jsxs as jsxs32 } from "preact/jsx-runtime";
3390
3372
  function NetworkRecordEditor(props) {
@@ -3445,7 +3427,7 @@ function NetworkRecordEditor(props) {
3445
3427
  pending.stage.replaceAll("_", " "),
3446
3428
  ". ",
3447
3429
  ownPending ? "Retry reconciles the same operation." : "The original operator must reconcile it."
3448
- ] }) : record.edit.allowedStages.length ? /* @__PURE__ */ jsx36(Field12, { label: "Action", children: /* @__PURE__ */ jsxs32(Select8, { "aria-label": "Action", value: stage, disabled: busy, onChange: (event) => setStage(event.currentTarget.value), children: [
3430
+ ] }) : 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: [
3449
3431
  /* @__PURE__ */ jsx36("option", { value: "", children: "Choose an action" }),
3450
3432
  record.edit.allowedStages.map((action) => /* @__PURE__ */ jsx36("option", { value: action.id, children: action.label }, action.id))
3451
3433
  ] }) }) : /* @__PURE__ */ jsx36("p", { children: "No membership action is currently allowed." }),
@@ -3523,6 +3505,126 @@ function NetworkSeatBilling(props) {
3523
3505
  ] });
3524
3506
  }
3525
3507
 
3508
+ // src/ui/membership-policy-versions.tsx
3509
+ import { DataTable as DataTable5 } from "@odla-ai/ui/components";
3510
+ import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs34 } from "preact/jsx-runtime";
3511
+ var PATH = "/api/admin/membership-policy-versions";
3512
+ var day = (ms) => new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
3513
+ var money4 = (cents, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
3514
+ var STATE = { active: "Active", scheduled: "Scheduled", superseded: "Superseded", draft: "Draft", retired: "Retired" };
3515
+ var MECHANISM = {
3516
+ none: "None: cohort status only, no provider discount",
3517
+ stripe_coupon_once: "Stripe coupon, applied once",
3518
+ stripe_coupon_forever: "Stripe coupon, applied while active",
3519
+ stripe_coupon_term_bound: "Stripe coupon, applied once; issuance refuses unless the offering's fixed term ends on the same date"
3520
+ };
3521
+ var State = ({ row }) => /* @__PURE__ */ jsx38("span", { className: row.state === "active" ? "badge" : "muted", children: STATE[row.state] });
3522
+ var superseded = (row) => row.supersededAt === void 0 ? "\u2014" : day(row.supersededAt);
3523
+ var Source = ({ row }) => /* @__PURE__ */ jsxs34("span", { children: [
3524
+ row.source ?? /* @__PURE__ */ jsx38("span", { className: "muted", children: "Unrecorded" }),
3525
+ row.actor ? /* @__PURE__ */ jsxs34(Fragment14, { children: [
3526
+ " \xB7 ",
3527
+ /* @__PURE__ */ jsx38("code", { children: row.actor })
3528
+ ] }) : null,
3529
+ row.recordedAt !== void 0 ? /* @__PURE__ */ jsxs34(Fragment14, { children: [
3530
+ " \xB7 ",
3531
+ day(row.recordedAt)
3532
+ ] }) : null
3533
+ ] });
3534
+ var offeringColumns = [
3535
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs34(Fragment14, { children: [
3536
+ /* @__PURE__ */ jsxs34("code", { children: [
3537
+ "v",
3538
+ row.version
3539
+ ] }),
3540
+ " ",
3541
+ /* @__PURE__ */ jsx38(State, { row })
3542
+ ] }), sortAs: "number", sortValue: (row) => row.version },
3543
+ { key: "sku", header: "SKU", cell: (row) => /* @__PURE__ */ jsx38("code", { children: row.baseSkuId }) },
3544
+ { key: "list", header: "List price", cell: (row) => `${money4(row.basePriceCents, row.currency)} / ${row.interval}` },
3545
+ { key: "term", header: "Term", cell: (row) => row.term.kind === "fixed_end" ? `Ends ${day(row.term.endsAt)}` : `Rolling ${row.term.months} months` },
3546
+ { key: "seat", header: "Seat", cell: (row) => `${money4(row.seatPriceCents, row.currency)} \xB7 ${row.seatPurchaseWindowCalendarMonths === void 0 ? "no window" : `${row.seatPurchaseWindowCalendarMonths}-month window`}` },
3547
+ { key: "effectiveFrom", header: "Effective from", cell: (row) => day(row.effectiveFrom), sortAs: "number", sortValue: (row) => row.effectiveFrom },
3548
+ { key: "effectiveUntil", header: "Effective until", cell: (row) => row.effectiveUntil === void 0 ? "Open" : day(row.effectiveUntil) },
3549
+ { key: "supersededAt", header: "Superseded", cell: superseded },
3550
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx38(Source, { row }) }
3551
+ ];
3552
+ var policyColumns = [
3553
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs34(Fragment14, { children: [
3554
+ /* @__PURE__ */ jsxs34("code", { children: [
3555
+ "v",
3556
+ row.version
3557
+ ] }),
3558
+ " ",
3559
+ /* @__PURE__ */ jsx38(State, { row })
3560
+ ] }), sortAs: "number", sortValue: (row) => row.version },
3561
+ { key: "cohort", header: "Cohort", cell: (row) => /* @__PURE__ */ jsxs34(Fragment14, { children: [
3562
+ row.cohortLabel,
3563
+ " ",
3564
+ /* @__PURE__ */ jsxs34("span", { className: "muted", children: [
3565
+ "\xB7 soft target ",
3566
+ row.softTarget
3567
+ ] })
3568
+ ] }) },
3569
+ { key: "percentOff", header: "Percent off", cell: (row) => `${row.percentOffBps / 100}%` },
3570
+ { key: "skus", header: "Discounted SKUs", cell: (row) => row.discountEligibleSkuIds.length ? row.discountEligibleSkuIds.join(", ") : "None" },
3571
+ { key: "qualifying", header: "Qualifying window", cell: (row) => `${day(row.qualifyingStartsAt)} \u2192 ${day(row.qualifyingEndsAt)}` },
3572
+ { key: "benefit", header: "Benefit duration", cell: (row) => row.benefitDuration.kind === "fixed_end" ? `Until ${day(row.benefitDuration.endsAt)}` : row.benefitDuration.kind === "first_term" ? "First term" : "While active" },
3573
+ { key: "mechanism", header: "Provider mechanism", cell: (row) => MECHANISM[row.mechanism] },
3574
+ { key: "supersededAt", header: "Superseded", cell: superseded },
3575
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx38(Source, { row }) }
3576
+ ];
3577
+ function MembershipPolicyVersions(props) {
3578
+ const path = props.chapterId ? `${PATH}?chapterId=${encodeURIComponent(props.chapterId)}` : PATH;
3579
+ const { data, loading, error } = useAdminResource(props.getToken, path);
3580
+ const scope = props.chapterId ? `chapter ${props.chapterId}` : "any chapter";
3581
+ if (loading) return /* @__PURE__ */ jsx38(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsx38("p", { role: "status", children: "Loading offering versions\u2026" }) });
3582
+ if (error || !data) return /* @__PURE__ */ jsx38(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsxs34("p", { role: "alert", children: [
3583
+ "Offering versions could not be loaded: ",
3584
+ error ?? "empty response"
3585
+ ] }) });
3586
+ return /* @__PURE__ */ jsxs34(Fragment14, { children: [
3587
+ /* @__PURE__ */ jsxs34(Panel, { title: "Membership offering versions", actions: data.truncated ? /* @__PURE__ */ jsx38("span", { className: "badge", children: "Truncated" }) : void 0, children: [
3588
+ /* @__PURE__ */ jsxs34("p", { className: "muted", children: [
3589
+ "The list price a card shows comes from the active offering; the payable price applies the active discount policy. State is judged as of ",
3590
+ day(data.now),
3591
+ ", exactly as checkout issuance judges it."
3592
+ ] }),
3593
+ /* @__PURE__ */ jsx38(
3594
+ DataTable5,
3595
+ {
3596
+ rows: data.offerings,
3597
+ columns: offeringColumns,
3598
+ rowKey: (row) => row.id,
3599
+ ariaLabel: "Membership offering versions",
3600
+ emptyState: `No offering versions are recorded for ${scope}.`
3601
+ }
3602
+ )
3603
+ ] }),
3604
+ /* @__PURE__ */ jsxs34(Panel, { title: "Discount policy versions", children: [
3605
+ /* @__PURE__ */ jsx38(
3606
+ DataTable5,
3607
+ {
3608
+ rows: data.discountPolicies,
3609
+ columns: policyColumns,
3610
+ rowKey: (row) => row.id,
3611
+ ariaLabel: "Discount policy versions",
3612
+ emptyState: `No discount policy versions are recorded for ${scope}.`
3613
+ }
3614
+ ),
3615
+ data.invalid.length ? /* @__PURE__ */ jsxs34("p", { role: "alert", children: [
3616
+ data.invalid.length,
3617
+ " recorded version",
3618
+ data.invalid.length === 1 ? " no longer normalizes and blocks" : "s no longer normalize and block",
3619
+ " ",
3620
+ "issuance: ",
3621
+ data.invalid.map((item) => `${item.collection} ${item.id} (${item.error})`).join("; "),
3622
+ "."
3623
+ ] }) : null
3624
+ ] })
3625
+ ] });
3626
+ }
3627
+
3526
3628
  export {
3527
3629
  AdminPage,
3528
3630
  Panel,
@@ -3565,6 +3667,7 @@ export {
3565
3667
  defaultAdminSections,
3566
3668
  meetingsSection,
3567
3669
  NetworkRecordEditor,
3568
- NetworkSeatBilling
3670
+ NetworkSeatBilling,
3671
+ MembershipPolicyVersions
3569
3672
  };
3570
- //# sourceMappingURL=chunk-4EA5V4JE.js.map
3673
+ //# sourceMappingURL=chunk-WWF2TO3H.js.map