@odla-ai/chapter 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
+ import {
2
+ BrandStyle
3
+ } from "./chunk-MKXHZMAP.js";
4
+
1
5
  // src/ui/admin.tsx
2
- import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
6
+ import { useEffect as useEffect5, useMemo, useState as useState10 } from "react";
3
7
  import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth, clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
4
8
  import { CrmClient } from "@odla-ai/crm";
5
9
 
@@ -68,7 +72,38 @@ function AdminNote({ children }) {
68
72
  // src/ui/chrome.tsx
69
73
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
70
74
  var badgeText = (brand) => brand.badge ?? brand.name.slice(0, 3).toUpperCase();
71
- var brandDisplay = (brand) => brand.wordmark ?? brand.name;
75
+ var brandDisplay = (brand) => {
76
+ const display = brand.wordmark ?? brand.name;
77
+ if (typeof display !== "string" || !display.includes("&")) return display;
78
+ return /* @__PURE__ */ jsx2(Fragment, { children: display.split("&").map((part, index, all) => /* @__PURE__ */ jsxs2("span", { children: [
79
+ part,
80
+ index < all.length - 1 ? /* @__PURE__ */ jsx2("span", { className: "mk-amp", children: "&" }) : null
81
+ ] }, `${part}:${index}`)) });
82
+ };
83
+ var cleanBase = (basePath) => basePath.length > 1 ? basePath.replace(/\/+$/, "") : basePath;
84
+ function adminSectionFromUrl(url, basePath, sectionIds) {
85
+ const fallback = sectionIds[0] ?? "";
86
+ const tab = url.searchParams.get("tab");
87
+ if (tab && sectionIds.includes(tab)) return tab;
88
+ const base = cleanBase(basePath);
89
+ const rest = url.pathname.startsWith(base) ? url.pathname.slice(base.length).replace(/^\//, "") : "";
90
+ const legacy = rest.split("/")[0] ?? "";
91
+ return sectionIds.includes(legacy) ? legacy : fallback;
92
+ }
93
+ function adminSectionHref(current, basePath, id, routing = "query") {
94
+ const next = new URL(current);
95
+ const base = cleanBase(basePath);
96
+ if (routing === "path") {
97
+ next.pathname = `${base}/${id}`;
98
+ next.searchParams.delete("tab");
99
+ } else {
100
+ const mountPaths = /* @__PURE__ */ new Set([base, `${base}/`, `${base}/index.html`]);
101
+ if (!mountPaths.has(next.pathname)) next.pathname = `${base}/`;
102
+ next.searchParams.set("tab", id);
103
+ }
104
+ next.hash = "";
105
+ return `${next.pathname}${next.search}${next.hash}`;
106
+ }
72
107
  var S = {
73
108
  gate: {
74
109
  minHeight: "100vh",
@@ -134,21 +169,18 @@ function Gate(props) {
134
169
  ] }) });
135
170
  }
