@odla-ai/chapter 0.62.0 → 0.63.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.
@@ -2,12 +2,13 @@ import {
2
2
  BrandStyle,
3
3
  ChapterCopyProvider,
4
4
  DEFAULT_CHAPTER_COPY,
5
+ fmtMoney,
5
6
  formatChapterCopy,
6
7
  useChapterCopy
7
- } from "./chunk-7CVP6VSO.js";
8
+ } from "./chunk-OXYMVK2O.js";
8
9
 
9
10
  // src/ui/admin.tsx
10
- import { useEffect as useEffect13, useMemo as useMemo3, useRef as useRef3, useState as useState29 } from "preact/hooks";
11
+ import { useEffect as useEffect14, useMemo as useMemo3, useRef as useRef3, useState as useState31 } from "preact/hooks";
11
12
  import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
12
13
  import { CrmClient } from "@odla-ai/crm";
13
14
 
@@ -3008,13 +3009,283 @@ function memberContentSection() {
3008
3009
  }
3009
3010
 
3010
3011
  // src/ui/admin-member-portal.tsx
3011
- import { useState as useState27 } from "preact/hooks";
3012
+ import { useState as useState29 } from "preact/hooks";
3013
+
3014
+ // src/ui/admin-member-course.tsx
3015
+ import { useEffect as useEffect12, useState as useState27 } from "preact/hooks";
3012
3016
  import { jsx as jsx32, jsxs as jsxs30 } from "preact/jsx-runtime";
