@odla-ai/chapter 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -214,16 +214,112 @@ function ChapterAdmin(props) {
214
214
  }
215
215
 
216
216
  // src/ui/admin-people.tsx
217
- import { useState as useState3 } from "react";
217
+ import { useState as useState5 } from "react";
218
218
  import { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from "@odla-ai/crm/ui";
219
+
220
+ // src/ui/admin-record-actions.tsx
221
+ import { useEffect as useEffect4, useState as useState4 } from "react";
222
+ import { Button } from "@odla-ai/ui/components";
223
+
224
+ // src/ui/admin-api.ts
225
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
226
+ async function adminFetch(getToken, path, init) {
227
+ const token = await getToken();
228
+ const headers = new Headers(init?.headers);
229
+ if (token) headers.set("authorization", `Bearer ${token}`);
230
+ if (init?.body) headers.set("content-type", "application/json");
231
+ const res = await fetch(path, { ...init, headers });
232
+ const body = await res.json().catch(() => ({}));
233
+ if (!res.ok) throw new Error(typeof body.error === "string" ? body.error : `request failed (${res.status})`);
234
+ return body;
235
+ }
236
+ function useAdminResource(getToken, path) {
237
+ const [data, setData] = useState3(null);
238
+ const [loading, setLoading] = useState3(true);
239
+ const [error, setError] = useState3(null);
240
+ const [nonce, setNonce] = useState3(0);
241
+ useEffect3(() => {
242
+ let live = true;
243
+ setLoading(true);
244
+ setError(null);
245
+ adminFetch(getToken, path).then((d) => live && setData(d)).catch((e) => live && setError(e instanceof Error ? e.message : String(e))).finally(() => live && setLoading(false));
246
+ return () => {
247
+ live = false;
248
+ };
249
+ }, [getToken, path, nonce]);
250
+ const refresh = useCallback2(() => setNonce((n) => n + 1), []);
251
+ return { data, loading, error, refresh };
252
+ }
253
+
254
+ // src/ui/admin-record-actions.tsx
219
255
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
256
+ function RecordActions({ getToken, record, roles, onChanged }) {
257
+ const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
258
+ const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
259
+ const [access, setAccess] = useState4(null);
260
+ const [busy, setBusy] = useState4(null);
261
+ const [note, setNote] = useState4(null);
262
+ useEffect4(() => {
263
+ if (!userId) {
264
+ setAccess(null);
265
+ return;
266
+ }
267
+ let live = true;
268
+ adminFetch(getToken, `/api/admin/people/access?userId=${encodeURIComponent(userId)}`).then((a) => live && setAccess(a)).catch(() => live && setAccess(null));
269
+ return () => {
270
+ live = false;
271
+ };
272
+ }, [getToken, userId]);
273
+ const act = async (key, run, ok) => {
274
+ setBusy(key);
275
+ setNote(null);
276
+ try {
277
+ await run();
278
+ setNote(ok);
279
+ onChanged();
280
+ } catch (e) {
281
+ setNote(e instanceof Error ? e.message : String(e));
282
+ } finally {
283
+ setBusy(null);
284
+ }
285
+ };
286
+ const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), `Role set to ${role}.`);
287
+ const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), "Approved.");
288
+ const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), "Refund issued.");
289
+ if (!userId && !applicationId) return null;
290
+ return /* @__PURE__ */ jsxs3("div", { className: "card record-actions", children: [
291
+ /* @__PURE__ */ jsx3("h4", { children: "Access & lifecycle" }),
292
+ userId ? /* @__PURE__ */ jsxs3("label", { className: "row", children: [
293
+ "Role",
294
+ /* @__PURE__ */ jsx3(
295
+ "select",
296
+ {
297
+ value: access?.role ?? "",
298
+ disabled: busy === "role" || access?.superAdmin,
299
+ onChange: (e) => void setRole(e.target.value),
300
+ children: roles.map((r) => /* @__PURE__ */ jsx3("option", { value: r, children: r }, r))
301
+ }
302
+ ),
303
+ access?.superAdmin ? /* @__PURE__ */ jsx3("span", { className: "muted", children: "super-admin (managed in Studio)" }) : null
304
+ ] }) : /* @__PURE__ */ jsx3("p", { className: "muted", children: "No linked account yet \u2014 role is set once they sign in." }),
305
+ applicationId ? /* @__PURE__ */ jsxs3("div", { className: "row", children: [
306
+ /* @__PURE__ */ jsx3(Button, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: "Approve" }),
307
+ /* @__PURE__ */ jsx3(Button, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: "Refund" })
308
+ ] }) : null,
309
+ note ? /* @__PURE__ */ jsx3("p", { className: "muted", children: note }) : null
310
+ ] });
311
+ }
312
+
313
+ // src/ui/admin-people.tsx
314
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
315
+ var DEFAULT_ROLES = ["provisional", "member", "admin"];
220
316
  function PeopleBody(props) {
221
- const { crm, client, type } = props;
317
+ const { crm, client, type, getToken, roles, lifecycle } = props;
222
318
  const hookClient = client;
223
319
  const query = useCrmQuery(hookClient, type);
224
- const [openId, setOpenId] = useState3(null);
320
+ const [openId, setOpenId] = useState5(null);
225
321
  const record = useCrmRecord(hookClient, openId);
226
- const [saving, setSaving] = useState3(false);
322
+ const [saving, setSaving] = useState5(false);
227
323
  const saveFields = async (input) => {
228
324
  if (!openId) return;
229
325
  setSaving(true);
@@ -253,20 +349,34 @@ function PeopleBody(props) {
253
349
  record.refresh();
254
350
  }
255
351
  };
256
- return /* @__PURE__ */ jsxs3("div", { className: "wrap", children: [
257
- /* @__PURE__ */ jsx3(CrmList, { crm, type, query, onOpenRecord: (r) => setOpenId(r.id) }),
258
- record.detail ? /* @__PURE__ */ jsx3(
259
- RecordPanel,
260
- {
261
- crm,
262
- detail: record.detail,
263
- onSaveFields: saveFields,
264
- onMoveStage: moveStage,
265
- onAddTag: addTag,
266
- onRemoveTag: removeTag,
267
- saving
268
- }
269
- ) : null
352
+ return /* @__PURE__ */ jsxs4("div", { className: "wrap", children: [
353
+ /* @__PURE__ */ jsx4(CrmList, { crm, type, query, onOpenRecord: (r) => setOpenId(r.id) }),
354
+ record.detail ? /* @__PURE__ */ jsxs4("div", { className: "record-drawer", children: [
355
+ /* @__PURE__ */ jsx4(
356
+ RecordPanel,
357
+ {
358
+ crm,
359
+ detail: record.detail,
360
+ onSaveFields: saveFields,
361
+ onMoveStage: moveStage,
362
+ onAddTag: addTag,
363
+ onRemoveTag: removeTag,
364
+ saving
365
+ }
366
+ ),
367
+ lifecycle ? /* @__PURE__ */ jsx4(
368
+ RecordActions,
369
+ {
370
+ getToken,
371
+ record: record.detail.record,
372
+ roles,
373
+ onChanged: () => {
374
+ record.refresh();
375
+ query.refresh();
376
+ }
377
+ }
378
+ ) : null
379
+ ] }) : null
270
380
  ] });
271
381
  }
272
382
  function collectionSection(options) {
@@ -276,189 +386,159 @@ function collectionSection(options) {
276
386
  fallbackLabel = crm.type(type).labelPlural ?? crm.type(type).label ?? type;
277
387
  } catch {
278
388
  }
279
- const { id = type, label = fallbackLabel } = options;
280
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx3(PeopleBody, { crm, client: ctx.client, type }) };
389
+ const { id = type, label = fallbackLabel, lifecycle = type === "person", roles = DEFAULT_ROLES } = options;
390
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx4(PeopleBody, { crm, client: ctx.client, type, getToken: ctx.getToken, roles, lifecycle }) };
281
391
  }
282
392
  function peopleSection(options) {
283
- const { crm, id = "people", label = "People", type = "person" } = options;
284
- return collectionSection({ crm, type, id, label });
285
- }
286
-
287
- // src/ui/admin-api.ts
288
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState4 } from "react";
289
- async function adminFetch(getToken, path, init) {
290
- const token = await getToken();
291
- const headers = new Headers(init?.headers);
292
- if (token) headers.set("authorization", `Bearer ${token}`);
293
- if (init?.body) headers.set("content-type", "application/json");
294
- const res = await fetch(path, { ...init, headers });
295
- const body = await res.json().catch(() => ({}));
296
- if (!res.ok) throw new Error(typeof body.error === "string" ? body.error : `request failed (${res.status})`);
297
- return body;
298
- }
299
- function useAdminResource(getToken, path) {
300
- const [data, setData] = useState4(null);
301
- const [loading, setLoading] = useState4(true);
302
- const [error, setError] = useState4(null);
303
- const [nonce, setNonce] = useState4(0);
304
- useEffect3(() => {
305
- let live = true;
306
- setLoading(true);
307
- setError(null);
308
- adminFetch(getToken, path).then((d) => live && setData(d)).catch((e) => live && setError(e instanceof Error ? e.message : String(e))).finally(() => live && setLoading(false));
309
- return () => {
310
- live = false;
311
- };
312
- }, [getToken, path, nonce]);
313
- const refresh = useCallback2(() => setNonce((n) => n + 1), []);
314
- return { data, loading, error, refresh };
393
+ const { crm, id = "people", label = "People", type = "person", roles } = options;
394
+ return collectionSection({ crm, type, id, label, ...roles ? { roles } : {} });
315
395
  }
316
396
 
317
397
  // src/ui/admin-dashboard.tsx
318
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
398
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
319
399
  var money = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