136
171
  function AdminShell(props) {
137
- const { sections, basePath, brand, client, getToken, signOut, email } = props;
172
+ const { sections, basePath, brand, client, getToken, signOut, email, routing = "query" } = props;
138
173
  const sectionFromPath = useCallback(() => {
139
- const fallback = sections[0]?.id ?? "";
140
- if (typeof window === "undefined") return fallback;
141
- const rest = window.location.pathname.slice(basePath.length).replace(/^\//, "");
142
- const id = rest.split("/")[0] ?? "";
143
- return sections.some((s) => s.id === id) ? id : fallback;
174
+ if (typeof window === "undefined") return sections[0]?.id ?? "";
175
+ return adminSectionFromUrl(new URL(window.location.href), basePath, sections.map((section2) => section2.id));
144
176
  }, [sections, basePath]);
145
177
  const [section, setSection] = useState2(sectionFromPath);
146
178
  const go = useCallback(
147
179
  (id) => {
148
- window.history.pushState(null, "", `${basePath}/${id}`);
180
+ window.history.pushState(null, "", adminSectionHref(new URL(window.location.href), basePath, id, routing));
149
181
  setSection(id);
150
182
  },
151
- [basePath]
183
+ [basePath, routing]
152
184
  );
153
185
  useEffect2(() => {
154
186
  const onPop = () => setSection(sectionFromPath());
@@ -179,113 +211,12 @@ function AdminShell(props) {
179
211
  ] });
180
212
  }
181
213
 
182
- // src/ui/admin.tsx
183
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
184
- function Authed(props) {
185
- const { sections, basePath, brand, crmBasePath, apiBase } = props;
186
- const { getToken, signOut } = useClerkAuth();
187
- const client = useMemo(
188
- () => new CrmClient({
189
- basePath: crmBasePath,
190
- headers: async () => {
191
- const t = await getToken();
192
- return t ? { authorization: `Bearer ${t}` } : {};
193
- }
194
- }),
195
- [getToken, crmBasePath]
196
- );
197
- const [state, setState] = useState3({
198
- status: "checking"
199
- });
200
- useEffect3(() => {
201
- let live = true;
202
- void (async () => {
203
- try {
204
- const t = await getToken();
205
- const res = await fetch(`${apiBase}/api/me`, { headers: t ? { authorization: `Bearer ${t}` } : {} });
206
- const body = await res.json().catch(() => ({}));
207
- if (live) setState({ status: body.authorized ? "ok" : "denied", email: body.email ?? null });
208
- } catch {
209
- if (live) setState({ status: "denied" });
210
- }
211
- })();
212
- return () => {
213
- live = false;
214
- };
215
- }, [getToken, apiBase]);
216
- if (state.status === "checking") {
217
- return /* @__PURE__ */ jsx3(Gate, { brand, children: /* @__PURE__ */ jsx3("p", { style: S.muted, children: "Checking access\u2026" }) });
218
- }
219
- if (state.status === "denied") {
220
- return /* @__PURE__ */ jsx3(Gate, { brand, children: /* @__PURE__ */ jsxs3("div", { className: "card", style: S.card, children: [
221
- /* @__PURE__ */ jsx3("h2", { style: { marginTop: 0 }, children: "Not authorized" }),
222
- /* @__PURE__ */ jsxs3("p", { style: S.muted, children: [
223
- state.email ? `${state.email} isn't` : "This account isn't",
224
- " on the admin list. Ask an existing admin to add you in odla Studio."
225
- ] }),
226
- /* @__PURE__ */ jsx3("button", { className: "btn secondary", onClick: () => signOut(), style: { marginTop: 12 }, children: "Sign out" })
227
- ] }) });
228
- }
229
- return /* @__PURE__ */ jsx3(
230
- AdminShell,
231
- {
232
- sections,
233
- basePath,
234
- brand,
235
- client,
236
- getToken,
237
- signOut,
238
- email: state.email ?? null
239
- }
240
- );
241
- }
242
- function ChapterAdmin(props) {
243
- const sections = props.sections;
244
- const basePath = props.basePath ?? "/admin";
245
- const brand = props.brand ?? { name: "Admin" };
246
- const crmBasePath = props.crmBasePath ?? "/api/crm";
247
- const apiBase = props.apiBase ?? "";
248
- const [pk, setPk] = useState3(void 0);
249
- useEffect3(() => {
250
- let live = true;
251
- void (async () => {
252
- try {
253
- const res = await fetch(`${apiBase}/api/config`);
254
- const body = await res.json();
255
- if (live) setPk(body.clerkPublishableKey ?? null);
256
- } catch {
257
- if (live) setPk(null);
258
- }
259
- })();
260
- return () => {
261
- live = false;
262
- };
263
- }, [apiBase]);
264
- if (pk === void 0) {
265
- return /* @__PURE__ */ jsx3(Gate, { brand, children: /* @__PURE__ */ jsx3("p", { style: S.muted, children: "Loading\u2026" }) });
266
- }
267
- if (!pk) {
268
- return /* @__PURE__ */ jsx3(Gate, { brand, children: /* @__PURE__ */ jsxs3("div", { className: "card", style: S.card, children: [
269
- /* @__PURE__ */ jsx3("h2", { style: { marginTop: 0 }, children: "Sign-in not configured" }),
270
- /* @__PURE__ */ jsx3("p", { style: S.muted, children: "No Clerk publishable key is set for this environment yet." })
271
- ] }) });
272
- }
273
- return /* @__PURE__ */ jsxs3(ClerkGate, { publishableKey: pk, appearance: clerkAppearanceFromTokens(), afterSignOutUrl: "/", children: [
274
- /* @__PURE__ */ jsx3(SignedOut, { children: /* @__PURE__ */ jsx3(Gate, { brand, tagline: "Admin sign-in \u2014 invite only", children: /* @__PURE__ */ jsx3(SignIn, { routing: "hash", forceRedirectUrl: basePath, signUpForceRedirectUrl: basePath }) }) }),
275
- /* @__PURE__ */ jsx3(SignedIn, { children: /* @__PURE__ */ jsx3(Authed, { sections, basePath, brand, crmBasePath, apiBase }) })
276
- ] });
277
- }
278
-
279
- // src/ui/admin-people.tsx
280
- import { useState as useState6 } from "react";
281
- import { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from "@odla-ai/crm/ui";
282
-
283
- // src/ui/admin-record-actions.tsx
284
- import { useEffect as useEffect5, useState as useState5 } from "react";
285
- import { Button, Field, Select } from "@odla-ai/ui/components";
214
+ // src/ui/admin-availability.tsx
215
+ import { useState as useState4 } from "react";
216
+ import { Button, Checkbox, Field, Input } from "@odla-ai/ui/components";
286
217
 
287
218
  // src/ui/admin-api.ts
288
- import { useCallback as useCallback2, useEffect as useEffect4, useState as useState4 } from "react";
219
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
289
220
  async function adminFetch(getToken, path, init) {
290
221
  const token = await getToken();
291
222
  const headers = new Headers(init?.headers);
@@ -297,11 +228,11 @@ async function adminFetch(getToken, path, init) {
297
228
  return body;
298
229
  }
299
230
  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
- useEffect4(() => {
231
+ const [data, setData] = useState3(null);
232
+ const [loading, setLoading] = useState3(true);
233
+ const [error, setError] = useState3(null);
234
+ const [nonce, setNonce] = useState3(0);
235
+ useEffect3(() => {
305
236
  let live = true;
306
237
  setLoading(true);
307
238
  setError(null);
@@ -314,271 +245,193 @@ function useAdminResource(getToken, path) {
314
245
  return { data, loading, error, refresh };
315
246
  }
316
247
 
317
- // src/ui/admin-record-actions.tsx
248
+ // src/ui/admin-availability.tsx
249
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
250
+ var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
251
+ function AvailabilityBody({ ctx }) {
252
+ const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
253
+ const [draft, setDraft] = useState4(null);
254
+ const [busy, setBusy] = useState4(false);
255
+ const [note, setNote] = useState4(null);
256
+ const [errors, setErrors] = useState4({});
257
+ const cfg = draft ?? data?.scheduling ?? null;
258
+ if (loading) return /* @__PURE__ */ jsx3(AdminNote, { children: "Loading\u2026" });
259
+ if (error || !cfg) return /* @__PURE__ */ jsxs3(AdminNote, { children: [
260
+ "Couldn\u2019t load availability",
261
+ error ? `: ${error}` : "",
262
+ "."
263
+ ] });
264
+ const edit = (patch) => setDraft({ ...cfg, ...patch });
265
+ const toggleDay = (d) => edit({ days: cfg.days.includes(d) ? cfg.days.filter((x) => x !== d) : [...cfg.days, d].sort() });
266
+ const num = (v, fallback) => Number.isFinite(Number(v)) ? Number(v) : fallback;
267
+ const save = async () => {
268
+ setBusy(true);
269
+ setNote(null);
270
+ setErrors({});
271
+ const token = await ctx.getToken();
272
+ try {
273
+ const r = await fetch("/api/admin/scheduling", { method: "PUT", headers: { authorization: `Bearer ${token ?? ""}`, "content-type": "application/json" }, body: JSON.stringify(cfg) });
274
+ const body = await r.json().catch(() => ({}));
275
+ if (!r.ok) {
276
+ setErrors(body.errors ?? {});
277
+ setNote(body.error ?? `save failed (${r.status})`);
278
+ return;
279
+ }
280
+ setDraft(null);
281
+ refresh();
282
+ setNote("Saved.");
283
+ } catch (e) {
284
+ setNote(e instanceof Error ? e.message : String(e));
285
+ } finally {
286
+ setBusy(false);
287
+ }
288
+ };
289
+ const numField = (key, label) => /* @__PURE__ */ jsx3(Field, { label, error: errors[key], children: /* @__PURE__ */ jsx3(Input, { type: "number", value: cfg[key], onChange: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
290
+ return /* @__PURE__ */ jsx3(AdminPage, { children: /* @__PURE__ */ jsxs3(Panel, { title: "Booking availability", actions: note ? /* @__PURE__ */ jsx3("span", { className: "muted", children: note }) : void 0, children: [
291
+ /* @__PURE__ */ jsx3(Field, { label: "Days", error: errors.days, children: /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: DAYS.map((d, i) => /* @__PURE__ */ jsx3(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
292
+ /* @__PURE__ */ jsxs3("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
293
+ numField("startHour", "Start hour (0\u201324)"),
294
+ numField("endHour", "End hour (0\u201324)"),
295
+ numField("slotMinutes", "Slot length (minutes)"),
296
+ numField("minNoticeHours", "Minimum notice (hours)"),
297
+ numField("windowDays", "Booking window (days)"),
298
+ /* @__PURE__ */ jsx3(Field, { label: "Timezone", error: errors.timezone, children: /* @__PURE__ */ jsx3(Input, { value: cfg.timezone, onChange: (e) => edit({ timezone: e.target.value }) }) })
299
+ ] }),
300
+ /* @__PURE__ */ jsx3(Field, { label: "Event summary template", error: errors.summaryTemplate, children: /* @__PURE__ */ jsx3(Input, { value: cfg.summaryTemplate, onChange: (e) => edit({ summaryTemplate: e.target.value }) }) }),
301
+ /* @__PURE__ */ jsx3("div", { children: /* @__PURE__ */ jsx3(Button, { disabled: !draft || busy, onClick: () => void save(), children: "Save availability" }) })
302
+ ] }) });
303
+ }
304
+ function availabilitySection(options = {}) {
305
+ const { id = "availability", label = "Availability" } = options;
306
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx3(AvailabilityBody, { ctx }) };
307
+ }
308
+
309
+ // src/ui/admin-billing.tsx
310
+ import { DataTable, StatBand } from "@odla-ai/ui/components";
318
311
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
319
- function RecordActions({ getToken, record, roles, onChanged }) {
320
- const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
321
- const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
322
- const [access, setAccess] = useState5(null);
312
+ var money = (cents) => `$${(cents / 100).toLocaleString()}`;
313
+ var date = (t) => t ? new Date(t).toLocaleDateString([], { dateStyle: "medium" }) : "\u2014";
314
+ var COLUMNS = [
315
+ { key: "name", header: "Name", cell: (r) => r.name, sortAs: "string" },
316
+ { key: "email", header: "Email", cell: (r) => r.email },
317
+ { key: "applicationStatus", header: "Application", cell: (r) => r.applicationStatus },
318
+ { key: "subscriptionStatus", header: "Subscription", cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? " \xB7 cancelling" : ""}` : "\u2014" },
319
+ { key: "amountCents", header: "Amount", cell: (r) => `${money(r.amountCents)}/${r.interval === "month" ? "mo" : "yr"}`, sortAs: "number", sortValue: (r) => r.amountCents },
320
+ { key: "renewalAt", header: "Renews", cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
321
+ ];
322
+ function BillingBody({ ctx }) {
323
+ const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/billing");
324
+ if (loading) return /* @__PURE__ */ jsx4(AdminNote, { children: "Loading\u2026" });
325
+ if (error || !data) return /* @__PURE__ */ jsxs4(AdminNote, { children: [
326
+ "Couldn\u2019t load billing",
327
+ error ? `: ${error}` : "",
328
+ "."
329
+ ] });
330
+ if (!data.billingReady) return /* @__PURE__ */ jsx4(AdminNote, { children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." });
331
+ const s = data.summary;
332
+ const stats = s ? [
333
+ { value: s.activeCount, label: "Active" },
334
+ { value: money(s.annualizedCents), label: "Annualized" },
335
+ { value: s.renewingSoonCount, label: "Renewing \u226460d" },
336
+ { value: s.pastDueCount, label: "Past due" }
337
+ ] : [];
338
+ return /* @__PURE__ */ jsxs4(AdminPage, { children: [
339
+ s ? /* @__PURE__ */ jsx4(StatBand, { stats }) : null,
340
+ /* @__PURE__ */ jsxs4(Panel, { title: "Subscriptions", actions: data.testMode ? /* @__PURE__ */ jsx4("span", { className: "badge", children: "Stripe test mode" }) : void 0, children: [
341
+ data.truncated ? /* @__PURE__ */ jsx4("p", { className: "badge", children: "Showing the first 100 subscriptions \u2014 the list is truncated." }) : null,
342
+ /* @__PURE__ */ jsx4(DataTable, { rows: data.rows, columns: COLUMNS, rowKey: (r) => r.id })
343
+ ] })
344
+ ] });
345
+ }
346
+ function billingSection(options = {}) {
347
+ const { id = "billing", label = "Billing" } = options;
348
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx4(BillingBody, { ctx }) };
349
+ }
350
+
351
+ // src/ui/admin-dashboard.tsx
352
+ import { StatBand as StatBand2 } from "@odla-ai/ui/components";
353
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
354
+ var money2 = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
355
+ var when = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
356
+ function DashboardBody({ ctx }) {
357
+ const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/dashboard");
358
+ if (loading) return /* @__PURE__ */ jsx5(AdminNote, { children: "Loading\u2026" });
359
+ if (error || !data) return /* @__PURE__ */ jsxs5(AdminNote, { children: [
360
+ "Couldn\u2019t load the dashboard",
361
+ error ? `: ${error}` : "",
362
+ "."
363
+ ] });
364
+ const kpis = [
365
+ { value: data.applications.total, label: "Applications", description: `+${data.applications.last7} in the last 7 days` },
366
+ { value: data.calls.upcoming, label: "Upcoming calls", description: data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : "all on track" }
367
+ ];
368
+ if (data.revenue.billingReady) {
369
+ kpis.push({ value: data.revenue.activeCount ?? 0, label: "Active members", description: "paid subscriptions" });
370
+ kpis.push({ value: money2(data.revenue.annualRunRateCents ?? 0), label: "Annual run rate", description: "from active subscriptions" });
371
+ }
372
+ const stages = Object.entries(data.pipeline).map(([stage, count]) => ({
373
+ value: count,
374
+ label: stage,
375
+ description: data.pipelineDelta[stage] ? `+${data.pipelineDelta[stage]} this week` : void 0
376
+ }));
377
+ return /* @__PURE__ */ jsxs5(AdminPage, { children: [
378
+ /* @__PURE__ */ jsx5(StatBand2, { stats: kpis }),
379
+ /* @__PURE__ */ jsx5(Panel, { title: "Pipeline", children: /* @__PURE__ */ jsx5(StatBand2, { stats: stages }) }),
380
+ /* @__PURE__ */ jsx5(Panel, { title: /* @__PURE__ */ jsxs5(Fragment2, { children: [
381
+ "Upcoming calls ",
382
+ /* @__PURE__ */ jsxs5("span", { className: "muted", style: { fontWeight: 400 }, children: [
383
+ "\xB7 ",
384
+ data.timezone
385
+ ] })
386
+ ] }), children: data.agenda.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "muted", style: { margin: 0 }, children: "No calls scheduled." }) : /* @__PURE__ */ jsx5("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 10 }, children: data.agenda.map((m) => /* @__PURE__ */ jsxs5("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
387
+ /* @__PURE__ */ jsx5("strong", { style: { minWidth: 170 }, children: when(m.startAt) }),
388
+ /* @__PURE__ */ jsxs5("span", { children: [
389
+ m.name,
390
+ m.email ? ` \xB7 ${m.email}` : ""
391
+ ] }),
392
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs5("span", { className: "badge", children: [
393
+ "drift: ",
394
+ m.drift
395
+ ] }) : null,
396
+ m.meetUrl ? /* @__PURE__ */ jsx5("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null
397
+ ] }, m.id)) }) })
398
+ ] });
399
+ }
400
+ function dashboardSection(options = {}) {
401
+ const { id = "dashboard", label = "Dashboard" } = options;
402
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx5(DashboardBody, { ctx }) };
403
+ }
404
+
405
+ // src/ui/admin-email.tsx
406
+ import { useState as useState5 } from "react";
407
+ import { Button as Button2, Checkbox as Checkbox2, DataTable as DataTable2, Field as Field2, Input as Input2, Textarea } from "@odla-ai/ui/components";
408
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
409
+ var LOG_COLUMNS = [
410
+ { key: "sentAt", header: "Sent", cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
411
+ { key: "template", header: "Template", cell: (r) => r.template },
412
+ { key: "to", header: "To", cell: (r) => r.to },
413
+ { key: "status", header: "Status", cell: (r) => r.error ? /* @__PURE__ */ jsx6("span", { className: "badge", children: "failed" }) : r.redirected ? /* @__PURE__ */ jsx6("span", { className: "badge", children: "redirected" }) : "delivered" }
414
+ ];
415
+ function EmailBody({ ctx }) {
416
+ const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
417
+ const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
418
+ const [draft, setDraft] = useState5(null);
323
419
  const [busy, setBusy] = useState5(null);
324
420
  const [note, setNote] = useState5(null);
325
- useEffect5(() => {
326
- if (!userId) {
327
- setAccess(null);
328
- return;
329
- }
330
- let live = true;
331
- adminFetch(getToken, `/api/admin/people/access?userId=${encodeURIComponent(userId)}`).then((a) => live && setAccess(a)).catch(() => live && setAccess(null));
332
- return () => {
333
- live = false;
334
- };
335
- }, [getToken, userId]);
336
- const act = async (key, run, ok) => {
421
+ const data = draft ?? cfg.data;
422
+ if (cfg.loading) return /* @__PURE__ */ jsx6(AdminNote, { children: "Loading\u2026" });
423
+ if (cfg.error || !data) return /* @__PURE__ */ jsxs6(AdminNote, { children: [
424
+ "Couldn\u2019t load email config",
425
+ cfg.error ? `: ${cfg.error}` : "",
426
+ "."
427
+ ] });
428
+ const edit = (patch) => setDraft({ ...data, ...patch });
429
+ const editTemplate = (key, patch) => edit({ emailTemplates: { ...data.emailTemplates, [key]: { ...data.emailTemplates[key], ...patch } } });
430
+ const run = async (key, fn) => {
337
431
  setBusy(key);
338
432
  setNote(null);
339
433
  try {
340
- await run();
341
- setNote(ok);
342
- onChanged();
343
- } catch (e) {
344
- setNote(e instanceof Error ? e.message : String(e));
345
- } finally {
346
- setBusy(null);
347
- }
348
- };
349
- const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), `Role set to ${role}.`);
350
- const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), "Approved.");
351
- const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), "Refund issued.");
352
- if (!userId && !applicationId) return null;
353
- return /* @__PURE__ */ jsxs4("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
354
- /* @__PURE__ */ jsx4("h3", { style: { margin: 0, fontSize: 16 }, children: "Access & lifecycle" }),
355
- userId ? /* @__PURE__ */ jsx4(Field, { label: "Role", hint: access?.superAdmin ? "Super-admin \u2014 managed in odla Studio." : void 0, children: /* @__PURE__ */ jsx4(
356
- Select,
357
- {
358
- value: access?.role ?? "",
359
- disabled: busy === "role" || access?.superAdmin,
360
- options: roles.map((r) => ({ value: r, label: r })),
361
- onChange: (e) => void setRole(e.target.value)
362
- }
363
- ) }) : /* @__PURE__ */ jsx4("p", { className: "muted", style: { margin: 0 }, children: "No linked account yet \u2014 role is set once they sign in." }),
364
- applicationId ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8 }, children: [
365
- /* @__PURE__ */ jsx4(Button, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: "Approve" }),
366
- /* @__PURE__ */ jsx4(Button, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: "Refund" })
367
- ] }) : null,
368
- note ? /* @__PURE__ */ jsx4("p", { className: "muted", style: { margin: 0 }, children: note }) : null
369
- ] });
370
- }
371
-
372
- // src/ui/admin-people.tsx
373
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
374
- var DEFAULT_ROLES = ["provisional", "member", "admin"];
375
- function PeopleBody(props) {
376
- const { crm, client, type, getToken, roles, lifecycle } = props;
377
- const hookClient = client;
378
- const query = useCrmQuery(hookClient, type);
379
- const [openId, setOpenId] = useState6(null);
380
- const record = useCrmRecord(hookClient, openId);
381
- const [saving, setSaving] = useState6(false);
382
- const saveFields = async (input) => {
383
- if (!openId) return;
384
- setSaving(true);
385
- try {
386
- await client.updateRecord(openId, { input });
387
- record.refresh();
388
- query.refresh();
389
- } finally {
390
- setSaving(false);
391
- }
392
- };
393
- const moveStage = async (to) => {
394
- if (!openId) return;
395
- await client.setStage(openId, to);
396
- record.refresh();
397
- query.refresh();
398
- };
399
- const addTag = async (tag) => {
400
- if (openId) {
401
- await client.addTag(openId, tag);
402
- record.refresh();
403
- }
404
- };
405
- const removeTag = async (tag) => {
406
- if (openId) {
407
- await client.removeTag(openId, tag);
408
- record.refresh();
409
- }
410
- };
411
- return /* @__PURE__ */ jsxs5(AdminPage, { children: [
412
- /* @__PURE__ */ jsx5(CrmList, { crm, type, query, onOpenRecord: (r) => setOpenId(r.id) }),
413
- record.detail ? /* @__PURE__ */ jsxs5("div", { children: [
414
- /* @__PURE__ */ jsx5(
415
- RecordPanel,
416
- {
417
- crm,
418
- detail: record.detail,
419
- onSaveFields: saveFields,
420
- onMoveStage: moveStage,
421
- onAddTag: addTag,
422
- onRemoveTag: removeTag,
423
- saving
424
- }
425
- ),
426
- lifecycle ? /* @__PURE__ */ jsx5(
427
- RecordActions,
428
- {
429
- getToken,
430
- record: record.detail.record,
431
- roles,
432
- onChanged: () => {
433
- record.refresh();
434
- query.refresh();
435
- }
436
- }
437
- ) : null
438
- ] }) : null
439
- ] });
440
- }
441
- function collectionSection(options) {
442
- const { crm, type } = options;
443
- let fallbackLabel = type;
444
- try {
445
- fallbackLabel = crm.type(type).labelPlural ?? crm.type(type).label ?? type;
446
- } catch {
447
- }
448
- const { id = type, label = fallbackLabel, lifecycle = type === "person", roles = DEFAULT_ROLES } = options;
449
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx5(PeopleBody, { crm, client: ctx.client, type, getToken: ctx.getToken, roles, lifecycle }) };
450
- }
451
- function peopleSection(options) {
452
- const { crm, id = "people", label = "People", type = "person", roles } = options;
453
- return collectionSection({ crm, type, id, label, ...roles ? { roles } : {} });
454
- }
455
-
456
- // src/ui/admin-dashboard.tsx
457
- import { StatBand } from "@odla-ai/ui/components";
458
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
459
- var money = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
460
- var when = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
461
- function DashboardBody({ ctx }) {
462
- const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/dashboard");
463
- if (loading) return /* @__PURE__ */ jsx6(AdminNote, { children: "Loading\u2026" });
464
- if (error || !data) return /* @__PURE__ */ jsxs6(AdminNote, { children: [
465
- "Couldn\u2019t load the dashboard",
466
- error ? `: ${error}` : "",
467
- "."
468
- ] });
469
- const kpis = [
470
- { value: data.applications.total, label: "Applications", description: `+${data.applications.last7} in the last 7 days` },
471
- { value: data.calls.upcoming, label: "Upcoming calls", description: data.calls.needsAttention ? `${data.calls.needsAttention} need attention` : "all on track" }
472
- ];
473
- if (data.revenue.billingReady) {
474
- kpis.push({ value: data.revenue.activeCount ?? 0, label: "Active members", description: "paid subscriptions" });
475
- kpis.push({ value: money(data.revenue.annualRunRateCents ?? 0), label: "Annual run rate", description: "from active subscriptions" });
476
- }
477
- const stages = Object.entries(data.pipeline).map(([stage, count]) => ({
478
- value: count,
479
- label: stage,
480
- description: data.pipelineDelta[stage] ? `+${data.pipelineDelta[stage]} this week` : void 0
481
- }));
482
- return /* @__PURE__ */ jsxs6(AdminPage, { children: [
483
- /* @__PURE__ */ jsx6(StatBand, { stats: kpis }),
484
- /* @__PURE__ */ jsx6(Panel, { title: "Pipeline", children: /* @__PURE__ */ jsx6(StatBand, { stats: stages }) }),
485
- /* @__PURE__ */ jsx6(Panel, { title: /* @__PURE__ */ jsxs6(Fragment2, { children: [
486
- "Upcoming calls ",
487
- /* @__PURE__ */ jsxs6("span", { className: "muted", style: { fontWeight: 400 }, children: [
488
- "\xB7 ",
489
- data.timezone
490
- ] })
491
- ] }), children: data.agenda.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "muted", style: { margin: 0 }, children: "No calls scheduled." }) : /* @__PURE__ */ jsx6("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 10 }, children: data.agenda.map((m) => /* @__PURE__ */ jsxs6("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
492
- /* @__PURE__ */ jsx6("strong", { style: { minWidth: 170 }, children: when(m.startAt) }),
493
- /* @__PURE__ */ jsxs6("span", { children: [
494
- m.name,
495
- m.email ? ` \xB7 ${m.email}` : ""
496
- ] }),
497
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs6("span", { className: "badge", children: [
498
- "drift: ",
499
- m.drift
500
- ] }) : null,
501
- m.meetUrl ? /* @__PURE__ */ jsx6("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null
502
- ] }, m.id)) }) })
503
- ] });
504
- }
505
- function dashboardSection(options = {}) {
506
- const { id = "dashboard", label = "Dashboard" } = options;
507
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx6(DashboardBody, { ctx }) };
508
- }
509
-
510
- // src/ui/admin-billing.tsx
511
- import { DataTable, StatBand as StatBand2 } from "@odla-ai/ui/components";
512
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
513
- var money2 = (cents) => `$${(cents / 100).toLocaleString()}`;
514
- var date = (t) => t ? new Date(t).toLocaleDateString([], { dateStyle: "medium" }) : "\u2014";
515
- var COLUMNS = [
516
- { key: "name", header: "Name", cell: (r) => r.name, sortAs: "string" },
517
- { key: "email", header: "Email", cell: (r) => r.email },
518
- { key: "applicationStatus", header: "Application", cell: (r) => r.applicationStatus },
519
- { key: "subscriptionStatus", header: "Subscription", cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? " \xB7 cancelling" : ""}` : "\u2014" },
520
- { key: "amountCents", header: "Amount", cell: (r) => `${money2(r.amountCents)}/${r.interval === "month" ? "mo" : "yr"}`, sortAs: "number", sortValue: (r) => r.amountCents },
521
- { key: "renewalAt", header: "Renews", cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
522
- ];
523
- function BillingBody({ ctx }) {
524
- const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/billing");
525
- if (loading) return /* @__PURE__ */ jsx7(AdminNote, { children: "Loading\u2026" });
526
- if (error || !data) return /* @__PURE__ */ jsxs7(AdminNote, { children: [
527
- "Couldn\u2019t load billing",
528
- error ? `: ${error}` : "",
529
- "."
530
- ] });
531
- if (!data.billingReady) return /* @__PURE__ */ jsx7(AdminNote, { children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." });
532
- const s = data.summary;
533
- const stats = s ? [
534
- { value: s.activeCount, label: "Active" },
535
- { value: money2(s.annualizedCents), label: "Annualized" },
536
- { value: s.renewingSoonCount, label: "Renewing \u226460d" },
537
- { value: s.pastDueCount, label: "Past due" }
538
- ] : [];
539
- return /* @__PURE__ */ jsxs7(AdminPage, { children: [
540
- s ? /* @__PURE__ */ jsx7(StatBand2, { stats }) : null,
541
- /* @__PURE__ */ jsxs7(Panel, { title: "Subscriptions", actions: data.testMode ? /* @__PURE__ */ jsx7("span", { className: "badge", children: "Stripe test mode" }) : void 0, children: [
542
- data.truncated ? /* @__PURE__ */ jsx7("p", { className: "badge", children: "Showing the first 100 subscriptions \u2014 the list is truncated." }) : null,
543
- /* @__PURE__ */ jsx7(DataTable, { rows: data.rows, columns: COLUMNS, rowKey: (r) => r.id })
544
- ] })
545
- ] });
546
- }
547
- function billingSection(options = {}) {
548
- const { id = "billing", label = "Billing" } = options;
549
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx7(BillingBody, { ctx }) };
550
- }
551
-
552
- // src/ui/admin-email.tsx
553
- import { useState as useState7 } from "react";
554
- import { Button as Button2, Checkbox, DataTable as DataTable2, Field as Field2, Input, Textarea } from "@odla-ai/ui/components";
555
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
556
- var LOG_COLUMNS = [
557
- { key: "sentAt", header: "Sent", cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
558
- { key: "template", header: "Template", cell: (r) => r.template },
559
- { key: "to", header: "To", cell: (r) => r.to },
560
- { key: "status", header: "Status", cell: (r) => r.error ? /* @__PURE__ */ jsx8("span", { className: "badge", children: "failed" }) : r.redirected ? /* @__PURE__ */ jsx8("span", { className: "badge", children: "redirected" }) : "delivered" }
561
- ];
562
- function EmailBody({ ctx }) {
563
- const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
564
- const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
565
- const [draft, setDraft] = useState7(null);
566
- const [busy, setBusy] = useState7(null);
567
- const [note, setNote] = useState7(null);
568
- const data = draft ?? cfg.data;
569
- if (cfg.loading) return /* @__PURE__ */ jsx8(AdminNote, { children: "Loading\u2026" });
570
- if (cfg.error || !data) return /* @__PURE__ */ jsxs8(AdminNote, { children: [
571
- "Couldn\u2019t load email config",
572
- cfg.error ? `: ${cfg.error}` : "",
573
- "."
574
- ] });
575
- const edit = (patch) => setDraft({ ...data, ...patch });
576
- const editTemplate = (key, patch) => edit({ emailTemplates: { ...data.emailTemplates, [key]: { ...data.emailTemplates[key], ...patch } } });
577
- const run = async (key, fn) => {
578
- setBusy(key);
579
- setNote(null);
580
- try {
581
- await fn();
434
+ await fn();
582
435
  } catch (e) {
583
436
  setNote(e instanceof Error ? e.message : String(e));
584
437
  } finally {
@@ -596,52 +449,52 @@ function EmailBody({ ctx }) {
596
449
  setNote(r.ok ? `Sent ${template} to ${r.to}${r.redirected ? " (dev-redirected)" : ""}.` : "Test send did not deliver.");
597
450
  log.refresh();
598
451
  });
599
- return /* @__PURE__ */ jsxs8(AdminPage, { children: [
600
- /* @__PURE__ */ jsxs8(
452
+ return /* @__PURE__ */ jsxs6(AdminPage, { children: [
453
+ /* @__PURE__ */ jsxs6(
601
454
  Panel,
602
455
  {
603
456
  title: "Delivery",
604
- actions: /* @__PURE__ */ jsxs8("span", { className: "muted", children: [
457
+ actions: /* @__PURE__ */ jsxs6("span", { className: "muted", children: [
605
458
  data.envName,
606
459
  " \xB7 ",
607
460
  data.transport,
608
461
  data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
609
462
  ] }),
610
463
  children: [
611
- /* @__PURE__ */ jsx8(Field2, { label: "Notification address", children: /* @__PURE__ */ jsx8(Input, { value: data.notificationEmail, onChange: (e) => edit({ notificationEmail: e.target.value }) }) }),
612
- /* @__PURE__ */ jsx8(Field2, { label: "Reply-to", children: /* @__PURE__ */ jsx8(Input, { value: data.replyTo, onChange: (e) => edit({ replyTo: e.target.value }) }) }),
613
- /* @__PURE__ */ jsx8(Field2, { label: "Debug inbox", hint: "Non-prod sends redirect here.", children: /* @__PURE__ */ jsx8(Input, { value: data.debugEmail, onChange: (e) => edit({ debugEmail: e.target.value }) }) })
464
+ /* @__PURE__ */ jsx6(Field2, { label: "Notification address", children: /* @__PURE__ */ jsx6(Input2, { value: data.notificationEmail, onChange: (e) => edit({ notificationEmail: e.target.value }) }) }),
465
+ /* @__PURE__ */ jsx6(Field2, { label: "Reply-to", children: /* @__PURE__ */ jsx6(Input2, { value: data.replyTo, onChange: (e) => edit({ replyTo: e.target.value }) }) }),
466
+ /* @__PURE__ */ jsx6(Field2, { label: "Debug inbox", hint: "Non-prod sends redirect here.", children: /* @__PURE__ */ jsx6(Input2, { value: data.debugEmail, onChange: (e) => edit({ debugEmail: e.target.value }) }) })
614
467
  ]
615
468
  }
616
469
  ),
617
- Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs8(Panel, { title: key, actions: /* @__PURE__ */ jsx8(Button2, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: "Send test" }), children: [
618
- /* @__PURE__ */ jsx8(Checkbox, { label: "Enabled", checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
619
- /* @__PURE__ */ jsx8(Field2, { label: "Subject", children: /* @__PURE__ */ jsx8(Input, { value: t.subject, onChange: (e) => editTemplate(key, { subject: e.target.value }) }) }),
620
- /* @__PURE__ */ jsx8(Field2, { label: "Body", children: /* @__PURE__ */ jsx8(Textarea, { prose: true, rows: 5, value: t.text, onChange: (e) => editTemplate(key, { text: e.target.value }) }) })
470
+ Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs6(Panel, { title: key, actions: /* @__PURE__ */ jsx6(Button2, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: "Send test" }), children: [
471
+ /* @__PURE__ */ jsx6(Checkbox2, { label: "Enabled", checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
472
+ /* @__PURE__ */ jsx6(Field2, { label: "Subject", children: /* @__PURE__ */ jsx6(Input2, { value: t.subject, onChange: (e) => editTemplate(key, { subject: e.target.value }) }) }),
473
+ /* @__PURE__ */ jsx6(Field2, { label: "Body", children: /* @__PURE__ */ jsx6(Textarea, { prose: true, rows: 5, value: t.text, onChange: (e) => editTemplate(key, { text: e.target.value }) }) })
621
474
  ] }, key)),
622
- /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
623
- /* @__PURE__ */ jsx8(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: "Save changes" }),
624
- note ? /* @__PURE__ */ jsx8("span", { className: "muted", children: note }) : null
475
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
476
+ /* @__PURE__ */ jsx6(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: "Save changes" }),
477
+ note ? /* @__PURE__ */ jsx6("span", { className: "muted", children: note }) : null
625
478
  ] }),
626
- /* @__PURE__ */ jsx8(Panel, { title: "Send log", children: /* @__PURE__ */ jsx8(DataTable2, { rows: log.data?.sends ?? [], columns: LOG_COLUMNS, rowKey: (r) => r.id }) })
479
+ /* @__PURE__ */ jsx6(Panel, { title: "Send log", children: /* @__PURE__ */ jsx6(DataTable2, { rows: log.data?.sends ?? [], columns: LOG_COLUMNS, rowKey: (r) => r.id }) })
627
480
  ] });
628
481
  }
629
482
  function emailSection(options = {}) {
630
483
  const { id = "email", label = "Email" } = options;
631
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx8(EmailBody, { ctx }) };
484
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx6(EmailBody, { ctx }) };
632
485
  }
633
486
 
634
487
  // src/ui/admin-meetings.tsx
635
- import { useState as useState8 } from "react";
488
+ import { useState as useState6 } from "react";
636
489
  import { Button as Button3 } from "@odla-ai/ui/components";
637
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
490
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
638
491
  var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
639
492
  function MeetingsBody({ ctx }) {
640
493
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
641
- const [busy, setBusy] = useState8(null);
642
- const [note, setNote] = useState8(null);
643
- if (loading) return /* @__PURE__ */ jsx9(AdminNote, { children: "Loading\u2026" });
644
- if (error || !data) return /* @__PURE__ */ jsxs9(AdminNote, { children: [
494
+ const [busy, setBusy] = useState6(null);
495
+ const [note, setNote] = useState6(null);
496
+ if (loading) return /* @__PURE__ */ jsx7(AdminNote, { children: "Loading\u2026" });
497
+ if (error || !data) return /* @__PURE__ */ jsxs7(AdminNote, { children: [
645
498
  "Couldn\u2019t load meetings",
646
499
  error ? `: ${error}` : "",
647
500
  "."
@@ -672,93 +525,377 @@ function MeetingsBody({ ctx }) {
672
525
  }
673
526
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
674
527
  };
675
- return /* @__PURE__ */ jsx9(AdminPage, { children: /* @__PURE__ */ jsx9(Panel, { title: /* @__PURE__ */ jsxs9(Fragment3, { children: [
528
+ return /* @__PURE__ */ jsx7(AdminPage, { children: /* @__PURE__ */ jsx7(Panel, { title: /* @__PURE__ */ jsxs7(Fragment3, { children: [
676
529
  "Agenda ",
677
- /* @__PURE__ */ jsxs9("span", { className: "muted", style: { fontWeight: 400 }, children: [
530
+ /* @__PURE__ */ jsxs7("span", { className: "muted", style: { fontWeight: 400 }, children: [
678
531
  "\xB7 ",
679
532
  data.timezone
680
533
  ] })
681
- ] }), actions: note ? /* @__PURE__ */ jsx9("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx9("p", { className: "muted", style: { margin: 0 }, children: "No meetings." }) : /* @__PURE__ */ jsx9("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs9("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
682
- /* @__PURE__ */ jsx9("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
683
- /* @__PURE__ */ jsx9("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
684
- /* @__PURE__ */ jsx9("span", { className: "badge", children: m.status }),
685
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs9("span", { className: "badge", children: [
534
+ ] }), actions: note ? /* @__PURE__ */ jsx7("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "muted", style: { margin: 0 }, children: "No meetings." }) : /* @__PURE__ */ jsx7("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs7("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
535
+ /* @__PURE__ */ jsx7("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
536
+ /* @__PURE__ */ jsx7("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
537
+ /* @__PURE__ */ jsx7("span", { className: "badge", children: m.status }),
538
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs7("span", { className: "badge", children: [
686
539
  "drift: ",
687
540
  m.drift
688
541
  ] }) : null,
689
- m.meetUrl ? /* @__PURE__ */ jsx9("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
690
- m.status === "scheduled" ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
691
- /* @__PURE__ */ jsx9(Button3, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: "Reschedule" }),
692
- /* @__PURE__ */ jsx9(Button3, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: "Cancel" })
542
+ m.meetUrl ? /* @__PURE__ */ jsx7("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
543
+ m.status === "scheduled" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
544
+ /* @__PURE__ */ jsx7(Button3, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: "Reschedule" }),
545
+ /* @__PURE__ */ jsx7(Button3, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: "Cancel" })
693
546
  ] }) : null
694
547
  ] }, m.id)) }) }) });
695
548
  }
696
549
  function meetingsSection(options = {}) {
697
550
  const { id = "meetings", label = "Meetings" } = options;
698
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx9(MeetingsBody, { ctx }) };
551
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx7(MeetingsBody, { ctx }) };
699
552
  }
700
553
 
701
- // src/ui/admin-availability.tsx
554
+ // src/ui/admin-people.tsx
702
555
  import { useState as useState9 } from "react";
703
- import { Button as Button4, Checkbox as Checkbox2, Field as Field3, Input as Input2 } from "@odla-ai/ui/components";
704
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
705
- var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
706
- function AvailabilityBody({ ctx }) {
707
- const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
708
- const [draft, setDraft] = useState9(null);
709
- const [busy, setBusy] = useState9(false);
710
- const [note, setNote] = useState9(null);
711
- const [errors, setErrors] = useState9({});
712
- const cfg = draft ?? data?.scheduling ?? null;
713
- if (loading) return /* @__PURE__ */ jsx10(AdminNote, { children: "Loading\u2026" });
714
- if (error || !cfg) return /* @__PURE__ */ jsxs10(AdminNote, { children: [
715
- "Couldn\u2019t load availability",
716
- error ? `: ${error}` : "",
717
- "."
718
- ] });
719
- const edit = (patch) => setDraft({ ...cfg, ...patch });
720
- const toggleDay = (d) => edit({ days: cfg.days.includes(d) ? cfg.days.filter((x) => x !== d) : [...cfg.days, d].sort() });
721
- const num = (v, fallback) => Number.isFinite(Number(v)) ? Number(v) : fallback;
722
- const save = async () => {
723
- setBusy(true);
556
+ import { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from "@odla-ai/crm/ui";
557
+
558
+ // src/ui/admin-record-actions.tsx
559
+ import { useEffect as useEffect4, useState as useState7 } from "react";
560
+ import { Button as Button4, Field as Field3, Select } from "@odla-ai/ui/components";
561
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
562
+ function RecordActions({ getToken, record, roles, onChanged }) {
563
+ const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
564
+ const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
565
+ const [access, setAccess] = useState7(null);
566
+ const [busy, setBusy] = useState7(null);
567
+ const [note, setNote] = useState7(null);
568
+ useEffect4(() => {
569
+ if (!userId) {
570
+ setAccess(null);
571
+ return;
572
+ }
573
+ let live = true;
574
+ adminFetch(getToken, `/api/admin/people/access?userId=${encodeURIComponent(userId)}`).then((a) => live && setAccess(a)).catch(() => live && setAccess(null));
575
+ return () => {
576
+ live = false;
577
+ };
578
+ }, [getToken, userId]);
579
+ const act = async (key, run, ok) => {
580
+ setBusy(key);
724
581
  setNote(null);
725
- setErrors({});
726
- const token = await ctx.getToken();
727
582
  try {
728
- const r = await fetch("/api/admin/scheduling", { method: "PUT", headers: { authorization: `Bearer ${token ?? ""}`, "content-type": "application/json" }, body: JSON.stringify(cfg) });
729
- const body = await r.json().catch(() => ({}));
730
- if (!r.ok) {
731
- setErrors(body.errors ?? {});
732
- setNote(body.error ?? `save failed (${r.status})`);
733
- return;
734
- }
735
- setDraft(null);
736
- refresh();
737
- setNote("Saved.");
583
+ await run();
584
+ setNote(ok);
585
+ onChanged();
738
586
  } catch (e) {
739
587
  setNote(e instanceof Error ? e.message : String(e));
740
588
  } finally {
741
- setBusy(false);
589
+ setBusy(null);
742
590
  }
743
591
  };
744
- const numField = (key, label) => /* @__PURE__ */ jsx10(Field3, { label, error: errors[key], children: /* @__PURE__ */ jsx10(Input2, { type: "number", value: cfg[key], onChange: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
745
- return /* @__PURE__ */ jsx10(AdminPage, { children: /* @__PURE__ */ jsxs10(Panel, { title: "Booking availability", actions: note ? /* @__PURE__ */ jsx10("span", { className: "muted", children: note }) : void 0, children: [
746
- /* @__PURE__ */ jsx10(Field3, { label: "Days", error: errors.days, children: /* @__PURE__ */ jsx10("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: DAYS.map((d, i) => /* @__PURE__ */ jsx10(Checkbox2, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
747
- /* @__PURE__ */ jsxs10("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
748
- numField("startHour", "Start hour (0\u201324)"),
749
- numField("endHour", "End hour (0\u201324)"),
750
- numField("slotMinutes", "Slot length (minutes)"),
751
- numField("minNoticeHours", "Minimum notice (hours)"),
752
- numField("windowDays", "Booking window (days)"),
753
- /* @__PURE__ */ jsx10(Field3, { label: "Timezone", error: errors.timezone, children: /* @__PURE__ */ jsx10(Input2, { value: cfg.timezone, onChange: (e) => edit({ timezone: e.target.value }) }) })
592
+ const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), `Role set to ${role}.`);
593
+ const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), "Approved.");
594
+ const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), "Refund issued.");
595
+ if (!userId && !applicationId) return null;
596
+ return /* @__PURE__ */ jsxs8("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
597
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: 16 }, children: "Access & lifecycle" }),
598
+ userId ? /* @__PURE__ */ jsx8(Field3, { label: "Role", hint: access?.superAdmin ? "Super-admin \u2014 managed in odla Studio." : void 0, children: /* @__PURE__ */ jsx8(
599
+ Select,
600
+ {
601
+ value: access?.role ?? "",
602
+ disabled: busy === "role" || access?.superAdmin,
603
+ options: roles.map((r) => ({ value: r, label: r })),
604
+ onChange: (e) => void setRole(e.target.value)
605
+ }
606
+ ) }) : /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: "No linked account yet \u2014 role is set once they sign in." }),
607
+ applicationId ? /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8 }, children: [
608
+ /* @__PURE__ */ jsx8(Button4, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: "Approve" }),
609
+ /* @__PURE__ */ jsx8(Button4, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: "Refund" })
610
+ ] }) : null,
611
+ note ? /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: note }) : null
612
+ ] });
613
+ }
614
+
615
+ // src/ui/admin-network.tsx
616
+ import { useState as useState8 } from "react";
617
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
618
+ function NetworkShareActions(props) {
619
+ const { recordId, recordType, getToken } = props;
620
+ const targets = useAdminResource(getToken, "/api/admin/network/targets");
621
+ const [busy, setBusy] = useState8(null);
622
+ const [message, setMessage] = useState8(null);
623
+ const compatible = (targets.data?.targets ?? []).filter((target) => target.types.includes(recordType));
624
+ if (targets.loading || targets.error || compatible.length === 0) return null;
625
+ const push = async (target) => {
626
+ setBusy(target.id);
627
+ setMessage(null);
628
+ try {
629
+ const result = await adminFetch(getToken, "/api/admin/network/push", {
630
+ method: "POST",
631
+ body: JSON.stringify({ recordId, targetIds: [target.id] })
632
+ });
633
+ const delivery = result.results[0];
634
+ setMessage(delivery?.ok ? `Shared with ${target.name}.` : delivery?.error ?? `Could not share with ${target.name}.`);
635
+ } catch (err) {
636
+ setMessage(err instanceof Error ? err.message : "Delivery failed.");
637
+ } finally {
638
+ setBusy(null);
639
+ }
640
+ };
641
+ return /* @__PURE__ */ jsxs9("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
642
+ /* @__PURE__ */ jsxs9("div", { children: [
643
+ /* @__PURE__ */ jsx9("h3", { style: { margin: 0, fontSize: 16 }, children: "Share to a follower" }),
644
+ /* @__PURE__ */ jsx9("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: "Sends the target's allowlisted fields. Its pipeline and account state remain local." })
754
645
  ] }),
755
- /* @__PURE__ */ jsx10(Field3, { label: "Event summary template", error: errors.summaryTemplate, children: /* @__PURE__ */ jsx10(Input2, { value: cfg.summaryTemplate, onChange: (e) => edit({ summaryTemplate: e.target.value }) }) }),
756
- /* @__PURE__ */ jsx10("div", { children: /* @__PURE__ */ jsx10(Button4, { disabled: !draft || busy, onClick: () => void save(), children: "Save availability" }) })
757
- ] }) });
646
+ /* @__PURE__ */ jsx9("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: compatible.map((target) => /* @__PURE__ */ jsx9("button", { className: "btn secondary mini", disabled: busy !== null, onClick: () => void push(target), children: busy === target.id ? "Sharing\u2026" : `Share with ${target.name}` }, target.id)) }),
647
+ message ? /* @__PURE__ */ jsx9("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
648
+ ] });
758
649
  }
759
- function availabilitySection(options = {}) {
760
- const { id = "availability", label = "Availability" } = options;
761
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx10(AvailabilityBody, { ctx }) };
650
+
651
+ // src/ui/admin-people.tsx
652
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
653
+ var DEFAULT_ROLES = ["provisional", "member", "admin"];
654
+ function PeopleBody(props) {
655
+ const { crm, client, type, getToken, roles, lifecycle } = props;
656
+ const hookClient = client;
657
+ const query = useCrmQuery(hookClient, type);
658
+ const [openId, setOpenId] = useState9(null);
659
+ const record = useCrmRecord(hookClient, openId);
660
+ const [saving, setSaving] = useState9(false);
661
+ const saveFields = async (input) => {
662
+ if (!openId) return;
663
+ setSaving(true);
664
+ try {
665
+ await client.updateRecord(openId, { input });
666
+ record.refresh();
667
+ query.refresh();
668
+ } finally {
669
+ setSaving(false);
670
+ }
671
+ };
672
+ const moveStage = async (to) => {
673
+ if (!openId) return;
674
+ await client.setStage(openId, to);
675
+ record.refresh();
676
+ query.refresh();
677
+ };
678
+ const addTag = async (tag) => {
679
+ if (openId) {
680
+ await client.addTag(openId, tag);
681
+ record.refresh();
682
+ }
683
+ };
684
+ const removeTag = async (tag) => {
685
+ if (openId) {
686
+ await client.removeTag(openId, tag);
687
+ record.refresh();
688
+ }
689
+ };
690
+ return /* @__PURE__ */ jsxs10(AdminPage, { children: [
691
+ /* @__PURE__ */ jsx10(CrmList, { crm, type, query, onOpenRecord: (r) => setOpenId(r.id) }),
692
+ record.detail ? /* @__PURE__ */ jsxs10("div", { children: [
693
+ /* @__PURE__ */ jsx10(
694
+ RecordPanel,
695
+ {
696
+ crm,
697
+ detail: record.detail,
698
+ onSaveFields: saveFields,
699
+ onMoveStage: moveStage,
700
+ onAddTag: addTag,
701
+ onRemoveTag: removeTag,
702
+ saving
703
+ }
704
+ ),
705
+ lifecycle ? /* @__PURE__ */ jsx10(
706
+ RecordActions,
707
+ {
708
+ getToken,
709
+ record: record.detail.record,
710
+ roles,
711
+ onChanged: () => {
712
+ record.refresh();
713
+ query.refresh();
714
+ }
715
+ }
716
+ ) : null,
717
+ /* @__PURE__ */ jsx10(
718
+ NetworkShareActions,
719
+ {
720
+ recordId: record.detail.record.id,
721
+ recordType: record.detail.record.type,
722
+ getToken
723
+ }
724
+ )
725
+ ] }) : null
726
+ ] });
727
+ }
728
+ function collectionSection(options) {
729
+ const { crm, type } = options;
730
+ let fallbackLabel = type;
731
+ try {
732
+ fallbackLabel = crm.type(type).labelPlural ?? crm.type(type).label ?? type;
733
+ } catch {
734
+ }
735
+ const { id = type, label = fallbackLabel, lifecycle = type === "person", roles = DEFAULT_ROLES } = options;
736
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx10(PeopleBody, { crm, client: ctx.client, type, getToken: ctx.getToken, roles, lifecycle }) };
737
+ }
738
+ function peopleSection(options) {
739
+ const { crm, id = "people", label = "People", type = "person", roles } = options;
740
+ return collectionSection({ crm, type, id, label, ...roles ? { roles } : {} });
741
+ }
742
+
743
+ // src/ui/admin-defaults.ts
744
+ function defaultAdminSections(chapter) {
745
+ const collections = Object.entries(chapter.crm.config.types).map(
746
+ ([type, def]) => collectionSection({
747
+ crm: chapter.crm,
748
+ type,
749
+ id: type === "person" ? "people" : type,
750
+ label: def.labelPlural ?? def.label,
751
+ lifecycle: chapter.mode === "chapter" && type === "person",
752
+ roles: chapter.auth.ladder
753
+ })
754
+ );
755
+ if (chapter.mode === "hub") return collections;
756
+ return [
757
+ dashboardSection(),
758
+ ...collections,
759
+ meetingsSection(),
760
+ availabilitySection(),
761
+ billingSection(),
762
+ emailSection()
763
+ ];
764
+ }
765
+
766
+ // src/ui/admin.tsx
767
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
768
+ function Authed(props) {
769
+ const { sections, basePath, brand, crmBasePath, apiBase, routing } = props;
770
+ const { getToken, signOut } = useClerkAuth();
771
+ const client = useMemo(
772
+ () => new CrmClient({
773
+ basePath: crmBasePath,
774
+ headers: async () => {
775
+ const t = await getToken();
776
+ return t ? { authorization: `Bearer ${t}` } : {};
777
+ }
778
+ }),
779
+ [getToken, crmBasePath]
780
+ );
781
+ const [state, setState] = useState10({
782
+ status: "checking"
783
+ });
784
+ useEffect5(() => {
785
+ let live = true;
786
+ void (async () => {
787
+ try {
788
+ const t = await getToken();
789
+ const res = await fetch(`${apiBase}/api/me`, { headers: t ? { authorization: `Bearer ${t}` } : {} });
790
+ const body = await res.json().catch(() => ({}));
791
+ if (live) setState({ status: body.authorized ? "ok" : "denied", email: body.email ?? null });
792
+ } catch {
793
+ if (live) setState({ status: "denied" });
794
+ }
795
+ })();
796
+ return () => {
797
+ live = false;
798
+ };
799
+ }, [getToken, apiBase]);
800
+ if (state.status === "checking") {
801
+ return /* @__PURE__ */ jsx11(Gate, { brand, children: /* @__PURE__ */ jsx11("p", { style: S.muted, children: "Checking access\u2026" }) });
802
+ }
803
+ if (state.status === "denied") {
804
+ return /* @__PURE__ */ jsx11(Gate, { brand, children: /* @__PURE__ */ jsxs11("div", { className: "card", style: S.card, children: [
805
+ /* @__PURE__ */ jsx11("h2", { style: { marginTop: 0 }, children: "Not authorized" }),
806
+ /* @__PURE__ */ jsxs11("p", { style: S.muted, children: [
807
+ state.email ? `${state.email} isn't` : "This account isn't",
808
+ " on the admin list. Ask an existing admin to add you in odla Studio."
809
+ ] }),
810
+ /* @__PURE__ */ jsx11("button", { className: "btn secondary", onClick: () => signOut(), style: { marginTop: 12 }, children: "Sign out" })
811
+ ] }) });
812
+ }
813
+ return /* @__PURE__ */ jsx11(
814
+ AdminShell,
815
+ {
816
+ sections,
817
+ basePath,
818
+ brand,
819
+ client,
820
+ getToken,
821
+ signOut,
822
+ email: state.email ?? null,
823
+ routing
824
+ }
825
+ );
826
+ }
827
+ function ChapterAdmin(props) {
828
+ const sections = useMemo(
829
+ () => props.sections ?? (props.chapter ? defaultAdminSections(props.chapter) : []),
830
+ [props.sections, props.chapter]
831
+ );
832
+ const basePath = props.basePath ?? "/admin";
833
+ const brand = props.brand ?? {
834
+ name: props.chapter?.name ?? "Admin",
835
+ badge: props.chapter?.brand.badge,
836
+ wordmark: props.chapter?.brand.wordmark
837
+ };
838
+ const crmBasePath = props.crmBasePath ?? "/api/crm";
839
+ const apiBase = props.apiBase ?? "";
840
+ const routing = props.routing ?? "query";
841
+ const brandStyle = /* @__PURE__ */ jsx11(BrandStyle, { brand: props.chapter?.brand });
842
+ const [pk, setPk] = useState10(void 0);
843
+ useEffect5(() => {
844
+ let live = true;
845
+ void (async () => {
846
+ try {
847
+ const res = await fetch(`${apiBase}/api/config`);
848
+ const body = await res.json();
849
+ if (live) setPk(body.clerkPublishableKey ?? null);
850
+ } catch {
851
+ if (live) setPk(null);
852
+ }
853
+ })();
854
+ return () => {
855
+ live = false;
856
+ };
857
+ }, [apiBase]);
858
+ if (pk === void 0) {
859
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
860
+ brandStyle,
861
+ /* @__PURE__ */ jsx11(Gate, { brand, children: /* @__PURE__ */ jsx11("p", { style: S.muted, children: "Loading\u2026" }) })
862
+ ] });
863
+ }
864
+ if (!pk) {
865
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
866
+ brandStyle,
867
+ /* @__PURE__ */ jsx11(Gate, { brand, children: /* @__PURE__ */ jsxs11("div", { className: "card", style: S.card, children: [
868
+ /* @__PURE__ */ jsx11("h2", { style: { marginTop: 0 }, children: "Sign-in not configured" }),
869
+ /* @__PURE__ */ jsx11("p", { style: S.muted, children: "No Clerk publishable key is set for this environment yet." })
870
+ ] }) })
871
+ ] });
872
+ }
873
+ if (sections.length === 0) {
874
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
875
+ brandStyle,
876
+ /* @__PURE__ */ jsx11(Gate, { brand, children: /* @__PURE__ */ jsxs11("div", { className: "card", style: S.card, children: [
877
+ /* @__PURE__ */ jsx11("h2", { style: { marginTop: 0 }, children: "No admin sections" }),
878
+ /* @__PURE__ */ jsx11("p", { style: S.muted, children: "Pass a chapter or at least one explicit section." })
879
+ ] }) })
880
+ ] });
881
+ }
882
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
883
+ brandStyle,
884
+ /* @__PURE__ */ jsxs11(ClerkGate, { publishableKey: pk, appearance: clerkAppearanceFromTokens(), afterSignOutUrl: "/", children: [
885
+ /* @__PURE__ */ jsx11(SignedOut, { children: /* @__PURE__ */ jsx11(Gate, { brand, tagline: "Admin sign-in \u2014 invite only", children: /* @__PURE__ */ jsx11(SignIn, { routing: "hash", forceRedirectUrl: basePath, signUpForceRedirectUrl: basePath }) }) }),
886
+ /* @__PURE__ */ jsx11(SignedIn, { children: /* @__PURE__ */ jsx11(
887
+ Authed,
888
+ {
889
+ sections,
890
+ basePath,
891
+ brand,
892
+ crmBasePath,
893
+ apiBase,
894
+ routing
895
+ }
896
+ ) })
897
+ ] })
898
+ ] });
762
899
  }
763
900
 
764
901
  export {
@@ -766,16 +903,20 @@ export {
766
903
  Panel,
767
904
  ThemeWarning,
768
905
  AdminNote,
769
- ChapterAdmin,
906
+ adminSectionFromUrl,
907
+ adminSectionHref,
770
908
  adminFetch,
771
909
  useAdminResource,
772
- RecordActions,
773
- collectionSection,
774
- peopleSection,
775
- dashboardSection,
910
+ availabilitySection,
776
911
  billingSection,
912
+ dashboardSection,
777
913
  emailSection,
778
914
  meetingsSection,
779
- availabilitySection
915
+ RecordActions,
916
+ NetworkShareActions,
917
+ collectionSection,
918
+ peopleSection,
919
+ defaultAdminSections,
920
+ ChapterAdmin
780
921
  };
781
- //# sourceMappingURL=chunk-D3QMK3ED.js.map
922
+ //# sourceMappingURL=chunk-UIWFEESI.js.map