3017
+ var EXAMPLE = { title: "", timezone: "America/Denver", capacity: 20, signupClosesAt: 0, content: {}, sessions: [], toolkit: [], guides: [] };
3018
+ function CourseAdminPanel({ ctx, courseId }) {
3019
+ const path = `/api/admin/courses/${encodeURIComponent(courseId)}`;
3020
+ const view = useAdminResource(ctx.getToken, path);
3021
+ const [draft, setDraft] = useState27("");
3022
+ const [status, setStatus] = useState27(null);
3023
+ useEffect12(() => {
3024
+ const course2 = view.data?.course;
3025
+ const { enrolledCount: _e, revision: _r, updatedAt: _u, updatedBy: _b, calendarEventId: _c, meetUrl: _m, ...settings } = course2 ?? EXAMPLE;
3026
+ setDraft(JSON.stringify(settings, null, 2));
3027
+ }, [view.data?.course?.revision]);
3028
+ const run = async (target, init, done) => {
3029
+ try {
3030
+ await adminFetch(ctx.getToken, target, init);
3031
+ setStatus(done);
3032
+ view.refresh();
3033
+ } catch (error) {
3034
+ const errors = error.errors;
3035
+ setStatus(errors?.length ? errors.join("; ") : error instanceof Error ? error.message : String(error));
3036
+ }
3037
+ };
3038
+ const save = () => {
3039
+ let course2;
3040
+ try {
3041
+ course2 = JSON.parse(draft);
3042
+ } catch {
3043
+ setStatus("Settings must be valid JSON");
3044
+ return;
3045
+ }
3046
+ void run(path, { method: "PUT", body: JSON.stringify({ course: course2, expectedRevision: Number(view.data?.course?.revision ?? 0) }) }, "Saved");
3047
+ };
3048
+ const download = async () => {
3049
+ const token = await ctx.getToken();
3050
+ const res = await fetch(`${path}/roster.csv`, { headers: token ? { authorization: `Bearer ${token}` } : {} });
3051
+ if (!res.ok) {
3052
+ setStatus("Export failed");
3053
+ return;
3054
+ }
3055
+ const url = URL.createObjectURL(await res.blob());
3056
+ Object.assign(document.createElement("a"), { href: url, download: `${courseId}-roster.csv` }).click();
3057
+ URL.revokeObjectURL(url);
3058
+ };
3059
+ const course = view.data?.course;
3060
+ const move = (memberId, action, label) => /* @__PURE__ */ jsx32(
3061
+ "button",
3062
+ {
3063
+ className: "btn secondary mini",
3064
+ type: "button",
3065
+ onClick: () => void run(`${path}/roster/${encodeURIComponent(String(memberId))}/${action}`, { method: "POST" }, `${label} done`),
3066
+ children: label
3067
+ }
3068
+ );
3069
+ return /* @__PURE__ */ jsxs30(Panel, { children: [
3070
+ /* @__PURE__ */ jsxs30("div", { className: "card-label", children: [
3071
+ "Course \xB7 ",
3072
+ courseId
3073
+ ] }),
3074
+ status ? /* @__PURE__ */ jsx32("p", { role: "status", children: status }) : null,
3075
+ course ? /* @__PURE__ */ jsxs30("p", { className: "meeting-note", children: [
3076
+ String(course.title),
3077
+ " \xB7 ",
3078
+ view.data?.seatsLeft,
3079
+ " of ",
3080
+ String(course.capacity),
3081
+ " seats left \xB7",
3082
+ course.calendarEventId ? " calendar event created" : " no calendar event yet"
3083
+ ] }) : /* @__PURE__ */ jsx32("p", { className: "meeting-note", children: "No settings saved yet." }),
3084
+ course && !course.calendarEventId ? /* @__PURE__ */ jsx32("button", { className: "btn primary mini", type: "button", onClick: () => void run(`${path}/calendar-event`, { method: "POST" }, "Calendar event created"), children: "Create calendar event" }) : null,
3085
+ course ? /* @__PURE__ */ jsx32("button", { className: "btn secondary mini", type: "button", onClick: () => void download(), children: "Export roster (CSV)" }) : null,
3086
+ /* @__PURE__ */ jsx32("h3", { children: "Enrolled" }),
3087
+ /* @__PURE__ */ jsx32("ul", { children: (view.data?.enrolled ?? []).map((row) => /* @__PURE__ */ jsxs30("li", { children: [
3088
+ String(row.email),
3089
+ " ",
3090
+ row.status === "pending" ? "(inviting)" : "",
3091
+ " ",
3092
+ move(row.memberId, "remove", "Remove")
3093
+ ] }, String(row.id))) }),
3094
+ /* @__PURE__ */ jsx32("h3", { children: "Waitlist" }),
3095
+ /* @__PURE__ */ jsx32("ol", { children: (view.data?.waitlist ?? []).map((row) => /* @__PURE__ */ jsxs30("li", { children: [
3096
+ String(row.email),
3097
+ " ",
3098
+ move(row.memberId, "promote", "Move up"),
3099
+ " ",
3100
+ move(row.memberId, "remove", "Remove")
3101
+ ] }, String(row.id))) }),
3102
+ /* @__PURE__ */ jsx32("h3", { children: "Settings" }),
3103
+ /* @__PURE__ */ jsx32("p", { className: "meeting-note", children: "Sessions, materials (a material without a link stays hidden), toolkit, guides, seat count and sign-up close. Capacity cannot drop below seats taken." }),
3104
+ /* @__PURE__ */ jsxs30("label", { className: "field", children: [
3105
+ /* @__PURE__ */ jsx32("span", { children: "Course settings (JSON)" }),
3106
+ /* @__PURE__ */ jsx32("textarea", { rows: 18, value: draft, onInput: (event) => setDraft(event.currentTarget.value) })
3107
+ ] }),
3108
+ /* @__PURE__ */ jsx32("button", { className: "btn primary mini", type: "button", onClick: save, children: "Save settings" })
3109
+ ] });
3110
+ }
3111
+ function CoursesAdminPanel({ ctx }) {
3112
+ const list = useAdminResource(ctx.getToken, "/api/admin/courses");
3113
+ const [newId, setNewId] = useState27("");
3114
+ const [open, setOpen] = useState27(null);
3115
+ const ids = [.../* @__PURE__ */ new Set([...(list.data?.courses ?? []).map((row) => String(row.id)), ...open ? [open] : []])];
3116
+ return /* @__PURE__ */ jsxs30("div", { className: "chapter-admin-stack", "data-courses-admin": true, children: [
3117
+ ids.map((id) => /* @__PURE__ */ jsx32(CourseAdminPanel, { ctx, courseId: id }, id)),
3118
+ /* @__PURE__ */ jsxs30(Panel, { children: [
3119
+ /* @__PURE__ */ jsx32("div", { className: "card-label", children: "New course" }),
3120
+ /* @__PURE__ */ jsxs30("label", { className: "field", children: [
3121
+ /* @__PURE__ */ jsx32("span", { children: "Course id (lowercase slug)" }),
3122
+ /* @__PURE__ */ jsx32("input", { value: newId, onInput: (event) => setNewId(event.currentTarget.value.trim()) })
3123
+ ] }),
3124
+ /* @__PURE__ */ jsx32("button", { className: "btn secondary mini", type: "button", disabled: !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(newId), onClick: () => setOpen(newId), children: "Start" })
3125
+ ] })
3126
+ ] });
3127
+ }
3128
+
3129
+ // src/ui/admin-member-referrals.tsx
3130
+ import { useState as useState28 } from "preact/hooks";
3131
+ import { Select as Select11 } from "@odla-ai/ui/components";
3132
+ import { jsx as jsx33, jsxs as jsxs31 } from "preact/jsx-runtime";
3133
+ function ReferralsAdminPanel({ ctx }) {
3134
+ const view = useAdminResource(ctx.getToken, "/api/admin/referrals");
3135
+ const [status, setStatus] = useState28(null);
3136
+ const run = async (path, done) => {
3137
+ try {
3138
+ await adminFetch(ctx.getToken, path, { method: "POST" });
3139
+ setStatus(done);
3140
+ view.refresh();
3141
+ } catch (error) {
3142
+ setStatus(error instanceof Error ? error.message : String(error));
3143
+ }
3144
+ };
3145
+ const data = view.data;
3146
+ return /* @__PURE__ */ jsxs31(Panel, { children: [
3147
+ /* @__PURE__ */ jsx33("div", { className: "card-label", children: "Referrals" }),
3148
+ status ? /* @__PURE__ */ jsx33("p", { role: "status", children: status }) : null,
3149
+ /* @__PURE__ */ jsx33("button", { className: "btn secondary mini", type: "button", onClick: () => void run("/api/admin/referrals/reconcile", "Reconciled approvals and lapses"), children: "Reconcile credit" }),
3150
+ /* @__PURE__ */ jsx33("h3", { children: "Top referrers" }),
3151
+ /* @__PURE__ */ jsx33("ol", { children: (data?.topReferrers ?? []).map((row) => /* @__PURE__ */ jsxs31("li", { children: [
3152
+ row.name ?? row.memberId,
3153
+ ": ",
3154
+ row.joins,
3155
+ " joined, ",
3156
+ row.applications,
3157
+ " applied"
3158
+ ] }, row.memberId)) }),
3159
+ /* @__PURE__ */ jsxs31("table", { children: [
3160
+ /* @__PURE__ */ jsx33("thead", { children: /* @__PURE__ */ jsxs31("tr", { children: [
3161
+ /* @__PURE__ */ jsx33("th", { children: "Member" }),
3162
+ /* @__PURE__ */ jsx33("th", { children: "Codes" }),
3163
+ /* @__PURE__ */ jsx33("th", { children: "Clicks" }),
3164
+ /* @__PURE__ */ jsx33("th", { children: "Applied" }),
3165
+ /* @__PURE__ */ jsx33("th", { children: "Joined" }),
3166
+ /* @__PURE__ */ jsx33("th", {})
3167
+ ] }) }),
3168
+ /* @__PURE__ */ jsx33("tbody", { children: (data?.referrers ?? []).map((row) => /* @__PURE__ */ jsxs31("tr", { children: [
3169
+ /* @__PURE__ */ jsx33("td", { children: row.name ?? row.memberId }),
3170
+ /* @__PURE__ */ jsx33("td", { children: row.codes.join(", ") }),
3171
+ /* @__PURE__ */ jsx33("td", { children: row.clicks }),
3172
+ /* @__PURE__ */ jsx33("td", { children: row.applications }),
3173
+ /* @__PURE__ */ jsx33("td", { children: row.joins }),
3174
+ /* @__PURE__ */ jsx33("td", { children: /* @__PURE__ */ jsx33(
3175
+ "button",
3176
+ {
3177
+ className: "btn secondary mini",
3178
+ type: "button",
3179
+ onClick: () => void run(`/api/admin/referrals/${encodeURIComponent(row.memberId)}/reissue`, `Issued a new code for ${row.name ?? row.memberId}`),
3180
+ children: "Deactivate and reissue"
3181
+ }
3182
+ ) })
3183
+ ] }, row.memberId)) })
3184
+ ] }),
3185
+ /* @__PURE__ */ jsx33("h3", { children: "How people found us" }),
3186
+ /* @__PURE__ */ jsxs31("table", { children: [
3187
+ /* @__PURE__ */ jsx33("thead", { children: /* @__PURE__ */ jsxs31("tr", { children: [
3188
+ /* @__PURE__ */ jsx33("th", { children: "Person" }),
3189
+ /* @__PURE__ */ jsx33("th", { children: "Referred by" }),
3190
+ /* @__PURE__ */ jsx33("th", { children: "Introduced by" })
3191
+ ] }) }),
3192
+ /* @__PURE__ */ jsx33("tbody", { children: (data?.people ?? []).map((row) => {
3193
+ const referred = row.referredBy;
3194
+ const introduced = row.introducedBy;
3195
+ return /* @__PURE__ */ jsxs31("tr", { children: [
3196
+ /* @__PURE__ */ jsx33("td", { children: String(row.name ?? row.memberId) }),
3197
+ /* @__PURE__ */ jsx33("td", { children: referred?.name ?? "" }),
3198
+ /* @__PURE__ */ jsx33("td", { children: introduced ? `${introduced.text}${introduced.name ? ` (matched: ${introduced.name})` : " (unmatched)"}` : "" })
3199
+ ] }, String(row.memberId));
3200
+ }) })
3201
+ ] })
3202
+ ] });
3203
+ }
3204
+ function CreditLedgerPanel({ ctx }) {
3205
+ const view = useAdminResource(ctx.getToken, "/api/admin/referral-ledger");
3206
+ const [form, setForm] = useState28({ referrerMemberId: "", status: "void", valueCents: "", reason: "" });
3207
+ const [status, setStatus] = useState28(null);
3208
+ const add = async () => {
3209
+ try {
3210
+ await adminFetch(ctx.getToken, "/api/admin/referral-ledger", { method: "POST", body: JSON.stringify({ ...form, valueCents: Math.round(Number(form.valueCents) * 100) }) });
3211
+ setStatus("Row added");
3212
+ setForm({ ...form, valueCents: "", reason: "" });
3213
+ view.refresh();
3214
+ } catch (error) {
3215
+ setStatus(error instanceof Error ? error.message : String(error));
3216
+ }
3217
+ };
3218
+ const money6 = (cents) => typeof cents === "number" ? fmtMoney(cents) : "";
3219
+ const set = (key) => (event) => setForm({ ...form, [key]: event.currentTarget.value });
3220
+ return /* @__PURE__ */ jsxs31(Panel, { children: [
3221
+ /* @__PURE__ */ jsx33("div", { className: "card-label", children: "Credit ledger" }),
3222
+ /* @__PURE__ */ jsxs31("table", { children: [
3223
+ /* @__PURE__ */ jsx33("thead", { children: /* @__PURE__ */ jsxs31("tr", { children: [
3224
+ /* @__PURE__ */ jsx33("th", { children: "Member" }),
3225
+ /* @__PURE__ */ jsx33("th", { children: "Earned" }),
3226
+ /* @__PURE__ */ jsx33("th", { children: "Applied" }),
3227
+ /* @__PURE__ */ jsx33("th", { children: "Carried forward" }),
3228
+ /* @__PURE__ */ jsx33("th", { children: "Void" }),
3229
+ /* @__PURE__ */ jsx33("th", { children: "Balance" }),
3230
+ /* @__PURE__ */ jsx33("th", { children: "Next year's dues" })
3231
+ ] }) }),
3232
+ /* @__PURE__ */ jsx33("tbody", { children: (view.data?.members ?? []).map((row) => /* @__PURE__ */ jsxs31("tr", { children: [
3233
+ /* @__PURE__ */ jsx33("td", { children: row.name ?? row.memberId }),
3234
+ /* @__PURE__ */ jsx33("td", { children: money6(row.totals.earnedCents) }),
3235
+ /* @__PURE__ */ jsx33("td", { children: money6(row.totals.appliedCents) }),
3236
+ /* @__PURE__ */ jsx33("td", { children: money6(row.totals.carriedForwardCents) }),
3237
+ /* @__PURE__ */ jsx33("td", { children: money6(row.totals.voidedCents) }),
3238
+ /* @__PURE__ */ jsx33("td", { children: money6(row.totals.balanceCents) }),
3239
+ /* @__PURE__ */ jsx33("td", { children: row.projectedDuesCents === null ? "" : `${money6(row.projectedDuesCents)} (of ${money6(row.renewalCents)})` })
3240
+ ] }, row.memberId)) })
3241
+ ] }),
3242
+ /* @__PURE__ */ jsx33("h3", { children: "Add a row" }),
3243
+ /* @__PURE__ */ jsx33("p", { className: "meeting-note", children: "Rows are never edited. Void or adjust by adding a row with a reason." }),
3244
+ /* @__PURE__ */ jsxs31("label", { className: "field", children: [
3245
+ /* @__PURE__ */ jsx33("span", { children: "Member id" }),
3246
+ /* @__PURE__ */ jsx33("input", { value: form.referrerMemberId, onInput: set("referrerMemberId") })
3247
+ ] }),
3248
+ /* @__PURE__ */ jsxs31("label", { className: "field", children: [
3249
+ /* @__PURE__ */ jsx33("span", { children: "Kind" }),
3250
+ /* @__PURE__ */ jsxs31(Select11, { value: form.status, onChange: set("status"), children: [
3251
+ /* @__PURE__ */ jsx33("option", { value: "void", children: "Void" }),
3252
+ /* @__PURE__ */ jsx33("option", { value: "earned", children: "Credit (adjustment)" }),
3253
+ /* @__PURE__ */ jsx33("option", { value: "applied", children: "Applied" }),
3254
+ /* @__PURE__ */ jsx33("option", { value: "carried_forward", children: "Carried forward" })
3255
+ ] })
3256
+ ] }),
3257
+ /* @__PURE__ */ jsxs31("label", { className: "field", children: [
3258
+ /* @__PURE__ */ jsx33("span", { children: "Amount" }),
3259
+ /* @__PURE__ */ jsx33("input", { inputMode: "decimal", value: form.valueCents, onInput: set("valueCents") })
3260
+ ] }),
3261
+ /* @__PURE__ */ jsxs31("label", { className: "field", children: [
3262
+ /* @__PURE__ */ jsx33("span", { children: "Reason" }),
3263
+ /* @__PURE__ */ jsx33("input", { value: form.reason, onInput: set("reason") })
3264
+ ] }),
3265
+ /* @__PURE__ */ jsx33("button", { className: "btn primary mini", type: "button", disabled: !form.reason.trim() || !form.referrerMemberId, onClick: () => void add(), children: "Add row" }),
3266
+ status ? /* @__PURE__ */ jsx33("p", { role: "status", children: status }) : null,
3267
+ /* @__PURE__ */ jsx33("h3", { children: "Rows" }),
3268
+ /* @__PURE__ */ jsx33("ul", { className: "meeting-note", children: (view.data?.rows ?? []).map((row) => /* @__PURE__ */ jsxs31("li", { children: [
3269
+ new Date(Number(row.at)).toLocaleDateString("en-US"),
3270
+ " \xB7 ",
3271
+ String(row.referrerMemberId),
3272
+ " \xB7 ",
3273
+ String(row.status),
3274
+ " ",
3275
+ money6(Number(row.valueCents)),
3276
+ row.reason ? ` \xB7 ${String(row.reason)}` : "",
3277
+ row.actor ? ` \xB7 ${String(row.actor)}` : ""
3278
+ ] }, String(row.id))) })
3279
+ ] });
3280
+ }
3281
+
3282
+ // src/ui/admin-member-portal.tsx
3283
+ import { jsx as jsx34, jsxs as jsxs32 } from "preact/jsx-runtime";
3013
3284
  var when2 = (at) => typeof at === "number" ? new Date(at).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }) : "";