320
400
  function DashboardBody({ ctx }) {
321
401
  const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/dashboard");
322
- if (loading) return /* @__PURE__ */ jsx4("div", { className: "wrap page", children: "Loading\u2026" });
323
- if (error || !data) return /* @__PURE__ */ jsxs4("div", { className: "wrap page", children: [
402
+ if (loading) return /* @__PURE__ */ jsx5("div", { className: "wrap page", children: "Loading\u2026" });
403
+ if (error || !data) return /* @__PURE__ */ jsxs5("div", { className: "wrap page", children: [
324
404
  "Couldn\u2019t load the dashboard: ",
325
405
  error
326
406
  ] });
327
407
  const when = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
328
- const Kpi = ({ label, value, sub }) => /* @__PURE__ */ jsxs4("div", { className: "card kpi", children: [
329
- /* @__PURE__ */ jsx4("div", { className: "kpi-value", children: value }),
330
- /* @__PURE__ */ jsx4("div", { className: "kpi-label", children: label }),
331
- sub ? /* @__PURE__ */ jsx4("div", { className: "kpi-sub muted", children: sub }) : null
408
+ const Kpi = ({ label, value, sub }) => /* @__PURE__ */ jsxs5("div", { className: "card kpi", children: [
409
+ /* @__PURE__ */ jsx5("div", { className: "kpi-value", children: value }),
410
+ /* @__PURE__ */ jsx5("div", { className: "kpi-label", children: label }),
411
+ sub ? /* @__PURE__ */ jsx5("div", { className: "kpi-sub muted", children: sub }) : null
332
412
  ] });
333
- return /* @__PURE__ */ jsxs4("div", { className: "wrap page", children: [
334
- /* @__PURE__ */ jsxs4("div", { className: "kpis", children: [
335
- /* @__PURE__ */ jsx4(Kpi, { label: "Applications", value: data.applications.total, sub: `+${data.applications.last7} in 7d` }),
336
- /* @__PURE__ */ jsx4(Kpi, { label: "Upcoming calls", value: data.calls.upcoming, sub: data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : void 0 }),
337
- data.revenue.billingReady ? /* @__PURE__ */ jsx4(Kpi, { label: "Active members", value: data.revenue.activeCount ?? 0 }) : null,
338
- data.revenue.billingReady ? /* @__PURE__ */ jsx4(Kpi, { label: "Annual run rate", value: money(data.revenue.annualRunRateCents ?? 0) }) : null
413
+ return /* @__PURE__ */ jsxs5("div", { className: "wrap page", children: [
414
+ /* @__PURE__ */ jsxs5("div", { className: "kpis", children: [
415
+ /* @__PURE__ */ jsx5(Kpi, { label: "Applications", value: data.applications.total, sub: `+${data.applications.last7} in 7d` }),
416
+ /* @__PURE__ */ jsx5(Kpi, { label: "Upcoming calls", value: data.calls.upcoming, sub: data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : void 0 }),
417
+ data.revenue.billingReady ? /* @__PURE__ */ jsx5(Kpi, { label: "Active members", value: data.revenue.activeCount ?? 0 }) : null,
418
+ data.revenue.billingReady ? /* @__PURE__ */ jsx5(Kpi, { label: "Annual run rate", value: money(data.revenue.annualRunRateCents ?? 0) }) : null
339
419
  ] }),
340
- /* @__PURE__ */ jsxs4("div", { className: "card", children: [
341
- /* @__PURE__ */ jsx4("h3", { children: "Pipeline" }),
342
- /* @__PURE__ */ jsx4("ul", { className: "pipeline-counts", children: Object.entries(data.pipeline).map(([stage, count]) => /* @__PURE__ */ jsxs4("li", { children: [
343
- /* @__PURE__ */ jsx4("span", { className: "stage", children: stage }),
344
- /* @__PURE__ */ jsx4("span", { className: "count", children: count }),
345
- data.pipelineDelta[stage] ? /* @__PURE__ */ jsxs4("span", { className: "delta", children: [
420
+ /* @__PURE__ */ jsxs5("div", { className: "card", children: [
421
+ /* @__PURE__ */ jsx5("h3", { children: "Pipeline" }),
422
+ /* @__PURE__ */ jsx5("ul", { className: "pipeline-counts", children: Object.entries(data.pipeline).map(([stage, count]) => /* @__PURE__ */ jsxs5("li", { children: [
423
+ /* @__PURE__ */ jsx5("span", { className: "stage", children: stage }),
424
+ /* @__PURE__ */ jsx5("span", { className: "count", children: count }),
425
+ data.pipelineDelta[stage] ? /* @__PURE__ */ jsxs5("span", { className: "delta", children: [
346
426
  "+",
347
427
  data.pipelineDelta[stage]
348
428
  ] }) : null
349
429
  ] }, stage)) })
350
430
  ] }),
351
- /* @__PURE__ */ jsxs4("div", { className: "card", children: [
352
- /* @__PURE__ */ jsxs4("h3", { children: [
431
+ /* @__PURE__ */ jsxs5("div", { className: "card", children: [
432
+ /* @__PURE__ */ jsxs5("h3", { children: [
353
433
  "Upcoming calls ",
354
- /* @__PURE__ */ jsxs4("span", { className: "muted", children: [
434
+ /* @__PURE__ */ jsxs5("span", { className: "muted", children: [
355
435
  "(",
356
436
  data.timezone,
357
437
  ")"
358
438
  ] })
359
439
  ] }),
360
- data.agenda.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "muted", children: "No calls scheduled." }) : /* @__PURE__ */ jsx4("ul", { className: "agenda", children: data.agenda.map((m) => /* @__PURE__ */ jsxs4("li", { children: [
361
- /* @__PURE__ */ jsx4("span", { className: "when", children: when(m.startAt) }),
362
- /* @__PURE__ */ jsxs4("span", { className: "who", children: [
440
+ data.agenda.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "muted", children: "No calls scheduled." }) : /* @__PURE__ */ jsx5("ul", { className: "agenda", children: data.agenda.map((m) => /* @__PURE__ */ jsxs5("li", { children: [
441
+ /* @__PURE__ */ jsx5("span", { className: "when", children: when(m.startAt) }),
442
+ /* @__PURE__ */ jsxs5("span", { className: "who", children: [
363
443
  m.name,
364
444
  m.email ? ` \xB7 ${m.email}` : ""
365
445
  ] }),
366
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs4("span", { className: "badge danger", children: [
446
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs5("span", { className: "badge danger", children: [
367
447
  "drift: ",
368
448
  m.drift
369
449
  ] }) : null,
370
- m.meetUrl ? /* @__PURE__ */ jsx4("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null
450
+ m.meetUrl ? /* @__PURE__ */ jsx5("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null
371
451
  ] }, m.id)) })
372
452
  ] })
373
453
  ] });
374
454
  }
375
455
  function dashboardSection(options = {}) {
376
456
  const { id = "dashboard", label = "Dashboard" } = options;
377
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx4(DashboardBody, { ctx }) };
457
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx5(DashboardBody, { ctx }) };
378
458
  }
379
459
 
380
460
  // src/ui/admin-billing.tsx
381
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
461
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
382
462
  var money2 = (cents) => `$${(cents / 100).toLocaleString(void 0, { minimumFractionDigits: 0 })}`;
383
463
  var date = (t) => t ? new Date(t).toLocaleDateString([], { dateStyle: "medium" }) : "\u2014";
384
464
  function BillingBody({ ctx }) {
385
465
  const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/billing");
386
- if (loading) return /* @__PURE__ */ jsx5("div", { className: "wrap page", children: "Loading\u2026" });
387
- if (error || !data) return /* @__PURE__ */ jsxs5("div", { className: "wrap page", children: [
466
+ if (loading) return /* @__PURE__ */ jsx6("div", { className: "wrap page", children: "Loading\u2026" });
467
+ if (error || !data) return /* @__PURE__ */ jsxs6("div", { className: "wrap page", children: [
388
468
  "Couldn\u2019t load billing: ",
389
469
  error
390
470
  ] });
391
- if (!data.billingReady) return /* @__PURE__ */ jsx5("div", { className: "wrap page", children: /* @__PURE__ */ jsx5("div", { className: "card", children: /* @__PURE__ */ jsx5("p", { className: "muted", children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." }) }) });
471
+ if (!data.billingReady) return /* @__PURE__ */ jsx6("div", { className: "wrap page", children: /* @__PURE__ */ jsx6("div", { className: "card", children: /* @__PURE__ */ jsx6("p", { className: "muted", children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." }) }) });
392
472
  const s = data.summary;
393
- return /* @__PURE__ */ jsxs5("div", { className: "wrap page", children: [
394
- data.testMode ? /* @__PURE__ */ jsx5("div", { className: "badge", children: "Stripe test mode" }) : null,
395
- s ? /* @__PURE__ */ jsxs5("div", { className: "kpis", children: [
396
- /* @__PURE__ */ jsxs5("div", { className: "card kpi", children: [
397
- /* @__PURE__ */ jsx5("div", { className: "kpi-value", children: s.activeCount }),
398
- /* @__PURE__ */ jsx5("div", { className: "kpi-label", children: "Active" })
473
+ return /* @__PURE__ */ jsxs6("div", { className: "wrap page", children: [
474
+ data.testMode ? /* @__PURE__ */ jsx6("div", { className: "badge", children: "Stripe test mode" }) : null,
475
+ s ? /* @__PURE__ */ jsxs6("div", { className: "kpis", children: [
476
+ /* @__PURE__ */ jsxs6("div", { className: "card kpi", children: [
477
+ /* @__PURE__ */ jsx6("div", { className: "kpi-value", children: s.activeCount }),
478
+ /* @__PURE__ */ jsx6("div", { className: "kpi-label", children: "Active" })
399
479
  ] }),
400
- /* @__PURE__ */ jsxs5("div", { className: "card kpi", children: [
401
- /* @__PURE__ */ jsx5("div", { className: "kpi-value", children: money2(s.annualizedCents) }),
402
- /* @__PURE__ */ jsx5("div", { className: "kpi-label", children: "Annualized" })
480
+ /* @__PURE__ */ jsxs6("div", { className: "card kpi", children: [
481
+ /* @__PURE__ */ jsx6("div", { className: "kpi-value", children: money2(s.annualizedCents) }),
482
+ /* @__PURE__ */ jsx6("div", { className: "kpi-label", children: "Annualized" })
403
483
  ] }),
404
- /* @__PURE__ */ jsxs5("div", { className: "card kpi", children: [
405
- /* @__PURE__ */ jsx5("div", { className: "kpi-value", children: s.renewingSoonCount }),
406
- /* @__PURE__ */ jsx5("div", { className: "kpi-label", children: "Renewing \u226460d" })
484
+ /* @__PURE__ */ jsxs6("div", { className: "card kpi", children: [
485
+ /* @__PURE__ */ jsx6("div", { className: "kpi-value", children: s.renewingSoonCount }),
486
+ /* @__PURE__ */ jsx6("div", { className: "kpi-label", children: "Renewing \u226460d" })
407
487
  ] }),
408
- /* @__PURE__ */ jsxs5("div", { className: "card kpi", children: [
409
- /* @__PURE__ */ jsx5("div", { className: "kpi-value", children: s.pastDueCount }),
410
- /* @__PURE__ */ jsx5("div", { className: "kpi-label", children: "Past due" })
488
+ /* @__PURE__ */ jsxs6("div", { className: "card kpi", children: [
489
+ /* @__PURE__ */ jsx6("div", { className: "kpi-value", children: s.pastDueCount }),
490
+ /* @__PURE__ */ jsx6("div", { className: "kpi-label", children: "Past due" })
411
491
  ] })
412
492
  ] }) : null,
413
- data.truncated ? /* @__PURE__ */ jsx5("p", { className: "badge danger", children: "Showing the first 100 subscriptions \u2014 the list is truncated." }) : null,
414
- /* @__PURE__ */ jsx5("div", { className: "card", children: /* @__PURE__ */ jsxs5("table", { className: "data-table", children: [
415
- /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
416
- /* @__PURE__ */ jsx5("th", { children: "Name" }),
417
- /* @__PURE__ */ jsx5("th", { children: "Email" }),
418
- /* @__PURE__ */ jsx5("th", { children: "Application" }),
419
- /* @__PURE__ */ jsx5("th", { children: "Subscription" }),
420
- /* @__PURE__ */ jsx5("th", { children: "Amount" }),
421
- /* @__PURE__ */ jsx5("th", { children: "Renews" })
493
+ data.truncated ? /* @__PURE__ */ jsx6("p", { className: "badge danger", children: "Showing the first 100 subscriptions \u2014 the list is truncated." }) : null,
494
+ /* @__PURE__ */ jsx6("div", { className: "card", children: /* @__PURE__ */ jsxs6("table", { className: "data-table", children: [
495
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { children: [
496
+ /* @__PURE__ */ jsx6("th", { children: "Name" }),
497
+ /* @__PURE__ */ jsx6("th", { children: "Email" }),
498
+ /* @__PURE__ */ jsx6("th", { children: "Application" }),
499
+ /* @__PURE__ */ jsx6("th", { children: "Subscription" }),
500
+ /* @__PURE__ */ jsx6("th", { children: "Amount" }),
501
+ /* @__PURE__ */ jsx6("th", { children: "Renews" })
422
502
  ] }) }),
423
- /* @__PURE__ */ jsxs5("tbody", { children: [
424
- data.rows.map((r) => /* @__PURE__ */ jsxs5("tr", { children: [
425
- /* @__PURE__ */ jsx5("td", { children: r.name }),
426
- /* @__PURE__ */ jsx5("td", { children: r.email }),
427
- /* @__PURE__ */ jsx5("td", { children: r.applicationStatus }),
428
- /* @__PURE__ */ jsxs5("td", { children: [
503
+ /* @__PURE__ */ jsxs6("tbody", { children: [
504
+ data.rows.map((r) => /* @__PURE__ */ jsxs6("tr", { children: [
505
+ /* @__PURE__ */ jsx6("td", { children: r.name }),
506
+ /* @__PURE__ */ jsx6("td", { children: r.email }),
507
+ /* @__PURE__ */ jsx6("td", { children: r.applicationStatus }),
508
+ /* @__PURE__ */ jsxs6("td", { children: [
429
509
  r.subscriptionStatus ?? "\u2014",
430
510
  r.cancelAtPeriodEnd ? " (cancelling)" : ""
431
511
  ] }),
432
- /* @__PURE__ */ jsxs5("td", { children: [
512
+ /* @__PURE__ */ jsxs6("td", { children: [
433
513
  money2(r.amountCents),
434
514
  "/",
435
515
  r.interval === "month" ? "mo" : "yr"
436
516
  ] }),
437
- /* @__PURE__ */ jsx5("td", { children: date(r.renewalAt) })
517
+ /* @__PURE__ */ jsx6("td", { children: date(r.renewalAt) })
438
518
  ] }, r.id)),
439
- data.rows.length === 0 ? /* @__PURE__ */ jsx5("tr", { children: /* @__PURE__ */ jsx5("td", { colSpan: 6, className: "muted", children: "No subscriptions yet." }) }) : null
519
+ data.rows.length === 0 ? /* @__PURE__ */ jsx6("tr", { children: /* @__PURE__ */ jsx6("td", { colSpan: 6, className: "muted", children: "No subscriptions yet." }) }) : null
440
520
  ] })
441
521
  ] }) })
442
522
  ] });
443
523
  }
444
524
  function billingSection(options = {}) {
445
525
  const { id = "billing", label = "Billing" } = options;
446
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx5(BillingBody, { ctx }) };
526
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx6(BillingBody, { ctx }) };
447
527
  }
448
528
 
449
529
  // src/ui/admin-email.tsx
450
- import { useState as useState5 } from "react";
451
- import { Button } from "@odla-ai/ui/components";
452
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
530
+ import { useState as useState6 } from "react";
531
+ import { Button as Button2 } from "@odla-ai/ui/components";
532
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
453
533
  function EmailBody({ ctx }) {
454
534
  const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
455
535
  const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
456
- const [draft, setDraft] = useState5(null);
457
- const [busy, setBusy] = useState5(null);
458
- const [note, setNote] = useState5(null);
536
+ const [draft, setDraft] = useState6(null);
537
+ const [busy, setBusy] = useState6(null);
538
+ const [note, setNote] = useState6(null);
459
539
  const data = draft ?? cfg.data;
460
- if (cfg.loading) return /* @__PURE__ */ jsx6("div", { className: "wrap page", children: "Loading\u2026" });
461
- if (cfg.error || !data) return /* @__PURE__ */ jsxs6("div", { className: "wrap page", children: [
540
+ if (cfg.loading) return /* @__PURE__ */ jsx7("div", { className: "wrap page", children: "Loading\u2026" });
541
+ if (cfg.error || !data) return /* @__PURE__ */ jsxs7("div", { className: "wrap page", children: [
462
542
  "Couldn\u2019t load email config: ",
463
543
  cfg.error
464
544
  ] });
@@ -491,76 +571,76 @@ function EmailBody({ ctx }) {
491
571
  setBusy(null);
492
572
  }
493
573
  };
494
- return /* @__PURE__ */ jsxs6("div", { className: "wrap page", children: [
495
- /* @__PURE__ */ jsxs6("div", { className: "card", children: [
496
- /* @__PURE__ */ jsx6("h3", { children: "Delivery" }),
497
- /* @__PURE__ */ jsxs6("p", { className: "muted", children: [
574
+ return /* @__PURE__ */ jsxs7("div", { className: "wrap page", children: [
575
+ /* @__PURE__ */ jsxs7("div", { className: "card", children: [
576
+ /* @__PURE__ */ jsx7("h3", { children: "Delivery" }),
577
+ /* @__PURE__ */ jsxs7("p", { className: "muted", children: [
498
578
  data.envName,
499
579
  " \xB7 ",
500
580
  data.transport,
501
581
  data.fromEmail ? ` \xB7 from ${data.fromEmail}` : ""
502
582
  ] }),
503
- /* @__PURE__ */ jsxs6("label", { children: [
583
+ /* @__PURE__ */ jsxs7("label", { children: [
504
584
  "Notification address",
505
- /* @__PURE__ */ jsx6("input", { value: data.notificationEmail, onChange: (e) => edit({ notificationEmail: e.target.value }) })
585
+ /* @__PURE__ */ jsx7("input", { value: data.notificationEmail, onChange: (e) => edit({ notificationEmail: e.target.value }) })
506
586
  ] }),
507
- /* @__PURE__ */ jsxs6("label", { children: [
587
+ /* @__PURE__ */ jsxs7("label", { children: [
508
588
  "Reply-to",
509
- /* @__PURE__ */ jsx6("input", { value: data.replyTo, onChange: (e) => edit({ replyTo: e.target.value }) })
589
+ /* @__PURE__ */ jsx7("input", { value: data.replyTo, onChange: (e) => edit({ replyTo: e.target.value }) })
510
590
  ] }),
511
- /* @__PURE__ */ jsxs6("label", { children: [
591
+ /* @__PURE__ */ jsxs7("label", { children: [
512
592
  "Debug inbox (non-prod redirect)",
513
- /* @__PURE__ */ jsx6("input", { value: data.debugEmail, onChange: (e) => edit({ debugEmail: e.target.value }) })
593
+ /* @__PURE__ */ jsx7("input", { value: data.debugEmail, onChange: (e) => edit({ debugEmail: e.target.value }) })
514
594
  ] })
515
595
  ] }),
516
- Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs6("div", { className: "card", children: [
517
- /* @__PURE__ */ jsx6("h3", { children: key }),
518
- /* @__PURE__ */ jsxs6("label", { className: "row", children: [
519
- /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
596
+ Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs7("div", { className: "card", children: [
597
+ /* @__PURE__ */ jsx7("h3", { children: key }),
598
+ /* @__PURE__ */ jsxs7("label", { className: "row", children: [
599
+ /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
520
600
  " enabled"
521
601
  ] }),
522
- /* @__PURE__ */ jsxs6("label", { children: [
602
+ /* @__PURE__ */ jsxs7("label", { children: [
523
603
  "Subject",
524
- /* @__PURE__ */ jsx6("input", { value: t.subject, onChange: (e) => editTemplate(key, { subject: e.target.value }) })
604
+ /* @__PURE__ */ jsx7("input", { value: t.subject, onChange: (e) => editTemplate(key, { subject: e.target.value }) })
525
605
  ] }),
526
- /* @__PURE__ */ jsxs6("label", { children: [
606
+ /* @__PURE__ */ jsxs7("label", { children: [
527
607
  "Body",
528
- /* @__PURE__ */ jsx6("textarea", { rows: 5, value: t.text, onChange: (e) => editTemplate(key, { text: e.target.value }) })
608
+ /* @__PURE__ */ jsx7("textarea", { rows: 5, value: t.text, onChange: (e) => editTemplate(key, { text: e.target.value }) })
529
609
  ] }),
530
- /* @__PURE__ */ jsx6(Button, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: "Send test" })
610
+ /* @__PURE__ */ jsx7(Button2, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: "Send test" })
531
611
  ] }, key)),
532
- /* @__PURE__ */ jsxs6("div", { className: "row", children: [
533
- /* @__PURE__ */ jsx6(Button, { disabled: !draft || busy === "save", onClick: () => void save(), children: "Save changes" }),
534
- note ? /* @__PURE__ */ jsx6("span", { className: "muted", children: note }) : null
612
+ /* @__PURE__ */ jsxs7("div", { className: "row", children: [
613
+ /* @__PURE__ */ jsx7(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: "Save changes" }),
614
+ note ? /* @__PURE__ */ jsx7("span", { className: "muted", children: note }) : null
535
615
  ] }),
536
- /* @__PURE__ */ jsxs6("div", { className: "card", children: [
537
- /* @__PURE__ */ jsx6("h3", { children: "Send log" }),
538
- (log.data?.sends ?? []).length === 0 ? /* @__PURE__ */ jsx6("p", { className: "muted", children: "No sends yet." }) : /* @__PURE__ */ jsx6("ul", { className: "log", children: (log.data?.sends ?? []).map((r) => /* @__PURE__ */ jsxs6("li", { children: [
539
- /* @__PURE__ */ jsx6("span", { children: new Date(r.sentAt).toLocaleString() }),
616
+ /* @__PURE__ */ jsxs7("div", { className: "card", children: [
617
+ /* @__PURE__ */ jsx7("h3", { children: "Send log" }),
618
+ (log.data?.sends ?? []).length === 0 ? /* @__PURE__ */ jsx7("p", { className: "muted", children: "No sends yet." }) : /* @__PURE__ */ jsx7("ul", { className: "log", children: (log.data?.sends ?? []).map((r) => /* @__PURE__ */ jsxs7("li", { children: [
619
+ /* @__PURE__ */ jsx7("span", { children: new Date(r.sentAt).toLocaleString() }),
540
620
  " \xB7 ",
541
- /* @__PURE__ */ jsx6("span", { children: r.template }),
621
+ /* @__PURE__ */ jsx7("span", { children: r.template }),
542
622
  " \u2192 ",
543
- /* @__PURE__ */ jsx6("span", { children: r.to }),
544
- r.error ? /* @__PURE__ */ jsx6("span", { className: "badge danger", children: "failed" }) : r.redirected ? /* @__PURE__ */ jsx6("span", { className: "badge", children: "redirected" }) : null
623
+ /* @__PURE__ */ jsx7("span", { children: r.to }),
624
+ r.error ? /* @__PURE__ */ jsx7("span", { className: "badge danger", children: "failed" }) : r.redirected ? /* @__PURE__ */ jsx7("span", { className: "badge", children: "redirected" }) : null
545
625
  ] }, r.id)) })
546
626
  ] })
547
627
  ] });
548
628
  }
549
629
  function emailSection(options = {}) {
550
630
  const { id = "email", label = "Email" } = options;
551
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx6(EmailBody, { ctx }) };
631
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx7(EmailBody, { ctx }) };
552
632
  }
553
633
 
554
634
  // src/ui/admin-meetings.tsx
555
- import { useState as useState6 } from "react";
556
- import { Button as Button2 } from "@odla-ai/ui/components";
557
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
635
+ import { useState as useState7 } from "react";
636
+ import { Button as Button3 } from "@odla-ai/ui/components";
637
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
558
638
  function MeetingsBody({ ctx }) {
559
639
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
560
- const [busy, setBusy] = useState6(null);
561
- const [note, setNote] = useState6(null);
562
- if (loading) return /* @__PURE__ */ jsx7("div", { className: "wrap page", children: "Loading\u2026" });
563
- if (error || !data) return /* @__PURE__ */ jsxs7("div", { className: "wrap page", children: [
640
+ const [busy, setBusy] = useState7(null);
641
+ const [note, setNote] = useState7(null);
642
+ if (loading) return /* @__PURE__ */ jsx8("div", { className: "wrap page", children: "Loading\u2026" });
643
+ if (error || !data) return /* @__PURE__ */ jsxs8("div", { className: "wrap page", children: [
564
644
  "Couldn\u2019t load meetings: ",
565
645
  error
566
646
  ] });
@@ -597,46 +677,125 @@ function MeetingsBody({ ctx }) {
597
677
  setBusy(null);
598
678
  }
599
679
  };
600
- return /* @__PURE__ */ jsxs7("div", { className: "wrap page", children: [
601
- /* @__PURE__ */ jsxs7("h3", { children: [
680
+ return /* @__PURE__ */ jsxs8("div", { className: "wrap page", children: [
681
+ /* @__PURE__ */ jsxs8("h3", { children: [
602
682
  "Agenda ",
603
- /* @__PURE__ */ jsxs7("span", { className: "muted", children: [
683
+ /* @__PURE__ */ jsxs8("span", { className: "muted", children: [
604
684
  "(",
605
685
  data.timezone,
606
686
  ")"
607
687
  ] })
608
688
  ] }),
609
- note ? /* @__PURE__ */ jsx7("p", { className: "muted", children: note }) : null,
610
- data.meetings.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "muted", children: "No meetings." }) : /* @__PURE__ */ jsx7("ul", { className: "agenda", children: data.meetings.map((m) => /* @__PURE__ */ jsxs7("li", { className: m.status === "cancelled" ? "cancelled" : "", children: [
611
- /* @__PURE__ */ jsx7("span", { className: "when", children: when(m.startAt) }),
612
- /* @__PURE__ */ jsx7("span", { className: "who", children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
613
- /* @__PURE__ */ jsx7("span", { className: "badge", children: m.status }),
614
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs7("span", { className: "badge danger", children: [
689
+ note ? /* @__PURE__ */ jsx8("p", { className: "muted", children: note }) : null,
690
+ data.meetings.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "muted", children: "No meetings." }) : /* @__PURE__ */ jsx8("ul", { className: "agenda", children: data.meetings.map((m) => /* @__PURE__ */ jsxs8("li", { className: m.status === "cancelled" ? "cancelled" : "", children: [
691
+ /* @__PURE__ */ jsx8("span", { className: "when", children: when(m.startAt) }),
692
+ /* @__PURE__ */ jsx8("span", { className: "who", children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
693
+ /* @__PURE__ */ jsx8("span", { className: "badge", children: m.status }),
694
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs8("span", { className: "badge danger", children: [
615
695
  "drift: ",
616
696
  m.drift
617
697
  ] }) : null,
618
- m.meetUrl ? /* @__PURE__ */ jsx7("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
619
- m.status === "scheduled" ? /* @__PURE__ */ jsxs7("span", { className: "actions", children: [
620
- /* @__PURE__ */ jsx7(Button2, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => void reschedule(m.id), children: "Reschedule" }),
621
- /* @__PURE__ */ jsx7(Button2, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => void cancel(m.id), children: "Cancel" })
698
+ m.meetUrl ? /* @__PURE__ */ jsx8("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
699
+ m.status === "scheduled" ? /* @__PURE__ */ jsxs8("span", { className: "actions", children: [
700
+ /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => void reschedule(m.id), children: "Reschedule" }),
701
+ /* @__PURE__ */ jsx8(Button3, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => void cancel(m.id), children: "Cancel" })
622
702
  ] }) : null
623
703
  ] }, m.id)) })
624
704
  ] });
625
705
  }
626
706
  function meetingsSection(options = {}) {
627
707
  const { id = "meetings", label = "Meetings" } = options;
628
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx7(MeetingsBody, { ctx }) };
708
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx8(MeetingsBody, { ctx }) };
709
+ }
710
+
711
+ // src/ui/admin-availability.tsx
712
+ import { useState as useState8 } from "react";
713
+ import { Button as Button4 } from "@odla-ai/ui/components";
714
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
715
+ var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
716
+ function AvailabilityBody({ ctx }) {
717
+ const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
718
+ const [draft, setDraft] = useState8(null);
719
+ const [busy, setBusy] = useState8(false);
720
+ const [note, setNote] = useState8(null);
721
+ const [fieldErrors, setFieldErrors] = useState8({});
722
+ const cfg = draft ?? data?.scheduling ?? null;
723
+ if (loading) return /* @__PURE__ */ jsx9("div", { className: "wrap page", children: "Loading\u2026" });
724
+ if (error || !cfg) return /* @__PURE__ */ jsxs9("div", { className: "wrap page", children: [
725
+ "Couldn\u2019t load availability: ",
726
+ error
727
+ ] });
728
+ const edit = (patch) => setDraft({ ...cfg, ...patch });
729
+ const toggleDay = (d) => edit({ days: cfg.days.includes(d) ? cfg.days.filter((x) => x !== d) : [...cfg.days, d].sort() });
730
+ const num = (v, fallback) => Number.isFinite(Number(v)) ? Number(v) : fallback;
731
+ const save = async () => {
732
+ setBusy(true);
733
+ setNote(null);
734
+ setFieldErrors({});
735
+ const token = await ctx.getToken();
736
+ try {
737
+ const r = await fetch("/api/admin/scheduling", {
738
+ method: "PUT",
739
+ headers: { authorization: `Bearer ${token ?? ""}`, "content-type": "application/json" },
740
+ body: JSON.stringify(cfg)
741
+ });
742
+ const body = await r.json().catch(() => ({}));
743
+ if (!r.ok) {
744
+ setFieldErrors(body.errors ?? {});
745
+ setNote(body.error ?? `save failed (${r.status})`);
746
+ return;
747
+ }
748
+ setDraft(null);
749
+ refresh();
750
+ setNote("Saved.");
751
+ } catch (e) {
752
+ setNote(e instanceof Error ? e.message : String(e));
753
+ } finally {
754
+ setBusy(false);
755
+ }
756
+ };
757
+ const field = (key, label, input) => /* @__PURE__ */ jsxs9("label", { children: [
758
+ label,
759
+ input,
760
+ fieldErrors[key] ? /* @__PURE__ */ jsx9("span", { className: "badge danger", children: fieldErrors[key] }) : null
761
+ ] });
762
+ return /* @__PURE__ */ jsx9("div", { className: "wrap page", children: /* @__PURE__ */ jsxs9("div", { className: "card", children: [
763
+ /* @__PURE__ */ jsx9("h3", { children: "Booking availability" }),
764
+ /* @__PURE__ */ jsx9("div", { className: "row", children: DAYS.map((d, i) => /* @__PURE__ */ jsxs9("label", { className: "row", children: [
765
+ /* @__PURE__ */ jsx9("input", { type: "checkbox", checked: cfg.days.includes(i), onChange: () => toggleDay(i) }),
766
+ " ",
767
+ d
768
+ ] }, d)) }),
769
+ fieldErrors.days ? /* @__PURE__ */ jsx9("span", { className: "badge danger", children: fieldErrors.days }) : null,
770
+ field("startHour", "Start hour (0\u201324)", /* @__PURE__ */ jsx9("input", { type: "number", value: cfg.startHour, onChange: (e) => edit({ startHour: num(e.target.value, cfg.startHour) }) })),
771
+ field("endHour", "End hour (0\u201324)", /* @__PURE__ */ jsx9("input", { type: "number", value: cfg.endHour, onChange: (e) => edit({ endHour: num(e.target.value, cfg.endHour) }) })),
772
+ field("slotMinutes", "Slot length (minutes)", /* @__PURE__ */ jsx9("input", { type: "number", value: cfg.slotMinutes, onChange: (e) => edit({ slotMinutes: num(e.target.value, cfg.slotMinutes) }) })),
773
+ field("timezone", "Timezone", /* @__PURE__ */ jsx9("input", { value: cfg.timezone, onChange: (e) => edit({ timezone: e.target.value }) })),
774
+ field("minNoticeHours", "Minimum notice (hours)", /* @__PURE__ */ jsx9("input", { type: "number", value: cfg.minNoticeHours, onChange: (e) => edit({ minNoticeHours: num(e.target.value, cfg.minNoticeHours) }) })),
775
+ field("windowDays", "Booking window (days)", /* @__PURE__ */ jsx9("input", { type: "number", value: cfg.windowDays, onChange: (e) => edit({ windowDays: num(e.target.value, cfg.windowDays) }) })),
776
+ field("summaryTemplate", "Event summary template", /* @__PURE__ */ jsx9("input", { value: cfg.summaryTemplate, onChange: (e) => edit({ summaryTemplate: e.target.value }) })),
777
+ /* @__PURE__ */ jsxs9("div", { className: "row", children: [
778
+ /* @__PURE__ */ jsx9(Button4, { disabled: !draft || busy, onClick: () => void save(), children: "Save availability" }),
779
+ note ? /* @__PURE__ */ jsx9("span", { className: "muted", children: note }) : null
780
+ ] })
781
+ ] }) });
782
+ }
783
+ function availabilitySection(options = {}) {
784
+ const { id = "availability", label = "Availability" } = options;
785
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx9(AvailabilityBody, { ctx }) };
629
786
  }
630
787
 
631
788
  export {
632
789
  ChapterAdmin,
633
- collectionSection,
634
- peopleSection,
635
790
  adminFetch,
636
791
  useAdminResource,
792
+ RecordActions,
793
+ collectionSection,
794
+ peopleSection,
637
795
  dashboardSection,
638
796
  billingSection,
639
797
  emailSection,
640
- meetingsSection
798
+ meetingsSection,
799
+ availabilitySection
641
800
  };
642
- //# sourceMappingURL=chunk-HB2HUJCW.js.map
801
+ //# sourceMappingURL=chunk-K6BDIFCJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/admin.tsx","../src/ui/chrome.tsx","../src/ui/admin-people.tsx","../src/ui/admin-record-actions.tsx","../src/ui/admin-api.ts","../src/ui/admin-dashboard.tsx","../src/ui/admin-billing.tsx","../src/ui/admin-email.tsx","../src/ui/admin-meetings.tsx","../src/ui/admin-availability.tsx"],"sourcesContent":["// ChapterAdmin — the whole admin console for a chapter/hub, owned by the package\n// so no site re-wires (or re-bugs) it: the Clerk publishable-key fetch\n// (/api/config), the themed sign-in with the post-sign-in redirect DERIVED from\n// basePath (so it never bounces to \"/\"), the odla-db admins-allowlist check\n// (/api/me), and a TopBar shell (chrome.tsx) that renders the caller's sections.\nimport { useEffect, useMemo, useState } from \"react\";\nimport { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth, clerkAppearanceFromTokens } from \"@odla-ai/auth-clerk\";\nimport { CrmClient } from \"@odla-ai/crm\";\nimport { S, Gate, AdminShell } from \"./chrome.js\";\nimport type { AdminSection, Brand } from \"./chrome.js\";\n\nexport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\n/** Props for {@link ChapterAdmin}. */\nexport interface ChapterAdminProps {\n /** The admin sections to render (nav label + body). */\n sections: AdminSection[];\n /** Admin mount path. The sign-in redirect + section routing derive from it,\n * so the \"bounce to home after sign-in\" bug is impossible. Default \"/admin\". */\n basePath?: string;\n /** Wordmark shown in the gate + top bar. */\n brand?: Brand;\n /** CRM route mount. Default \"/api/crm\". */\n crmBasePath?: string;\n /** Origin for /api/config and /api/me. Default same origin. */\n apiBase?: string;\n}\n\n// After sign-in: build a token-bound CrmClient, confirm the account is an\n// allowlisted admin (/api/me), then render the shell.\nfunction Authed(props: {\n sections: AdminSection[];\n basePath: string;\n brand: Brand;\n crmBasePath: string;\n apiBase: string;\n}) {\n const { sections, basePath, brand, crmBasePath, apiBase } = props;\n const { getToken, signOut } = useClerkAuth();\n const client = useMemo(\n () =>\n new CrmClient({\n basePath: crmBasePath,\n headers: async (): Promise<Record<string, string>> => {\n const t = await getToken();\n return t ? { authorization: `Bearer ${t}` } : {};\n },\n }),\n [getToken, crmBasePath],\n );\n const [state, setState] = useState<{ status: \"checking\" | \"ok\" | \"denied\"; email?: string | null }>({\n status: \"checking\",\n });\n\n useEffect(() => {\n let live = true;\n void (async () => {\n try {\n const t = await getToken();\n const res = await fetch(`${apiBase}/api/me`, { headers: t ? { authorization: `Bearer ${t}` } : {} });\n const body = (await res.json().catch(() => ({}))) as { authorized?: boolean; email?: string | null };\n if (live) setState({ status: body.authorized ? \"ok\" : \"denied\", email: body.email ?? null });\n } catch {\n if (live) setState({ status: \"denied\" });\n }\n })();\n return () => {\n live = false;\n };\n }, [getToken, apiBase]);\n\n if (state.status === \"checking\") {\n return (\n <Gate brand={brand}>\n <p style={S.muted}>Checking access…</p>\n </Gate>\n );\n }\n if (state.status === \"denied\") {\n return (\n <Gate brand={brand}>\n <div className=\"card\" style={S.card}>\n <h2 style={{ marginTop: 0 }}>Not authorized</h2>\n <p style={S.muted}>\n {state.email ? `${state.email} isn't` : \"This account isn't\"} on the admin list. Ask an existing admin to add\n you in odla Studio.\n </p>\n <button className=\"btn secondary\" onClick={() => signOut()} style={{ marginTop: 12 }}>\n Sign out\n </button>\n </div>\n </Gate>\n );\n }\n return (\n <AdminShell\n sections={sections}\n basePath={basePath}\n brand={brand}\n client={client}\n getToken={getToken}\n signOut={signOut}\n email={state.email ?? null}\n />\n );\n}\n\n/**\n * The whole admin console for a chapter/hub: fetches the Clerk publishable key\n * (/api/config), gates on sign-in with the post-sign-in redirect derived from\n * `basePath`, checks the odla-db admins allowlist (/api/me), and renders a\n * TopBar shell over the caller's `sections`. The gate/shell/redirect are owned\n * here so no site re-wires (or re-bugs) them.\n */\nexport function ChapterAdmin(props: ChapterAdminProps) {\n const sections = props.sections;\n const basePath = props.basePath ?? \"/admin\";\n const brand = props.brand ?? { name: \"Admin\" };\n const crmBasePath = props.crmBasePath ?? \"/api/crm\";\n const apiBase = props.apiBase ?? \"\";\n\n const [pk, setPk] = useState<string | null | undefined>(undefined);\n useEffect(() => {\n let live = true;\n void (async () => {\n try {\n const res = await fetch(`${apiBase}/api/config`);\n const body = (await res.json()) as { clerkPublishableKey?: string | null };\n if (live) setPk(body.clerkPublishableKey ?? null);\n } catch {\n if (live) setPk(null);\n }\n })();\n return () => {\n live = false;\n };\n }, [apiBase]);\n\n if (pk === undefined) {\n return (\n <Gate brand={brand}>\n <p style={S.muted}>Loading…</p>\n </Gate>\n );\n }\n if (!pk) {\n return (\n <Gate brand={brand}>\n <div className=\"card\" style={S.card}>\n <h2 style={{ marginTop: 0 }}>Sign-in not configured</h2>\n <p style={S.muted}>No Clerk publishable key is set for this environment yet.</p>\n </div>\n </Gate>\n );\n }\n return (\n <ClerkGate publishableKey={pk} appearance={clerkAppearanceFromTokens()} afterSignOutUrl=\"/\">\n <SignedOut>\n <Gate brand={brand} tagline=\"Admin sign-in — invite only\">\n <SignIn routing=\"hash\" forceRedirectUrl={basePath} signUpForceRedirectUrl={basePath} />\n </Gate>\n </SignedOut>\n <SignedIn>\n <Authed sections={sections} basePath={basePath} brand={brand} crmBasePath={crmBasePath} apiBase={apiBase} />\n </SignedIn>\n </ClerkGate>\n );\n}\n","// Shared chrome for ChapterAdmin: the section contract, the centered gate\n// screen, the signed-in TopBar shell, and the inline style tokens. Split out of\n// admin.tsx to stay under the repo's per-file LOC cap. Uses only @odla-ai/ui\n// classes + --ui-* tokens, so a consumer needs nothing beyond the odla-ui sheet.\nimport { useCallback, useEffect, useState } from \"react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { TopBar, TopBarLink } from \"@odla-ai/ui/components\";\nimport type { CrmClient } from \"@odla-ai/crm\";\n\n/** What each admin section receives. `client` is a CrmClient bound to the\n * signed-in admin's bearer token; `navigate` switches sections. */\nexport interface AdminSectionContext {\n client: CrmClient;\n getToken: () => Promise<string | null>;\n /** Switch to another section by id (e.g. Overview \"jump in\" buttons). */\n navigate: (sectionId: string) => void;\n}\n\n/** One admin-console section: a nav label and a body renderer. */\nexport interface AdminSection {\n id: string;\n label: string;\n render: (ctx: AdminSectionContext) => ReactNode;\n}\n\n/** Wordmark shown in the gate and top bar. `name` is the plain-text identity (and\n * the badge fallback); `wordmark` is an optional rich display node for brands\n * whose typography can't be a plain string (e.g. an ampersand wrapped to render\n * upright), and `badge` overrides the glyph. */\nexport type Brand = { name: string; badge?: string; wordmark?: ReactNode };\n\n/** The badge glyph: the explicit `badge`, else the first 3 letters of `name`. */\nexport const badgeText = (brand: Brand): string => brand.badge ?? brand.name.slice(0, 3).toUpperCase();\n\n/** The display node: the rich `wordmark` if given, else the plain `name`. */\nexport const brandDisplay = (brand: Brand): ReactNode => brand.wordmark ?? brand.name;\n\n/** Inline style tokens (keyed to --ui-* custom properties). */\nexport const S: Record<string, CSSProperties> = {\n gate: {\n minHeight: \"100vh\",\n display: \"grid\",\n placeItems: \"center\",\n padding: 24,\n background:\n \"radial-gradient(900px 480px at 50% -8%, var(--ui-accent-soft), transparent 70%),\" +\n \" radial-gradient(700px 500px at 110% 10%, var(--ui-good-soft), transparent 60%)\",\n },\n gateInner: { width: \"100%\", maxWidth: 400, display: \"flex\", flexDirection: \"column\", alignItems: \"center\" },\n brandBox: { textAlign: \"center\", marginBottom: 22 },\n badge: {\n width: 52,\n height: 52,\n margin: \"0 auto 14px\",\n display: \"grid\",\n placeItems: \"center\",\n fontSize: 15,\n fontWeight: 700,\n color: \"var(--ui-on-accent)\",\n background: \"linear-gradient(135deg, var(--ui-accent), var(--ui-accent-strong))\",\n borderRadius: 14,\n boxShadow: \"0 10px 28px var(--ui-accent-soft)\",\n },\n badgeSm: {\n width: 26,\n height: 26,\n display: \"grid\",\n placeItems: \"center\",\n fontSize: 10,\n fontWeight: 700,\n color: \"var(--ui-on-accent)\",\n background: \"linear-gradient(135deg, var(--ui-accent), var(--ui-accent-strong))\",\n borderRadius: 7,\n marginRight: 9,\n },\n h1: { margin: 0, fontSize: 28, letterSpacing: \"-0.02em\", fontWeight: 700 },\n muted: { color: \"var(--ui-text-muted)\" },\n role: {\n fontFamily: \"var(--ui-font-mono)\",\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: \"0.04em\",\n padding: \"2px 8px\",\n borderRadius: 999,\n background: \"var(--ui-accent-soft)\",\n border: \"1px solid var(--ui-accent)\",\n color: \"var(--ui-accent-strong)\",\n },\n whoami: { display: \"flex\", alignItems: \"center\", gap: 8, fontSize: 12 },\n // Sections own their own width/padding (e.g. a .wrap container).\n main: { minHeight: \"calc(100vh - 61px)\" },\n card: { width: \"100%\", textAlign: \"center\" },\n};\n\n/** Centered gate screen used for loading / sign-in / denied / not-configured. */\nexport function Gate(props: { brand: Brand; tagline?: string; children: ReactNode }) {\n const { brand, tagline, children } = props;\n return (\n <div style={S.gate}>\n <div style={S.gateInner}>\n <div style={S.brandBox}>\n <div style={S.badge}>{badgeText(brand)}</div>\n <h1 style={S.h1}>{brandDisplay(brand)}</h1>\n {tagline ? <p style={{ ...S.muted, margin: \"8px 0 0\", fontSize: 14 }}>{tagline}</p> : null}\n </div>\n {children}\n </div>\n </div>\n );\n}\n\n/** The signed-in shell: a TopBar of the caller's sections + the active body. */\nexport function AdminShell(props: {\n sections: AdminSection[];\n basePath: string;\n brand: Brand;\n client: CrmClient;\n getToken: () => Promise<string | null>;\n signOut: () => void;\n email: string | null;\n}) {\n const { sections, basePath, brand, client, getToken, signOut, email } = props;\n\n const sectionFromPath = useCallback(() => {\n const fallback = sections[0]?.id ?? \"\";\n if (typeof window === \"undefined\") return fallback;\n const rest = window.location.pathname.slice(basePath.length).replace(/^\\//, \"\");\n const id = rest.split(\"/\")[0] ?? \"\";\n return sections.some((s) => s.id === id) ? id : fallback;\n }, [sections, basePath]);\n\n const [section, setSection] = useState(sectionFromPath);\n\n const go = useCallback(\n (id: string) => {\n window.history.pushState(null, \"\", `${basePath}/${id}`);\n setSection(id);\n },\n [basePath],\n );\n\n useEffect(() => {\n const onPop = () => setSection(sectionFromPath());\n window.addEventListener(\"popstate\", onPop);\n return () => window.removeEventListener(\"popstate\", onPop);\n }, [sectionFromPath]);\n\n const active = sections.find((s) => s.id === section) ?? sections[0];\n\n return (\n <>\n <TopBar>\n <a className=\"topbar-brand\" href=\"/\" style={{ display: \"inline-flex\", alignItems: \"center\" }}>\n <span style={S.badgeSm}>{badgeText(brand)}</span>\n {brandDisplay(brand)}\n </a>\n <nav className=\"topbar-nav\" aria-label=\"Admin navigation\">\n {sections.map((s) => (\n <TopBarLink key={s.id} active={section === s.id} onClick={() => go(s.id)}>\n {s.label}\n </TopBarLink>\n ))}\n </nav>\n <div className=\"topbar-spacer\" />\n <div className=\"topbar-actions\">\n <span style={S.whoami}>\n <span style={S.role}>admin</span>\n {email ? <span style={S.muted}>{email}</span> : null}\n </span>\n <button className=\"btn secondary mini\" onClick={() => signOut()}>\n Sign out\n </button>\n </div>\n </TopBar>\n <main className=\"shell-main\" style={S.main}>\n {active ? active.render({ client, getToken, navigate: go }) : null}\n </main>\n </>\n );\n}\n","// People/CRM admin section — the core admin workflow, assembled from the shipped\n// @odla-ai/crm/ui kit against the /api/crm/* routes the worker already mounts.\n// It's a FACTORY: peopleSection({ crm }) → an AdminSection you drop into\n// ChapterAdmin's `sections`. Every lifecycle action (edit fields, move pipeline\n// stage, tag) goes through the token-bound CrmClient — never a direct db write,\n// so the package's operational invariants hold (G7).\nimport { useState } from \"react\";\nimport { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from \"@odla-ai/crm/ui\";\nimport type { Crm, CrmClient, CrmRecord } from \"@odla-ai/crm\";\nimport type { AdminSection } from \"./chrome.js\";\nimport { RecordActions } from \"./admin-record-actions.js\";\n\nconst DEFAULT_ROLES = [\"provisional\", \"member\", \"admin\"] as const;\n\nfunction PeopleBody(props: { crm: Crm; client: CrmClient; type: string; getToken: () => Promise<string | null>; roles: readonly string[]; lifecycle: boolean }) {\n const { crm, client, type, getToken, roles, lifecycle } = props;\n // The crm/ui hooks type their client against @odla-ai/crm's ui-entry bundled\n // declaration; ctx.client is the core-entry declaration. Same runtime class,\n // but their private field makes the two .d.ts copies nominally distinct — cast\n // to the param type the hooks expect.\n const hookClient = client as unknown as Parameters<typeof useCrmQuery>[0];\n const query = useCrmQuery(hookClient, type);\n const [openId, setOpenId] = useState<string | null>(null);\n const record = useCrmRecord(hookClient, openId);\n const [saving, setSaving] = useState(false);\n\n const saveFields = async (input: Record<string, unknown>) => {\n if (!openId) return;\n setSaving(true);\n try {\n await client.updateRecord(openId, { input });\n record.refresh();\n query.refresh();\n } finally {\n setSaving(false);\n }\n };\n const moveStage = async (to: string) => {\n if (!openId) return;\n await client.setStage(openId, to);\n record.refresh();\n query.refresh();\n };\n const addTag = async (tag: string) => {\n if (openId) {\n await client.addTag(openId, tag);\n record.refresh();\n }\n };\n const removeTag = async (tag: string) => {\n if (openId) {\n await client.removeTag(openId, tag);\n record.refresh();\n }\n };\n\n return (\n <div className=\"wrap\">\n <CrmList crm={crm} type={type} query={query} onOpenRecord={(r: CrmRecord) => setOpenId(r.id)} />\n {record.detail ? (\n <div className=\"record-drawer\">\n <RecordPanel\n crm={crm}\n detail={record.detail}\n onSaveFields={saveFields}\n onMoveStage={moveStage}\n onAddTag={addTag}\n onRemoveTag={removeTag}\n saving={saving}\n />\n {lifecycle ? (\n <RecordActions\n getToken={getToken}\n record={record.detail.record}\n roles={roles}\n onChanged={() => {\n record.refresh();\n query.refresh();\n }}\n />\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/** Options for {@link peopleSection}. */\nexport interface PeopleSectionOptions {\n /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */\n crm: Crm;\n /** Section id / route segment. Default \"people\". */\n id?: string;\n /** Nav label. Default \"People\". */\n label?: string;\n /** The record type to list. Default \"person\". */\n type?: string;\n}\n\n/** Options for {@link collectionSection}. */\nexport interface CollectionSectionOptions {\n /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */\n crm: Crm;\n /** The CRM record type to list (e.g. `\"person\"`, `\"deal\"`, `\"investment\"`). */\n type: string;\n /** Section id / route segment. Defaults to `type`. */\n id?: string;\n /** Nav label. Defaults to the type's plural label from the CRM config. */\n label?: string;\n /** Show the lifecycle action panel (role change / approve / refund) on an open\n * record — for application-backed people. Default `true` for `type: \"person\"`,\n * `false` otherwise (a `deal`/`investment` has no approve/refund). */\n lifecycle?: boolean;\n /** Role ladder for the role selector. Default `[\"provisional\",\"member\",\"admin\"]`\n * — pass your `chapter.auth.ladder` if you use a custom ladder. */\n roles?: readonly string[];\n}\n\n/**\n * Build a CRM collection admin section for ANY declared record type: a\n * server-driven records table plus a facet-composed detail drawer (fields,\n * pipeline stage, tags) over the token-bound CrmClient, and — for people — a\n * lifecycle action panel (role change, approve, refund) wired to the\n * `/api/admin/*` routes. This is the generic kit — call it once per collection a\n * site runs (members, deals, investments), and each renders with the same UX.\n * CRM edits go through `/api/crm/*` and lifecycle actions through `/api/admin/*`,\n * never a direct db write, so the invariants hold.\n */\nexport function collectionSection(options: CollectionSectionOptions): AdminSection {\n const { crm, type } = options;\n let fallbackLabel = type;\n try {\n fallbackLabel = crm.type(type).labelPlural ?? crm.type(type).label ?? type;\n } catch {\n // Unknown type — the section still renders; the CRM route reports the error.\n }\n const { id = type, label = fallbackLabel, lifecycle = type === \"person\", roles = DEFAULT_ROLES } = options;\n return { id, label, render: (ctx) => <PeopleBody crm={crm} client={ctx.client} type={type} getToken={ctx.getToken} roles={roles} lifecycle={lifecycle} /> };\n}\n\n/**\n * Build the People/CRM admin section — {@link collectionSection} preset to the\n * `person` type (so the lifecycle actions are on). New collections use\n * `collectionSection`.\n */\nexport function peopleSection(options: PeopleSectionOptions & { roles?: readonly string[] }): AdminSection {\n const { crm, id = \"people\", label = \"People\", type = \"person\", roles } = options;\n return collectionSection({ crm, type, id, label, ...(roles ? { roles } : {}) });\n}\n","// The lifecycle action panel shown beside an open person record: role change\n// (POST /api/admin/people/role), approve (POST /api/admin/applications/:id/\n// approve) and refund (POST .../refund). Each control appears only when the\n// record carries what it needs — a linked Clerk account for role, an\n// applicationId for approve/refund — so a bare CRM record shows nothing to act on.\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { adminFetch } from \"./admin-api.js\";\nimport type { CrmRecord } from \"@odla-ai/crm\";\n\ninterface Access {\n role: string;\n superAdmin: boolean;\n}\n\n/** Props for {@link RecordActions}. */\nexport interface RecordActionsProps {\n getToken: () => Promise<string | null>;\n record: CrmRecord;\n /** The role ladder offered by the role selector. Default provisional→admin. */\n roles: readonly string[];\n /** Called after any action mutates state, so the caller can refresh. */\n onChanged: () => void;\n}\n\n/** The role/approve/refund controls for one person record. Renders only the\n * actions the record supports. */\nexport function RecordActions({ getToken, record, roles, onChanged }: RecordActionsProps) {\n const userId = typeof record.clerkUserId === \"string\" ? record.clerkUserId : null;\n const applicationId = typeof record.fields?.applicationId === \"string\" ? (record.fields.applicationId as string) : null;\n const [access, setAccess] = useState<Access | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n const [note, setNote] = useState<string | null>(null);\n\n useEffect(() => {\n if (!userId) {\n setAccess(null);\n return;\n }\n let live = true;\n adminFetch<Access>(getToken, `/api/admin/people/access?userId=${encodeURIComponent(userId)}`)\n .then((a) => live && setAccess(a))\n .catch(() => live && setAccess(null));\n return () => {\n live = false;\n };\n }, [getToken, userId]);\n\n const act = async (key: string, run: () => Promise<unknown>, ok: string) => {\n setBusy(key);\n setNote(null);\n try {\n await run();\n setNote(ok);\n onChanged();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n const setRole = (role: string) =>\n act(\"role\", () => adminFetch(getToken, \"/api/admin/people/role\", { method: \"POST\", body: JSON.stringify({ userId, role }) }), `Role set to ${role}.`);\n const approve = () =>\n act(\"approve\", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: \"POST\" }), \"Approved.\");\n const refund = () =>\n act(\"refund\", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: \"POST\" }), \"Refund issued.\");\n\n if (!userId && !applicationId) return null;\n return (\n <div className=\"card record-actions\">\n <h4>Access &amp; lifecycle</h4>\n {userId ? (\n <label className=\"row\">\n Role\n <select\n value={access?.role ?? \"\"}\n disabled={busy === \"role\" || access?.superAdmin}\n onChange={(e) => void setRole((e.target as HTMLSelectElement).value)}\n >\n {roles.map((r) => (\n <option key={r} value={r}>{r}</option>\n ))}\n </select>\n {access?.superAdmin ? <span className=\"muted\">super-admin (managed in Studio)</span> : null}\n </label>\n ) : (\n <p className=\"muted\">No linked account yet — role is set once they sign in.</p>\n )}\n {applicationId ? (\n <div className=\"row\">\n <Button variant=\"secondary\" mini disabled={busy === \"approve\"} onClick={() => void approve()}>Approve</Button>\n <Button variant=\"danger\" mini disabled={busy === \"refund\"} onClick={() => void refund()}>Refund</Button>\n </div>\n ) : null}\n {note ? <p className=\"muted\">{note}</p> : null}\n </div>\n );\n}\n","// Token-bound access to the /api/admin/* routes for the console sections. Every\n// section fetches with the signed-in admin's bearer token (from ChapterAdmin's\n// getToken) — the worker gates each route with verifyUser + isAdmin, so the UI\n// carries no privilege of its own.\nimport { useCallback, useEffect, useState } from \"react\";\n\n/** Fetch a JSON `/api/admin/*` route with the admin bearer token. Throws on a\n * non-2xx (the body's `error`, else the status). */\nexport async function adminFetch<T = unknown>(\n getToken: () => Promise<string | null>,\n path: string,\n init?: RequestInit,\n): Promise<T> {\n const token = await getToken();\n const headers = new Headers(init?.headers);\n if (token) headers.set(\"authorization\", `Bearer ${token}`);\n if (init?.body) headers.set(\"content-type\", \"application/json\");\n const res = await fetch(path, { ...init, headers });\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) throw new Error(typeof body.error === \"string\" ? body.error : `request failed (${res.status})`);\n return body as T;\n}\n\n/** The state of one admin resource load. */\nexport interface AdminResource<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n /** Re-fetch (e.g. after a mutation). */\n refresh: () => void;\n}\n\n/** Load a `/api/admin/*` GET route into React state, re-fetching on `refresh()`\n * or when `path` changes. */\nexport function useAdminResource<T = unknown>(getToken: () => Promise<string | null>, path: string): AdminResource<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [nonce, setNonce] = useState(0);\n\n useEffect(() => {\n let live = true;\n setLoading(true);\n setError(null);\n adminFetch<T>(getToken, path)\n .then((d) => live && setData(d))\n .catch((e: unknown) => live && setError(e instanceof Error ? e.message : String(e)))\n .finally(() => live && setLoading(false));\n return () => {\n live = false;\n };\n }, [getToken, path, nonce]);\n\n const refresh = useCallback(() => setNonce((n) => n + 1), []);\n return { data, loading, error, refresh };\n}\n","// Dashboard section: the operational overview from GET /api/admin/dashboard —\n// application flow, pipeline stage counts, the upcoming call agenda, and (when\n// billing is wired) live revenue. Renders the response the route already shapes,\n// so a hub gets the identical overview per chapter.\nimport { useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface DashboardData {\n applications: { total: number; last7: number; last30: number };\n pipeline: Record<string, number>;\n pipelineDelta: Record<string, number>;\n calls: { upcoming: number; needsAttention: number };\n agenda: Array<{ id: string; startAt: number; name: string; email: string | null; drift: string; meetUrl: string | null }>;\n timezone: string;\n revenue: { billingReady: boolean; activeCount?: number; annualRunRateCents?: number };\n}\n\nconst money = (cents: number): string => `$${Math.round(cents / 100).toLocaleString()}`;\n\nfunction DashboardBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error } = useAdminResource<DashboardData>(ctx.getToken, \"/api/admin/dashboard\");\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load the dashboard: {error}</div>;\n\n const when = (t: number): string => new Date(t).toLocaleString([], { dateStyle: \"medium\", timeStyle: \"short\" });\n const Kpi = ({ label, value, sub }: { label: string; value: string | number; sub?: string }) => (\n <div className=\"card kpi\">\n <div className=\"kpi-value\">{value}</div>\n <div className=\"kpi-label\">{label}</div>\n {sub ? <div className=\"kpi-sub muted\">{sub}</div> : null}\n </div>\n );\n return (\n <div className=\"wrap page\">\n <div className=\"kpis\">\n <Kpi label=\"Applications\" value={data.applications.total} sub={`+${data.applications.last7} in 7d`} />\n <Kpi label=\"Upcoming calls\" value={data.calls.upcoming} sub={data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : undefined} />\n {data.revenue.billingReady ? <Kpi label=\"Active members\" value={data.revenue.activeCount ?? 0} /> : null}\n {data.revenue.billingReady ? <Kpi label=\"Annual run rate\" value={money(data.revenue.annualRunRateCents ?? 0)} /> : null}\n </div>\n\n <div className=\"card\">\n <h3>Pipeline</h3>\n <ul className=\"pipeline-counts\">\n {Object.entries(data.pipeline).map(([stage, count]) => (\n <li key={stage}>\n <span className=\"stage\">{stage}</span>\n <span className=\"count\">{count}</span>\n {data.pipelineDelta[stage] ? <span className=\"delta\">+{data.pipelineDelta[stage]}</span> : null}\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"card\">\n <h3>Upcoming calls <span className=\"muted\">({data.timezone})</span></h3>\n {data.agenda.length === 0 ? (\n <p className=\"muted\">No calls scheduled.</p>\n ) : (\n <ul className=\"agenda\">\n {data.agenda.map((m) => (\n <li key={m.id}>\n <span className=\"when\">{when(m.startAt)}</span>\n <span className=\"who\">{m.name}{m.email ? ` · ${m.email}` : \"\"}</span>\n {m.drift && m.drift !== \"none\" ? <span className=\"badge danger\">drift: {m.drift}</span> : null}\n {m.meetUrl ? <a href={m.meetUrl} target=\"_blank\" rel=\"noreferrer\">Meet</a> : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n\n/** Options for {@link dashboardSection}. */\nexport interface DashboardSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Dashboard admin section over `GET /api/admin/dashboard`. Drop the\n * result into `ChapterAdmin`'s `sections`. */\nexport function dashboardSection(options: DashboardSectionOptions = {}): AdminSection {\n const { id = \"dashboard\", label = \"Dashboard\" } = options;\n return { id, label, render: (ctx) => <DashboardBody ctx={ctx} /> };\n}\n","// Billing section: applications joined with live Stripe subscription state from\n// GET /api/admin/billing. Degrades to a \"not configured\" note when the route\n// reports billingReady:false (no stripe_secret_key vaulted), and surfaces the\n// route's `truncated` flag rather than pretending the list is complete.\nimport { useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface BillingRow {\n id: string;\n name: string;\n email: string;\n applicationStatus: string;\n subscriptionStatus: string | null;\n cancelAtPeriodEnd: boolean;\n amountCents: number;\n interval: string;\n renewalAt: number | null;\n}\ninterface BillingData {\n billingReady: boolean;\n testMode?: boolean;\n truncated?: boolean;\n rows: BillingRow[];\n summary: { activeCount: number; annualizedCents: number; renewingSoonCount: number; pastDueCount: number; canceledCount: number; refundedCount: number } | null;\n}\n\nconst money = (cents: number): string => `$${(cents / 100).toLocaleString(undefined, { minimumFractionDigits: 0 })}`;\nconst date = (t: number | null): string => (t ? new Date(t).toLocaleDateString([], { dateStyle: \"medium\" }) : \"—\");\n\nfunction BillingBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error } = useAdminResource<BillingData>(ctx.getToken, \"/api/admin/billing\");\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load billing: {error}</div>;\n if (!data.billingReady) return <div className=\"wrap page\"><div className=\"card\"><p className=\"muted\">Billing isn’t configured — no Stripe key is vaulted for this group.</p></div></div>;\n\n const s = data.summary;\n return (\n <div className=\"wrap page\">\n {data.testMode ? <div className=\"badge\">Stripe test mode</div> : null}\n {s ? (\n <div className=\"kpis\">\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.activeCount}</div><div className=\"kpi-label\">Active</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{money(s.annualizedCents)}</div><div className=\"kpi-label\">Annualized</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.renewingSoonCount}</div><div className=\"kpi-label\">Renewing ≤60d</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.pastDueCount}</div><div className=\"kpi-label\">Past due</div></div>\n </div>\n ) : null}\n {data.truncated ? <p className=\"badge danger\">Showing the first 100 subscriptions — the list is truncated.</p> : null}\n <div className=\"card\">\n <table className=\"data-table\">\n <thead>\n <tr><th>Name</th><th>Email</th><th>Application</th><th>Subscription</th><th>Amount</th><th>Renews</th></tr>\n </thead>\n <tbody>\n {data.rows.map((r) => (\n <tr key={r.id}>\n <td>{r.name}</td>\n <td>{r.email}</td>\n <td>{r.applicationStatus}</td>\n <td>{r.subscriptionStatus ?? \"—\"}{r.cancelAtPeriodEnd ? \" (cancelling)\" : \"\"}</td>\n <td>{money(r.amountCents)}/{r.interval === \"month\" ? \"mo\" : \"yr\"}</td>\n <td>{date(r.renewalAt)}</td>\n </tr>\n ))}\n {data.rows.length === 0 ? <tr><td colSpan={6} className=\"muted\">No subscriptions yet.</td></tr> : null}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n\n/** Options for {@link billingSection}. */\nexport interface BillingSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Billing admin section over `GET /api/admin/billing`. Renders a\n * \"not configured\" note when the group has no Stripe key vaulted. */\nexport function billingSection(options: BillingSectionOptions = {}): AdminSection {\n const { id = \"billing\", label = \"Billing\" } = options;\n return { id, label, render: (ctx) => <BillingBody ctx={ctx} /> };\n}\n","// Email section: the owner-editable email config (templates + addresses) from\n// GET/PUT /api/admin/group/email, the send audit from GET /api/admin/email/log,\n// and a POST /api/admin/email/test button that exercises the real transport.\n// Everything through the token-bound /api/admin routes — no direct db write.\nimport { useState } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { adminFetch, useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface Template { subject: string; text: string; enabled: boolean }\ninterface EmailConfig {\n emailTemplates: Record<string, Template>;\n replyTo: string;\n notificationEmail: string;\n debugEmail: string;\n commitmentText: string;\n normsText: string;\n transport: string;\n fromEmail: string | null;\n envName: string;\n}\ninterface LogRow { id: string; template: string; to: string; subject: string; transport: string; redirected: boolean; error: string | null; sentAt: number }\n\nfunction EmailBody({ ctx }: { ctx: AdminSectionContext }) {\n const cfg = useAdminResource<EmailConfig>(ctx.getToken, \"/api/admin/group/email\");\n const log = useAdminResource<{ sends: LogRow[] }>(ctx.getToken, \"/api/admin/email/log\");\n const [draft, setDraft] = useState<EmailConfig | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n const [note, setNote] = useState<string | null>(null);\n\n const data = draft ?? cfg.data;\n if (cfg.loading) return <div className=\"wrap page\">Loading…</div>;\n if (cfg.error || !data) return <div className=\"wrap page\">Couldn’t load email config: {cfg.error}</div>;\n\n const edit = (patch: Partial<EmailConfig>) => setDraft({ ...data, ...patch });\n const editTemplate = (key: string, patch: Partial<Template>) =>\n edit({ emailTemplates: { ...data.emailTemplates, [key]: { ...data.emailTemplates[key], ...patch } as Template } });\n\n const save = async () => {\n setBusy(\"save\");\n setNote(null);\n try {\n await adminFetch(ctx.getToken, \"/api/admin/group/email\", { method: \"PUT\", body: JSON.stringify(data) });\n setDraft(null);\n cfg.refresh();\n setNote(\"Saved.\");\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n const test = async (template: string) => {\n setBusy(`test:${template}`);\n setNote(null);\n try {\n const r = await adminFetch<{ ok: boolean; to: string; redirected: boolean }>(ctx.getToken, \"/api/admin/email/test\", { method: \"POST\", body: JSON.stringify({ template }) });\n setNote(r.ok ? `Sent ${template} to ${r.to}${r.redirected ? \" (dev-redirected)\" : \"\"}.` : `Test send did not deliver.`);\n log.refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n\n return (\n <div className=\"wrap page\">\n <div className=\"card\">\n <h3>Delivery</h3>\n <p className=\"muted\">{data.envName} · {data.transport}{data.fromEmail ? ` · from ${data.fromEmail}` : \"\"}</p>\n <label>Notification address<input value={data.notificationEmail} onChange={(e) => edit({ notificationEmail: (e.target as HTMLInputElement).value })} /></label>\n <label>Reply-to<input value={data.replyTo} onChange={(e) => edit({ replyTo: (e.target as HTMLInputElement).value })} /></label>\n <label>Debug inbox (non-prod redirect)<input value={data.debugEmail} onChange={(e) => edit({ debugEmail: (e.target as HTMLInputElement).value })} /></label>\n </div>\n\n {Object.entries(data.emailTemplates).map(([key, t]) => (\n <div className=\"card\" key={key}>\n <h3>{key}</h3>\n <label className=\"row\"><input type=\"checkbox\" checked={t.enabled} onChange={(e) => editTemplate(key, { enabled: (e.target as HTMLInputElement).checked })} /> enabled</label>\n <label>Subject<input value={t.subject} onChange={(e) => editTemplate(key, { subject: (e.target as HTMLInputElement).value })} /></label>\n <label>Body<textarea rows={5} value={t.text} onChange={(e) => editTemplate(key, { text: (e.target as HTMLTextAreaElement).value })} /></label>\n <Button variant=\"secondary\" mini disabled={busy === `test:${key}`} onClick={() => void test(key)}>Send test</Button>\n </div>\n ))}\n\n <div className=\"row\">\n <Button disabled={!draft || busy === \"save\"} onClick={() => void save()}>Save changes</Button>\n {note ? <span className=\"muted\">{note}</span> : null}\n </div>\n\n <div className=\"card\">\n <h3>Send log</h3>\n {(log.data?.sends ?? []).length === 0 ? <p className=\"muted\">No sends yet.</p> : (\n <ul className=\"log\">\n {(log.data?.sends ?? []).map((r) => (\n <li key={r.id}>\n <span>{new Date(r.sentAt).toLocaleString()}</span> · <span>{r.template}</span> → <span>{r.to}</span>\n {r.error ? <span className=\"badge danger\">failed</span> : r.redirected ? <span className=\"badge\">redirected</span> : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n\n/** Options for {@link emailSection}. */\nexport interface EmailSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Email admin section: template + address editor over\n * `GET/PUT /api/admin/group/email`, the send log, and per-template test sends. */\nexport function emailSection(options: EmailSectionOptions = {}): AdminSection {\n const { id = \"email\", label = \"Email\" } = options;\n return { id, label, render: (ctx) => <EmailBody ctx={ctx} /> };\n}\n","// Meetings section: the reconciled agenda from GET /api/admin/meetings, with the\n// reschedule and cancel actions wired to POST /api/admin/meetings/:id/{...}.\n// Drift against live Google state is flagged, never auto-adopted.\nimport { useState } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { adminFetch, useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface MeetingRow {\n id: string;\n startAt: number;\n endAt: number;\n status: string;\n drift: string;\n meetUrl: string | null;\n htmlLink: string | null;\n applicant: { name: string; email: string; status: string } | null;\n}\n\nfunction MeetingsBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error, refresh } = useAdminResource<{ meetings: MeetingRow[]; timezone: string }>(ctx.getToken, \"/api/admin/meetings?all=1\");\n const [busy, setBusy] = useState<string | null>(null);\n const [note, setNote] = useState<string | null>(null);\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load meetings: {error}</div>;\n\n const when = (t: number): string => new Date(t).toLocaleString([], { dateStyle: \"medium\", timeStyle: \"short\" });\n const cancel = async (id: string) => {\n if (!globalThis.confirm?.(\"Cancel this call? Google notifies the attendee.\")) return;\n setBusy(id);\n setNote(null);\n try {\n await adminFetch(ctx.getToken, `/api/admin/meetings/${id}/cancel`, { method: \"POST\" });\n refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n const reschedule = async (id: string) => {\n const input = globalThis.prompt?.(\"New start time (ISO or 'YYYY-MM-DD HH:MM'):\");\n if (!input) return;\n const startAt = Date.parse(input);\n if (!Number.isFinite(startAt)) {\n setNote(\"Couldn’t parse that time.\");\n return;\n }\n setBusy(id);\n setNote(null);\n try {\n await adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: \"POST\", body: JSON.stringify({ startAt }) });\n refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n\n return (\n <div className=\"wrap page\">\n <h3>Agenda <span className=\"muted\">({data.timezone})</span></h3>\n {note ? <p className=\"muted\">{note}</p> : null}\n {data.meetings.length === 0 ? <p className=\"muted\">No meetings.</p> : (\n <ul className=\"agenda\">\n {data.meetings.map((m) => (\n <li key={m.id} className={m.status === \"cancelled\" ? \"cancelled\" : \"\"}>\n <span className=\"when\">{when(m.startAt)}</span>\n <span className=\"who\">{m.applicant ? `${m.applicant.name} · ${m.applicant.email}` : \"(unknown)\"}</span>\n <span className=\"badge\">{m.status}</span>\n {m.drift && m.drift !== \"none\" ? <span className=\"badge danger\">drift: {m.drift}</span> : null}\n {m.meetUrl ? <a href={m.meetUrl} target=\"_blank\" rel=\"noreferrer\">Meet</a> : null}\n {m.status === \"scheduled\" ? (\n <span className=\"actions\">\n <Button variant=\"secondary\" mini disabled={busy === m.id} onClick={() => void reschedule(m.id)}>Reschedule</Button>\n <Button variant=\"danger\" mini disabled={busy === m.id} onClick={() => void cancel(m.id)}>Cancel</Button>\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n/** Options for {@link meetingsSection}. */\nexport interface MeetingsSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Meetings admin section: the reconciled agenda over\n * `GET /api/admin/meetings`, with reschedule/cancel actions. */\nexport function meetingsSection(options: MeetingsSectionOptions = {}): AdminSection {\n const { id = \"meetings\", label = \"Meetings\" } = options;\n return { id, label, render: (ctx) => <MeetingsBody ctx={ctx} /> };\n}\n","// Availability section: the owner-editable booking rules from GET/PUT\n// /api/admin/scheduling. A form over the resolved scheduling config; the PUT\n// returns field-keyed validation messages, which are surfaced inline.\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface Scheduling {\n slotMinutes: number;\n days: number[];\n startHour: number;\n endHour: number;\n timezone: string;\n minNoticeHours: number;\n windowDays: number;\n summaryTemplate: string;\n}\nconst DAYS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n\nfunction AvailabilityBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error, refresh } = useAdminResource<{ scheduling: Scheduling }>(ctx.getToken, \"/api/admin/scheduling\");\n const [draft, setDraft] = useState<Scheduling | null>(null);\n const [busy, setBusy] = useState(false);\n const [note, setNote] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});\n\n const cfg = draft ?? data?.scheduling ?? null;\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !cfg) return <div className=\"wrap page\">Couldn’t load availability: {error}</div>;\n\n const edit = (patch: Partial<Scheduling>) => setDraft({ ...cfg, ...patch });\n const toggleDay = (d: number) => edit({ days: cfg.days.includes(d) ? cfg.days.filter((x) => x !== d) : [...cfg.days, d].sort() });\n const num = (v: string, fallback: number) => (Number.isFinite(Number(v)) ? Number(v) : fallback);\n\n const save = async () => {\n setBusy(true);\n setNote(null);\n setFieldErrors({});\n // Direct fetch (not adminFetch) so a 400 gives us BOTH the top-level error and\n // the route's field-keyed `errors` map in one request.\n const token = await ctx.getToken();\n try {\n const r = await fetch(\"/api/admin/scheduling\", {\n method: \"PUT\",\n headers: { authorization: `Bearer ${token ?? \"\"}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(cfg),\n });\n const body = (await r.json().catch(() => ({}))) as { error?: string; errors?: Record<string, string> };\n if (!r.ok) {\n setFieldErrors(body.errors ?? {});\n setNote(body.error ?? `save failed (${r.status})`);\n return;\n }\n setDraft(null);\n refresh();\n setNote(\"Saved.\");\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(false);\n }\n };\n\n const field = (key: keyof Scheduling, label: string, input: ReactNode) => (\n <label>\n {label}\n {input}\n {fieldErrors[key] ? <span className=\"badge danger\">{fieldErrors[key]}</span> : null}\n </label>\n );\n\n return (\n <div className=\"wrap page\">\n <div className=\"card\">\n <h3>Booking availability</h3>\n <div className=\"row\">\n {DAYS.map((d, i) => (\n <label key={d} className=\"row\">\n <input type=\"checkbox\" checked={cfg.days.includes(i)} onChange={() => toggleDay(i)} /> {d}\n </label>\n ))}\n </div>\n {fieldErrors.days ? <span className=\"badge danger\">{fieldErrors.days}</span> : null}\n {field(\"startHour\", \"Start hour (0–24)\", <input type=\"number\" value={cfg.startHour} onChange={(e) => edit({ startHour: num((e.target as HTMLInputElement).value, cfg.startHour) })} />)}\n {field(\"endHour\", \"End hour (0–24)\", <input type=\"number\" value={cfg.endHour} onChange={(e) => edit({ endHour: num((e.target as HTMLInputElement).value, cfg.endHour) })} />)}\n {field(\"slotMinutes\", \"Slot length (minutes)\", <input type=\"number\" value={cfg.slotMinutes} onChange={(e) => edit({ slotMinutes: num((e.target as HTMLInputElement).value, cfg.slotMinutes) })} />)}\n {field(\"timezone\", \"Timezone\", <input value={cfg.timezone} onChange={(e) => edit({ timezone: (e.target as HTMLInputElement).value })} />)}\n {field(\"minNoticeHours\", \"Minimum notice (hours)\", <input type=\"number\" value={cfg.minNoticeHours} onChange={(e) => edit({ minNoticeHours: num((e.target as HTMLInputElement).value, cfg.minNoticeHours) })} />)}\n {field(\"windowDays\", \"Booking window (days)\", <input type=\"number\" value={cfg.windowDays} onChange={(e) => edit({ windowDays: num((e.target as HTMLInputElement).value, cfg.windowDays) })} />)}\n {field(\"summaryTemplate\", \"Event summary template\", <input value={cfg.summaryTemplate} onChange={(e) => edit({ summaryTemplate: (e.target as HTMLInputElement).value })} />)}\n <div className=\"row\">\n <Button disabled={!draft || busy} onClick={() => void save()}>Save availability</Button>\n {note ? <span className=\"muted\">{note}</span> : null}\n </div>\n </div>\n </div>\n );\n}\n\n/** Options for {@link availabilitySection}. */\nexport interface AvailabilitySectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Availability admin section: the booking-rules editor over\n * `GET/PUT /api/admin/scheduling`, with field-keyed validation surfaced inline. */\nexport function availabilitySection(options: AvailabilitySectionOptions = {}): AdminSection {\n const { id = \"availability\", label = \"Availability\" } = options;\n return { id, label, render: (ctx) => <AvailabilityBody ctx={ctx} /> };\n}\n"],"mappings":";AAKA,SAAS,aAAAA,YAAW,SAAS,YAAAC,iBAAgB;AAC7C,SAAS,WAAW,UAAU,WAAW,QAAQ,cAAc,iCAAiC;AAChG,SAAS,iBAAiB;;;ACH1B,SAAS,aAAa,WAAW,gBAAgB;AAEjD,SAAS,QAAQ,kBAAkB;AA8F3B,SAkDJ,UAjDM,KADF;AApED,IAAM,YAAY,CAAC,UAAyB,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,CAAC,EAAE,YAAY;AAG9F,IAAM,eAAe,CAAC,UAA4B,MAAM,YAAY,MAAM;AAG1E,IAAM,IAAmC;AAAA,EAC9C,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,YACE;AAAA,EAEJ;AAAA,EACA,WAAW,EAAE,OAAO,QAAQ,UAAU,KAAK,SAAS,QAAQ,eAAe,UAAU,YAAY,SAAS;AAAA,EAC1G,UAAU,EAAE,WAAW,UAAU,cAAc,GAAG;AAAA,EAClD,OAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA,EACA,SAAS;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,aAAa;AAAA,EACf;AAAA,EACA,IAAI,EAAE,QAAQ,GAAG,UAAU,IAAI,eAAe,WAAW,YAAY,IAAI;AAAA,EACzE,OAAO,EAAE,OAAO,uBAAuB;AAAA,EACvC,MAAM;AAAA,IACJ,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,eAAe;AAAA,IACf,eAAe;AAAA,IACf,SAAS;AAAA,IACT,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,OAAO;AAAA,EACT;AAAA,EACA,QAAQ,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,UAAU,GAAG;AAAA;AAAA,EAEtE,MAAM,EAAE,WAAW,qBAAqB;AAAA,EACxC,MAAM,EAAE,OAAO,QAAQ,WAAW,SAAS;AAC7C;AAGO,SAAS,KAAK,OAAgE;AACnF,QAAM,EAAE,OAAO,SAAS,SAAS,IAAI;AACrC,SACE,oBAAC,SAAI,OAAO,EAAE,MACZ,+BAAC,SAAI,OAAO,EAAE,WACZ;AAAA,yBAAC,SAAI,OAAO,EAAE,UACZ;AAAA,0BAAC,SAAI,OAAO,EAAE,OAAQ,oBAAU,KAAK,GAAE;AAAA,MACvC,oBAAC,QAAG,OAAO,EAAE,IAAK,uBAAa,KAAK,GAAE;AAAA,MACrC,UAAU,oBAAC,OAAE,OAAO,EAAE,GAAG,EAAE,OAAO,QAAQ,WAAW,UAAU,GAAG,GAAI,mBAAQ,IAAO;AAAA,OACxF;AAAA,IACC;AAAA,KACH,GACF;AAEJ;AAGO,SAAS,WAAW,OAQxB;AACD,QAAM,EAAE,UAAU,UAAU,OAAO,QAAQ,UAAU,SAAS,MAAM,IAAI;AAExE,QAAM,kBAAkB,YAAY,MAAM;AACxC,UAAM,WAAW,SAAS,CAAC,GAAG,MAAM;AACpC,QAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,UAAM,OAAO,OAAO,SAAS,SAAS,MAAM,SAAS,MAAM,EAAE,QAAQ,OAAO,EAAE;AAC9E,UAAM,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AACjC,WAAO,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,KAAK;AAAA,EAClD,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,eAAe;AAEtD,QAAM,KAAK;AAAA,IACT,CAAC,OAAe;AACd,aAAO,QAAQ,UAAU,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,EAAE;AACtD,iBAAW,EAAE;AAAA,IACf;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,YAAU,MAAM;AACd,UAAM,QAAQ,MAAM,WAAW,gBAAgB,CAAC;AAChD,WAAO,iBAAiB,YAAY,KAAK;AACzC,WAAO,MAAM,OAAO,oBAAoB,YAAY,KAAK;AAAA,EAC3D,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,KAAK,SAAS,CAAC;AAEnE,SACE,iCACE;AAAA,yBAAC,UACC;AAAA,2BAAC,OAAE,WAAU,gBAAe,MAAK,KAAI,OAAO,EAAE,SAAS,eAAe,YAAY,SAAS,GACzF;AAAA,4BAAC,UAAK,OAAO,EAAE,SAAU,oBAAU,KAAK,GAAE;AAAA,QACzC,aAAa,KAAK;AAAA,SACrB;AAAA,MACA,oBAAC,SAAI,WAAU,cAAa,cAAW,oBACpC,mBAAS,IAAI,CAAC,MACb,oBAAC,cAAsB,QAAQ,YAAY,EAAE,IAAI,SAAS,MAAM,GAAG,EAAE,EAAE,GACpE,YAAE,SADY,EAAE,EAEnB,CACD,GACH;AAAA,MACA,oBAAC,SAAI,WAAU,iBAAgB;AAAA,MAC/B,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,UAAK,OAAO,EAAE,QACb;AAAA,8BAAC,UAAK,OAAO,EAAE,MAAM,mBAAK;AAAA,UACzB,QAAQ,oBAAC,UAAK,OAAO,EAAE,OAAQ,iBAAM,IAAU;AAAA,WAClD;AAAA,QACA,oBAAC,YAAO,WAAU,sBAAqB,SAAS,MAAM,QAAQ,GAAG,sBAEjE;AAAA,SACF;AAAA,OACF;AAAA,IACA,oBAAC,UAAK,WAAU,cAAa,OAAO,EAAE,MACnC,mBAAS,OAAO,OAAO,EAAE,QAAQ,UAAU,UAAU,GAAG,CAAC,IAAI,MAChE;AAAA,KACF;AAEJ;;;ADzGQ,gBAAAC,MASE,QAAAC,aATF;AA5CR,SAAS,OAAO,OAMb;AACD,QAAM,EAAE,UAAU,UAAU,OAAO,aAAa,QAAQ,IAAI;AAC5D,QAAM,EAAE,UAAU,QAAQ,IAAI,aAAa;AAC3C,QAAM,SAAS;AAAA,IACb,MACE,IAAI,UAAU;AAAA,MACZ,UAAU;AAAA,MACV,SAAS,YAA6C;AACpD,cAAM,IAAI,MAAM,SAAS;AACzB,eAAO,IAAI,EAAE,eAAe,UAAU,CAAC,GAAG,IAAI,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,IACH,CAAC,UAAU,WAAW;AAAA,EACxB;AACA,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA0E;AAAA,IAClG,QAAQ;AAAA,EACV,CAAC;AAED,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO;AACX,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,IAAI,MAAM,SAAS;AACzB,cAAM,MAAM,MAAM,MAAM,GAAG,OAAO,WAAW,EAAE,SAAS,IAAI,EAAE,eAAe,UAAU,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AACnG,cAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAI,KAAM,UAAS,EAAE,QAAQ,KAAK,aAAa,OAAO,UAAU,OAAO,KAAK,SAAS,KAAK,CAAC;AAAA,MAC7F,QAAQ;AACN,YAAI,KAAM,UAAS,EAAE,QAAQ,SAAS,CAAC;AAAA,MACzC;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,UAAU,OAAO,CAAC;AAEtB,MAAI,MAAM,WAAW,YAAY;AAC/B,WACE,gBAAAH,KAAC,QAAK,OACJ,0BAAAA,KAAC,OAAE,OAAO,EAAE,OAAO,mCAAgB,GACrC;AAAA,EAEJ;AACA,MAAI,MAAM,WAAW,UAAU;AAC7B,WACE,gBAAAA,KAAC,QAAK,OACJ,0BAAAC,MAAC,SAAI,WAAU,QAAO,OAAO,EAAE,MAC7B;AAAA,sBAAAD,KAAC,QAAG,OAAO,EAAE,WAAW,EAAE,GAAG,4BAAc;AAAA,MAC3C,gBAAAC,MAAC,OAAE,OAAO,EAAE,OACT;AAAA,cAAM,QAAQ,GAAG,MAAM,KAAK,WAAW;AAAA,QAAqB;AAAA,SAE/D;AAAA,MACA,gBAAAD,KAAC,YAAO,WAAU,iBAAgB,SAAS,MAAM,QAAQ,GAAG,OAAO,EAAE,WAAW,GAAG,GAAG,sBAEtF;AAAA,OACF,GACF;AAAA,EAEJ;AACA,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,MAAM,SAAS;AAAA;AAAA,EACxB;AAEJ;AASO,SAAS,aAAa,OAA0B;AACrD,QAAM,WAAW,MAAM;AACvB,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,QAAQ,MAAM,SAAS,EAAE,MAAM,QAAQ;AAC7C,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,UAAU,MAAM,WAAW;AAEjC,QAAM,CAAC,IAAI,KAAK,IAAIE,UAAoC,MAAS;AACjE,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO;AACX,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,OAAO,aAAa;AAC/C,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,YAAI,KAAM,OAAM,KAAK,uBAAuB,IAAI;AAAA,MAClD,QAAQ;AACN,YAAI,KAAM,OAAM,IAAI;AAAA,MACtB;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,MAAI,OAAO,QAAW;AACpB,WACE,gBAAAH,KAAC,QAAK,OACJ,0BAAAA,KAAC,OAAE,OAAO,EAAE,OAAO,2BAAQ,GAC7B;AAAA,EAEJ;AACA,MAAI,CAAC,IAAI;AACP,WACE,gBAAAA,KAAC,QAAK,OACJ,0BAAAC,MAAC,SAAI,WAAU,QAAO,OAAO,EAAE,MAC7B;AAAA,sBAAAD,KAAC,QAAG,OAAO,EAAE,WAAW,EAAE,GAAG,oCAAsB;AAAA,MACnD,gBAAAA,KAAC,OAAE,OAAO,EAAE,OAAO,uEAAyD;AAAA,OAC9E,GACF;AAAA,EAEJ;AACA,SACE,gBAAAC,MAAC,aAAU,gBAAgB,IAAI,YAAY,0BAA0B,GAAG,iBAAgB,KACtF;AAAA,oBAAAD,KAAC,aACC,0BAAAA,KAAC,QAAK,OAAc,SAAQ,oCAC1B,0BAAAA,KAAC,UAAO,SAAQ,QAAO,kBAAkB,UAAU,wBAAwB,UAAU,GACvF,GACF;AAAA,IACA,gBAAAA,KAAC,YACC,0BAAAA,KAAC,UAAO,UAAoB,UAAoB,OAAc,aAA0B,SAAkB,GAC5G;AAAA,KACF;AAEJ;;;AEjKA,SAAS,YAAAI,iBAAgB;AACzB,SAAS,SAAS,aAAa,aAAa,oBAAoB;;;ACFhE,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;AACpC,SAAS,cAAc;;;ACFvB,SAAS,eAAAC,cAAa,aAAAC,YAAW,YAAAC,iBAAgB;AAIjD,eAAsB,WACpB,UACA,MACA,MACY;AACZ,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;AACzC,MAAI,MAAO,SAAQ,IAAI,iBAAiB,UAAU,KAAK,EAAE;AACzD,MAAI,MAAM,KAAM,SAAQ,IAAI,gBAAgB,kBAAkB;AAC9D,QAAM,MAAM,MAAM,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;AAClD,QAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,mBAAmB,IAAI,MAAM,GAAG;AAC3G,SAAO;AACT;AAaO,SAAS,iBAA8B,UAAwC,MAAgC;AACpH,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAmB,IAAI;AAC/C,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAwB,IAAI;AACtD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,CAAC;AAEpC,EAAAD,WAAU,MAAM;AACd,QAAI,OAAO;AACX,eAAW,IAAI;AACf,aAAS,IAAI;AACb,eAAc,UAAU,IAAI,EACzB,KAAK,CAAC,MAAM,QAAQ,QAAQ,CAAC,CAAC,EAC9B,MAAM,CAAC,MAAe,QAAQ,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,CAAC,EAClF,QAAQ,MAAM,QAAQ,WAAW,KAAK,CAAC;AAC1C,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,UAAU,MAAM,KAAK,CAAC;AAE1B,QAAM,UAAUD,aAAY,MAAM,SAAS,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5D,SAAO,EAAE,MAAM,SAAS,OAAO,QAAQ;AACzC;;;ADgBM,gBAAAG,MAEE,QAAAC,aAFF;AA5CC,SAAS,cAAc,EAAE,UAAU,QAAQ,OAAO,UAAU,GAAuB;AACxF,QAAM,SAAS,OAAO,OAAO,gBAAgB,WAAW,OAAO,cAAc;AAC7E,QAAM,gBAAgB,OAAO,OAAO,QAAQ,kBAAkB,WAAY,OAAO,OAAO,gBAA2B;AACnH,QAAM,CAAC,QAAQ,SAAS,IAAIC,UAAwB,IAAI;AACxD,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AACpD,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AAEpD,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,gBAAU,IAAI;AACd;AAAA,IACF;AACA,QAAI,OAAO;AACX,eAAmB,UAAU,mCAAmC,mBAAmB,MAAM,CAAC,EAAE,EACzF,KAAK,CAAC,MAAM,QAAQ,UAAU,CAAC,CAAC,EAChC,MAAM,MAAM,QAAQ,UAAU,IAAI,CAAC;AACtC,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,UAAU,MAAM,CAAC;AAErB,QAAM,MAAM,OAAO,KAAa,KAA6B,OAAe;AAC1E,YAAQ,GAAG;AACX,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,IAAI;AACV,cAAQ,EAAE;AACV,gBAAU;AAAA,IACZ,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,QAAM,UAAU,CAAC,SACf,IAAI,QAAQ,MAAM,WAAW,UAAU,0BAA0B,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC,GAAG,eAAe,IAAI,GAAG;AACtJ,QAAM,UAAU,MACd,IAAI,WAAW,MAAM,WAAW,UAAU,2BAA2B,aAAa,YAAY,EAAE,QAAQ,OAAO,CAAC,GAAG,WAAW;AAChI,QAAM,SAAS,MACb,IAAI,UAAU,MAAM,WAAW,UAAU,2BAA2B,aAAa,WAAW,EAAE,QAAQ,OAAO,CAAC,GAAG,gBAAgB;AAEnI,MAAI,CAAC,UAAU,CAAC,cAAe,QAAO;AACtC,SACE,gBAAAF,MAAC,SAAI,WAAU,uBACb;AAAA,oBAAAD,KAAC,QAAG,gCAAsB;AAAA,IACzB,SACC,gBAAAC,MAAC,WAAM,WAAU,OAAM;AAAA;AAAA,MAErB,gBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,QAAQ,QAAQ;AAAA,UACvB,UAAU,SAAS,UAAU,QAAQ;AAAA,UACrC,UAAU,CAAC,MAAM,KAAK,QAAS,EAAE,OAA6B,KAAK;AAAA,UAElE,gBAAM,IAAI,CAAC,MACV,gBAAAA,KAAC,YAAe,OAAO,GAAI,eAAd,CAAgB,CAC9B;AAAA;AAAA,MACH;AAAA,MACC,QAAQ,aAAa,gBAAAA,KAAC,UAAK,WAAU,SAAQ,6CAA+B,IAAU;AAAA,OACzF,IAEA,gBAAAA,KAAC,OAAE,WAAU,SAAQ,yEAAsD;AAAA,IAE5E,gBACC,gBAAAC,MAAC,SAAI,WAAU,OACb;AAAA,sBAAAD,KAAC,UAAO,SAAQ,aAAY,MAAI,MAAC,UAAU,SAAS,WAAW,SAAS,MAAM,KAAK,QAAQ,GAAG,qBAAO;AAAA,MACrG,gBAAAA,KAAC,UAAO,SAAQ,UAAS,MAAI,MAAC,UAAU,SAAS,UAAU,SAAS,MAAM,KAAK,OAAO,GAAG,oBAAM;AAAA,OACjG,IACE;AAAA,IACH,OAAO,gBAAAA,KAAC,OAAE,WAAU,SAAS,gBAAK,IAAO;AAAA,KAC5C;AAEJ;;;ADxCM,gBAAAI,MAEE,QAAAC,aAFF;AA9CN,IAAM,gBAAgB,CAAC,eAAe,UAAU,OAAO;AAEvD,SAAS,WAAW,OAA4I;AAC9J,QAAM,EAAE,KAAK,QAAQ,MAAM,UAAU,OAAO,UAAU,IAAI;AAK1D,QAAM,aAAa;AACnB,QAAM,QAAQ,YAAY,YAAY,IAAI;AAC1C,QAAM,CAAC,QAAQ,SAAS,IAAIC,UAAwB,IAAI;AACxD,QAAM,SAAS,aAAa,YAAY,MAAM;AAC9C,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,KAAK;AAE1C,QAAM,aAAa,OAAO,UAAmC;AAC3D,QAAI,CAAC,OAAQ;AACb,cAAU,IAAI;AACd,QAAI;AACF,YAAM,OAAO,aAAa,QAAQ,EAAE,MAAM,CAAC;AAC3C,aAAO,QAAQ;AACf,YAAM,QAAQ;AAAA,IAChB,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AACA,QAAM,YAAY,OAAO,OAAe;AACtC,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,SAAS,QAAQ,EAAE;AAChC,WAAO,QAAQ;AACf,UAAM,QAAQ;AAAA,EAChB;AACA,QAAM,SAAS,OAAO,QAAgB;AACpC,QAAI,QAAQ;AACV,YAAM,OAAO,OAAO,QAAQ,GAAG;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACA,QAAM,YAAY,OAAO,QAAgB;AACvC,QAAI,QAAQ;AACV,YAAM,OAAO,UAAU,QAAQ,GAAG;AAClC,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,SACE,gBAAAD,MAAC,SAAI,WAAU,QACb;AAAA,oBAAAD,KAAC,WAAQ,KAAU,MAAY,OAAc,cAAc,CAAC,MAAiB,UAAU,EAAE,EAAE,GAAG;AAAA,IAC7F,OAAO,SACN,gBAAAC,MAAC,SAAI,WAAU,iBACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,QAAQ,OAAO;AAAA,UACf,cAAc;AAAA,UACd,aAAa;AAAA,UACb,UAAU;AAAA,UACV,aAAa;AAAA,UACb;AAAA;AAAA,MACF;AAAA,MACC,YACC,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,QAAQ,OAAO,OAAO;AAAA,UACtB;AAAA,UACA,WAAW,MAAM;AACf,mBAAO,QAAQ;AACf,kBAAM,QAAQ;AAAA,UAChB;AAAA;AAAA,MACF,IACE;AAAA,OACN,IACE;AAAA,KACN;AAEJ;AA2CO,SAAS,kBAAkB,SAAiD;AACjF,QAAM,EAAE,KAAK,KAAK,IAAI;AACtB,MAAI,gBAAgB;AACpB,MAAI;AACF,oBAAgB,IAAI,KAAK,IAAI,EAAE,eAAe,IAAI,KAAK,IAAI,EAAE,SAAS;AAAA,EACxE,QAAQ;AAAA,EAER;AACA,QAAM,EAAE,KAAK,MAAM,QAAQ,eAAe,YAAY,SAAS,UAAU,QAAQ,cAAc,IAAI;AACnG,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAA,KAAC,cAAW,KAAU,QAAQ,IAAI,QAAQ,MAAY,UAAU,IAAI,UAAU,OAAc,WAAsB,EAAG;AAC5J;AAOO,SAAS,cAAc,SAA6E;AACzG,QAAM,EAAE,KAAK,KAAK,UAAU,QAAQ,UAAU,OAAO,UAAU,MAAM,IAAI;AACzE,SAAO,kBAAkB,EAAE,KAAK,MAAM,IAAI,OAAO,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAChF;;;AG/HsB,gBAAAG,MACO,QAAAC,aADP;AAJtB,IAAM,QAAQ,CAAC,UAA0B,IAAI,KAAK,MAAM,QAAQ,GAAG,EAAE,eAAe,CAAC;AAErF,SAAS,cAAc,EAAE,IAAI,GAAiC;AAC5D,QAAM,EAAE,MAAM,SAAS,MAAM,IAAI,iBAAgC,IAAI,UAAU,sBAAsB;AACrG,MAAI,QAAS,QAAO,gBAAAD,KAAC,SAAI,WAAU,aAAY,2BAAQ;AACvD,MAAI,SAAS,CAAC,KAAM,QAAO,gBAAAC,MAAC,SAAI,WAAU,aAAY;AAAA;AAAA,IAA8B;AAAA,KAAM;AAE1F,QAAM,OAAO,CAAC,MAAsB,IAAI,KAAK,CAAC,EAAE,eAAe,CAAC,GAAG,EAAE,WAAW,UAAU,WAAW,QAAQ,CAAC;AAC9G,QAAM,MAAM,CAAC,EAAE,OAAO,OAAO,IAAI,MAC/B,gBAAAA,MAAC,SAAI,WAAU,YACb;AAAA,oBAAAD,KAAC,SAAI,WAAU,aAAa,iBAAM;AAAA,IAClC,gBAAAA,KAAC,SAAI,WAAU,aAAa,iBAAM;AAAA,IACjC,MAAM,gBAAAA,KAAC,SAAI,WAAU,iBAAiB,eAAI,IAAS;AAAA,KACtD;AAEF,SACE,gBAAAC,MAAC,SAAI,WAAU,aACb;AAAA,oBAAAA,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAD,KAAC,OAAI,OAAM,gBAAe,OAAO,KAAK,aAAa,OAAO,KAAK,IAAI,KAAK,aAAa,KAAK,UAAU;AAAA,MACpG,gBAAAA,KAAC,OAAI,OAAM,kBAAiB,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,MAAM,iBAAiB,GAAG,KAAK,MAAM,cAAc,oBAAoB,QAAW;AAAA,MACnJ,KAAK,QAAQ,eAAe,gBAAAA,KAAC,OAAI,OAAM,kBAAiB,OAAO,KAAK,QAAQ,eAAe,GAAG,IAAK;AAAA,MACnG,KAAK,QAAQ,eAAe,gBAAAA,KAAC,OAAI,OAAM,mBAAkB,OAAO,MAAM,KAAK,QAAQ,sBAAsB,CAAC,GAAG,IAAK;AAAA,OACrH;AAAA,IAEA,gBAAAC,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAD,KAAC,QAAG,sBAAQ;AAAA,MACZ,gBAAAA,KAAC,QAAG,WAAU,mBACX,iBAAO,QAAQ,KAAK,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,MAC/C,gBAAAC,MAAC,QACC;AAAA,wBAAAD,KAAC,UAAK,WAAU,SAAS,iBAAM;AAAA,QAC/B,gBAAAA,KAAC,UAAK,WAAU,SAAS,iBAAM;AAAA,QAC9B,KAAK,cAAc,KAAK,IAAI,gBAAAC,MAAC,UAAK,WAAU,SAAQ;AAAA;AAAA,UAAE,KAAK,cAAc,KAAK;AAAA,WAAE,IAAU;AAAA,WAHpF,KAIT,CACD,GACH;AAAA,OACF;AAAA,IAEA,gBAAAA,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAA,MAAC,QAAG;AAAA;AAAA,QAAe,gBAAAA,MAAC,UAAK,WAAU,SAAQ;AAAA;AAAA,UAAE,KAAK;AAAA,UAAS;AAAA,WAAC;AAAA,SAAO;AAAA,MAClE,KAAK,OAAO,WAAW,IACtB,gBAAAD,KAAC,OAAE,WAAU,SAAQ,iCAAmB,IAExC,gBAAAA,KAAC,QAAG,WAAU,UACX,eAAK,OAAO,IAAI,CAAC,MAChB,gBAAAC,MAAC,QACC;AAAA,wBAAAD,KAAC,UAAK,WAAU,QAAQ,eAAK,EAAE,OAAO,GAAE;AAAA,QACxC,gBAAAC,MAAC,UAAK,WAAU,OAAO;AAAA,YAAE;AAAA,UAAM,EAAE,QAAQ,SAAM,EAAE,KAAK,KAAK;AAAA,WAAG;AAAA,QAC7D,EAAE,SAAS,EAAE,UAAU,SAAS,gBAAAA,MAAC,UAAK,WAAU,gBAAe;AAAA;AAAA,UAAQ,EAAE;AAAA,WAAM,IAAU;AAAA,QACzF,EAAE,UAAU,gBAAAD,KAAC,OAAE,MAAM,EAAE,SAAS,QAAO,UAAS,KAAI,cAAa,kBAAI,IAAO;AAAA,WAJtE,EAAE,EAKX,CACD,GACH;AAAA,OAEJ;AAAA,KACF;AAEJ;AAUO,SAAS,iBAAiB,UAAmC,CAAC,GAAiB;AACpF,QAAM,EAAE,KAAK,aAAa,QAAQ,YAAY,IAAI;AAClD,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAA,KAAC,iBAAc,KAAU,EAAG;AACnE;;;ACvDsB,gBAAAE,MACO,QAAAC,aADP;AALtB,IAAMC,SAAQ,CAAC,UAA0B,KAAK,QAAQ,KAAK,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAClH,IAAM,OAAO,CAAC,MAA8B,IAAI,IAAI,KAAK,CAAC,EAAE,mBAAmB,CAAC,GAAG,EAAE,WAAW,SAAS,CAAC,IAAI;AAE9G,SAAS,YAAY,EAAE,IAAI,GAAiC;AAC1D,QAAM,EAAE,MAAM,SAAS,MAAM,IAAI,iBAA8B,IAAI,UAAU,oBAAoB;AACjG,MAAI,QAAS,QAAO,gBAAAF,KAAC,SAAI,WAAU,aAAY,2BAAQ;AACvD,MAAI,SAAS,CAAC,KAAM,QAAO,gBAAAC,MAAC,SAAI,WAAU,aAAY;AAAA;AAAA,IAAwB;AAAA,KAAM;AACpF,MAAI,CAAC,KAAK,aAAc,QAAO,gBAAAD,KAAC,SAAI,WAAU,aAAY,0BAAAA,KAAC,SAAI,WAAU,QAAO,0BAAAA,KAAC,OAAE,WAAU,SAAQ,2FAAmE,GAAI,GAAM;AAElL,QAAM,IAAI,KAAK;AACf,SACE,gBAAAC,MAAC,SAAI,WAAU,aACZ;AAAA,SAAK,WAAW,gBAAAD,KAAC,SAAI,WAAU,SAAQ,8BAAgB,IAAS;AAAA,IAChE,IACC,gBAAAC,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAA,MAAC,SAAI,WAAU,YAAW;AAAA,wBAAAD,KAAC,SAAI,WAAU,aAAa,YAAE,aAAY;AAAA,QAAM,gBAAAA,KAAC,SAAI,WAAU,aAAY,oBAAM;AAAA,SAAM;AAAA,MACjH,gBAAAC,MAAC,SAAI,WAAU,YAAW;AAAA,wBAAAD,KAAC,SAAI,WAAU,aAAa,UAAAE,OAAM,EAAE,eAAe,GAAE;AAAA,QAAM,gBAAAF,KAAC,SAAI,WAAU,aAAY,wBAAU;AAAA,SAAM;AAAA,MAChI,gBAAAC,MAAC,SAAI,WAAU,YAAW;AAAA,wBAAAD,KAAC,SAAI,WAAU,aAAa,YAAE,mBAAkB;AAAA,QAAM,gBAAAA,KAAC,SAAI,WAAU,aAAY,gCAAa;AAAA,SAAM;AAAA,MAC9H,gBAAAC,MAAC,SAAI,WAAU,YAAW;AAAA,wBAAAD,KAAC,SAAI,WAAU,aAAa,YAAE,cAAa;AAAA,QAAM,gBAAAA,KAAC,SAAI,WAAU,aAAY,sBAAQ;AAAA,SAAM;AAAA,OACtH,IACE;AAAA,IACH,KAAK,YAAY,gBAAAA,KAAC,OAAE,WAAU,gBAAe,+EAA4D,IAAO;AAAA,IACjH,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAC,MAAC,WAAM,WAAU,cACf;AAAA,sBAAAD,KAAC,WACC,0BAAAC,MAAC,QAAG;AAAA,wBAAAD,KAAC,QAAG,kBAAI;AAAA,QAAK,gBAAAA,KAAC,QAAG,mBAAK;AAAA,QAAK,gBAAAA,KAAC,QAAG,yBAAW;AAAA,QAAK,gBAAAA,KAAC,QAAG,0BAAY;AAAA,QAAK,gBAAAA,KAAC,QAAG,oBAAM;AAAA,QAAK,gBAAAA,KAAC,QAAG,oBAAM;AAAA,SAAK,GACxG;AAAA,MACA,gBAAAC,MAAC,WACE;AAAA,aAAK,KAAK,IAAI,CAAC,MACd,gBAAAA,MAAC,QACC;AAAA,0BAAAD,KAAC,QAAI,YAAE,MAAK;AAAA,UACZ,gBAAAA,KAAC,QAAI,YAAE,OAAM;AAAA,UACb,gBAAAA,KAAC,QAAI,YAAE,mBAAkB;AAAA,UACzB,gBAAAC,MAAC,QAAI;AAAA,cAAE,sBAAsB;AAAA,YAAK,EAAE,oBAAoB,kBAAkB;AAAA,aAAG;AAAA,UAC7E,gBAAAA,MAAC,QAAI;AAAA,YAAAC,OAAM,EAAE,WAAW;AAAA,YAAE;AAAA,YAAE,EAAE,aAAa,UAAU,OAAO;AAAA,aAAK;AAAA,UACjE,gBAAAF,KAAC,QAAI,eAAK,EAAE,SAAS,GAAE;AAAA,aANhB,EAAE,EAOX,CACD;AAAA,QACA,KAAK,KAAK,WAAW,IAAI,gBAAAA,KAAC,QAAG,0BAAAA,KAAC,QAAG,SAAS,GAAG,WAAU,SAAQ,mCAAqB,GAAK,IAAQ;AAAA,SACpG;AAAA,OACF,GACF;AAAA,KACF;AAEJ;AAUO,SAAS,eAAe,UAAiC,CAAC,GAAiB;AAChF,QAAM,EAAE,KAAK,WAAW,QAAQ,UAAU,IAAI;AAC9C,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAA,KAAC,eAAY,KAAU,EAAG;AACjE;;;AC/EA,SAAS,YAAAG,iBAAgB;AACzB,SAAS,UAAAC,eAAc;AA0BG,gBAAAC,MACO,QAAAC,aADP;AAR1B,SAAS,UAAU,EAAE,IAAI,GAAiC;AACxD,QAAM,MAAM,iBAA8B,IAAI,UAAU,wBAAwB;AAChF,QAAM,MAAM,iBAAsC,IAAI,UAAU,sBAAsB;AACtF,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA6B,IAAI;AAC3D,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AACpD,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AAEpD,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,IAAI,QAAS,QAAO,gBAAAF,KAAC,SAAI,WAAU,aAAY,2BAAQ;AAC3D,MAAI,IAAI,SAAS,CAAC,KAAM,QAAO,gBAAAC,MAAC,SAAI,WAAU,aAAY;AAAA;AAAA,IAA6B,IAAI;AAAA,KAAM;AAEjG,QAAM,OAAO,CAAC,UAAgC,SAAS,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC;AAC5E,QAAM,eAAe,CAAC,KAAa,UACjC,KAAK,EAAE,gBAAgB,EAAE,GAAG,KAAK,gBAAgB,CAAC,GAAG,GAAG,EAAE,GAAG,KAAK,eAAe,GAAG,GAAG,GAAG,MAAM,EAAc,EAAE,CAAC;AAEnH,QAAM,OAAO,YAAY;AACvB,YAAQ,MAAM;AACd,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,WAAW,IAAI,UAAU,0BAA0B,EAAE,QAAQ,OAAO,MAAM,KAAK,UAAU,IAAI,EAAE,CAAC;AACtG,eAAS,IAAI;AACb,UAAI,QAAQ;AACZ,cAAQ,QAAQ;AAAA,IAClB,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,QAAM,OAAO,OAAO,aAAqB;AACvC,YAAQ,QAAQ,QAAQ,EAAE;AAC1B,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,IAAI,MAAM,WAA6D,IAAI,UAAU,yBAAyB,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC;AAC1K,cAAQ,EAAE,KAAK,QAAQ,QAAQ,OAAO,EAAE,EAAE,GAAG,EAAE,aAAa,sBAAsB,EAAE,MAAM,4BAA4B;AACtH,UAAI,QAAQ;AAAA,IACd,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,SACE,gBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,oBAAAA,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAD,KAAC,QAAG,sBAAQ;AAAA,MACZ,gBAAAC,MAAC,OAAE,WAAU,SAAS;AAAA,aAAK;AAAA,QAAQ;AAAA,QAAI,KAAK;AAAA,QAAW,KAAK,YAAY,cAAW,KAAK,SAAS,KAAK;AAAA,SAAG;AAAA,MACzG,gBAAAA,MAAC,WAAM;AAAA;AAAA,QAAoB,gBAAAD,KAAC,WAAM,OAAO,KAAK,mBAAmB,UAAU,CAAC,MAAM,KAAK,EAAE,mBAAoB,EAAE,OAA4B,MAAM,CAAC,GAAG;AAAA,SAAE;AAAA,MACvJ,gBAAAC,MAAC,WAAM;AAAA;AAAA,QAAQ,gBAAAD,KAAC,WAAM,OAAO,KAAK,SAAS,UAAU,CAAC,MAAM,KAAK,EAAE,SAAU,EAAE,OAA4B,MAAM,CAAC,GAAG;AAAA,SAAE;AAAA,MACvH,gBAAAC,MAAC,WAAM;AAAA;AAAA,QAA+B,gBAAAD,KAAC,WAAM,OAAO,KAAK,YAAY,UAAU,CAAC,MAAM,KAAK,EAAE,YAAa,EAAE,OAA4B,MAAM,CAAC,GAAG;AAAA,SAAE;AAAA,OACtJ;AAAA,IAEC,OAAO,QAAQ,KAAK,cAAc,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,MAC/C,gBAAAC,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAD,KAAC,QAAI,eAAI;AAAA,MACT,gBAAAC,MAAC,WAAM,WAAU,OAAM;AAAA,wBAAAD,KAAC,WAAM,MAAK,YAAW,SAAS,EAAE,SAAS,UAAU,CAAC,MAAM,aAAa,KAAK,EAAE,SAAU,EAAE,OAA4B,QAAQ,CAAC,GAAG;AAAA,QAAE;AAAA,SAAQ;AAAA,MACrK,gBAAAC,MAAC,WAAM;AAAA;AAAA,QAAO,gBAAAD,KAAC,WAAM,OAAO,EAAE,SAAS,UAAU,CAAC,MAAM,aAAa,KAAK,EAAE,SAAU,EAAE,OAA4B,MAAM,CAAC,GAAG;AAAA,SAAE;AAAA,MAChI,gBAAAC,MAAC,WAAM;AAAA;AAAA,QAAI,gBAAAD,KAAC,cAAS,MAAM,GAAG,OAAO,EAAE,MAAM,UAAU,CAAC,MAAM,aAAa,KAAK,EAAE,MAAO,EAAE,OAA+B,MAAM,CAAC,GAAG;AAAA,SAAE;AAAA,MACtI,gBAAAA,KAACG,SAAA,EAAO,SAAQ,aAAY,MAAI,MAAC,UAAU,SAAS,QAAQ,GAAG,IAAI,SAAS,MAAM,KAAK,KAAK,GAAG,GAAG,uBAAS;AAAA,SALlF,GAM3B,CACD;AAAA,IAED,gBAAAF,MAAC,SAAI,WAAU,OACb;AAAA,sBAAAD,KAACG,SAAA,EAAO,UAAU,CAAC,SAAS,SAAS,QAAQ,SAAS,MAAM,KAAK,KAAK,GAAG,0BAAY;AAAA,MACpF,OAAO,gBAAAH,KAAC,UAAK,WAAU,SAAS,gBAAK,IAAU;AAAA,OAClD;AAAA,IAEA,gBAAAC,MAAC,SAAI,WAAU,QACb;AAAA,sBAAAD,KAAC,QAAG,sBAAQ;AAAA,OACV,IAAI,MAAM,SAAS,CAAC,GAAG,WAAW,IAAI,gBAAAA,KAAC,OAAE,WAAU,SAAQ,2BAAa,IACxE,gBAAAA,KAAC,QAAG,WAAU,OACV,eAAI,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,MAC5B,gBAAAC,MAAC,QACC;AAAA,wBAAAD,KAAC,UAAM,cAAI,KAAK,EAAE,MAAM,EAAE,eAAe,GAAE;AAAA,QAAO;AAAA,QAAG,gBAAAA,KAAC,UAAM,YAAE,UAAS;AAAA,QAAO;AAAA,QAAG,gBAAAA,KAAC,UAAM,YAAE,IAAG;AAAA,QAC5F,EAAE,QAAQ,gBAAAA,KAAC,UAAK,WAAU,gBAAe,oBAAM,IAAU,EAAE,aAAa,gBAAAA,KAAC,UAAK,WAAU,SAAQ,wBAAU,IAAU;AAAA,WAF9G,EAAE,EAGX,CACD,GACH;AAAA,OAEJ;AAAA,KACF;AAEJ;AAUO,SAAS,aAAa,UAA+B,CAAC,GAAiB;AAC5E,QAAM,EAAE,KAAK,SAAS,QAAQ,QAAQ,IAAI;AAC1C,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAA,KAAC,aAAU,KAAU,EAAG;AAC/D;;;ACpHA,SAAS,YAAAI,iBAAgB;AACzB,SAAS,UAAAC,eAAc;AAmBD,gBAAAC,MACO,QAAAC,aADP;AAJtB,SAAS,aAAa,EAAE,IAAI,GAAiC;AAC3D,QAAM,EAAE,MAAM,SAAS,OAAO,QAAQ,IAAI,iBAA+D,IAAI,UAAU,2BAA2B;AAClJ,QAAM,CAAC,MAAM,OAAO,IAAIC,UAAwB,IAAI;AACpD,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AACpD,MAAI,QAAS,QAAO,gBAAAF,KAAC,SAAI,WAAU,aAAY,2BAAQ;AACvD,MAAI,SAAS,CAAC,KAAM,QAAO,gBAAAC,MAAC,SAAI,WAAU,aAAY;AAAA;AAAA,IAAyB;AAAA,KAAM;AAErF,QAAM,OAAO,CAAC,MAAsB,IAAI,KAAK,CAAC,EAAE,eAAe,CAAC,GAAG,EAAE,WAAW,UAAU,WAAW,QAAQ,CAAC;AAC9G,QAAM,SAAS,OAAO,OAAe;AACnC,QAAI,CAAC,WAAW,UAAU,iDAAiD,EAAG;AAC9E,YAAQ,EAAE;AACV,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,WAAW,IAAI,UAAU,uBAAuB,EAAE,WAAW,EAAE,QAAQ,OAAO,CAAC;AACrF,cAAQ;AAAA,IACV,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,QAAM,aAAa,OAAO,OAAe;AACvC,UAAM,QAAQ,WAAW,SAAS,6CAA6C;AAC/E,QAAI,CAAC,MAAO;AACZ,UAAM,UAAU,KAAK,MAAM,KAAK;AAChC,QAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC7B,cAAQ,gCAA2B;AACnC;AAAA,IACF;AACA,YAAQ,EAAE;AACV,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,WAAW,IAAI,UAAU,uBAAuB,EAAE,eAAe,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;AAC5H,cAAQ;AAAA,IACV,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,SACE,gBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,oBAAAA,MAAC,QAAG;AAAA;AAAA,MAAO,gBAAAA,MAAC,UAAK,WAAU,SAAQ;AAAA;AAAA,QAAE,KAAK;AAAA,QAAS;AAAA,SAAC;AAAA,OAAO;AAAA,IAC1D,OAAO,gBAAAD,KAAC,OAAE,WAAU,SAAS,gBAAK,IAAO;AAAA,IACzC,KAAK,SAAS,WAAW,IAAI,gBAAAA,KAAC,OAAE,WAAU,SAAQ,0BAAY,IAC7D,gBAAAA,KAAC,QAAG,WAAU,UACX,eAAK,SAAS,IAAI,CAAC,MAClB,gBAAAC,MAAC,QAAc,WAAW,EAAE,WAAW,cAAc,cAAc,IACjE;AAAA,sBAAAD,KAAC,UAAK,WAAU,QAAQ,eAAK,EAAE,OAAO,GAAE;AAAA,MACxC,gBAAAA,KAAC,UAAK,WAAU,OAAO,YAAE,YAAY,GAAG,EAAE,UAAU,IAAI,SAAM,EAAE,UAAU,KAAK,KAAK,aAAY;AAAA,MAChG,gBAAAA,KAAC,UAAK,WAAU,SAAS,YAAE,QAAO;AAAA,MACjC,EAAE,SAAS,EAAE,UAAU,SAAS,gBAAAC,MAAC,UAAK,WAAU,gBAAe;AAAA;AAAA,QAAQ,EAAE;AAAA,SAAM,IAAU;AAAA,MACzF,EAAE,UAAU,gBAAAD,KAAC,OAAE,MAAM,EAAE,SAAS,QAAO,UAAS,KAAI,cAAa,kBAAI,IAAO;AAAA,MAC5E,EAAE,WAAW,cACZ,gBAAAC,MAAC,UAAK,WAAU,WACd;AAAA,wBAAAD,KAACG,SAAA,EAAO,SAAQ,aAAY,MAAI,MAAC,UAAU,SAAS,EAAE,IAAI,SAAS,MAAM,KAAK,WAAW,EAAE,EAAE,GAAG,wBAAU;AAAA,QAC1G,gBAAAH,KAACG,SAAA,EAAO,SAAQ,UAAS,MAAI,MAAC,UAAU,SAAS,EAAE,IAAI,SAAS,MAAM,KAAK,OAAO,EAAE,EAAE,GAAG,oBAAM;AAAA,SACjG,IACE;AAAA,SAXG,EAAE,EAYX,CACD,GACH;AAAA,KAEJ;AAEJ;AAUO,SAAS,gBAAgB,UAAkC,CAAC,GAAiB;AAClF,QAAM,EAAE,KAAK,YAAY,QAAQ,WAAW,IAAI;AAChD,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAH,KAAC,gBAAa,KAAU,EAAG;AAClE;;;AC/FA,SAAS,YAAAI,iBAAgB;AAEzB,SAAS,UAAAC,eAAc;AAwBD,gBAAAC,MACM,QAAAC,aADN;AAVtB,IAAM,OAAO,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAE7D,SAAS,iBAAiB,EAAE,IAAI,GAAiC;AAC/D,QAAM,EAAE,MAAM,SAAS,OAAO,QAAQ,IAAI,iBAA6C,IAAI,UAAU,uBAAuB;AAC5H,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA4B,IAAI;AAC1D,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAS,KAAK;AACtC,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAwB,IAAI;AACpD,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAiC,CAAC,CAAC;AAEzE,QAAM,MAAM,SAAS,MAAM,cAAc;AACzC,MAAI,QAAS,QAAO,gBAAAF,KAAC,SAAI,WAAU,aAAY,2BAAQ;AACvD,MAAI,SAAS,CAAC,IAAK,QAAO,gBAAAC,MAAC,SAAI,WAAU,aAAY;AAAA;AAAA,IAA6B;AAAA,KAAM;AAExF,QAAM,OAAO,CAAC,UAA+B,SAAS,EAAE,GAAG,KAAK,GAAG,MAAM,CAAC;AAC1E,QAAM,YAAY,CAAC,MAAc,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,IAAI,IAAI,KAAK,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;AAChI,QAAM,MAAM,CAAC,GAAW,aAAsB,OAAO,SAAS,OAAO,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI;AAEvF,QAAM,OAAO,YAAY;AACvB,YAAQ,IAAI;AACZ,YAAQ,IAAI;AACZ,mBAAe,CAAC,CAAC;AAGjB,UAAM,QAAQ,MAAM,IAAI,SAAS;AACjC,QAAI;AACF,YAAM,IAAI,MAAM,MAAM,yBAAyB;AAAA,QAC7C,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,SAAS,EAAE,IAAI,gBAAgB,mBAAmB;AAAA,QACtF,MAAM,KAAK,UAAU,GAAG;AAAA,MAC1B,CAAC;AACD,YAAM,OAAQ,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC7C,UAAI,CAAC,EAAE,IAAI;AACT,uBAAe,KAAK,UAAU,CAAC,CAAC;AAChC,gBAAQ,KAAK,SAAS,gBAAgB,EAAE,MAAM,GAAG;AACjD;AAAA,MACF;AACA,eAAS,IAAI;AACb,cAAQ;AACR,cAAQ,QAAQ;AAAA,IAClB,SAAS,GAAG;AACV,cAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,QAAM,QAAQ,CAAC,KAAuB,OAAe,UACnD,gBAAAA,MAAC,WACE;AAAA;AAAA,IACA;AAAA,IACA,YAAY,GAAG,IAAI,gBAAAD,KAAC,UAAK,WAAU,gBAAgB,sBAAY,GAAG,GAAE,IAAU;AAAA,KACjF;AAGF,SACE,gBAAAA,KAAC,SAAI,WAAU,aACb,0BAAAC,MAAC,SAAI,WAAU,QACb;AAAA,oBAAAD,KAAC,QAAG,kCAAoB;AAAA,IACxB,gBAAAA,KAAC,SAAI,WAAU,OACZ,eAAK,IAAI,CAAC,GAAG,MACZ,gBAAAC,MAAC,WAAc,WAAU,OACvB;AAAA,sBAAAD,KAAC,WAAM,MAAK,YAAW,SAAS,IAAI,KAAK,SAAS,CAAC,GAAG,UAAU,MAAM,UAAU,CAAC,GAAG;AAAA,MAAE;AAAA,MAAE;AAAA,SAD9E,CAEZ,CACD,GACH;AAAA,IACC,YAAY,OAAO,gBAAAA,KAAC,UAAK,WAAU,gBAAgB,sBAAY,MAAK,IAAU;AAAA,IAC9E,MAAM,aAAa,0BAAqB,gBAAAA,KAAC,WAAM,MAAK,UAAS,OAAO,IAAI,WAAW,UAAU,CAAC,MAAM,KAAK,EAAE,WAAW,IAAK,EAAE,OAA4B,OAAO,IAAI,SAAS,EAAE,CAAC,GAAG,CAAE;AAAA,IACrL,MAAM,WAAW,wBAAmB,gBAAAA,KAAC,WAAM,MAAK,UAAS,OAAO,IAAI,SAAS,UAAU,CAAC,MAAM,KAAK,EAAE,SAAS,IAAK,EAAE,OAA4B,OAAO,IAAI,OAAO,EAAE,CAAC,GAAG,CAAE;AAAA,IAC3K,MAAM,eAAe,yBAAyB,gBAAAA,KAAC,WAAM,MAAK,UAAS,OAAO,IAAI,aAAa,UAAU,CAAC,MAAM,KAAK,EAAE,aAAa,IAAK,EAAE,OAA4B,OAAO,IAAI,WAAW,EAAE,CAAC,GAAG,CAAE;AAAA,IACjM,MAAM,YAAY,YAAY,gBAAAA,KAAC,WAAM,OAAO,IAAI,UAAU,UAAU,CAAC,MAAM,KAAK,EAAE,UAAW,EAAE,OAA4B,MAAM,CAAC,GAAG,CAAE;AAAA,IACvI,MAAM,kBAAkB,0BAA0B,gBAAAA,KAAC,WAAM,MAAK,UAAS,OAAO,IAAI,gBAAgB,UAAU,CAAC,MAAM,KAAK,EAAE,gBAAgB,IAAK,EAAE,OAA4B,OAAO,IAAI,cAAc,EAAE,CAAC,GAAG,CAAE;AAAA,IAC9M,MAAM,cAAc,yBAAyB,gBAAAA,KAAC,WAAM,MAAK,UAAS,OAAO,IAAI,YAAY,UAAU,CAAC,MAAM,KAAK,EAAE,YAAY,IAAK,EAAE,OAA4B,OAAO,IAAI,UAAU,EAAE,CAAC,GAAG,CAAE;AAAA,IAC7L,MAAM,mBAAmB,0BAA0B,gBAAAA,KAAC,WAAM,OAAO,IAAI,iBAAiB,UAAU,CAAC,MAAM,KAAK,EAAE,iBAAkB,EAAE,OAA4B,MAAM,CAAC,GAAG,CAAE;AAAA,IAC3K,gBAAAC,MAAC,SAAI,WAAU,OACb;AAAA,sBAAAD,KAACG,SAAA,EAAO,UAAU,CAAC,SAAS,MAAM,SAAS,MAAM,KAAK,KAAK,GAAG,+BAAiB;AAAA,MAC9E,OAAO,gBAAAH,KAAC,UAAK,WAAU,SAAS,gBAAK,IAAU;AAAA,OAClD;AAAA,KACF,GACF;AAEJ;AAUO,SAAS,oBAAoB,UAAsC,CAAC,GAAiB;AAC1F,QAAM,EAAE,KAAK,gBAAgB,QAAQ,eAAe,IAAI;AACxD,SAAO,EAAE,IAAI,OAAO,QAAQ,CAAC,QAAQ,gBAAAA,KAAC,oBAAiB,KAAU,EAAG;AACtE;","names":["useEffect","useState","jsx","jsxs","useState","useEffect","useState","useEffect","useState","useCallback","useEffect","useState","jsx","jsxs","useState","useEffect","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","money","useState","Button","jsx","jsxs","useState","Button","useState","Button","jsx","jsxs","useState","Button","useState","Button","jsx","jsxs","useState","Button"]}
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { CrmClient, Crm } from '@odla-ai/crm';
3
+ import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
4
4
 
5
5
  /** What each admin section receives. `client` is a CrmClient bound to the
6
6
  * signed-in admin's bearer token; `navigate` switches sections. */
@@ -70,21 +70,33 @@ interface CollectionSectionOptions {
70
70
  id?: string;
71
71
  /** Nav label. Defaults to the type's plural label from the CRM config. */
72
72
  label?: string;
73
+ /** Show the lifecycle action panel (role change / approve / refund) on an open
74
+ * record — for application-backed people. Default `true` for `type: "person"`,
75
+ * `false` otherwise (a `deal`/`investment` has no approve/refund). */
76
+ lifecycle?: boolean;
77
+ /** Role ladder for the role selector. Default `["provisional","member","admin"]`
78
+ * — pass your `chapter.auth.ladder` if you use a custom ladder. */
79
+ roles?: readonly string[];
73
80
  }
74
81
  /**
75
82
  * Build a CRM collection admin section for ANY declared record type: a
76
83
  * server-driven records table plus a facet-composed detail drawer (fields,
77
- * pipeline stage, tags) over the token-bound CrmClient. This is the generic kit
78
- * call it once per collection a site runs (members, deals, investments), and each
79
- * renders with the same UX. Every action goes through `/api/crm/*`, never a direct
80
- * db write, so the CRM invariants hold.
84
+ * pipeline stage, tags) over the token-bound CrmClient, and for people a
85
+ * lifecycle action panel (role change, approve, refund) wired to the
86
+ * `/api/admin/*` routes. This is the generic kit call it once per collection a
87
+ * site runs (members, deals, investments), and each renders with the same UX.
88
+ * CRM edits go through `/api/crm/*` and lifecycle actions through `/api/admin/*`,
89
+ * never a direct db write, so the invariants hold.
81
90
  */
82
91
  declare function collectionSection(options: CollectionSectionOptions): AdminSection;
83
92
  /**
84
93
  * Build the People/CRM admin section — {@link collectionSection} preset to the
85
- * `person` type. Kept as a convenience; new collections use `collectionSection`.
94
+ * `person` type (so the lifecycle actions are on). New collections use
95
+ * `collectionSection`.
86
96
  */
87
- declare function peopleSection(options: PeopleSectionOptions): AdminSection;
97
+ declare function peopleSection(options: PeopleSectionOptions & {
98
+ roles?: readonly string[];
99
+ }): AdminSection;
88
100
 
89
101
  /** Options for {@link dashboardSection}. */
90
102
  interface DashboardSectionOptions {
@@ -122,6 +134,28 @@ interface MeetingsSectionOptions {
122
134
  * `GET /api/admin/meetings`, with reschedule/cancel actions. */
123
135
  declare function meetingsSection(options?: MeetingsSectionOptions): AdminSection;
124
136
 
137
+ /** Options for {@link availabilitySection}. */
138
+ interface AvailabilitySectionOptions {
139
+ id?: string;
140
+ label?: string;
141
+ }
142
+ /** Build the Availability admin section: the booking-rules editor over
143
+ * `GET/PUT /api/admin/scheduling`, with field-keyed validation surfaced inline. */
144
+ declare function availabilitySection(options?: AvailabilitySectionOptions): AdminSection;
145
+
146
+ /** Props for {@link RecordActions}. */
147
+ interface RecordActionsProps {
148
+ getToken: () => Promise<string | null>;
149
+ record: CrmRecord;
150
+ /** The role ladder offered by the role selector. Default provisional→admin. */
151
+ roles: readonly string[];
152
+ /** Called after any action mutates state, so the caller can refresh. */
153
+ onChanged: () => void;
154
+ }
155
+ /** The role/approve/refund controls for one person record. Renders only the
156
+ * actions the record supports. */
157
+ declare function RecordActions({ getToken, record, roles, onChanged }: RecordActionsProps): react.JSX.Element | null;
158
+
125
159
  /** Fetch a JSON `/api/admin/*` route with the admin bearer token. Throws on a
126
160
  * non-2xx (the body's `error`, else the status). */
127
161
  declare function adminFetch<T = unknown>(getToken: () => Promise<string | null>, path: string, init?: RequestInit): Promise<T>;
@@ -137,4 +171,4 @@ interface AdminResource<T> {
137
171
  * or when `path` changes. */
138
172
  declare function useAdminResource<T = unknown>(getToken: () => Promise<string | null>, path: string): AdminResource<T>;
139
173
 
140
- export { type AdminResource, type AdminSection, type AdminSectionContext, type BillingSectionOptions, ChapterAdmin, type ChapterAdminProps, type CollectionSectionOptions, type DashboardSectionOptions, type EmailSectionOptions, type MeetingsSectionOptions, type PeopleSectionOptions, adminFetch, billingSection, collectionSection, dashboardSection, emailSection, meetingsSection, peopleSection, useAdminResource };
174
+ export { type AdminResource, type AdminSection, type AdminSectionContext, type AvailabilitySectionOptions, type BillingSectionOptions, ChapterAdmin, type ChapterAdminProps, type CollectionSectionOptions, type DashboardSectionOptions, type EmailSectionOptions, type MeetingsSectionOptions, type PeopleSectionOptions, RecordActions, type RecordActionsProps, adminFetch, availabilitySection, billingSection, collectionSection, dashboardSection, emailSection, meetingsSection, peopleSection, useAdminResource };
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ChapterAdmin,
3
+ RecordActions,
3
4
  adminFetch,
5
+ availabilitySection,
4
6
  billingSection,
5
7
  collectionSection,
6
8
  dashboardSection,
@@ -8,10 +10,12 @@ import {
8
10
  meetingsSection,
9
11
  peopleSection,
10
12
  useAdminResource
11
- } from "../../chunk-HB2HUJCW.js";
13
+ } from "../../chunk-K6BDIFCJ.js";
12
14
  export {
13
15
  ChapterAdmin,
16
+ RecordActions,
14
17
  adminFetch,
18
+ availabilitySection,
15
19
  billingSection,
16
20
  collectionSection,
17
21
  dashboardSection,
@@ -1,4 +1,4 @@
1
- export { AdminResource, AdminSection, AdminSectionContext, BillingSectionOptions, ChapterAdmin, ChapterAdminProps, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, PeopleSectionOptions, adminFetch, billingSection, collectionSection, dashboardSection, emailSection, meetingsSection, peopleSection, useAdminResource } from './admin/index.js';
1
+ export { AdminResource, AdminSection, AdminSectionContext, AvailabilitySectionOptions, BillingSectionOptions, ChapterAdmin, ChapterAdminProps, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, PeopleSectionOptions, RecordActions, RecordActionsProps, adminFetch, availabilitySection, billingSection, collectionSection, dashboardSection, emailSection, meetingsSection, peopleSection, useAdminResource } from './admin/index.js';
2
2
  export { ApiFn, BrandStyle, BrandStyleProps, JoinConfig, JoinIsland, JoinIslandProps, MembersArea, MembersAreaProps, PaymentStep, PaymentStepProps, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, timeLabel, tzShort } from './member/index.js';
3
3
  import 'react';
4
4
  import '@odla-ai/crm';
package/dist/ui/index.js CHANGED
@@ -16,7 +16,9 @@ import {
16
16
  } from "../chunk-ZCZK6QLC.js";
17
17
  import {
18
18
  ChapterAdmin,
19
+ RecordActions,
19
20
  adminFetch,
21
+ availabilitySection,
20
22
  billingSection,
21
23
  collectionSection,
22
24
  dashboardSection,
@@ -24,16 +26,18 @@ import {
24
26
  meetingsSection,
25
27
  peopleSection,
26
28
  useAdminResource
27
- } from "../chunk-HB2HUJCW.js";
29
+ } from "../chunk-K6BDIFCJ.js";
28
30
  export {
29
31
  BrandStyle,
30
32
  ChapterAdmin,
31
33
  JoinIsland,
32
34
  MembersArea,
33
35
  PaymentStep,
36
+ RecordActions,
34
37
  Rescheduler,
35
38
  SlotPicker,
36
39
  adminFetch,
40
+ availabilitySection,
37
41
  billingSection,
38
42
  collectionSection,
39
43
  dashboardSection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/chapter",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "A foundation for membership sites: one config (defineChapter) stands up a full member site (join, Stripe membership, Google booking, member area, admin, CRM) or an admin-only hub, on odla-db + Clerk + @odla-ai/crm + calendar + email.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://odla.ai/docs/packages/chapter",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui/admin.tsx","../src/ui/chrome.tsx","../src/ui/admin-people.tsx","../src/ui/admin-api.ts","../src/ui/admin-dashboard.tsx","../src/ui/admin-billing.tsx","../src/ui/admin-email.tsx","../src/ui/admin-meetings.tsx"],"sourcesContent":["// ChapterAdmin — the whole admin console for a chapter/hub, owned by the package\n// so no site re-wires (or re-bugs) it: the Clerk publishable-key fetch\n// (/api/config), the themed sign-in with the post-sign-in redirect DERIVED from\n// basePath (so it never bounces to \"/\"), the odla-db admins-allowlist check\n// (/api/me), and a TopBar shell (chrome.tsx) that renders the caller's sections.\nimport { useEffect, useMemo, useState } from \"react\";\nimport { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth, clerkAppearanceFromTokens } from \"@odla-ai/auth-clerk\";\nimport { CrmClient } from \"@odla-ai/crm\";\nimport { S, Gate, AdminShell } from \"./chrome.js\";\nimport type { AdminSection, Brand } from \"./chrome.js\";\n\nexport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\n/** Props for {@link ChapterAdmin}. */\nexport interface ChapterAdminProps {\n /** The admin sections to render (nav label + body). */\n sections: AdminSection[];\n /** Admin mount path. The sign-in redirect + section routing derive from it,\n * so the \"bounce to home after sign-in\" bug is impossible. Default \"/admin\". */\n basePath?: string;\n /** Wordmark shown in the gate + top bar. */\n brand?: Brand;\n /** CRM route mount. Default \"/api/crm\". */\n crmBasePath?: string;\n /** Origin for /api/config and /api/me. Default same origin. */\n apiBase?: string;\n}\n\n// After sign-in: build a token-bound CrmClient, confirm the account is an\n// allowlisted admin (/api/me), then render the shell.\nfunction Authed(props: {\n sections: AdminSection[];\n basePath: string;\n brand: Brand;\n crmBasePath: string;\n apiBase: string;\n}) {\n const { sections, basePath, brand, crmBasePath, apiBase } = props;\n const { getToken, signOut } = useClerkAuth();\n const client = useMemo(\n () =>\n new CrmClient({\n basePath: crmBasePath,\n headers: async (): Promise<Record<string, string>> => {\n const t = await getToken();\n return t ? { authorization: `Bearer ${t}` } : {};\n },\n }),\n [getToken, crmBasePath],\n );\n const [state, setState] = useState<{ status: \"checking\" | \"ok\" | \"denied\"; email?: string | null }>({\n status: \"checking\",\n });\n\n useEffect(() => {\n let live = true;\n void (async () => {\n try {\n const t = await getToken();\n const res = await fetch(`${apiBase}/api/me`, { headers: t ? { authorization: `Bearer ${t}` } : {} });\n const body = (await res.json().catch(() => ({}))) as { authorized?: boolean; email?: string | null };\n if (live) setState({ status: body.authorized ? \"ok\" : \"denied\", email: body.email ?? null });\n } catch {\n if (live) setState({ status: \"denied\" });\n }\n })();\n return () => {\n live = false;\n };\n }, [getToken, apiBase]);\n\n if (state.status === \"checking\") {\n return (\n <Gate brand={brand}>\n <p style={S.muted}>Checking access…</p>\n </Gate>\n );\n }\n if (state.status === \"denied\") {\n return (\n <Gate brand={brand}>\n <div className=\"card\" style={S.card}>\n <h2 style={{ marginTop: 0 }}>Not authorized</h2>\n <p style={S.muted}>\n {state.email ? `${state.email} isn't` : \"This account isn't\"} on the admin list. Ask an existing admin to add\n you in odla Studio.\n </p>\n <button className=\"btn secondary\" onClick={() => signOut()} style={{ marginTop: 12 }}>\n Sign out\n </button>\n </div>\n </Gate>\n );\n }\n return (\n <AdminShell\n sections={sections}\n basePath={basePath}\n brand={brand}\n client={client}\n getToken={getToken}\n signOut={signOut}\n email={state.email ?? null}\n />\n );\n}\n\n/**\n * The whole admin console for a chapter/hub: fetches the Clerk publishable key\n * (/api/config), gates on sign-in with the post-sign-in redirect derived from\n * `basePath`, checks the odla-db admins allowlist (/api/me), and renders a\n * TopBar shell over the caller's `sections`. The gate/shell/redirect are owned\n * here so no site re-wires (or re-bugs) them.\n */\nexport function ChapterAdmin(props: ChapterAdminProps) {\n const sections = props.sections;\n const basePath = props.basePath ?? \"/admin\";\n const brand = props.brand ?? { name: \"Admin\" };\n const crmBasePath = props.crmBasePath ?? \"/api/crm\";\n const apiBase = props.apiBase ?? \"\";\n\n const [pk, setPk] = useState<string | null | undefined>(undefined);\n useEffect(() => {\n let live = true;\n void (async () => {\n try {\n const res = await fetch(`${apiBase}/api/config`);\n const body = (await res.json()) as { clerkPublishableKey?: string | null };\n if (live) setPk(body.clerkPublishableKey ?? null);\n } catch {\n if (live) setPk(null);\n }\n })();\n return () => {\n live = false;\n };\n }, [apiBase]);\n\n if (pk === undefined) {\n return (\n <Gate brand={brand}>\n <p style={S.muted}>Loading…</p>\n </Gate>\n );\n }\n if (!pk) {\n return (\n <Gate brand={brand}>\n <div className=\"card\" style={S.card}>\n <h2 style={{ marginTop: 0 }}>Sign-in not configured</h2>\n <p style={S.muted}>No Clerk publishable key is set for this environment yet.</p>\n </div>\n </Gate>\n );\n }\n return (\n <ClerkGate publishableKey={pk} appearance={clerkAppearanceFromTokens()} afterSignOutUrl=\"/\">\n <SignedOut>\n <Gate brand={brand} tagline=\"Admin sign-in — invite only\">\n <SignIn routing=\"hash\" forceRedirectUrl={basePath} signUpForceRedirectUrl={basePath} />\n </Gate>\n </SignedOut>\n <SignedIn>\n <Authed sections={sections} basePath={basePath} brand={brand} crmBasePath={crmBasePath} apiBase={apiBase} />\n </SignedIn>\n </ClerkGate>\n );\n}\n","// Shared chrome for ChapterAdmin: the section contract, the centered gate\n// screen, the signed-in TopBar shell, and the inline style tokens. Split out of\n// admin.tsx to stay under the repo's per-file LOC cap. Uses only @odla-ai/ui\n// classes + --ui-* tokens, so a consumer needs nothing beyond the odla-ui sheet.\nimport { useCallback, useEffect, useState } from \"react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { TopBar, TopBarLink } from \"@odla-ai/ui/components\";\nimport type { CrmClient } from \"@odla-ai/crm\";\n\n/** What each admin section receives. `client` is a CrmClient bound to the\n * signed-in admin's bearer token; `navigate` switches sections. */\nexport interface AdminSectionContext {\n client: CrmClient;\n getToken: () => Promise<string | null>;\n /** Switch to another section by id (e.g. Overview \"jump in\" buttons). */\n navigate: (sectionId: string) => void;\n}\n\n/** One admin-console section: a nav label and a body renderer. */\nexport interface AdminSection {\n id: string;\n label: string;\n render: (ctx: AdminSectionContext) => ReactNode;\n}\n\n/** Wordmark shown in the gate and top bar. `name` is the plain-text identity (and\n * the badge fallback); `wordmark` is an optional rich display node for brands\n * whose typography can't be a plain string (e.g. an ampersand wrapped to render\n * upright), and `badge` overrides the glyph. */\nexport type Brand = { name: string; badge?: string; wordmark?: ReactNode };\n\n/** The badge glyph: the explicit `badge`, else the first 3 letters of `name`. */\nexport const badgeText = (brand: Brand): string => brand.badge ?? brand.name.slice(0, 3).toUpperCase();\n\n/** The display node: the rich `wordmark` if given, else the plain `name`. */\nexport const brandDisplay = (brand: Brand): ReactNode => brand.wordmark ?? brand.name;\n\n/** Inline style tokens (keyed to --ui-* custom properties). */\nexport const S: Record<string, CSSProperties> = {\n gate: {\n minHeight: \"100vh\",\n display: \"grid\",\n placeItems: \"center\",\n padding: 24,\n background:\n \"radial-gradient(900px 480px at 50% -8%, var(--ui-accent-soft), transparent 70%),\" +\n \" radial-gradient(700px 500px at 110% 10%, var(--ui-good-soft), transparent 60%)\",\n },\n gateInner: { width: \"100%\", maxWidth: 400, display: \"flex\", flexDirection: \"column\", alignItems: \"center\" },\n brandBox: { textAlign: \"center\", marginBottom: 22 },\n badge: {\n width: 52,\n height: 52,\n margin: \"0 auto 14px\",\n display: \"grid\",\n placeItems: \"center\",\n fontSize: 15,\n fontWeight: 700,\n color: \"var(--ui-on-accent)\",\n background: \"linear-gradient(135deg, var(--ui-accent), var(--ui-accent-strong))\",\n borderRadius: 14,\n boxShadow: \"0 10px 28px var(--ui-accent-soft)\",\n },\n badgeSm: {\n width: 26,\n height: 26,\n display: \"grid\",\n placeItems: \"center\",\n fontSize: 10,\n fontWeight: 700,\n color: \"var(--ui-on-accent)\",\n background: \"linear-gradient(135deg, var(--ui-accent), var(--ui-accent-strong))\",\n borderRadius: 7,\n marginRight: 9,\n },\n h1: { margin: 0, fontSize: 28, letterSpacing: \"-0.02em\", fontWeight: 700 },\n muted: { color: \"var(--ui-text-muted)\" },\n role: {\n fontFamily: \"var(--ui-font-mono)\",\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: \"0.04em\",\n padding: \"2px 8px\",\n borderRadius: 999,\n background: \"var(--ui-accent-soft)\",\n border: \"1px solid var(--ui-accent)\",\n color: \"var(--ui-accent-strong)\",\n },\n whoami: { display: \"flex\", alignItems: \"center\", gap: 8, fontSize: 12 },\n // Sections own their own width/padding (e.g. a .wrap container).\n main: { minHeight: \"calc(100vh - 61px)\" },\n card: { width: \"100%\", textAlign: \"center\" },\n};\n\n/** Centered gate screen used for loading / sign-in / denied / not-configured. */\nexport function Gate(props: { brand: Brand; tagline?: string; children: ReactNode }) {\n const { brand, tagline, children } = props;\n return (\n <div style={S.gate}>\n <div style={S.gateInner}>\n <div style={S.brandBox}>\n <div style={S.badge}>{badgeText(brand)}</div>\n <h1 style={S.h1}>{brandDisplay(brand)}</h1>\n {tagline ? <p style={{ ...S.muted, margin: \"8px 0 0\", fontSize: 14 }}>{tagline}</p> : null}\n </div>\n {children}\n </div>\n </div>\n );\n}\n\n/** The signed-in shell: a TopBar of the caller's sections + the active body. */\nexport function AdminShell(props: {\n sections: AdminSection[];\n basePath: string;\n brand: Brand;\n client: CrmClient;\n getToken: () => Promise<string | null>;\n signOut: () => void;\n email: string | null;\n}) {\n const { sections, basePath, brand, client, getToken, signOut, email } = props;\n\n const sectionFromPath = useCallback(() => {\n const fallback = sections[0]?.id ?? \"\";\n if (typeof window === \"undefined\") return fallback;\n const rest = window.location.pathname.slice(basePath.length).replace(/^\\//, \"\");\n const id = rest.split(\"/\")[0] ?? \"\";\n return sections.some((s) => s.id === id) ? id : fallback;\n }, [sections, basePath]);\n\n const [section, setSection] = useState(sectionFromPath);\n\n const go = useCallback(\n (id: string) => {\n window.history.pushState(null, \"\", `${basePath}/${id}`);\n setSection(id);\n },\n [basePath],\n );\n\n useEffect(() => {\n const onPop = () => setSection(sectionFromPath());\n window.addEventListener(\"popstate\", onPop);\n return () => window.removeEventListener(\"popstate\", onPop);\n }, [sectionFromPath]);\n\n const active = sections.find((s) => s.id === section) ?? sections[0];\n\n return (\n <>\n <TopBar>\n <a className=\"topbar-brand\" href=\"/\" style={{ display: \"inline-flex\", alignItems: \"center\" }}>\n <span style={S.badgeSm}>{badgeText(brand)}</span>\n {brandDisplay(brand)}\n </a>\n <nav className=\"topbar-nav\" aria-label=\"Admin navigation\">\n {sections.map((s) => (\n <TopBarLink key={s.id} active={section === s.id} onClick={() => go(s.id)}>\n {s.label}\n </TopBarLink>\n ))}\n </nav>\n <div className=\"topbar-spacer\" />\n <div className=\"topbar-actions\">\n <span style={S.whoami}>\n <span style={S.role}>admin</span>\n {email ? <span style={S.muted}>{email}</span> : null}\n </span>\n <button className=\"btn secondary mini\" onClick={() => signOut()}>\n Sign out\n </button>\n </div>\n </TopBar>\n <main className=\"shell-main\" style={S.main}>\n {active ? active.render({ client, getToken, navigate: go }) : null}\n </main>\n </>\n );\n}\n","// People/CRM admin section — the core admin workflow, assembled from the shipped\n// @odla-ai/crm/ui kit against the /api/crm/* routes the worker already mounts.\n// It's a FACTORY: peopleSection({ crm }) → an AdminSection you drop into\n// ChapterAdmin's `sections`. Every lifecycle action (edit fields, move pipeline\n// stage, tag) goes through the token-bound CrmClient — never a direct db write,\n// so the package's operational invariants hold (G7).\nimport { useState } from \"react\";\nimport { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from \"@odla-ai/crm/ui\";\nimport type { Crm, CrmClient, CrmRecord } from \"@odla-ai/crm\";\nimport type { AdminSection } from \"./chrome.js\";\n\nfunction PeopleBody(props: { crm: Crm; client: CrmClient; type: string }) {\n const { crm, client, type } = props;\n // The crm/ui hooks type their client against @odla-ai/crm's ui-entry bundled\n // declaration; ctx.client is the core-entry declaration. Same runtime class,\n // but their private field makes the two .d.ts copies nominally distinct — cast\n // to the param type the hooks expect.\n const hookClient = client as unknown as Parameters<typeof useCrmQuery>[0];\n const query = useCrmQuery(hookClient, type);\n const [openId, setOpenId] = useState<string | null>(null);\n const record = useCrmRecord(hookClient, openId);\n const [saving, setSaving] = useState(false);\n\n const saveFields = async (input: Record<string, unknown>) => {\n if (!openId) return;\n setSaving(true);\n try {\n await client.updateRecord(openId, { input });\n record.refresh();\n query.refresh();\n } finally {\n setSaving(false);\n }\n };\n const moveStage = async (to: string) => {\n if (!openId) return;\n await client.setStage(openId, to);\n record.refresh();\n query.refresh();\n };\n const addTag = async (tag: string) => {\n if (openId) {\n await client.addTag(openId, tag);\n record.refresh();\n }\n };\n const removeTag = async (tag: string) => {\n if (openId) {\n await client.removeTag(openId, tag);\n record.refresh();\n }\n };\n\n return (\n <div className=\"wrap\">\n <CrmList crm={crm} type={type} query={query} onOpenRecord={(r: CrmRecord) => setOpenId(r.id)} />\n {record.detail ? (\n <RecordPanel\n crm={crm}\n detail={record.detail}\n onSaveFields={saveFields}\n onMoveStage={moveStage}\n onAddTag={addTag}\n onRemoveTag={removeTag}\n saving={saving}\n />\n ) : null}\n </div>\n );\n}\n\n/** Options for {@link peopleSection}. */\nexport interface PeopleSectionOptions {\n /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */\n crm: Crm;\n /** Section id / route segment. Default \"people\". */\n id?: string;\n /** Nav label. Default \"People\". */\n label?: string;\n /** The record type to list. Default \"person\". */\n type?: string;\n}\n\n/** Options for {@link collectionSection}. */\nexport interface CollectionSectionOptions {\n /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */\n crm: Crm;\n /** The CRM record type to list (e.g. `\"person\"`, `\"deal\"`, `\"investment\"`). */\n type: string;\n /** Section id / route segment. Defaults to `type`. */\n id?: string;\n /** Nav label. Defaults to the type's plural label from the CRM config. */\n label?: string;\n}\n\n/**\n * Build a CRM collection admin section for ANY declared record type: a\n * server-driven records table plus a facet-composed detail drawer (fields,\n * pipeline stage, tags) over the token-bound CrmClient. This is the generic kit —\n * call it once per collection a site runs (members, deals, investments), and each\n * renders with the same UX. Every action goes through `/api/crm/*`, never a direct\n * db write, so the CRM invariants hold.\n */\nexport function collectionSection(options: CollectionSectionOptions): AdminSection {\n const { crm, type } = options;\n let fallbackLabel = type;\n try {\n fallbackLabel = crm.type(type).labelPlural ?? crm.type(type).label ?? type;\n } catch {\n // Unknown type — the section still renders; the CRM route reports the error.\n }\n const { id = type, label = fallbackLabel } = options;\n return { id, label, render: (ctx) => <PeopleBody crm={crm} client={ctx.client} type={type} /> };\n}\n\n/**\n * Build the People/CRM admin section — {@link collectionSection} preset to the\n * `person` type. Kept as a convenience; new collections use `collectionSection`.\n */\nexport function peopleSection(options: PeopleSectionOptions): AdminSection {\n const { crm, id = \"people\", label = \"People\", type = \"person\" } = options;\n return collectionSection({ crm, type, id, label });\n}\n","// Token-bound access to the /api/admin/* routes for the console sections. Every\n// section fetches with the signed-in admin's bearer token (from ChapterAdmin's\n// getToken) — the worker gates each route with verifyUser + isAdmin, so the UI\n// carries no privilege of its own.\nimport { useCallback, useEffect, useState } from \"react\";\n\n/** Fetch a JSON `/api/admin/*` route with the admin bearer token. Throws on a\n * non-2xx (the body's `error`, else the status). */\nexport async function adminFetch<T = unknown>(\n getToken: () => Promise<string | null>,\n path: string,\n init?: RequestInit,\n): Promise<T> {\n const token = await getToken();\n const headers = new Headers(init?.headers);\n if (token) headers.set(\"authorization\", `Bearer ${token}`);\n if (init?.body) headers.set(\"content-type\", \"application/json\");\n const res = await fetch(path, { ...init, headers });\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) throw new Error(typeof body.error === \"string\" ? body.error : `request failed (${res.status})`);\n return body as T;\n}\n\n/** The state of one admin resource load. */\nexport interface AdminResource<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n /** Re-fetch (e.g. after a mutation). */\n refresh: () => void;\n}\n\n/** Load a `/api/admin/*` GET route into React state, re-fetching on `refresh()`\n * or when `path` changes. */\nexport function useAdminResource<T = unknown>(getToken: () => Promise<string | null>, path: string): AdminResource<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [nonce, setNonce] = useState(0);\n\n useEffect(() => {\n let live = true;\n setLoading(true);\n setError(null);\n adminFetch<T>(getToken, path)\n .then((d) => live && setData(d))\n .catch((e: unknown) => live && setError(e instanceof Error ? e.message : String(e)))\n .finally(() => live && setLoading(false));\n return () => {\n live = false;\n };\n }, [getToken, path, nonce]);\n\n const refresh = useCallback(() => setNonce((n) => n + 1), []);\n return { data, loading, error, refresh };\n}\n","// Dashboard section: the operational overview from GET /api/admin/dashboard —\n// application flow, pipeline stage counts, the upcoming call agenda, and (when\n// billing is wired) live revenue. Renders the response the route already shapes,\n// so a hub gets the identical overview per chapter.\nimport { useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface DashboardData {\n applications: { total: number; last7: number; last30: number };\n pipeline: Record<string, number>;\n pipelineDelta: Record<string, number>;\n calls: { upcoming: number; needsAttention: number };\n agenda: Array<{ id: string; startAt: number; name: string; email: string | null; drift: string; meetUrl: string | null }>;\n timezone: string;\n revenue: { billingReady: boolean; activeCount?: number; annualRunRateCents?: number };\n}\n\nconst money = (cents: number): string => `$${Math.round(cents / 100).toLocaleString()}`;\n\nfunction DashboardBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error } = useAdminResource<DashboardData>(ctx.getToken, \"/api/admin/dashboard\");\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load the dashboard: {error}</div>;\n\n const when = (t: number): string => new Date(t).toLocaleString([], { dateStyle: \"medium\", timeStyle: \"short\" });\n const Kpi = ({ label, value, sub }: { label: string; value: string | number; sub?: string }) => (\n <div className=\"card kpi\">\n <div className=\"kpi-value\">{value}</div>\n <div className=\"kpi-label\">{label}</div>\n {sub ? <div className=\"kpi-sub muted\">{sub}</div> : null}\n </div>\n );\n return (\n <div className=\"wrap page\">\n <div className=\"kpis\">\n <Kpi label=\"Applications\" value={data.applications.total} sub={`+${data.applications.last7} in 7d`} />\n <Kpi label=\"Upcoming calls\" value={data.calls.upcoming} sub={data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : undefined} />\n {data.revenue.billingReady ? <Kpi label=\"Active members\" value={data.revenue.activeCount ?? 0} /> : null}\n {data.revenue.billingReady ? <Kpi label=\"Annual run rate\" value={money(data.revenue.annualRunRateCents ?? 0)} /> : null}\n </div>\n\n <div className=\"card\">\n <h3>Pipeline</h3>\n <ul className=\"pipeline-counts\">\n {Object.entries(data.pipeline).map(([stage, count]) => (\n <li key={stage}>\n <span className=\"stage\">{stage}</span>\n <span className=\"count\">{count}</span>\n {data.pipelineDelta[stage] ? <span className=\"delta\">+{data.pipelineDelta[stage]}</span> : null}\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"card\">\n <h3>Upcoming calls <span className=\"muted\">({data.timezone})</span></h3>\n {data.agenda.length === 0 ? (\n <p className=\"muted\">No calls scheduled.</p>\n ) : (\n <ul className=\"agenda\">\n {data.agenda.map((m) => (\n <li key={m.id}>\n <span className=\"when\">{when(m.startAt)}</span>\n <span className=\"who\">{m.name}{m.email ? ` · ${m.email}` : \"\"}</span>\n {m.drift && m.drift !== \"none\" ? <span className=\"badge danger\">drift: {m.drift}</span> : null}\n {m.meetUrl ? <a href={m.meetUrl} target=\"_blank\" rel=\"noreferrer\">Meet</a> : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n\n/** Options for {@link dashboardSection}. */\nexport interface DashboardSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Dashboard admin section over `GET /api/admin/dashboard`. Drop the\n * result into `ChapterAdmin`'s `sections`. */\nexport function dashboardSection(options: DashboardSectionOptions = {}): AdminSection {\n const { id = \"dashboard\", label = \"Dashboard\" } = options;\n return { id, label, render: (ctx) => <DashboardBody ctx={ctx} /> };\n}\n","// Billing section: applications joined with live Stripe subscription state from\n// GET /api/admin/billing. Degrades to a \"not configured\" note when the route\n// reports billingReady:false (no stripe_secret_key vaulted), and surfaces the\n// route's `truncated` flag rather than pretending the list is complete.\nimport { useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface BillingRow {\n id: string;\n name: string;\n email: string;\n applicationStatus: string;\n subscriptionStatus: string | null;\n cancelAtPeriodEnd: boolean;\n amountCents: number;\n interval: string;\n renewalAt: number | null;\n}\ninterface BillingData {\n billingReady: boolean;\n testMode?: boolean;\n truncated?: boolean;\n rows: BillingRow[];\n summary: { activeCount: number; annualizedCents: number; renewingSoonCount: number; pastDueCount: number; canceledCount: number; refundedCount: number } | null;\n}\n\nconst money = (cents: number): string => `$${(cents / 100).toLocaleString(undefined, { minimumFractionDigits: 0 })}`;\nconst date = (t: number | null): string => (t ? new Date(t).toLocaleDateString([], { dateStyle: \"medium\" }) : \"—\");\n\nfunction BillingBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error } = useAdminResource<BillingData>(ctx.getToken, \"/api/admin/billing\");\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load billing: {error}</div>;\n if (!data.billingReady) return <div className=\"wrap page\"><div className=\"card\"><p className=\"muted\">Billing isn’t configured — no Stripe key is vaulted for this group.</p></div></div>;\n\n const s = data.summary;\n return (\n <div className=\"wrap page\">\n {data.testMode ? <div className=\"badge\">Stripe test mode</div> : null}\n {s ? (\n <div className=\"kpis\">\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.activeCount}</div><div className=\"kpi-label\">Active</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{money(s.annualizedCents)}</div><div className=\"kpi-label\">Annualized</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.renewingSoonCount}</div><div className=\"kpi-label\">Renewing ≤60d</div></div>\n <div className=\"card kpi\"><div className=\"kpi-value\">{s.pastDueCount}</div><div className=\"kpi-label\">Past due</div></div>\n </div>\n ) : null}\n {data.truncated ? <p className=\"badge danger\">Showing the first 100 subscriptions — the list is truncated.</p> : null}\n <div className=\"card\">\n <table className=\"data-table\">\n <thead>\n <tr><th>Name</th><th>Email</th><th>Application</th><th>Subscription</th><th>Amount</th><th>Renews</th></tr>\n </thead>\n <tbody>\n {data.rows.map((r) => (\n <tr key={r.id}>\n <td>{r.name}</td>\n <td>{r.email}</td>\n <td>{r.applicationStatus}</td>\n <td>{r.subscriptionStatus ?? \"—\"}{r.cancelAtPeriodEnd ? \" (cancelling)\" : \"\"}</td>\n <td>{money(r.amountCents)}/{r.interval === \"month\" ? \"mo\" : \"yr\"}</td>\n <td>{date(r.renewalAt)}</td>\n </tr>\n ))}\n {data.rows.length === 0 ? <tr><td colSpan={6} className=\"muted\">No subscriptions yet.</td></tr> : null}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n\n/** Options for {@link billingSection}. */\nexport interface BillingSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Billing admin section over `GET /api/admin/billing`. Renders a\n * \"not configured\" note when the group has no Stripe key vaulted. */\nexport function billingSection(options: BillingSectionOptions = {}): AdminSection {\n const { id = \"billing\", label = \"Billing\" } = options;\n return { id, label, render: (ctx) => <BillingBody ctx={ctx} /> };\n}\n","// Email section: the owner-editable email config (templates + addresses) from\n// GET/PUT /api/admin/group/email, the send audit from GET /api/admin/email/log,\n// and a POST /api/admin/email/test button that exercises the real transport.\n// Everything through the token-bound /api/admin routes — no direct db write.\nimport { useState } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { adminFetch, useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface Template { subject: string; text: string; enabled: boolean }\ninterface EmailConfig {\n emailTemplates: Record<string, Template>;\n replyTo: string;\n notificationEmail: string;\n debugEmail: string;\n commitmentText: string;\n normsText: string;\n transport: string;\n fromEmail: string | null;\n envName: string;\n}\ninterface LogRow { id: string; template: string; to: string; subject: string; transport: string; redirected: boolean; error: string | null; sentAt: number }\n\nfunction EmailBody({ ctx }: { ctx: AdminSectionContext }) {\n const cfg = useAdminResource<EmailConfig>(ctx.getToken, \"/api/admin/group/email\");\n const log = useAdminResource<{ sends: LogRow[] }>(ctx.getToken, \"/api/admin/email/log\");\n const [draft, setDraft] = useState<EmailConfig | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n const [note, setNote] = useState<string | null>(null);\n\n const data = draft ?? cfg.data;\n if (cfg.loading) return <div className=\"wrap page\">Loading…</div>;\n if (cfg.error || !data) return <div className=\"wrap page\">Couldn’t load email config: {cfg.error}</div>;\n\n const edit = (patch: Partial<EmailConfig>) => setDraft({ ...data, ...patch });\n const editTemplate = (key: string, patch: Partial<Template>) =>\n edit({ emailTemplates: { ...data.emailTemplates, [key]: { ...data.emailTemplates[key], ...patch } as Template } });\n\n const save = async () => {\n setBusy(\"save\");\n setNote(null);\n try {\n await adminFetch(ctx.getToken, \"/api/admin/group/email\", { method: \"PUT\", body: JSON.stringify(data) });\n setDraft(null);\n cfg.refresh();\n setNote(\"Saved.\");\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n const test = async (template: string) => {\n setBusy(`test:${template}`);\n setNote(null);\n try {\n const r = await adminFetch<{ ok: boolean; to: string; redirected: boolean }>(ctx.getToken, \"/api/admin/email/test\", { method: \"POST\", body: JSON.stringify({ template }) });\n setNote(r.ok ? `Sent ${template} to ${r.to}${r.redirected ? \" (dev-redirected)\" : \"\"}.` : `Test send did not deliver.`);\n log.refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n\n return (\n <div className=\"wrap page\">\n <div className=\"card\">\n <h3>Delivery</h3>\n <p className=\"muted\">{data.envName} · {data.transport}{data.fromEmail ? ` · from ${data.fromEmail}` : \"\"}</p>\n <label>Notification address<input value={data.notificationEmail} onChange={(e) => edit({ notificationEmail: (e.target as HTMLInputElement).value })} /></label>\n <label>Reply-to<input value={data.replyTo} onChange={(e) => edit({ replyTo: (e.target as HTMLInputElement).value })} /></label>\n <label>Debug inbox (non-prod redirect)<input value={data.debugEmail} onChange={(e) => edit({ debugEmail: (e.target as HTMLInputElement).value })} /></label>\n </div>\n\n {Object.entries(data.emailTemplates).map(([key, t]) => (\n <div className=\"card\" key={key}>\n <h3>{key}</h3>\n <label className=\"row\"><input type=\"checkbox\" checked={t.enabled} onChange={(e) => editTemplate(key, { enabled: (e.target as HTMLInputElement).checked })} /> enabled</label>\n <label>Subject<input value={t.subject} onChange={(e) => editTemplate(key, { subject: (e.target as HTMLInputElement).value })} /></label>\n <label>Body<textarea rows={5} value={t.text} onChange={(e) => editTemplate(key, { text: (e.target as HTMLTextAreaElement).value })} /></label>\n <Button variant=\"secondary\" mini disabled={busy === `test:${key}`} onClick={() => void test(key)}>Send test</Button>\n </div>\n ))}\n\n <div className=\"row\">\n <Button disabled={!draft || busy === \"save\"} onClick={() => void save()}>Save changes</Button>\n {note ? <span className=\"muted\">{note}</span> : null}\n </div>\n\n <div className=\"card\">\n <h3>Send log</h3>\n {(log.data?.sends ?? []).length === 0 ? <p className=\"muted\">No sends yet.</p> : (\n <ul className=\"log\">\n {(log.data?.sends ?? []).map((r) => (\n <li key={r.id}>\n <span>{new Date(r.sentAt).toLocaleString()}</span> · <span>{r.template}</span> → <span>{r.to}</span>\n {r.error ? <span className=\"badge danger\">failed</span> : r.redirected ? <span className=\"badge\">redirected</span> : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n\n/** Options for {@link emailSection}. */\nexport interface EmailSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Email admin section: template + address editor over\n * `GET/PUT /api/admin/group/email`, the send log, and per-template test sends. */\nexport function emailSection(options: EmailSectionOptions = {}): AdminSection {\n const { id = \"email\", label = \"Email\" } = options;\n return { id, label, render: (ctx) => <EmailBody ctx={ctx} /> };\n}\n","// Meetings section: the reconciled agenda from GET /api/admin/meetings, with the\n// reschedule and cancel actions wired to POST /api/admin/meetings/:id/{...}.\n// Drift against live Google state is flagged, never auto-adopted.\nimport { useState } from \"react\";\nimport { Button } from \"@odla-ai/ui/components\";\nimport { adminFetch, useAdminResource } from \"./admin-api.js\";\nimport type { AdminSection, AdminSectionContext } from \"./chrome.js\";\n\ninterface MeetingRow {\n id: string;\n startAt: number;\n endAt: number;\n status: string;\n drift: string;\n meetUrl: string | null;\n htmlLink: string | null;\n applicant: { name: string; email: string; status: string } | null;\n}\n\nfunction MeetingsBody({ ctx }: { ctx: AdminSectionContext }) {\n const { data, loading, error, refresh } = useAdminResource<{ meetings: MeetingRow[]; timezone: string }>(ctx.getToken, \"/api/admin/meetings?all=1\");\n const [busy, setBusy] = useState<string | null>(null);\n const [note, setNote] = useState<string | null>(null);\n if (loading) return <div className=\"wrap page\">Loading…</div>;\n if (error || !data) return <div className=\"wrap page\">Couldn’t load meetings: {error}</div>;\n\n const when = (t: number): string => new Date(t).toLocaleString([], { dateStyle: \"medium\", timeStyle: \"short\" });\n const cancel = async (id: string) => {\n if (!globalThis.confirm?.(\"Cancel this call? Google notifies the attendee.\")) return;\n setBusy(id);\n setNote(null);\n try {\n await adminFetch(ctx.getToken, `/api/admin/meetings/${id}/cancel`, { method: \"POST\" });\n refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n const reschedule = async (id: string) => {\n const input = globalThis.prompt?.(\"New start time (ISO or 'YYYY-MM-DD HH:MM'):\");\n if (!input) return;\n const startAt = Date.parse(input);\n if (!Number.isFinite(startAt)) {\n setNote(\"Couldn’t parse that time.\");\n return;\n }\n setBusy(id);\n setNote(null);\n try {\n await adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: \"POST\", body: JSON.stringify({ startAt }) });\n refresh();\n } catch (e) {\n setNote(e instanceof Error ? e.message : String(e));\n } finally {\n setBusy(null);\n }\n };\n\n return (\n <div className=\"wrap page\">\n <h3>Agenda <span className=\"muted\">({data.timezone})</span></h3>\n {note ? <p className=\"muted\">{note}</p> : null}\n {data.meetings.length === 0 ? <p className=\"muted\">No meetings.</p> : (\n <ul className=\"agenda\">\n {data.meetings.map((m) => (\n <li key={m.id} className={m.status === \"cancelled\" ? \"cancelled\" : \"\"}>\n <span className=\"when\">{when(m.startAt)}</span>\n <span className=\"who\">{m.applicant ? `${m.applicant.name} · ${m.applicant.email}` : \"(unknown)\"}</span>\n <span className=\"badge\">{m.status}</span>\n {m.drift && m.drift !== \"none\" ? <span className=\"badge danger\">drift: {m.drift}</span> : null}\n {m.meetUrl ? <a href={m.meetUrl} target=\"_blank\" rel=\"noreferrer\">Meet</a> : null}\n {m.status === \"scheduled\" ? (\n <span className=\"actions\">\n <Button variant=\"secondary\" mini disabled={busy === m.id} onClick={() => void reschedule(m.id)}>Reschedule</Button>\n <Button variant=\"danger\" mini disabled={busy === m.id} onClick={() => void cancel(m.id)}>Cancel</Button>\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n/** Options for {@link meetingsSection}. */\nexport interface MeetingsSectionOptions {\n id?: string;\n label?: string;\n}\n\n/** Build the Meetings admin section: the reconciled agenda over\n * `GET /api/admin/meetings`, with reschedule/cancel actions. */\nexport function meetingsSection(options: MeetingsSectionOptions = {}): AdminSection {\n const { id = \"meetings\", label = \"Meetings\" } = options;\n return { id, label, render: (ctx) => <MeetingsBody ctx={ctx} /> };\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","jsxs","useState","useEffect","useState","jsx","jsxs","useCallback","useEffect","useState","jsx","jsxs","jsx","jsxs","money","useState","jsx","jsxs","useState","useState","Button","jsx","jsxs","useState","Button"]}