3014
3285
  function HubRosterPanel({ ctx }) {
3015
3286
  const roster = useAdminResource(ctx.getToken, "/api/admin/hub-members");
3016
- const [email, setEmail] = useState27("");
3017
- const [status, setStatus] = useState27(null);
3287
+ const [email, setEmail] = useState29("");
3288
+ const [status, setStatus] = useState29(null);
3018
3289
  const act = async (path, init, done) => {
3019
3290
  try {
3020
3291
  await adminFetch(ctx.getToken, path, init);
@@ -3024,39 +3295,39 @@ function HubRosterPanel({ ctx }) {
3024
3295
  setStatus(error instanceof Error ? error.message : String(error));
3025
3296
  }
3026
3297
  };
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) => {
3298
+ return /* @__PURE__ */ jsxs32(Panel, { children: [
3299
+ /* @__PURE__ */ jsx34("div", { className: "card-label", children: "Hub members" }),
3300
+ /* @__PURE__ */ jsx34("p", { className: "meeting-note", children: "Members see the portal and its all-members pages. Granting membership never grants admin access." }),
3301
+ /* @__PURE__ */ jsxs32("form", { onSubmit: (event) => {
3031
3302
  event.preventDefault();
3032
3303
  void act("/api/admin/hub-members", { method: "POST", body: JSON.stringify({ email }) }, `Granted ${email}`).then(() => setEmail(""));
3033
3304
  }, 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) })
3305
+ /* @__PURE__ */ jsxs32("label", { className: "field", children: [
3306
+ /* @__PURE__ */ jsx34("span", { children: "Email" }),
3307
+ /* @__PURE__ */ jsx34("input", { type: "email", required: true, value: email, onInput: (event) => setEmail(event.currentTarget.value) })
3037
3308
  ] }),
3038
- /* @__PURE__ */ jsx32("button", { className: "btn primary", type: "submit", children: "Grant member access" })
3309
+ /* @__PURE__ */ jsx34("button", { className: "btn primary", type: "submit", children: "Grant member access" })
3039
3310
  ] }),
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", {})
3311
+ status ? /* @__PURE__ */ jsx34("p", { role: "status", children: status }) : null,
3312
+ /* @__PURE__ */ jsxs32("table", { children: [
3313
+ /* @__PURE__ */ jsx34("thead", { children: /* @__PURE__ */ jsxs32("tr", { children: [
3314
+ /* @__PURE__ */ jsx34("th", { children: "Email" }),
3315
+ /* @__PURE__ */ jsx34("th", { children: "Status" }),
3316
+ /* @__PURE__ */ jsx34("th", { children: "Granted" }),
3317
+ /* @__PURE__ */ jsx34("th", {})
3047
3318
  ] }) }),
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: [
3319
+ /* @__PURE__ */ jsx34("tbody", { children: (roster.data?.members ?? []).map((member) => /* @__PURE__ */ jsxs32("tr", { children: [
3320
+ /* @__PURE__ */ jsx34("td", { children: String(member.email) }),
3321
+ /* @__PURE__ */ jsxs32("td", { children: [
3051
3322
  String(member.status),
3052
3323
  member.revokedBy ? ` by ${String(member.revokedBy)}` : ""
3053
3324
  ] }),
3054
- /* @__PURE__ */ jsxs30("td", { children: [
3325
+ /* @__PURE__ */ jsxs32("td", { children: [
3055
3326
  when2(member.grantedAt),
3056
3327
  " by ",
3057
3328
  String(member.grantedBy)
3058
3329
  ] }),
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" }) })
3330
+ /* @__PURE__ */ jsx34("td", { children: member.status === "active" ? /* @__PURE__ */ jsx34("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__ */ jsx34("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
3331
  ] }, String(member.id))) })
3061
3332
  ] })
3062
3333
  ] });
@@ -3069,8 +3340,8 @@ var KIND_LABELS = {
3069
3340
  };
3070
3341
  function PortalReviewPanel({ ctx }) {
3071
3342
  const queue = useAdminResource(ctx.getToken, "/api/admin/portal-review");
3072
- const [decisions, setDecisions] = useState27({});
3073
- const [status, setStatus] = useState27(null);
3343
+ const [decisions, setDecisions] = useState29({});
3344
+ const [status, setStatus] = useState29(null);
3074
3345
  const resolve2 = async (id) => {
3075
3346
  try {
3076
3347
  await adminFetch(ctx.getToken, `/api/admin/portal-review/${encodeURIComponent(id)}`, { method: "POST", body: JSON.stringify({ decision: decisions[id] ?? "" }) });
@@ -3081,29 +3352,29 @@ function PortalReviewPanel({ ctx }) {
3081
3352
  }
3082
3353
  };
3083
3354
  const items = queue.data?.items ?? [];
3084
- return /* @__PURE__ */ jsxs30(Panel, { children: [
3085
- /* @__PURE__ */ jsxs30("div", { className: "card-label", children: [
3355
+ return /* @__PURE__ */ jsxs32(Panel, { children: [
3356
+ /* @__PURE__ */ jsxs32("div", { className: "card-label", children: [
3086
3357
  "Review queue \xB7 ",
3087
3358
  items.length,
3088
3359
  " open"
3089
3360
  ] }),
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) => {
3361
+ status ? /* @__PURE__ */ jsx34("p", { role: "status", children: status }) : null,
3362
+ items.length === 0 ? /* @__PURE__ */ jsx34("p", { className: "meeting-note", children: "Nothing to review." }) : items.map((item) => {
3092
3363
  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: [
3364
+ return /* @__PURE__ */ jsxs32("div", { className: "card", "data-review-item": id, children: [
3365
+ /* @__PURE__ */ jsx34("strong", { children: KIND_LABELS[String(item.kind)] ?? String(item.kind) }),
3366
+ /* @__PURE__ */ jsx34("pre", { style: { whiteSpace: "pre-wrap" }, children: JSON.stringify(item.detail) }),
3367
+ /* @__PURE__ */ jsxs32("div", { className: "meeting-note", children: [
3097
3368
  "Member ",
3098
3369
  String(item.memberId ?? ""),
3099
3370
  " \xB7 ",
3100
3371
  when2(item.createdAt)
3101
3372
  ] }),
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 }) })
3373
+ /* @__PURE__ */ jsxs32("label", { className: "field", children: [
3374
+ /* @__PURE__ */ jsx34("span", { children: "Decision" }),
3375
+ /* @__PURE__ */ jsx34("input", { value: decisions[id] ?? "", onInput: (event) => setDecisions({ ...decisions, [id]: event.currentTarget.value }) })
3105
3376
  ] }),
3106
- /* @__PURE__ */ jsx32("button", { className: "btn primary mini", type: "button", disabled: !(decisions[id] ?? "").trim(), onClick: () => void resolve2(id), children: "Resolve" })
3377
+ /* @__PURE__ */ jsx34("button", { className: "btn primary mini", type: "button", disabled: !(decisions[id] ?? "").trim(), onClick: () => void resolve2(id), children: "Resolve" })
3107
3378
  ] }, id);
3108
3379
  })
3109
3380
  ] });
@@ -3112,15 +3383,18 @@ function memberPortalSection(chapter) {
3112
3383
  return {
3113
3384
  id: "member-portal",
3114
3385
  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
3386
+ render: (ctx) => /* @__PURE__ */ jsxs32("div", { className: "chapter-admin-stack", children: [
3387
+ chapter.mode === "hub" ? /* @__PURE__ */ jsx34(HubRosterPanel, { ctx }) : null,
3388
+ chapter.memberPortal ? /* @__PURE__ */ jsx34(PortalReviewPanel, { ctx }) : null,
3389
+ chapter.memberPortal?.courses ? /* @__PURE__ */ jsx34(CoursesAdminPanel, { ctx }) : null,
3390
+ chapter.memberPortal?.referrals ? /* @__PURE__ */ jsx34(ReferralsAdminPanel, { ctx }) : null,
3391
+ chapter.memberPortal?.referrals?.creditCents ? /* @__PURE__ */ jsx34(CreditLedgerPanel, { ctx }) : null
3118
3392
  ] })
3119
3393
  };
3120
3394
  }
3121
3395
 
3122
3396
  // src/ui/admin-settings-workspace.tsx
3123
- import { jsx as jsx33 } from "preact/jsx-runtime";
3397
+ import { jsx as jsx35 } from "preact/jsx-runtime";
3124
3398
  var viewContext = (ctx, viewId, active) => {
3125
3399
  const route = { workspaceId: ctx.route.workspaceId, viewId, ...active && ctx.route.recordId ? { recordId: ctx.route.recordId } : {}, ...active && ctx.route.detailTab ? { detailTab: ctx.route.detailTab } : {} };
3126
3400
  const targetFromView = (target) => {
@@ -3138,7 +3412,7 @@ function SettingsWorkspace({ chapter, ctx }) {
3138
3412
  const email = emailSection({ label: copy.workspaces.email });
3139
3413
  const content = memberContentSection();
3140
3414
  const library = builderLibrarySection();
3141
- return /* @__PURE__ */ jsx33(Tabs2, { ariaLabel: copy.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
3415
+ return /* @__PURE__ */ jsx35(Tabs2, { ariaLabel: copy.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
3142
3416
  { value: "calendar", label: copy.workspaces.calendar, href: ctx.href({ viewId: "calendar", recordId: null, detailTab: null }), panel: calendar.render(viewContext(ctx, "calendar", active === "calendar")) },
3143
3417
  { value: "email", label: copy.workspaces.email, href: ctx.href({ viewId: "email", recordId: null, detailTab: null }), panel: email.render(viewContext(ctx, "email", active === "email")) },
3144
3418
  ...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")) }] : [],
@@ -3157,7 +3431,7 @@ function LeaderSettingsWorkspace({ chapter, ctx }) {
3157
3431
  const library = builderLibrarySection();
3158
3432
  const members = Boolean(chapter.memberPortal || chapter.memberContent);
3159
3433
  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: [
3434
+ return /* @__PURE__ */ jsx35(Tabs2, { ariaLabel: chapter.copy.admin.workspaces.settingsViewsLabel, value: active, mount: "active", items: [
3161
3435
  { 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
3436
  ...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
3437
  ...members ? [portalTab(chapter, ctx, active)] : [],
@@ -3166,7 +3440,7 @@ function LeaderSettingsWorkspace({ chapter, ctx }) {
3166
3440
  }
3167
3441
 
3168
3442
  // src/ui/admin-workspaces.tsx
3169
- import { jsx as jsx34 } from "preact/jsx-runtime";
3443
+ import { jsx as jsx36 } from "preact/jsx-runtime";
3170
3444
  var adminViewContext = (ctx, viewId, active) => {
3171
3445
  const route = {
3172
3446
  workspaceId: ctx.route.workspaceId,
@@ -3192,7 +3466,7 @@ function DashboardWorkspace({ chapter, ctx }) {
3192
3466
  const active = ctx.route.viewId === "billing" ? "billing" : "overview";
3193
3467
  const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
3194
3468
  const billing = billingSection({ label: copy.workspaces.billing });
3195
- return /* @__PURE__ */ jsx34(
3469
+ return /* @__PURE__ */ jsx36(
3196
3470
  Tabs3,
3197
3471
  {
3198
3472
  ariaLabel: copy.workspaces.dashboardViewsLabel,
@@ -3220,7 +3494,7 @@ function PeopleWorkspace({ chapter, ctx }) {
3220
3494
  const fallback = types[0]?.[0] ?? "person";
3221
3495
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
3222
3496
  const networkSharing = chapter.network.targets.length > 0;
3223
- return /* @__PURE__ */ jsx34(
3497
+ return /* @__PURE__ */ jsx36(
3224
3498
  Tabs3,
3225
3499
  {
3226
3500
  ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
@@ -3251,8 +3525,8 @@ function PortfolioWorkspace({ chapter, ctx }) {
3251
3525
  const types = Object.entries(chapter.crm.config.types).filter(([, def]) => def.workspace === "portfolio");
3252
3526
  const fallback = types[0]?.[0] ?? "";
3253
3527
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
3254
- if (!active) return /* @__PURE__ */ jsx34(LeaderNetworkOverview, { ctx });
3255
- return /* @__PURE__ */ jsx34(
3528
+ if (!active) return /* @__PURE__ */ jsx36(LeaderNetworkOverview, { ctx });
3529
+ return /* @__PURE__ */ jsx36(
3256
3530
  Tabs3,
3257
3531
  {
3258
3532
  ariaLabel: copy.workspaces.portfolioViewsLabel,
@@ -3276,7 +3550,7 @@ function PortfolioWorkspace({ chapter, ctx }) {
3276
3550
  );
3277
3551
  }
3278
3552
  function LeaderDashboardWorkspace({ chapter, ctx }) {
3279
- return /* @__PURE__ */ jsx34(
3553
+ return /* @__PURE__ */ jsx36(
3280
3554
  Tabs3,
3281
3555
  {
3282
3556
  ariaLabel: chapter.copy.admin.workspaces.dashboardViewsLabel,
@@ -3286,7 +3560,7 @@ function LeaderDashboardWorkspace({ chapter, ctx }) {
3286
3560
  value: "overview",
3287
3561
  label: chapter.copy.admin.workspaces.overview,
3288
3562
  href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
3289
- panel: /* @__PURE__ */ jsx34(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
3563
+ panel: /* @__PURE__ */ jsx36(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
3290
3564
  }]
3291
3565
  }
3292
3566
  );
@@ -3297,7 +3571,7 @@ function defaultAdminWorkspaces(chapter) {
3297
3571
  id: "people",
3298
3572
  label: copy.workspaces.people,
3299
3573
  defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
3300
- render: (ctx) => /* @__PURE__ */ jsx34(PeopleWorkspace, { chapter, ctx })
3574
+ render: (ctx) => /* @__PURE__ */ jsx36(PeopleWorkspace, { chapter, ctx })
3301
3575
  };
3302
3576
  if (chapter.mode === "hub" && chapter.network.targets.length === 0) return [people];
3303
3577
  if (chapter.mode === "hub") {
@@ -3307,7 +3581,7 @@ function defaultAdminWorkspaces(chapter) {
3307
3581
  id: "dashboard",
3308
3582
  label: copy.workspaces.dashboard,
3309
3583
  defaultViewId: "overview",
3310
- render: (ctx) => /* @__PURE__ */ jsx34(LeaderDashboardWorkspace, { chapter, ctx })
3584
+ render: (ctx) => /* @__PURE__ */ jsx36(LeaderDashboardWorkspace, { chapter, ctx })
3311
3585
  },
3312
3586
  networkRecordsWorkspace(chapter),
3313
3587
  people,
@@ -3315,13 +3589,13 @@ function defaultAdminWorkspaces(chapter) {
3315
3589
  id: "portfolio",
3316
3590
  label: copy.workspaces.portfolio,
3317
3591
  defaultViewId: portfolioTypes[0]?.[0],
3318
- render: (ctx) => /* @__PURE__ */ jsx34(PortfolioWorkspace, { chapter, ctx })
3592
+ render: (ctx) => /* @__PURE__ */ jsx36(PortfolioWorkspace, { chapter, ctx })
3319
3593
  },
3320
3594
  {
3321
3595
  id: "settings",
3322
3596
  label: copy.workspaces.settings,
3323
3597
  defaultViewId: "email",
3324
- render: (ctx) => /* @__PURE__ */ jsx34(LeaderSettingsWorkspace, { chapter, ctx })
3598
+ render: (ctx) => /* @__PURE__ */ jsx36(LeaderSettingsWorkspace, { chapter, ctx })
3325
3599
  }
3326
3600
  ];
3327
3601
  }
@@ -3330,7 +3604,7 @@ function defaultAdminWorkspaces(chapter) {
3330
3604
  id: "dashboard",
3331
3605
  label: copy.workspaces.dashboard,
3332
3606
  defaultViewId: "overview",
3333
- render: (ctx) => /* @__PURE__ */ jsx34(DashboardWorkspace, { chapter, ctx })
3607
+ render: (ctx) => /* @__PURE__ */ jsx36(DashboardWorkspace, { chapter, ctx })
3334
3608
  },
3335
3609
  people,
3336
3610
  admissionGrantsWorkspace(copy.workspaces.admission),
@@ -3338,17 +3612,17 @@ function defaultAdminWorkspaces(chapter) {
3338
3612
  id: "settings",
3339
3613
  label: copy.workspaces.settings,
3340
3614
  defaultViewId: "calendar",
3341
- render: (ctx) => /* @__PURE__ */ jsx34(SettingsWorkspace, { chapter, ctx })
3615
+ render: (ctx) => /* @__PURE__ */ jsx36(SettingsWorkspace, { chapter, ctx })
3342
3616
  }
3343
3617
  ];
3344
3618
  }
3345
3619
 
3346
3620
  // src/ui/admin-frame.tsx
3347
3621
  import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
3348
- import { jsx as jsx35, jsxs as jsxs31 } from "preact/jsx-runtime";
3622
+ import { jsx as jsx37, jsxs as jsxs33 } from "preact/jsx-runtime";
3349
3623
  function AdminTheme(props) {
3350
3624
  const brand = props.chapter?.brand;
3351
- return /* @__PURE__ */ jsx35(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs31(
3625
+ return /* @__PURE__ */ jsx37(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs33(
3352
3626
  ThemeScope2,
3353
3627
  {
3354
3628
  ref: props.scopeRef,
@@ -3357,7 +3631,7 @@ function AdminTheme(props) {
3357
3631
  accent: brand?.accent,
3358
3632
  colorScheme: brand?.colorScheme ?? "light",
3359
3633
  children: [
3360
- /* @__PURE__ */ jsx35(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
3634
+ /* @__PURE__ */ jsx37(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
3361
3635
  props.children
3362
3636
  ]
3363
3637
  }
@@ -3377,11 +3651,11 @@ function signInRedirect(basePath, workspaces) {
3377
3651
  }
3378
3652
 
3379
3653
  // src/ui/admin-appearance.ts
3380
- import { useEffect as useEffect12, useState as useState28 } from "preact/hooks";
3654
+ import { useEffect as useEffect13, useState as useState30 } from "preact/hooks";
3381
3655
  import { clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
3382
3656
  function useScopedClerkAppearance(scope) {
3383
- const [appearance, setAppearance] = useState28();
3384
- useEffect12(() => {
3657
+ const [appearance, setAppearance] = useState30();
3658
+ useEffect13(() => {
3385
3659
  setAppearance(clerkAppearanceFromTokens(scope.current ?? void 0));
3386
3660
  }, [scope]);
3387
3661
  return appearance;
@@ -3412,7 +3686,7 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
3412
3686
  }
3413
3687
 
3414
3688
  // src/ui/admin.tsx
3415
- import { jsx as jsx36, jsxs as jsxs32 } from "preact/jsx-runtime";
3689
+ import { jsx as jsx38, jsxs as jsxs34 } from "preact/jsx-runtime";
3416
3690
  function Authed(props) {
3417
3691
  const copy = useChapterCopy().admin;
3418
3692
  const {
@@ -3440,8 +3714,8 @@ function Authed(props) {
3440
3714
  }),
3441
3715
  [getToken, crmBasePath]
3442
3716
  );
3443
- const [state, setState] = useState29({ status: "checking" });
3444
- useEffect13(() => {
3717
+ const [state, setState] = useState31({ status: "checking" });
3718
+ useEffect14(() => {
3445
3719
  let live = true;
3446
3720
  void (async () => {
3447
3721
  try {
@@ -3460,17 +3734,17 @@ function Authed(props) {
3460
3734
  live = false;
3461
3735
  };
3462
3736
  }, [getToken, apiBase, auth]);
3463
- if (state.status === "checking") return /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.auth.checking }) });
3737
+ if (state.status === "checking") return /* @__PURE__ */ jsx38(Gate, { brand, children: /* @__PURE__ */ jsx38("p", { style: S.muted, children: copy.auth.checking }) });
3464
3738
  if (state.status === "denied") {
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, {
3739
+ return /* @__PURE__ */ jsx38(Gate, { brand, children: /* @__PURE__ */ jsxs34("div", { className: "card", style: S.card, children: [
3740
+ /* @__PURE__ */ jsx38("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
3741
+ /* @__PURE__ */ jsx38("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
3468
3742
  account: state.email ?? copy.auth.thisAccount
3469
3743
  }) }),
3470
- /* @__PURE__ */ jsx36("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
3744
+ /* @__PURE__ */ jsx38("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
3471
3745
  ] }) });
3472
3746
  }
3473
- return /* @__PURE__ */ jsx36(
3747
+ return /* @__PURE__ */ jsx38(
3474
3748
  AdminShell,
3475
3749
  {
3476
3750
  workspaces,
@@ -3512,10 +3786,10 @@ function ChapterAdmin(props) {
3512
3786
  const chrome = props.chrome ?? "embedded";
3513
3787
  const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
3514
3788
  const redirect = signInRedirect(basePath, workspaces);
3515
- const [publishableKey, setPublishableKey] = useState29(void 0);
3789
+ const [publishableKey, setPublishableKey] = useState31(void 0);
3516
3790
  const scopeRef = useRef3(null);
3517
3791
  const appearance = useScopedClerkAppearance(scopeRef);
3518
- useEffect13(() => {
3792
+ useEffect14(() => {
3519
3793
  let live = true;
3520
3794
  void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
3521
3795
  return () => {
@@ -3524,23 +3798,23 @@ function ChapterAdmin(props) {
3524
3798
  }, [apiBase, auth]);
3525
3799
  let content;
3526
3800
  if (publishableKey === void 0 || publishableKey && !appearance) {
3527
- content = /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.admin.auth.loading }) });
3801
+ content = /* @__PURE__ */ jsx38(Gate, { brand, children: /* @__PURE__ */ jsx38("p", { style: S.muted, children: copy.admin.auth.loading }) });
3528
3802
  } else if (!publishableKey) {
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 })
3803
+ content = /* @__PURE__ */ jsx38(Gate, { brand, children: /* @__PURE__ */ jsxs34("div", { className: "card", style: S.card, children: [
3804
+ /* @__PURE__ */ jsx38("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
3805
+ /* @__PURE__ */ jsx38("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
3532
3806
  ] }) });
3533
3807
  } else if (workspaces.length === 0) {
3534
- content = /* @__PURE__ */ jsx36(Gate, { brand, children: /* @__PURE__ */ jsx36("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
3808
+ content = /* @__PURE__ */ jsx38(Gate, { brand, children: /* @__PURE__ */ jsx38("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
3535
3809
  } else {
3536
- content = /* @__PURE__ */ jsxs32(
3810
+ content = /* @__PURE__ */ jsxs34(
3537
3811
  ClerkGate,
3538
3812
  {
3539
3813
  publishableKey,
3540
3814
  appearance,
3541
3815
  afterSignOutUrl: "/",
3542
3816
  children: [
3543
- /* @__PURE__ */ jsx36(SignedOut, { children: /* @__PURE__ */ jsx36(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx36(
3817
+ /* @__PURE__ */ jsx38(SignedOut, { children: /* @__PURE__ */ jsx38(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx38(
3544
3818
  SignIn,
3545
3819
  {
3546
3820
  routing: "hash",
@@ -3548,7 +3822,7 @@ function ChapterAdmin(props) {
3548
3822
  signUpForceRedirectUrl: redirect
3549
3823
  }
3550
3824
  ) }) }),
3551
- /* @__PURE__ */ jsx36(SignedIn, { children: /* @__PURE__ */ jsx36(
3825
+ /* @__PURE__ */ jsx38(SignedIn, { children: /* @__PURE__ */ jsx38(
3552
3826
  Authed,
3553
3827
  {
3554
3828
  workspaces,
@@ -3569,7 +3843,7 @@ function ChapterAdmin(props) {
3569
3843
  }
3570
3844
  );
3571
3845
  }
3572
- return /* @__PURE__ */ jsx36(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
3846
+ return /* @__PURE__ */ jsx38(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
3573
3847
  }
3574
3848
 
3575
3849
  // src/ui/admin-defaults.ts
@@ -3578,17 +3852,17 @@ function defaultAdminSections(chapter) {
3578
3852
  }
3579
3853
 
3580
3854
  // src/ui/admin-meetings.tsx
3581
- import { useState as useState30 } from "preact/hooks";
3855
+ import { useState as useState32 } from "preact/hooks";
3582
3856
  import { Button as Button17 } from "@odla-ai/ui/components";
3583
- import { Fragment as Fragment13, jsx as jsx37, jsxs as jsxs33 } from "preact/jsx-runtime";
3857
+ import { Fragment as Fragment13, jsx as jsx39, jsxs as jsxs35 } from "preact/jsx-runtime";
3584
3858
  var when3 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
3585
3859
  function MeetingsBody({ ctx }) {
3586
3860
  const copy = useChapterCopy().admin.meetings;
3587
3861
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
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: [
3862
+ const [busy, setBusy] = useState32(null);
3863
+ const [note, setNote] = useState32(null);
3864
+ if (loading) return /* @__PURE__ */ jsx39(AdminNote, { children: copy.loading });
3865
+ if (error || !data) return /* @__PURE__ */ jsxs35(AdminNote, { children: [
3592
3866
  copy.loadFailed,
3593
3867
  error ? `: ${error}` : "",
3594
3868
  "."
@@ -3619,43 +3893,43 @@ function MeetingsBody({ ctx }) {
3619
3893
  }
3620
3894
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
3621
3895
  };
3622
- return /* @__PURE__ */ jsx37(AdminPage, { children: /* @__PURE__ */ jsx37(Panel, { title: /* @__PURE__ */ jsxs33(Fragment13, { children: [
3896
+ return /* @__PURE__ */ jsx39(AdminPage, { children: /* @__PURE__ */ jsx39(Panel, { title: /* @__PURE__ */ jsxs35(Fragment13, { children: [
3623
3897
  copy.agenda,
3624
3898
  " ",
3625
- /* @__PURE__ */ jsxs33("span", { className: "muted", style: { fontWeight: 400 }, children: [
3899
+ /* @__PURE__ */ jsxs35("span", { className: "muted", style: { fontWeight: 400 }, children: [
3626
3900
  "\xB7 ",
3627
3901
  data.timezone
3628
3902
  ] })
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: [
3903
+ ] }), actions: note ? /* @__PURE__ */ jsx39("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx39("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx39("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs35("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
3904
+ /* @__PURE__ */ jsx39("strong", { style: { minWidth: 170 }, children: when3(m.startAt) }),
3905
+ /* @__PURE__ */ jsx39("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
3906
+ /* @__PURE__ */ jsx39("span", { className: "badge", children: m.status }),
3907
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs35("span", { className: "badge", children: [
3634
3908
  copy.drift,
3635
3909
  ": ",
3636
3910
  m.drift
3637
3911
  ] }) : null,
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 })
3912
+ m.meetUrl ? /* @__PURE__ */ jsx39("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
3913
+ m.status === "scheduled" ? /* @__PURE__ */ jsxs35(Fragment13, { children: [
3914
+ /* @__PURE__ */ jsx39(Button17, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
3915
+ /* @__PURE__ */ jsx39(Button17, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
3642
3916
  ] }) : null
3643
3917
  ] }, m.id)) }) }) });
3644
3918
  }
3645
3919
  function meetingsSection(options = {}) {
3646
3920
  const { id = "meetings", label = "Meetings" } = options;
3647
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx37(MeetingsBody, { ctx }) };
3921
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx39(MeetingsBody, { ctx }) };
3648
3922
  }
3649
3923
 
3650
3924
  // src/ui/admin-network-record-editor.tsx
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";
3925
+ import { Button as Button18, Field as Field14, Select as Select12 } from "@odla-ai/ui/components";
3926
+ import { useRef as useRef4, useState as useState33 } from "preact/hooks";
3927
+ import { Fragment as Fragment14, jsx as jsx40, jsxs as jsxs36 } from "preact/jsx-runtime";
3654
3928
  function NetworkRecordEditor(props) {
3655
3929
  const { ctx, record } = props;
3656
- const [stage, setStage] = useState31("");
3657
- const [busy, setBusy] = useState31(false);
3658
- const [error, setError] = useState31(null);
3930
+ const [stage, setStage] = useState33("");
3931
+ const [busy, setBusy] = useState33(false);
3932
+ const [error, setError] = useState33(null);
3659
3933
  const attempt = useRef4(null);
3660
3934
  const pending = record.edit?.pendingAction;
3661
3935
  const superAdmin = ctx.currentUser?.superAdmin === true;
@@ -3688,11 +3962,11 @@ function NetworkRecordEditor(props) {
3688
3962
  setBusy(false);
3689
3963
  }
3690
3964
  };
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(
3965
+ return /* @__PURE__ */ jsxs36("div", { style: { display: "grid", gap: 24 }, children: [
3966
+ superAdmin && record.edit ? /* @__PURE__ */ jsxs36(Fragment14, { children: [
3967
+ /* @__PURE__ */ jsxs36("section", { children: [
3968
+ /* @__PURE__ */ jsx40("h3", { children: "Chapter profile" }),
3969
+ /* @__PURE__ */ jsx40(
3696
3970
  OperationalProfileForm,
3697
3971
  {
3698
3972
  fields: record.edit.fields,
@@ -3702,27 +3976,27 @@ function NetworkRecordEditor(props) {
3702
3976
  `${record.id}:${record.revision}`
3703
3977
  )
3704
3978
  ] }),
3705
- /* @__PURE__ */ jsxs34("section", { style: { display: "grid", gap: 12 }, children: [
3706
- /* @__PURE__ */ jsx38("h3", { children: "Membership actions" }),
3707
- pending ? /* @__PURE__ */ jsxs34("p", { children: [
3979
+ /* @__PURE__ */ jsxs36("section", { style: { display: "grid", gap: 12 }, children: [
3980
+ /* @__PURE__ */ jsx40("h3", { children: "Membership actions" }),
3981
+ pending ? /* @__PURE__ */ jsxs36("p", { children: [
3708
3982
  "An application action is pending: ",
3709
3983
  pending.stage.replaceAll("_", " "),
3710
3984
  ". ",
3711
3985
  ownPending ? "Retry reconciles the same operation." : "The original operator must reconcile it."
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
3986
+ ] }) : record.edit.allowedStages.length ? /* @__PURE__ */ jsx40(Field14, { label: "Action", children: /* @__PURE__ */ jsxs36(Select12, { "aria-label": "Action", value: stage, disabled: busy, onChange: (event) => setStage(event.currentTarget.value), children: [
3987
+ /* @__PURE__ */ jsx40("option", { value: "", children: "Choose an action" }),
3988
+ record.edit.allowedStages.map((action) => /* @__PURE__ */ jsx40("option", { value: action.id, children: action.label }, action.id))
3989
+ ] }) }) : /* @__PURE__ */ jsx40("p", { children: "No membership action is currently allowed." }),
3990
+ pending || record.edit.allowedStages.length ? /* @__PURE__ */ jsx40("div", { children: /* @__PURE__ */ jsx40(Button18, { disabled: busy || !ownPending || !pending && !stage, onClick: () => void move(), children: busy ? "Applying\u2026" : pending ? "Retry pending action" : "Apply action" }) }) : null,
3991
+ error ? /* @__PURE__ */ jsx40("p", { role: "alert", children: error }) : null
3718
3992
  ] })
3719
3993
  ] }) : null,
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 })
3994
+ superAdmin && props.admissionGrants ? /* @__PURE__ */ jsxs36("section", { children: [
3995
+ /* @__PURE__ */ jsx40("h3", { children: "Next invitation" }),
3996
+ /* @__PURE__ */ jsx40("p", { children: "This setting applies to the next eligible signup; it does not replace approval of an existing application." }),
3997
+ /* @__PURE__ */ jsx40(LeaderAdmissionAction, { getToken: ctx.getToken, targetId: props.targetId, sourceRecordId: record.id })
3724
3998
  ] }) : null,
3725
- /* @__PURE__ */ jsx38(
3999
+ /* @__PURE__ */ jsx40(
3726
4000
  NetworkRecordDetail,
3727
4001
  {
3728
4002
  ctx,
@@ -3737,29 +4011,29 @@ function NetworkRecordEditor(props) {
3737
4011
  }
3738
4012
 
3739
4013
  // src/ui/network-seat-billing.tsx
3740
- import { useState as useState32 } from "preact/hooks";
4014
+ import { useState as useState34 } from "preact/hooks";
3741
4015
  import { Button as Button19, DataTable as DataTable4 } from "@odla-ai/ui/components";
3742
- import { jsx as jsx39, jsxs as jsxs35 } from "preact/jsx-runtime";
4016
+ import { jsx as jsx41, jsxs as jsxs37 } from "preact/jsx-runtime";
3743
4017
  var money3 = (row, cents) => row.currency ? new Intl.NumberFormat("en-US", {
3744
4018
  style: "currency",
3745
4019
  currency: row.currency
3746
4020
  }).format(cents / 100) : "Unknown";
3747
4021
  var columns2 = (refund, busy) => [
3748
- { key: "seatId", header: "Seat", cell: (row) => /* @__PURE__ */ jsx39("code", { children: row.seatId }) },
4022
+ { key: "seatId", header: "Seat", cell: (row) => /* @__PURE__ */ jsx41("code", { children: row.seatId }) },
3749
4023
  { key: "sourceKey", header: "Chapter / runtime", cell: (row) => row.sourceKey },
3750
4024
  { key: "status", header: "Payment", cell: (row) => row.status ?? "Unknown" },
3751
4025
  { key: "amountReceivedCents", header: "Collected", cell: (row) => money3(row, row.amountReceivedCents) },
3752
4026
  { key: "refundedCents", header: "Refunded", cell: (row) => money3(row, row.refundedCents) },
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" },
4027
+ { key: "membershipId", header: "Primary membership", cell: (row) => /* @__PURE__ */ jsx41("code", { children: row.membershipId }) },
4028
+ { key: "paymentIntentId", header: "Stripe payment", cell: (row) => /* @__PURE__ */ jsx41("code", { children: row.paymentIntentId }) },
4029
+ { key: "subscriptionItemId", header: "Renewal item", cell: (row) => row.subscriptionItemId ? /* @__PURE__ */ jsx41("code", { children: row.subscriptionItemId }) : "Awaiting provider linkage" },
3756
4030
  { key: "currentPeriodEnd", header: "Paid through", cell: (row) => row.currentPeriodEnd ? new Date(row.currentPeriodEnd * 1e3).toLocaleDateString() : "Awaiting provider linkage" },
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" }
4031
+ { key: "refund", header: "Refund", cell: (row) => row.refundedCents < row.amountReceivedCents ? /* @__PURE__ */ jsx41(Button19, { variant: "danger", mini: true, disabled: busy !== null, onClick: () => refund(row), children: busy === row.seatId ? "Refunding\u2026" : "Refund seat" }) : "Refunded" }
3758
4032
  ];
3759
4033
  function NetworkSeatBilling(props) {
3760
4034
  const { data, loading, error, refresh } = useAdminResource(props.getToken, "/api/admin/network-commercial-read-model");
3761
- const [busy, setBusy] = useState32(null);
3762
- const [note, setNote] = useState32(null);
4035
+ const [busy, setBusy] = useState34(null);
4036
+ const [note, setNote] = useState34(null);
3763
4037
  const refund = async (row) => {
3764
4038
  setBusy(row.seatId);
3765
4039
  setNote(null);
@@ -3777,20 +4051,20 @@ function NetworkSeatBilling(props) {
3777
4051
  setBusy(null);
3778
4052
  }
3779
4053
  };
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: [
4054
+ return /* @__PURE__ */ jsxs37(Panel, { title: "Additional membership seats", children: [
4055
+ /* @__PURE__ */ jsx41("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." }),
4056
+ loading ? /* @__PURE__ */ jsx41("p", { children: "Loading seats\u2026" }) : error ? /* @__PURE__ */ jsxs37("p", { role: "alert", children: [
3783
4057
  "Seats could not be loaded: ",
3784
4058
  error
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
4059
+ ] }) : /* @__PURE__ */ jsx41(DataTable4, { rows: data?.seats ?? [], columns: columns2((row) => void refund(row), busy), rowKey: (row) => `${row.sourceKey}:${row.seatId}` }),
4060
+ note ? /* @__PURE__ */ jsx41("p", { role: "status", children: note }) : null
3787
4061
  ] });
3788
4062
  }
3789
4063
 
3790
4064
  // src/ui/membership-disputes.tsx
3791
- import { useState as useState33 } from "preact/hooks";
4065
+ import { useState as useState35 } from "preact/hooks";
3792
4066
  import { Button as Button20, DataTable as DataTable5 } from "@odla-ai/ui/components";
3793
- import { jsx as jsx40, jsxs as jsxs36 } from "preact/jsx-runtime";
4067
+ import { jsx as jsx42, jsxs as jsxs38 } from "preact/jsx-runtime";
3794
4068
  var money4 = (row, cents) => new Intl.NumberFormat("en-US", { style: "currency", currency: row.currency }).format(cents / 100);
3795
4069
  var day = (at) => at ? new Date(at).toLocaleDateString() : "\u2014";
3796
4070
  var DISPUTE_STATUS_WORDS = {
@@ -3804,35 +4078,35 @@ var DISPUTE_STATUS_WORDS = {
3804
4078
  recharged: "re-charged"
3805
4079
  };
3806
4080
  var columns3 = (begin, busy) => [
3807
- { key: "member", header: "Member", cell: (row) => row.kind === "seat" ? /* @__PURE__ */ jsxs36("span", { children: [
4081
+ { key: "member", header: "Member", cell: (row) => row.kind === "seat" ? /* @__PURE__ */ jsxs38("span", { children: [
3808
4082
  "Seat for ",
3809
4083
  row.recipientName ?? "the named recipient",
3810
4084
  " \xB7 ",
3811
- /* @__PURE__ */ jsx40("code", { children: row.applicationId })
3812
- ] }) : /* @__PURE__ */ jsxs36("span", { children: [
4085
+ /* @__PURE__ */ jsx42("code", { children: row.applicationId })
4086
+ ] }) : /* @__PURE__ */ jsxs38("span", { children: [
3813
4087
  row.memberNumber ? `#${row.memberNumber} \xB7 ` : "",
3814
- /* @__PURE__ */ jsx40("code", { children: row.applicationId })
4088
+ /* @__PURE__ */ jsx42("code", { children: row.applicationId })
3815
4089
  ] }) },
3816
4090
  { key: "chapter", header: "Chapter / runtime", cell: (row) => `${row.chapterId} / ${row.runtime}` },
3817
4091
  { key: "status", header: "Record", cell: (row) => row.status === "payment_disputed" ? `${row.kind === "seat" ? "seat" : "membership"} suspended by dispute` : row.status },
3818
- { key: "dispute", header: "Dispute", cell: (row) => /* @__PURE__ */ jsxs36("span", { children: [
4092
+ { key: "dispute", header: "Dispute", cell: (row) => /* @__PURE__ */ jsxs38("span", { children: [
3819
4093
  DISPUTE_STATUS_WORDS[row.disputeStatus] ?? row.disputeStatus,
3820
4094
  row.disputeReason ? ` \xB7 ${row.disputeReason}` : ""
3821
4095
  ] }) },
3822
4096
  { key: "amount", header: "Amount", cell: (row) => money4(row, row.disputeAmountCents ?? row.amountCents) },
3823
4097
  { key: "opened", header: "Opened", cell: (row) => day(row.disputedAt) },
3824
4098
  { key: "closed", header: "Closed", cell: (row) => day(row.disputeClosedAt) },
3825
- { key: "recharge", header: "Re-charge", cell: (row) => row.rechargePaymentIntentId ? /* @__PURE__ */ jsxs36("span", { children: [
4099
+ { key: "recharge", header: "Re-charge", cell: (row) => row.rechargePaymentIntentId ? /* @__PURE__ */ jsxs38("span", { children: [
3826
4100
  day(row.rechargedAt),
3827
4101
  " \xB7 ",
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" }
4102
+ /* @__PURE__ */ jsx42("code", { children: row.rechargePaymentIntentId })
4103
+ ] }) : row.status === "payment_disputed" && row.disputeStatus === "lost" ? /* @__PURE__ */ jsx42(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" }
3830
4104
  ];
3831
4105
  function MembershipDisputes(props) {
3832
4106
  const { data, loading, error, refresh } = useAdminResource(props.getToken, "/api/admin/membership-disputes");
3833
- const [busy, setBusy] = useState33(null);
3834
- const [confirming, setConfirming] = useState33(null);
3835
- const [note, setNote] = useState33(null);
4107
+ const [busy, setBusy] = useState35(null);
4108
+ const [confirming, setConfirming] = useState35(null);
4109
+ const [note, setNote] = useState35(null);
3836
4110
  const recharge = async (row) => {
3837
4111
  setBusy(row.id);
3838
4112
  setNote(null);
@@ -3853,33 +4127,33 @@ function MembershipDisputes(props) {
3853
4127
  }
3854
4128
  };
3855
4129
  const rows = data?.disputes ?? [];
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: [
4130
+ return /* @__PURE__ */ jsxs38(Panel, { title: "Card disputes", children: [
4131
+ /* @__PURE__ */ jsx42("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." }),
4132
+ loading ? /* @__PURE__ */ jsx42("p", { children: "Loading disputes\u2026" }) : error ? /* @__PURE__ */ jsxs38("p", { role: "alert", children: [
3859
4133
  "Disputes could not be loaded: ",
3860
4134
  error
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: [
4135
+ ] }) : rows.length ? /* @__PURE__ */ jsx42(DataTable5, { rows, columns: columns3(setConfirming, busy), rowKey: (row) => row.id }) : /* @__PURE__ */ jsx42("p", { children: "No membership carries a dispute." }),
4136
+ data?.truncated ? /* @__PURE__ */ jsx42("p", { role: "alert", children: "Only the most recent memberships were checked; older disputes may exist." }) : null,
4137
+ confirming ? /* @__PURE__ */ jsxs38("div", { role: "group", "aria-label": "Confirm re-charge", children: [
4138
+ /* @__PURE__ */ jsxs38("p", { children: [
3865
4139
  "Charge ",
3866
4140
  money4(confirming, confirming.disputeAmountCents ?? confirming.amountCents),
3867
4141
  " again to the card on file for ",
3868
4142
  confirming.kind === "seat" ? `the seat for ${confirming.recipientName ?? "the recipient"}` : confirming.applicationId,
3869
4143
  "?"
3870
4144
  ] }),
3871
- /* @__PURE__ */ jsx40(Button20, { mini: true, onClick: () => void recharge(confirming), children: "Yes, charge the card" }),
4145
+ /* @__PURE__ */ jsx42(Button20, { mini: true, onClick: () => void recharge(confirming), children: "Yes, charge the card" }),
3872
4146
  " ",
3873
- /* @__PURE__ */ jsx40(Button20, { mini: true, onClick: () => setConfirming(null), children: "Cancel" })
4147
+ /* @__PURE__ */ jsx42(Button20, { mini: true, onClick: () => setConfirming(null), children: "Cancel" })
3874
4148
  ] }) : null,
3875
- note ? /* @__PURE__ */ jsx40("p", { role: "status", children: note }) : null
4149
+ note ? /* @__PURE__ */ jsx42("p", { role: "status", children: note }) : null
3876
4150
  ] });
3877
4151
  }
3878
4152
 
3879
4153
  // src/ui/membership-policy-versions.tsx
3880
4154
  import { Button as Button21, DataTable as DataTable6 } from "@odla-ai/ui/components";
3881
- import { useState as useState34 } from "preact/hooks";
3882
- import { Fragment as Fragment15, jsx as jsx41, jsxs as jsxs37 } from "preact/jsx-runtime";
4155
+ import { useState as useState36 } from "preact/hooks";
4156
+ import { Fragment as Fragment15, jsx as jsx43, jsxs as jsxs39 } from "preact/jsx-runtime";
3883
4157
  var PATH = "/api/admin/membership-policy-versions";
3884
4158
  var RETERM = "/api/admin/membership-discount/reterm";
3885
4159
  var day2 = (ms) => new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
@@ -3891,50 +4165,50 @@ var MECHANISM = {
3891
4165
  stripe_coupon_forever: "Stripe coupon, applied while active",
3892
4166
  stripe_coupon_term_bound: "Stripe coupon, applied once; issuance refuses unless the offering's fixed term ends on the same date"
3893
4167
  };
3894
- var State = ({ row }) => /* @__PURE__ */ jsx41("span", { className: row.state === "active" ? "badge" : "muted", children: STATE[row.state] });
4168
+ var State = ({ row }) => /* @__PURE__ */ jsx43("span", { className: row.state === "active" ? "badge" : "muted", children: STATE[row.state] });
3895
4169
  var superseded = (row) => row.supersededAt === void 0 ? "\u2014" : day2(row.supersededAt);
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: [
4170
+ var Source = ({ row }) => /* @__PURE__ */ jsxs39("span", { children: [
4171
+ row.source ?? /* @__PURE__ */ jsx43("span", { className: "muted", children: "Unrecorded" }),
4172
+ row.actor ? /* @__PURE__ */ jsxs39(Fragment15, { children: [
3899
4173
  " \xB7 ",
3900
- /* @__PURE__ */ jsx41("code", { children: row.actor })
4174
+ /* @__PURE__ */ jsx43("code", { children: row.actor })
3901
4175
  ] }) : null,
3902
- row.recordedAt !== void 0 ? /* @__PURE__ */ jsxs37(Fragment15, { children: [
4176
+ row.recordedAt !== void 0 ? /* @__PURE__ */ jsxs39(Fragment15, { children: [
3903
4177
  " \xB7 ",
3904
4178
  day2(row.recordedAt)
3905
4179
  ] }) : null
3906
4180
  ] });
3907
4181
  var offeringColumns = [
3908
- { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
3909
- /* @__PURE__ */ jsxs37("code", { children: [
4182
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs39(Fragment15, { children: [
4183
+ /* @__PURE__ */ jsxs39("code", { children: [
3910
4184
  "v",
3911
4185
  row.version
3912
4186
  ] }),
3913
4187
  " ",
3914
- /* @__PURE__ */ jsx41(State, { row })
4188
+ /* @__PURE__ */ jsx43(State, { row })
3915
4189
  ] }), sortAs: "number", sortValue: (row) => row.version },
3916
- { key: "sku", header: "SKU", cell: (row) => /* @__PURE__ */ jsx41("code", { children: row.baseSkuId }) },
4190
+ { key: "sku", header: "SKU", cell: (row) => /* @__PURE__ */ jsx43("code", { children: row.baseSkuId }) },
3917
4191
  { key: "list", header: "List price", cell: (row) => `${money5(row.basePriceCents, row.currency)} / ${row.interval}` },
3918
4192
  { key: "term", header: "Term", cell: (row) => row.term.kind === "fixed_end" ? `Ends ${day2(row.term.endsAt)}` : `Rolling ${row.term.months} months` },
3919
4193
  { 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`}` },
3920
4194
  { key: "effectiveFrom", header: "Effective from", cell: (row) => day2(row.effectiveFrom), sortAs: "number", sortValue: (row) => row.effectiveFrom },
3921
4195
  { key: "effectiveUntil", header: "Effective until", cell: (row) => row.effectiveUntil === void 0 ? "Open" : day2(row.effectiveUntil) },
3922
4196
  { key: "supersededAt", header: "Superseded", cell: superseded },
3923
- { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx41(Source, { row }) }
4197
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx43(Source, { row }) }
3924
4198
  ];
3925
4199
  var policyColumns = (getToken) => [
3926
- { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
3927
- /* @__PURE__ */ jsxs37("code", { children: [
4200
+ { key: "version", header: "Version", cell: (row) => /* @__PURE__ */ jsxs39(Fragment15, { children: [
4201
+ /* @__PURE__ */ jsxs39("code", { children: [
3928
4202
  "v",
3929
4203
  row.version
3930
4204
  ] }),
3931
4205
  " ",
3932
- /* @__PURE__ */ jsx41(State, { row })
4206
+ /* @__PURE__ */ jsx43(State, { row })
3933
4207
  ] }), sortAs: "number", sortValue: (row) => row.version },
3934
- { key: "cohort", header: "Cohort", cell: (row) => /* @__PURE__ */ jsxs37(Fragment15, { children: [
4208
+ { key: "cohort", header: "Cohort", cell: (row) => /* @__PURE__ */ jsxs39(Fragment15, { children: [
3935
4209
  row.cohortLabel,
3936
4210
  " ",
3937
- /* @__PURE__ */ jsxs37("span", { className: "muted", children: [
4211
+ /* @__PURE__ */ jsxs39("span", { className: "muted", children: [
3938
4212
  "\xB7 soft target ",
3939
4213
  row.softTarget
3940
4214
  ] })
@@ -3945,8 +4219,8 @@ var policyColumns = (getToken) => [
3945
4219
  { 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" },
3946
4220
  { key: "mechanism", header: "Provider mechanism", cell: (row) => MECHANISM[row.mechanism] },
3947
4221
  { key: "supersededAt", header: "Superseded", cell: superseded },
3948
- { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx41(Source, { row }) },
3949
- { key: "members", header: "Existing members", cell: (row) => /* @__PURE__ */ jsx41(RetermAction, { row, getToken }) }
4222
+ { key: "source", header: "Source", cell: (row) => /* @__PURE__ */ jsx43(Source, { row }) },
4223
+ { key: "members", header: "Existing members", cell: (row) => /* @__PURE__ */ jsx43(RetermAction, { row, getToken }) }
3950
4224
  ];
3951
4225
  var summary = (receipt) => {
3952
4226
  const skipped = receipt.skipped.reduce((acc, item) => ({ ...acc, [item.reason]: (acc[item.reason] ?? 0) + 1 }), {});
@@ -3958,9 +4232,9 @@ var summary = (receipt) => {
3958
4232
  return `${parts.join(" \xB7 ")}${receipt.more ? " \xB7 more remain, run again" : ""}`;
3959
4233
  };
3960
4234
  function RetermAction({ row, getToken }) {
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" });
4235
+ const [busy, setBusy] = useState36(false);
4236
+ const [result, setResult] = useState36(null);
4237
+ if (row.state !== "active" || row.mechanism !== "stripe_coupon_forever") return /* @__PURE__ */ jsx43("span", { className: "muted", children: row.state !== "active" ? "Not the active version" : "Benefit is not while-active" });
3964
4238
  const run = async () => {
3965
4239
  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;
3966
4240
  setBusy(true);
@@ -3974,28 +4248,28 @@ function RetermAction({ row, getToken }) {
3974
4248
  setBusy(false);
3975
4249
  }
3976
4250
  };
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
4251
+ return /* @__PURE__ */ jsxs39("span", { style: { display: "inline-flex", flexDirection: "column", gap: 4 }, children: [
4252
+ /* @__PURE__ */ jsx43(Button21, { mini: true, disabled: busy, onClick: run, children: busy ? "Moving\u2026" : "Move members to this benefit" }),
4253
+ result ? /* @__PURE__ */ jsx43("span", { role: result.tone === "good" ? "status" : "alert", className: result.tone === "danger" ? "danger" : "muted", children: result.text }) : null
3980
4254
  ] });
3981
4255
  }
3982
4256
  function MembershipPolicyVersions(props) {
3983
4257
  const path = props.chapterId ? `${PATH}?chapterId=${encodeURIComponent(props.chapterId)}` : PATH;
3984
4258
  const { data, loading, error } = useAdminResource(props.getToken, path);
3985
4259
  const scope = props.chapterId ? `chapter ${props.chapterId}` : "any chapter";
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: [
4260
+ if (loading) return /* @__PURE__ */ jsx43(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsx43("p", { role: "status", children: "Loading offering versions\u2026" }) });
4261
+ if (error || !data) return /* @__PURE__ */ jsx43(Panel, { title: "Membership offering versions", children: /* @__PURE__ */ jsxs39("p", { role: "alert", children: [
3988
4262
  "Offering versions could not be loaded: ",
3989
4263
  error ?? "empty response"
3990
4264
  ] }) });
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: [
4265
+ return /* @__PURE__ */ jsxs39(Fragment15, { children: [
4266
+ /* @__PURE__ */ jsxs39(Panel, { title: "Membership offering versions", actions: data.truncated ? /* @__PURE__ */ jsx43("span", { className: "badge", children: "Truncated" }) : void 0, children: [
4267
+ /* @__PURE__ */ jsxs39("p", { className: "muted", children: [
3994
4268
  "The list price a card shows comes from the active offering; the payable price applies the active discount policy. State is judged as of ",
3995
4269
  day2(data.now),
3996
4270
  ", exactly as checkout issuance judges it."
3997
4271
  ] }),
3998
- /* @__PURE__ */ jsx41(
4272
+ /* @__PURE__ */ jsx43(
3999
4273
  DataTable6,
4000
4274
  {
4001
4275
  rows: data.offerings,
@@ -4006,8 +4280,8 @@ function MembershipPolicyVersions(props) {
4006
4280
  }
4007
4281
  )
4008
4282
  ] }),
4009
- /* @__PURE__ */ jsxs37(Panel, { title: "Discount policy versions", children: [
4010
- /* @__PURE__ */ jsx41(
4283
+ /* @__PURE__ */ jsxs39(Panel, { title: "Discount policy versions", children: [
4284
+ /* @__PURE__ */ jsx43(
4011
4285
  DataTable6,
4012
4286
  {
4013
4287
  rows: data.discountPolicies,
@@ -4017,7 +4291,7 @@ function MembershipPolicyVersions(props) {
4017
4291
  emptyState: `No discount policy versions are recorded for ${scope}.`
4018
4292
  }
4019
4293
  ),
4020
- data.invalid.length ? /* @__PURE__ */ jsxs37("p", { role: "alert", children: [
4294
+ data.invalid.length ? /* @__PURE__ */ jsxs39("p", { role: "alert", children: [
4021
4295
  data.invalid.length,
4022
4296
  " recorded version",
4023
4297
  data.invalid.length === 1 ? " no longer normalizes and blocks" : "s no longer normalize and block",
@@ -4077,4 +4351,4 @@ export {
4077
4351
  MembershipDisputes,
4078
4352
  MembershipPolicyVersions
4079
4353
  };
4080
- //# sourceMappingURL=chunk-MUUBATZJ.js.map
4354
+ //# sourceMappingURL=chunk-S6VPJFHL.js.map