@odla-ai/chapter 0.22.0 → 0.23.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.
@@ -1,9 +1,13 @@
1
1
  import {
2
- BrandStyle
3
- } from "./chunk-VZPBTTO3.js";
2
+ BrandStyle,
3
+ ChapterCopyProvider,
4
+ DEFAULT_CHAPTER_COPY,
5
+ formatChapterCopy,
6
+ useChapterCopy
7
+ } from "./chunk-NTZSXAFT.js";
4
8
 
5
9
  // src/ui/admin.tsx
6
- import { useEffect as useEffect7, useMemo, useState as useState11 } from "react";
10
+ import { useEffect as useEffect7, useMemo, useState as useState11 } from "preact/hooks";
7
11
  import {
8
12
  ClerkGate,
9
13
  SignedIn,
@@ -15,12 +19,12 @@ import {
15
19
  import { CrmClient } from "@odla-ai/crm";
16
20
 
17
21
  // src/ui/chrome.tsx
18
- import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
19
- import { Tabs, TopBar, TopBarLink } from "@odla-ai/ui/components";
22
+ import { useCallback, useEffect as useEffect2, useState as useState2 } from "preact/hooks";
23
+ import { Tabs, ThemeScope, TopBar, TopBarLink } from "@odla-ai/ui/components";
20
24
 
21
25
  // src/ui/admin-layout.tsx
22
- import { useEffect, useState } from "react";
23
- import { jsx, jsxs } from "react/jsx-runtime";
26
+ import { useEffect, useState } from "preact/hooks";
27
+ import { jsx, jsxs } from "preact/jsx-runtime";
24
28
  var PAGE = {
25
29
  maxWidth: "var(--ui-content-width, 1120px)",
26
30
  margin: "0 auto",
@@ -84,7 +88,7 @@ function AdminNote({ children }) {
84
88
  }
85
89
 
86
90
  // src/ui/admin-brand.tsx
87
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
91
+ import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
88
92
  var badgeText = (brand) => brand.badge ?? brand.name.slice(0, 3).toUpperCase();
89
93
  var brandDisplay = (brand) => {
90
94
  const display = brand.wordmark ?? brand.name;
@@ -269,7 +273,7 @@ function adminSectionHref(current, basePath, id, routing = "fragment") {
269
273
  }
270
274
 
271
275
  // src/ui/chrome.tsx
272
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
276
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
273
277
  var mergeRoute = (current, target) => {
274
278
  if (typeof target === "string") return { workspaceId: target };
275
279
  const changingWorkspace = target.workspaceId != null && target.workspaceId !== current.workspaceId;
@@ -301,6 +305,7 @@ function AdminShell({
301
305
  renderAccountMenu,
302
306
  renderWorkspaceNav
303
307
  }) {
308
+ const copy = useChapterCopy().admin;
304
309
  const normalizeRoute = useCallback((candidate) => {
305
310
  if (candidate.viewId) return candidate;
306
311
  const workspace = workspaces.find((item) => item.id === candidate.workspaceId);
@@ -371,10 +376,12 @@ function AdminShell({
371
376
  signOut
372
377
  };
373
378
  const identity = renderAccountMenu?.(accountProps) ?? /* @__PURE__ */ jsxs3(Fragment, { children: [
374
- /* @__PURE__ */ jsx3("span", { style: { ...S.role, color: "inherit", borderColor: "currentColor", background: "transparent" }, children: "admin" }),
379
+ /* @__PURE__ */ jsx3("span", { style: { ...S.role, color: "inherit", borderColor: "currentColor", background: "transparent" }, children: copy.shell.adminRole }),
375
380
  email ? /* @__PURE__ */ jsx3("span", { style: { opacity: 0.72, fontSize: 12 }, children: email }) : null,
376
- /* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: "Sign out" })
381
+ /* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: copy.auth.signOut })
377
382
  ] });
383
+ const activeContent = active?.render(context);
384
+ const workspaceContent = active?.accent ? /* @__PURE__ */ jsx3(ThemeScope, { accent: active.accent, children: activeContent }) : activeContent;
378
385
  if (chrome === "topbar") {
379
386
  return /* @__PURE__ */ jsxs3(Fragment, { children: [
380
387
  /* @__PURE__ */ jsxs3(TopBar, { children: [
@@ -382,13 +389,13 @@ function AdminShell({
382
389
  /* @__PURE__ */ jsx3("span", { style: S.badgeSm, children: badgeText(brand) }),
383
390
  brandDisplay(brand)
384
391
  ] }),
385
- /* @__PURE__ */ jsx3("nav", { className: "topbar-nav", "aria-label": "Admin navigation", children: workspaces.map((workspace) => /* @__PURE__ */ jsx3(TopBarLink, { active: workspace.id === route.workspaceId, onClick: () => navigate(workspace.id), children: workspace.label }, workspace.id)) }),
392
+ /* @__PURE__ */ jsx3("nav", { className: "topbar-nav", "aria-label": copy.shell.navigationLabel, children: workspaces.map((workspace) => /* @__PURE__ */ jsx3(TopBarLink, { active: workspace.id === route.workspaceId, onClick: () => navigate(workspace.id), children: workspace.label }, workspace.id)) }),
386
393
  /* @__PURE__ */ jsx3("span", { className: "topbar-spacer" }),
387
394
  /* @__PURE__ */ jsx3("div", { className: "topbar-actions", children: identity })
388
395
  ] }),
389
396
  /* @__PURE__ */ jsxs3("main", { className: "shell-main", style: S.main, children: [
390
397
  /* @__PURE__ */ jsx3(ThemeWarning, {}),
391
- active?.render(context)
398
+ workspaceContent
392
399
  ] })
393
400
  ] });
394
401
  }
@@ -397,7 +404,7 @@ function AdminShell({
397
404
  /* @__PURE__ */ jsx3("span", { style: S.badgeSm, children: badgeText(brand) }),
398
405
  brandDisplay(brand)
399
406
  ] }),
400
- /* @__PURE__ */ jsx3("span", { style: { opacity: 0.68 }, children: "Admin Console" }),
407
+ /* @__PURE__ */ jsx3("span", { style: { opacity: 0.68 }, children: copy.shell.adminConsole }),
401
408
  /* @__PURE__ */ jsx3("span", { style: { flex: 1 } }),
402
409
  /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: identity })
403
410
  ] }) : null;
@@ -407,12 +414,12 @@ function AdminShell({
407
414
  /* @__PURE__ */ jsx3(ThemeWarning, {}),
408
415
  renderWorkspaceNav ? /* @__PURE__ */ jsxs3(Fragment, { children: [
409
416
  /* @__PURE__ */ jsx3("div", { style: S.workspaceTabs, children: renderWorkspaceNav({ workspaces, route, navigate, href }) }),
410
- active?.render(context)
417
+ workspaceContent
411
418
  ] }) : /* @__PURE__ */ jsx3(
412
419
  Tabs,
413
420
  {
414
421
  style: S.workspaceTabs,
415
- ariaLabel: "Admin workspaces",
422
+ ariaLabel: copy.shell.navigationLabel,
416
423
  value: route.workspaceId,
417
424
  mount: "active",
418
425
  onValueChange: (workspaceId) => setRoute(normalizeRoute({ workspaceId })),
@@ -420,7 +427,7 @@ function AdminShell({
420
427
  value: workspace.id,
421
428
  label: workspace.label,
422
429
  href: href(workspace.id),
423
- panel: workspace.id === route.workspaceId ? workspace.render(context) : null
430
+ panel: workspace.id === route.workspaceId ? workspace.accent ? /* @__PURE__ */ jsx3(ThemeScope, { accent: workspace.accent, children: workspace.render(context) }) : workspace.render(context) : null
424
431
  }))
425
432
  }
426
433
  )
@@ -432,11 +439,11 @@ function AdminShell({
432
439
  import { Tabs as Tabs2 } from "@odla-ai/ui/components";
433
440
 
434
441
  // src/ui/admin-availability.tsx
435
- import { useState as useState4 } from "react";
442
+ import { useState as useState4 } from "preact/hooks";
436
443
  import { Button, Checkbox, Field, Input } from "@odla-ai/ui/components";
437
444
 
438
445
  // src/ui/admin-api.ts
439
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
446
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "preact/hooks";
440
447
  async function adminFetch(getToken, path, init) {
441
448
  const token = await getToken();
442
449
  const headers = new Headers(init?.headers);
@@ -466,18 +473,18 @@ function useAdminResource(getToken, path) {
466
473
  }
467
474
 
468
475
  // src/ui/admin-availability.tsx
469
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
470
- var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
476
+ import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
471
477
  function AvailabilityBody({ ctx }) {
478
+ const copy = useChapterCopy().admin.availability;
472
479
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
473
480
  const [draft, setDraft] = useState4(null);
474
481
  const [busy, setBusy] = useState4(false);
475
482
  const [note, setNote] = useState4(null);
476
483
  const [errors, setErrors] = useState4({});
477
484
  const cfg = draft ?? data?.scheduling ?? null;
478
- if (loading) return /* @__PURE__ */ jsx4(AdminNote, { children: "Loading\u2026" });
485
+ if (loading) return /* @__PURE__ */ jsx4(AdminNote, { children: copy.loading });
479
486
  if (error || !cfg) return /* @__PURE__ */ jsxs4(AdminNote, { children: [
480
- "Couldn\u2019t load availability",
487
+ copy.loadFailed,
481
488
  error ? `: ${error}` : "",
482
489
  "."
483
490
  ] });
@@ -499,26 +506,26 @@ function AvailabilityBody({ ctx }) {
499
506
  }
500
507
  setDraft(null);
501
508
  refresh();
502
- setNote("Saved.");
509
+ setNote(copy.saved);
503
510
  } catch (e) {
504
511
  setNote(e instanceof Error ? e.message : String(e));
505
512
  } finally {
506
513
  setBusy(false);
507
514
  }
508
515
  };
509
- const numField = (key, label) => /* @__PURE__ */ jsx4(Field, { label, error: errors[key], children: /* @__PURE__ */ jsx4(Input, { type: "number", value: cfg[key], onChange: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
510
- return /* @__PURE__ */ jsx4(AdminPage, { children: /* @__PURE__ */ jsxs4(Panel, { title: "Booking availability", actions: note ? /* @__PURE__ */ jsx4("span", { className: "muted", children: note }) : void 0, children: [
511
- /* @__PURE__ */ jsx4(Field, { label: "Days", error: errors.days, children: /* @__PURE__ */ jsx4("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: DAYS.map((d, i) => /* @__PURE__ */ jsx4(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
516
+ const numField = (key, label) => /* @__PURE__ */ jsx4(Field, { label, error: errors[key], children: /* @__PURE__ */ jsx4(Input, { type: "number", value: cfg[key], onInput: (e) => edit({ [key]: num(e.target.value, cfg[key]) }) }) });
517
+ return /* @__PURE__ */ jsx4(AdminPage, { children: /* @__PURE__ */ jsxs4(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx4("span", { className: "muted", children: note }) : void 0, children: [
518
+ /* @__PURE__ */ jsx4(Field, { label: copy.days, error: errors.days, children: /* @__PURE__ */ jsx4("div", { style: { display: "flex", gap: 14, flexWrap: "wrap" }, children: copy.dayLabels.map((d, i) => /* @__PURE__ */ jsx4(Checkbox, { label: d, checked: cfg.days.includes(i), onChange: () => toggleDay(i) }, d)) }) }),
512
519
  /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 14 }, children: [
513
- numField("startHour", "Start hour (0\u201324)"),
514
- numField("endHour", "End hour (0\u201324)"),
515
- numField("slotMinutes", "Slot length (minutes)"),
516
- numField("minNoticeHours", "Minimum notice (hours)"),
517
- numField("windowDays", "Booking window (days)"),
518
- /* @__PURE__ */ jsx4(Field, { label: "Timezone", error: errors.timezone, children: /* @__PURE__ */ jsx4(Input, { value: cfg.timezone, onChange: (e) => edit({ timezone: e.target.value }) }) })
520
+ numField("startHour", copy.startHour),
521
+ numField("endHour", copy.endHour),
522
+ numField("slotMinutes", copy.slotMinutes),
523
+ numField("minNoticeHours", copy.minNoticeHours),
524
+ numField("windowDays", copy.windowDays),
525
+ /* @__PURE__ */ jsx4(Field, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx4(Input, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
519
526
  ] }),
520
- /* @__PURE__ */ jsx4(Field, { label: "Event summary template", error: errors.summaryTemplate, children: /* @__PURE__ */ jsx4(Input, { value: cfg.summaryTemplate, onChange: (e) => edit({ summaryTemplate: e.target.value }) }) }),
521
- /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(Button, { disabled: !draft || busy, onClick: () => void save(), children: "Save availability" }) })
527
+ /* @__PURE__ */ jsx4(Field, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx4(Input, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
528
+ /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(Button, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
522
529
  ] }) });
523
530
  }
524
531
  function availabilitySection(options = {}) {
@@ -528,38 +535,39 @@ function availabilitySection(options = {}) {
528
535
 
529
536
  // src/ui/admin-billing.tsx
530
537
  import { DataTable, StatBand } from "@odla-ai/ui/components";
531
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
538
+ import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
532
539
  var money = (cents) => `$${(cents / 100).toLocaleString()}`;
533
540
  var date = (t) => t ? new Date(t).toLocaleDateString([], { dateStyle: "medium" }) : "\u2014";
534
- var COLUMNS = [
535
- { key: "name", header: "Name", cell: (r) => r.name, sortAs: "string" },
536
- { key: "email", header: "Email", cell: (r) => r.email },
537
- { key: "applicationStatus", header: "Application", cell: (r) => r.applicationStatus },
538
- { key: "subscriptionStatus", header: "Subscription", cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? " \xB7 cancelling" : ""}` : "\u2014" },
539
- { key: "amountCents", header: "Amount", cell: (r) => `${money(r.amountCents)}/${r.interval === "month" ? "mo" : "yr"}`, sortAs: "number", sortValue: (r) => r.amountCents },
540
- { key: "renewalAt", header: "Renews", cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
541
+ var columns = (copy) => [
542
+ { key: "name", header: copy.name, cell: (r) => r.name, sortAs: "string" },
543
+ { key: "email", header: copy.email, cell: (r) => r.email },
544
+ { key: "applicationStatus", header: copy.application, cell: (r) => r.applicationStatus },
545
+ { key: "subscriptionStatus", header: copy.subscription, cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? ` \xB7 ${copy.cancelling}` : ""}` : "\u2014" },
546
+ { key: "amountCents", header: copy.amount, cell: (r) => `${money(r.amountCents)}/${r.interval === "month" ? "mo" : "yr"}`, sortAs: "number", sortValue: (r) => r.amountCents },
547
+ { key: "renewalAt", header: copy.renews, cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
541
548
  ];
542
549
  function BillingBody({ ctx }) {
550
+ const copy = useChapterCopy().admin.billing;
543
551
  const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/billing");
544
- if (loading) return /* @__PURE__ */ jsx5(AdminNote, { children: "Loading\u2026" });
552
+ if (loading) return /* @__PURE__ */ jsx5(AdminNote, { children: copy.loading });
545
553
  if (error || !data) return /* @__PURE__ */ jsxs5(AdminNote, { children: [
546
- "Couldn\u2019t load billing",
554
+ copy.loadFailed,
547
555
  error ? `: ${error}` : "",
548
556
  "."
549
557
  ] });
550
- if (!data.billingReady) return /* @__PURE__ */ jsx5(AdminNote, { children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." });
558
+ if (!data.billingReady) return /* @__PURE__ */ jsx5(AdminNote, { children: copy.notConfigured });
551
559
  const s = data.summary;
552
560
  const stats = s ? [
553
- { value: s.activeCount, label: "Active" },
554
- { value: money(s.annualizedCents), label: "Annualized" },
555
- { value: s.renewingSoonCount, label: "Renewing \u226460d" },
556
- { value: s.pastDueCount, label: "Past due" }
561
+ { value: s.activeCount, label: copy.active },
562
+ { value: money(s.annualizedCents), label: copy.annualized },
563
+ { value: s.renewingSoonCount, label: copy.renewingSoon },
564
+ { value: s.pastDueCount, label: copy.pastDue }
557
565
  ] : [];
558
566
  return /* @__PURE__ */ jsxs5(AdminPage, { children: [
559
567
  s ? /* @__PURE__ */ jsx5(StatBand, { stats }) : null,
560
- /* @__PURE__ */ jsxs5(Panel, { title: "Subscriptions", actions: data.testMode ? /* @__PURE__ */ jsx5("span", { className: "badge", children: "Stripe test mode" }) : void 0, children: [
561
- data.truncated ? /* @__PURE__ */ jsx5("p", { className: "badge", children: "Showing the first 100 subscriptions \u2014 the list is truncated." }) : null,
562
- /* @__PURE__ */ jsx5(DataTable, { rows: data.rows, columns: COLUMNS, rowKey: (r) => r.id })
568
+ /* @__PURE__ */ jsxs5(Panel, { title: copy.subscriptions, actions: data.testMode ? /* @__PURE__ */ jsx5("span", { className: "badge", children: copy.testMode }) : void 0, children: [
569
+ data.truncated ? /* @__PURE__ */ jsx5("p", { className: "badge", children: copy.truncated }) : null,
570
+ /* @__PURE__ */ jsx5(DataTable, { rows: data.rows, columns: columns(copy), rowKey: (r) => r.id })
563
571
  ] })
564
572
  ] });
565
573
  }
@@ -570,25 +578,26 @@ function billingSection(options = {}) {
570
578
 
571
579
  // src/ui/admin-dashboard.tsx
572
580
  import { MetricWidget, StepPipeline } from "@odla-ai/ui/components";
573
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
581
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
574
582
  var number = (value) => Math.round(value).toLocaleString();
575
583
  var money2 = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
576
584
  var when = (time) => new Date(time).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
577
585
  function DashboardBody({ ctx }) {
586
+ const copy = useChapterCopy().admin.dashboard;
578
587
  const { data, loading, error } = useAdminResource(
579
588
  ctx.getToken,
580
589
  "/api/admin/dashboard"
581
590
  );
582
- if (loading) return /* @__PURE__ */ jsx6(AdminNote, { children: "Loading dashboard\u2026" });
591
+ if (loading) return /* @__PURE__ */ jsx6(AdminNote, { children: copy.loading });
583
592
  if (error || !data) return /* @__PURE__ */ jsxs6(AdminNote, { children: [
584
- "Couldn\u2019t load the dashboard",
593
+ copy.loadFailed,
585
594
  error ? `: ${error}` : "",
586
595
  "."
587
596
  ] });
588
597
  const pipelineSteps = Object.entries(data.pipeline).filter(([stage]) => !["declined", "refunded"].includes(stage)).map(([stage, count]) => ({
589
598
  icon: /* @__PURE__ */ jsx6("span", { style: { fontSize: 22, fontWeight: 700 }, children: count }),
590
599
  title: stage.replaceAll("_", " "),
591
- description: data.pipelineDelta[stage] ? `+${data.pipelineDelta[stage]} this week` : "no change"
600
+ description: data.pipelineDelta[stage] ? formatChapterCopy(copy.thisWeek, { count: data.pipelineDelta[stage] }) : copy.noChange
592
601
  }));
593
602
  const exceptions = ["declined", "refunded"].filter((stage) => stage in data.pipeline).map((stage) => `${stage} ${data.pipeline[stage]}`).join(" \xB7 ");
594
603
  return /* @__PURE__ */ jsxs6(AdminPage, { children: [
@@ -596,54 +605,54 @@ function DashboardBody({ ctx }) {
596
605
  Panel,
597
606
  {
598
607
  title: /* @__PURE__ */ jsxs6(Fragment2, { children: [
599
- "Upcoming calls",
608
+ copy.upcomingCalls,
600
609
  " ",
601
610
  /* @__PURE__ */ jsxs6("span", { className: "muted", style: { fontWeight: 400 }, children: [
602
611
  "\xB7 ",
603
612
  data.timezone
604
613
  ] })
605
614
  ] }),
606
- actions: data.calls.needsAttention ? /* @__PURE__ */ jsxs6("span", { className: "badge", children: [
607
- data.calls.needsAttention,
608
- " need attention"
609
- ] }) : void 0,
610
- children: data.agenda.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "muted", style: { margin: 0 }, children: "No upcoming calls." }) : /* @__PURE__ */ jsx6("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }, children: data.agenda.map((meeting) => /* @__PURE__ */ jsxs6("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
615
+ actions: data.calls.needsAttention ? /* @__PURE__ */ jsx6("span", { className: "badge", children: formatChapterCopy(copy.callsNeedAttention, { count: data.calls.needsAttention }) }) : void 0,
616
+ children: data.agenda.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "muted", style: { margin: 0 }, children: copy.noUpcomingCalls }) : /* @__PURE__ */ jsx6("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }, children: data.agenda.map((meeting) => /* @__PURE__ */ jsxs6("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
611
617
  /* @__PURE__ */ jsx6("strong", { style: { minWidth: 170 }, children: when(meeting.startAt) }),
612
618
  /* @__PURE__ */ jsxs6("span", { style: { flex: 1 }, children: [
613
619
  meeting.name,
614
620
  meeting.email ? ` \xB7 ${meeting.email}` : ""
615
621
  ] }),
616
- meeting.drift && meeting.drift !== "none" ? /* @__PURE__ */ jsx6("span", { className: "badge", children: "drift" }) : null,
617
- meeting.htmlLink ? /* @__PURE__ */ jsx6("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: "Open" }) : null,
618
- meeting.meetUrl ? /* @__PURE__ */ jsx6("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null
622
+ meeting.drift && meeting.drift !== "none" ? /* @__PURE__ */ jsx6("span", { className: "badge", children: copy.drift }) : null,
623
+ meeting.htmlLink ? /* @__PURE__ */ jsx6("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.open }) : null,
624
+ meeting.meetUrl ? /* @__PURE__ */ jsx6("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null
619
625
  ] }, meeting.id)) })
620
626
  }
621
627
  ),
622
628
  /* @__PURE__ */ jsxs6("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 18 }, children: [
623
- /* @__PURE__ */ jsx6(MetricWidget, { label: "Applications", data: data.applicationsSeries, format: number }),
624
- data.membersSeries ? /* @__PURE__ */ jsx6(MetricWidget, { label: "New members", data: data.membersSeries, format: number }) : /* @__PURE__ */ jsx6(Panel, { title: "New members", children: /* @__PURE__ */ jsx6("p", { className: "muted", children: "Appears once billing is connected." }) })
629
+ /* @__PURE__ */ jsx6(MetricWidget, { label: copy.applications, data: data.applicationsSeries, format: number }),
630
+ data.membersSeries ? /* @__PURE__ */ jsx6(MetricWidget, { label: copy.newMembers, data: data.membersSeries, format: number }) : /* @__PURE__ */ jsx6(Panel, { title: copy.newMembers, children: /* @__PURE__ */ jsx6("p", { className: "muted", children: copy.newMembersUnavailable }) })
625
631
  ] }),
626
632
  data.revenueSeries && data.revenue.billingReady ? /* @__PURE__ */ jsxs6("div", { children: [
627
633
  /* @__PURE__ */ jsx6(
628
634
  MetricWidget,
629
635
  {
630
- label: "Revenue \xB7 annual run rate added",
636
+ label: copy.revenueAdded,
631
637
  data: data.revenueSeries,
632
638
  format: money2,
633
639
  size: "large"
634
640
  }
635
641
  ),
636
642
  /* @__PURE__ */ jsxs6("p", { className: "muted", children: [
637
- "Active memberships: ",
643
+ copy.activeMemberships,
644
+ ": ",
638
645
  data.revenue.activeCount ?? 0,
639
- " \xB7 Annual run rate:",
646
+ " \xB7 ",
647
+ copy.annualRunRate,
648
+ ":",
640
649
  " ",
641
650
  money2(data.revenue.annualRunRateCents ?? 0),
642
- data.revenue.testMode ? " \xB7 test mode" : ""
651
+ data.revenue.testMode ? ` \xB7 ${copy.testMode}` : ""
643
652
  ] })
644
- ] }) : /* @__PURE__ */ jsx6(Panel, { title: "Revenue", children: /* @__PURE__ */ jsx6("p", { className: "muted", children: "Appears once billing is connected." }) }),
645
- /* @__PURE__ */ jsxs6(Panel, { title: "Pipeline", children: [
646
- /* @__PURE__ */ jsx6(StepPipeline, { ariaLabel: "Membership pipeline", steps: pipelineSteps }),
653
+ ] }) : /* @__PURE__ */ jsx6(Panel, { title: copy.revenue, children: /* @__PURE__ */ jsx6("p", { className: "muted", children: copy.revenueUnavailable }) }),
654
+ /* @__PURE__ */ jsxs6(Panel, { title: copy.pipeline, children: [
655
+ /* @__PURE__ */ jsx6(StepPipeline, { ariaLabel: copy.pipelineLabel, steps: pipelineSteps }),
647
656
  exceptions ? /* @__PURE__ */ jsx6("p", { className: "muted", style: { margin: 0 }, children: exceptions }) : null
648
657
  ] })
649
658
  ] });
@@ -654,25 +663,26 @@ function dashboardSection(options = {}) {
654
663
  }
655
664
 
656
665
  // src/ui/admin-email.tsx
657
- import { useState as useState5 } from "react";
666
+ import { useState as useState5 } from "preact/hooks";
658
667
  import { Button as Button2, Checkbox as Checkbox2, DataTable as DataTable2, Field as Field2, Input as Input2, Textarea } from "@odla-ai/ui/components";
659
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
660
- var LOG_COLUMNS = [
661
- { key: "sentAt", header: "Sent", cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
662
- { key: "template", header: "Template", cell: (r) => r.template },
663
- { key: "to", header: "To", cell: (r) => r.to },
664
- { key: "status", header: "Status", cell: (r) => r.error ? /* @__PURE__ */ jsx7("span", { className: "badge", children: "failed" }) : r.redirected ? /* @__PURE__ */ jsx7("span", { className: "badge", children: "redirected" }) : "delivered" }
668
+ import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
669
+ var logColumns = (copy) => [
670
+ { key: "sentAt", header: copy.sentColumn, cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
671
+ { key: "template", header: copy.templateColumn, cell: (r) => r.template },
672
+ { key: "to", header: copy.toColumn, cell: (r) => r.to },
673
+ { key: "status", header: copy.statusColumn, cell: (r) => r.error ? /* @__PURE__ */ jsx7("span", { className: "badge", children: copy.failed }) : r.redirected ? /* @__PURE__ */ jsx7("span", { className: "badge", children: copy.redirected }) : copy.delivered }
665
674
  ];
666
675
  function EmailBody({ ctx }) {
676
+ const copy = useChapterCopy().admin.email;
667
677
  const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
668
678
  const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
669
679
  const [draft, setDraft] = useState5(null);
670
680
  const [busy, setBusy] = useState5(null);
671
681
  const [note, setNote] = useState5(null);
672
682
  const data = draft ?? cfg.data;
673
- if (cfg.loading) return /* @__PURE__ */ jsx7(AdminNote, { children: "Loading\u2026" });
683
+ if (cfg.loading) return /* @__PURE__ */ jsx7(AdminNote, { children: copy.loading });
674
684
  if (cfg.error || !data) return /* @__PURE__ */ jsxs7(AdminNote, { children: [
675
- "Couldn\u2019t load email config",
685
+ copy.loadFailed,
676
686
  cfg.error ? `: ${cfg.error}` : "",
677
687
  "."
678
688
  ] });
@@ -693,18 +703,22 @@ function EmailBody({ ctx }) {
693
703
  await adminFetch(ctx.getToken, "/api/admin/group/email", { method: "PUT", body: JSON.stringify(data) });
694
704
  setDraft(null);
695
705
  cfg.refresh();
696
- setNote("Saved.");
706
+ setNote(copy.saved);
697
707
  });
698
708
  const test = (template) => run(`test:${template}`, async () => {
699
709
  const r = await adminFetch(ctx.getToken, "/api/admin/email/test", { method: "POST", body: JSON.stringify({ template }) });
700
- setNote(r.ok ? `Sent ${template} to ${r.to}${r.redirected ? " (dev-redirected)" : ""}.` : "Test send did not deliver.");
710
+ setNote(r.ok ? formatChapterCopy(copy.sent, {
711
+ template,
712
+ address: r.to,
713
+ redirected: r.redirected ? ` (${copy.redirected})` : ""
714
+ }) : copy.testFailed);
701
715
  log.refresh();
702
716
  });
703
717
  return /* @__PURE__ */ jsxs7(AdminPage, { children: [
704
718
  /* @__PURE__ */ jsxs7(
705
719
  Panel,
706
720
  {
707
- title: "Delivery",
721
+ title: copy.delivery,
708
722
  actions: /* @__PURE__ */ jsxs7("span", { className: "muted", children: [
709
723
  data.envName,
710
724
  " \xB7 ",
@@ -712,22 +726,22 @@ function EmailBody({ ctx }) {
712
726
  data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
713
727
  ] }),
714
728
  children: [
715
- /* @__PURE__ */ jsx7(Field2, { label: "Notification address", children: /* @__PURE__ */ jsx7(Input2, { value: data.notificationEmail, onChange: (e) => edit({ notificationEmail: e.target.value }) }) }),
716
- /* @__PURE__ */ jsx7(Field2, { label: "Reply-to", children: /* @__PURE__ */ jsx7(Input2, { value: data.replyTo, onChange: (e) => edit({ replyTo: e.target.value }) }) }),
717
- /* @__PURE__ */ jsx7(Field2, { label: "Debug inbox", hint: "Non-prod sends redirect here.", children: /* @__PURE__ */ jsx7(Input2, { value: data.debugEmail, onChange: (e) => edit({ debugEmail: e.target.value }) }) })
729
+ /* @__PURE__ */ jsx7(Field2, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx7(Input2, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
730
+ /* @__PURE__ */ jsx7(Field2, { label: copy.replyTo, children: /* @__PURE__ */ jsx7(Input2, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
731
+ /* @__PURE__ */ jsx7(Field2, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx7(Input2, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
718
732
  ]
719
733
  }
720
734
  ),
721
- Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs7(Panel, { title: key, actions: /* @__PURE__ */ jsx7(Button2, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: "Send test" }), children: [
722
- /* @__PURE__ */ jsx7(Checkbox2, { label: "Enabled", checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
723
- /* @__PURE__ */ jsx7(Field2, { label: "Subject", children: /* @__PURE__ */ jsx7(Input2, { value: t.subject, onChange: (e) => editTemplate(key, { subject: e.target.value }) }) }),
724
- /* @__PURE__ */ jsx7(Field2, { label: "Body", children: /* @__PURE__ */ jsx7(Textarea, { prose: true, rows: 5, value: t.text, onChange: (e) => editTemplate(key, { text: e.target.value }) }) })
735
+ Object.entries(data.emailTemplates).map(([key, t]) => /* @__PURE__ */ jsxs7(Panel, { title: key, actions: /* @__PURE__ */ jsx7(Button2, { variant: "secondary", mini: true, disabled: busy === `test:${key}`, onClick: () => void test(key), children: copy.sendTest }), children: [
736
+ /* @__PURE__ */ jsx7(Checkbox2, { label: copy.enabled, checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
737
+ /* @__PURE__ */ jsx7(Field2, { label: copy.subject, children: /* @__PURE__ */ jsx7(Input2, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
738
+ /* @__PURE__ */ jsx7(Field2, { label: copy.body, children: /* @__PURE__ */ jsx7(Textarea, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) })
725
739
  ] }, key)),
726
740
  /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
727
- /* @__PURE__ */ jsx7(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: "Save changes" }),
741
+ /* @__PURE__ */ jsx7(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
728
742
  note ? /* @__PURE__ */ jsx7("span", { className: "muted", children: note }) : null
729
743
  ] }),
730
- /* @__PURE__ */ jsx7(Panel, { title: "Send log", children: /* @__PURE__ */ jsx7(DataTable2, { rows: log.data?.sends ?? [], columns: LOG_COLUMNS, rowKey: (r) => r.id }) })
744
+ /* @__PURE__ */ jsx7(Panel, { title: copy.sendLog, children: /* @__PURE__ */ jsx7(DataTable2, { rows: log.data?.sends ?? [], columns: logColumns(copy), rowKey: (r) => r.id }) })
731
745
  ] });
732
746
  }
733
747
  function emailSection(options = {}) {
@@ -739,10 +753,11 @@ function emailSection(options = {}) {
739
753
  import { BillingCard, CrmWorkspace } from "@odla-ai/crm/ui";
740
754
 
741
755
  // src/ui/admin-record-actions.tsx
742
- import { useEffect as useEffect4, useState as useState6 } from "react";
756
+ import { useEffect as useEffect4, useState as useState6 } from "preact/hooks";
743
757
  import { Button as Button3, Field as Field3, Select } from "@odla-ai/ui/components";
744
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
758
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
745
759
  function RecordActions({ getToken, record, roles, onChanged }) {
760
+ const copy = useChapterCopy().admin.records;
746
761
  const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
747
762
  const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
748
763
  const [access, setAccess] = useState6(null);
@@ -772,13 +787,13 @@ function RecordActions({ getToken, record, roles, onChanged }) {
772
787
  setBusy(null);
773
788
  }
774
789
  };
775
- const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), `Role set to ${role}.`);
776
- const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), "Approved.");
777
- const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), "Refund issued.");
790
+ const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), formatChapterCopy(copy.roleSet, { role }));
791
+ const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), copy.approved);
792
+ const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), copy.refunded);
778
793
  if (!userId && !applicationId) return null;
779
794
  return /* @__PURE__ */ jsxs8("section", { className: "panel", style: { display: "flex", flexDirection: "column", gap: 14, marginTop: 16 }, children: [
780
- /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: 16 }, children: "Access & lifecycle" }),
781
- userId ? /* @__PURE__ */ jsx8(Field3, { label: "Role", hint: access?.superAdmin ? "Super-admin \u2014 managed in odla Studio." : void 0, children: /* @__PURE__ */ jsx8(
795
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: 16 }, children: copy.lifecycle }),
796
+ userId ? /* @__PURE__ */ jsx8(Field3, { label: copy.role, hint: access?.superAdmin ? copy.superAdminHint : void 0, children: /* @__PURE__ */ jsx8(
782
797
  Select,
783
798
  {
784
799
  value: access?.role ?? "",
@@ -786,19 +801,20 @@ function RecordActions({ getToken, record, roles, onChanged }) {
786
801
  options: roles.map((r) => ({ value: r, label: r })),
787
802
  onChange: (e) => void setRole(e.target.value)
788
803
  }
789
- ) }) : /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: "No linked account yet \u2014 role is set once they sign in." }),
804
+ ) }) : /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: copy.noAccount }),
790
805
  applicationId ? /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8 }, children: [
791
- /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: "Approve" }),
792
- /* @__PURE__ */ jsx8(Button3, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: "Refund" })
806
+ /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: copy.approve }),
807
+ /* @__PURE__ */ jsx8(Button3, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: copy.refund })
793
808
  ] }) : null,
794
809
  note ? /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: note }) : null
795
810
  ] });
796
811
  }
797
812
 
798
813
  // src/ui/admin-network.tsx
799
- import { useState as useState7 } from "react";
800
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
814
+ import { useState as useState7 } from "preact/hooks";
815
+ import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
801
816
  function NetworkShareActions(props) {
817
+ const copy = useChapterCopy().admin.network;
802
818
  const { recordId, recordType, getToken } = props;
803
819
  const targets = useAdminResource(getToken, "/api/admin/network/targets");
804
820
  const [busy, setBusy] = useState7(null);
@@ -814,27 +830,27 @@ function NetworkShareActions(props) {
814
830
  body: JSON.stringify({ recordId, targetIds: [target.id] })
815
831
  });
816
832
  const delivery = result.results[0];
817
- setMessage(delivery?.ok ? `Shared with ${target.name}.` : delivery?.error ?? `Could not share with ${target.name}.`);
833
+ setMessage(delivery?.ok ? formatChapterCopy(copy.shared, { target: target.name }) : delivery?.error ?? formatChapterCopy(copy.shareFailed, { target: target.name }));
818
834
  } catch (err) {
819
- setMessage(err instanceof Error ? err.message : "Delivery failed.");
835
+ setMessage(err instanceof Error ? err.message : copy.deliveryFailed);
820
836
  } finally {
821
837
  setBusy(null);
822
838
  }
823
839
  };
824
840
  return /* @__PURE__ */ jsxs9("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
825
841
  /* @__PURE__ */ jsxs9("div", { children: [
826
- /* @__PURE__ */ jsx9("h3", { style: { margin: 0, fontSize: 16 }, children: "Share to a follower" }),
827
- /* @__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." })
842
+ /* @__PURE__ */ jsx9("h3", { style: { margin: 0, fontSize: 16 }, children: copy.title }),
843
+ /* @__PURE__ */ jsx9("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: copy.allowlistDescription })
828
844
  ] }),
829
- /* @__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)) }),
845
+ /* @__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 ? copy.sharing : formatChapterCopy(copy.shareWith, { target: target.name }) }, target.id)) }),
830
846
  message ? /* @__PURE__ */ jsx9("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
831
847
  ] });
832
848
  }
833
849
 
834
850
  // src/ui/admin-record-activity.tsx
835
- import { useCallback as useCallback3, useEffect as useEffect5, useState as useState8 } from "react";
851
+ import { useCallback as useCallback3, useEffect as useEffect5, useState as useState8 } from "preact/hooks";
836
852
  import { ActivityFeed } from "@odla-ai/crm/ui";
837
- import { jsx as jsx10 } from "react/jsx-runtime";
853
+ import { jsx as jsx10 } from "preact/jsx-runtime";
838
854
  function RecordActivityPanel(props) {
839
855
  const { client, recordId } = props;
840
856
  const [activities, setActivities] = useState8([]);
@@ -873,10 +889,11 @@ function RecordActivityPanel(props) {
873
889
  }
874
890
 
875
891
  // src/ui/admin-record-comms.tsx
876
- import { useCallback as useCallback4, useEffect as useEffect6, useState as useState9 } from "react";
892
+ import { useCallback as useCallback4, useEffect as useEffect6, useState as useState9 } from "preact/hooks";
877
893
  import { Button as Button4, Field as Field4, Select as Select2 } from "@odla-ai/ui/components";
878
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
894
+ import { jsx as jsx11, jsxs as jsxs10 } from "preact/jsx-runtime";
879
895
  function RecordCommsPanel(props) {
896
+ const copy = useChapterCopy().admin.records;
880
897
  const { client, recordId, getToken } = props;
881
898
  const config = useAdminResource(getToken, "/api/admin/group/email");
882
899
  const templates = Object.entries(config.data?.emailTemplates ?? {}).filter(([, value]) => value.enabled);
@@ -889,32 +906,33 @@ function RecordCommsPanel(props) {
889
906
  setNote(null);
890
907
  try {
891
908
  const result = await client.sendEmail(recordId, { templateId: template });
892
- setNote(result.sent ? "Message sent." : result.reason ?? "Message was not sent.");
909
+ setNote(result.sent ? copy.messageSent : result.reason ?? copy.messageNotSent);
893
910
  } catch (failure) {
894
911
  setNote(failure instanceof Error ? failure.message : String(failure));
895
912
  } finally {
896
913
  setBusy(false);
897
914
  }
898
915
  };
899
- if (config.loading) return /* @__PURE__ */ jsx11("p", { className: "muted", children: "Loading templates\u2026" });
916
+ if (config.loading) return /* @__PURE__ */ jsx11("p", { className: "muted", children: copy.loadingTemplates });
900
917
  if (config.error) return /* @__PURE__ */ jsx11("p", { role: "alert", className: "muted", children: config.error });
901
918
  return /* @__PURE__ */ jsxs10("div", { style: { display: "grid", gap: 14, maxWidth: 560 }, children: [
902
- /* @__PURE__ */ jsx11(Field4, { label: "Template", children: /* @__PURE__ */ jsx11(
919
+ /* @__PURE__ */ jsx11(Field4, { label: copy.template, children: /* @__PURE__ */ jsx11(
903
920
  Select2,
904
921
  {
905
922
  value: template,
906
923
  onChange: (event) => setTemplate(event.currentTarget.value),
907
924
  options: [
908
- { value: "", label: "Choose a template\u2026" },
925
+ { value: "", label: copy.chooseTemplate },
909
926
  ...templates.map(([id, value]) => ({ value: id, label: value.subject || id }))
910
927
  ]
911
928
  }
912
929
  ) }),
913
- /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsx11(Button4, { disabled: !template || busy, onClick: () => void send(), children: busy ? "Sending\u2026" : "Send" }) }),
930
+ /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsx11(Button4, { disabled: !template || busy, onClick: () => void send(), children: busy ? copy.sending : copy.send }) }),
914
931
  note ? /* @__PURE__ */ jsx11("p", { role: "status", className: "muted", children: note }) : null
915
932
  ] });
916
933
  }
917
934
  function RecordCommsHistory(props) {
935
+ const copy = useChapterCopy().admin.records;
918
936
  const { client, recordId } = props;
919
937
  const [rows, setRows] = useState9([]);
920
938
  const [error, setError] = useState9(null);
@@ -929,9 +947,9 @@ function RecordCommsHistory(props) {
929
947
  }, [client, recordId]);
930
948
  useEffect6(() => void load(), [load]);
931
949
  if (error) return /* @__PURE__ */ jsx11("p", { role: "alert", className: "muted", children: error });
932
- if (rows.length === 0) return /* @__PURE__ */ jsx11("p", { className: "muted", children: "No messages recorded." });
950
+ if (rows.length === 0) return /* @__PURE__ */ jsx11("p", { className: "muted", children: copy.noMessages });
933
951
  return /* @__PURE__ */ jsx11("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 10 }, children: rows.map((row, index) => /* @__PURE__ */ jsxs10("li", { className: "panel", children: [
934
- /* @__PURE__ */ jsx11("strong", { children: String(row["templateId"] ?? row["template"] ?? "Message") }),
952
+ /* @__PURE__ */ jsx11("strong", { children: String(row["templateId"] ?? row["template"] ?? copy.message) }),
935
953
  /* @__PURE__ */ jsxs10("div", { className: "muted", children: [
936
954
  row["to"] ? String(row["to"]) : "",
937
955
  row["sentAt"] ? ` \xB7 ${new Date(Number(row["sentAt"])).toLocaleString()}` : ""
@@ -940,10 +958,11 @@ function RecordCommsHistory(props) {
940
958
  }
941
959
 
942
960
  // src/ui/admin-record-scheduling.tsx
943
- import { useState as useState10 } from "react";
961
+ import { useState as useState10 } from "preact/hooks";
944
962
  import { Button as Button5 } from "@odla-ai/ui/components";
945
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
963
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "preact/jsx-runtime";
946
964
  function RecordSchedulingPanel(props) {
965
+ const copy = useChapterCopy().admin.records;
947
966
  const { getToken, applicationId } = props;
948
967
  const resource = useAdminResource(
949
968
  getToken,
@@ -955,32 +974,32 @@ function RecordSchedulingPanel(props) {
955
974
  const run = async (meeting, action) => {
956
975
  let body;
957
976
  if (action === "reschedule") {
958
- const input = globalThis.prompt?.("New start time:");
977
+ const input = globalThis.prompt?.(copy.newStartPrompt);
959
978
  if (!input) return;
960
979
  const startAt = Date.parse(input);
961
980
  if (!Number.isFinite(startAt)) {
962
- setNote("Couldn\u2019t parse that time.");
981
+ setNote(copy.parseFailed);
963
982
  return;
964
983
  }
965
984
  body = JSON.stringify({ startAt });
966
- } else if (!globalThis.confirm?.("Cancel this call?")) {
985
+ } else if (!globalThis.confirm?.(copy.cancelConfirm)) {
967
986
  return;
968
987
  }
969
988
  setBusy(meeting.id);
970
989
  try {
971
990
  await adminFetch(getToken, `/api/admin/meetings/${meeting.id}/${action}`, { method: "POST", body });
972
991
  resource.refresh();
973
- setNote(action === "cancel" ? "Call cancelled." : "Call rescheduled.");
992
+ setNote(action === "cancel" ? copy.callCancelled : copy.callRescheduled);
974
993
  } catch (failure) {
975
994
  setNote(failure instanceof Error ? failure.message : String(failure));
976
995
  } finally {
977
996
  setBusy(null);
978
997
  }
979
998
  };
980
- if (!applicationId) return /* @__PURE__ */ jsx12("p", { className: "muted", children: "No application is linked to this record." });
981
- if (resource.loading) return /* @__PURE__ */ jsx12("p", { className: "muted", children: "Loading meetings\u2026" });
999
+ if (!applicationId) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.noApplication });
1000
+ if (resource.loading) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.loadingMeetings });
982
1001
  if (resource.error) return /* @__PURE__ */ jsx12("p", { role: "alert", className: "muted", children: resource.error });
983
- if (meetings.length === 0) return /* @__PURE__ */ jsx12("p", { className: "muted", children: "No meetings recorded." });
1002
+ if (meetings.length === 0) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.noMeetings });
984
1003
  return /* @__PURE__ */ jsxs11("div", { style: { display: "grid", gap: 12 }, children: [
985
1004
  meetings.map((meeting) => /* @__PURE__ */ jsxs11("section", { className: "panel", children: [
986
1005
  /* @__PURE__ */ jsx12("strong", { children: new Date(meeting.startAt).toLocaleString() }),
@@ -990,11 +1009,11 @@ function RecordSchedulingPanel(props) {
990
1009
  resource.data?.timezone
991
1010
  ] }),
992
1011
  /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
993
- meeting.meetUrl ? /* @__PURE__ */ jsx12("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: "Join meeting" }) : null,
994
- meeting.htmlLink ? /* @__PURE__ */ jsx12("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: "Open calendar" }) : null,
1012
+ meeting.meetUrl ? /* @__PURE__ */ jsx12("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.joinMeeting }) : null,
1013
+ meeting.htmlLink ? /* @__PURE__ */ jsx12("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.openCalendar }) : null,
995
1014
  meeting.status === "scheduled" ? /* @__PURE__ */ jsxs11(Fragment3, { children: [
996
- /* @__PURE__ */ jsx12(Button5, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: "Reschedule" }),
997
- /* @__PURE__ */ jsx12(Button5, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: "Cancel" })
1015
+ /* @__PURE__ */ jsx12(Button5, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: copy.reschedule }),
1016
+ /* @__PURE__ */ jsx12(Button5, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: copy.cancel })
998
1017
  ] }) : null
999
1018
  ] })
1000
1019
  ] }, meeting.id)),
@@ -1003,13 +1022,13 @@ function RecordSchedulingPanel(props) {
1003
1022
  }
1004
1023
 
1005
1024
  // src/ui/admin-people-workspace.tsx
1006
- import { jsx as jsx13 } from "react/jsx-runtime";
1007
- function applicationLifecycleAdapter(ctx) {
1025
+ import { jsx as jsx13 } from "preact/jsx-runtime";
1026
+ function applicationLifecycleAdapter(ctx, copy = DEFAULT_CHAPTER_COPY.admin.records) {
1008
1027
  return {
1009
1028
  transitionStage: async ({ record, to }) => {
1010
1029
  const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
1011
1030
  if (!applicationId) {
1012
- throw new Error("This record is not linked to an application workflow.");
1031
+ throw new Error(copy.workflowMissing);
1013
1032
  }
1014
1033
  const path = `/api/admin/applications/${encodeURIComponent(applicationId)}`;
1015
1034
  if (to === "approved") {
@@ -1026,6 +1045,7 @@ function applicationLifecycleAdapter(ctx) {
1026
1045
  };
1027
1046
  }
1028
1047
  function PeopleBody(props) {
1048
+ const copy = useChapterCopy().admin.records;
1029
1049
  const {
1030
1050
  crm,
1031
1051
  client,
@@ -1045,7 +1065,7 @@ function PeopleBody(props) {
1045
1065
  } = props;
1046
1066
  const def = crm.type(type);
1047
1067
  const recordId = ctx.route?.recordId ?? null;
1048
- const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx) : void 0;
1068
+ const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx, copy) : void 0;
1049
1069
  const resolvedLifecycle = typeof lifecycleAdapter === "function" ? lifecycleAdapter(ctx) : lifecycleAdapter ?? builtInLifecycle;
1050
1070
  const workspaceClient = client;
1051
1071
  return /* @__PURE__ */ jsx13(AdminPage, { children: /* @__PURE__ */ jsx13(
@@ -1081,35 +1101,35 @@ function PeopleBody(props) {
1081
1101
  if (def.facets?.email !== false) {
1082
1102
  tabs.push({
1083
1103
  id: "comms",
1084
- label: "Comms",
1104
+ label: copy.comms,
1085
1105
  panel: /* @__PURE__ */ jsx13(RecordCommsPanel, { client, recordId: record.id, getToken: ctx.getToken })
1086
1106
  });
1087
1107
  tabs.push({
1088
1108
  id: "history",
1089
- label: "Comms history",
1109
+ label: copy.commsHistory,
1090
1110
  panel: /* @__PURE__ */ jsx13(RecordCommsHistory, { client, recordId: record.id })
1091
1111
  });
1092
1112
  }
1093
1113
  if (lifecycle) {
1094
1114
  tabs.push({
1095
1115
  id: "scheduling",
1096
- label: "Scheduling",
1116
+ label: copy.scheduling,
1097
1117
  panel: /* @__PURE__ */ jsx13(RecordSchedulingPanel, { getToken: ctx.getToken, applicationId })
1098
1118
  });
1099
- tabs.push({ id: "billing", label: "Billing", panel: /* @__PURE__ */ jsx13(BillingCard, { record }) });
1119
+ tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */ jsx13(BillingCard, { record }) });
1100
1120
  } else {
1101
1121
  add("billing");
1102
1122
  }
1103
1123
  tabs.push({
1104
1124
  id: "notes",
1105
- label: "Notes",
1125
+ label: copy.notes,
1106
1126
  panel: /* @__PURE__ */ jsx13(RecordActivityPanel, { client, recordId: record.id })
1107
1127
  });
1108
1128
  add("connections");
1109
1129
  if (lifecycle) {
1110
1130
  tabs.push({
1111
1131
  id: "access",
1112
- label: "Access",
1132
+ label: copy.access,
1113
1133
  panel: /* @__PURE__ */ jsx13(
1114
1134
  RecordActions,
1115
1135
  {
@@ -1128,7 +1148,7 @@ function PeopleBody(props) {
1128
1148
  if (networkSharing) {
1129
1149
  tabs.push({
1130
1150
  id: "sharing",
1131
- label: "Sharing",
1151
+ label: copy.sharing,
1132
1152
  panel: /* @__PURE__ */ jsx13(NetworkShareActions, { recordId: record.id, recordType: record.type, getToken: ctx.getToken })
1133
1153
  });
1134
1154
  }
@@ -1139,7 +1159,7 @@ function PeopleBody(props) {
1139
1159
  }
1140
1160
 
1141
1161
  // src/ui/admin-people.tsx
1142
- import { jsx as jsx14 } from "react/jsx-runtime";
1162
+ import { jsx as jsx14 } from "preact/jsx-runtime";
1143
1163
  var DEFAULT_ROLES = ["provisional", "member", "admin"];
1144
1164
  function collectionSection(options) {
1145
1165
  const { crm, type } = options;
@@ -1224,7 +1244,7 @@ function peopleSection(options) {
1224
1244
  }
1225
1245
 
1226
1246
  // src/ui/admin-workspaces.tsx
1227
- import { jsx as jsx15 } from "react/jsx-runtime";
1247
+ import { jsx as jsx15 } from "preact/jsx-runtime";
1228
1248
  var adminViewContext = (ctx, viewId, active) => {
1229
1249
  const route = {
1230
1250
  workspaceId: ctx.route.workspaceId,
@@ -1245,26 +1265,27 @@ var adminViewContext = (ctx, viewId, active) => {
1245
1265
  href: (target) => ctx.href(targetFromView(target))
1246
1266
  };
1247
1267
  };
1248
- function DashboardWorkspace({ ctx }) {
1268
+ function DashboardWorkspace({ chapter, ctx }) {
1269
+ const copy = chapter.copy.admin;
1249
1270
  const active = ctx.route.viewId === "billing" ? "billing" : "overview";
1250
- const overview = dashboardSection({ id: "overview", label: "Overview" });
1251
- const billing = billingSection();
1271
+ const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
1272
+ const billing = billingSection({ label: copy.workspaces.billing });
1252
1273
  return /* @__PURE__ */ jsx15(
1253
1274
  Tabs2,
1254
1275
  {
1255
- ariaLabel: "Dashboard views",
1276
+ ariaLabel: copy.workspaces.dashboardViewsLabel,
1256
1277
  value: active,
1257
1278
  mount: "active",
1258
1279
  items: [
1259
1280
  {
1260
1281
  value: "overview",
1261
- label: "Overview",
1282
+ label: copy.workspaces.overview,
1262
1283
  href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
1263
1284
  panel: overview.render(adminViewContext(ctx, "overview", active === "overview"))
1264
1285
  },
1265
1286
  {
1266
1287
  value: "billing",
1267
- label: "Billing",
1288
+ label: copy.workspaces.billing,
1268
1289
  href: ctx.href({ viewId: "billing", recordId: null, detailTab: null }),
1269
1290
  panel: billing.render(adminViewContext(ctx, "billing", active === "billing"))
1270
1291
  }
@@ -1272,26 +1293,27 @@ function DashboardWorkspace({ ctx }) {
1272
1293
  }
1273
1294
  );
1274
1295
  }
1275
- function SettingsWorkspace({ ctx }) {
1296
+ function SettingsWorkspace({ chapter, ctx }) {
1297
+ const copy = chapter.copy.admin;
1276
1298
  const active = ctx.route.viewId === "email" ? "email" : "calendar";
1277
- const calendar = availabilitySection({ id: "calendar", label: "Calendar" });
1278
- const email = emailSection();
1299
+ const calendar = availabilitySection({ id: "calendar", label: copy.workspaces.calendar });
1300
+ const email = emailSection({ label: copy.workspaces.email });
1279
1301
  return /* @__PURE__ */ jsx15(
1280
1302
  Tabs2,
1281
1303
  {
1282
- ariaLabel: "Settings views",
1304
+ ariaLabel: copy.workspaces.settingsViewsLabel,
1283
1305
  value: active,
1284
1306
  mount: "active",
1285
1307
  items: [
1286
1308
  {
1287
1309
  value: "calendar",
1288
- label: "Calendar",
1310
+ label: copy.workspaces.calendar,
1289
1311
  href: ctx.href({ viewId: "calendar", recordId: null, detailTab: null }),
1290
1312
  panel: calendar.render(adminViewContext(ctx, "calendar", active === "calendar"))
1291
1313
  },
1292
1314
  {
1293
1315
  value: "email",
1294
- label: "Email",
1316
+ label: copy.workspaces.email,
1295
1317
  href: ctx.href({ viewId: "email", recordId: null, detailTab: null }),
1296
1318
  panel: email.render(adminViewContext(ctx, "email", active === "email"))
1297
1319
  }
@@ -1307,7 +1329,7 @@ function PeopleWorkspace({ chapter, ctx }) {
1307
1329
  return /* @__PURE__ */ jsx15(
1308
1330
  Tabs2,
1309
1331
  {
1310
- ariaLabel: "CRM collections",
1332
+ ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
1311
1333
  value: active,
1312
1334
  variant: "pill",
1313
1335
  mount: "active",
@@ -1330,9 +1352,10 @@ function PeopleWorkspace({ chapter, ctx }) {
1330
1352
  );
1331
1353
  }
1332
1354
  function defaultAdminWorkspaces(chapter) {
1355
+ const copy = chapter.copy.admin;
1333
1356
  const people = {
1334
1357
  id: "people",
1335
- label: "People",
1358
+ label: copy.workspaces.people,
1336
1359
  defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
1337
1360
  render: (ctx) => /* @__PURE__ */ jsx15(PeopleWorkspace, { chapter, ctx })
1338
1361
  };
@@ -1340,37 +1363,38 @@ function defaultAdminWorkspaces(chapter) {
1340
1363
  return [
1341
1364
  {
1342
1365
  id: "dashboard",
1343
- label: "Dashboard",
1366
+ label: copy.workspaces.dashboard,
1344
1367
  defaultViewId: "overview",
1345
- render: (ctx) => /* @__PURE__ */ jsx15(DashboardWorkspace, { ctx })
1368
+ render: (ctx) => /* @__PURE__ */ jsx15(DashboardWorkspace, { chapter, ctx })
1346
1369
  },
1347
1370
  people,
1348
1371
  {
1349
1372
  id: "settings",
1350
- label: "Settings",
1373
+ label: copy.workspaces.settings,
1351
1374
  defaultViewId: "calendar",
1352
- render: (ctx) => /* @__PURE__ */ jsx15(SettingsWorkspace, { ctx })
1375
+ render: (ctx) => /* @__PURE__ */ jsx15(SettingsWorkspace, { chapter, ctx })
1353
1376
  }
1354
1377
  ];
1355
1378
  }
1356
1379
 
1357
1380
  // src/ui/admin-frame.tsx
1358
- import { ThemeScope } from "@odla-ai/ui/components";
1359
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1381
+ import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
1382
+ import { jsx as jsx16, jsxs as jsxs12 } from "preact/jsx-runtime";
1360
1383
  function AdminTheme(props) {
1361
1384
  const brand = props.chapter?.brand;
1362
- return /* @__PURE__ */ jsxs12(
1363
- ThemeScope,
1385
+ return /* @__PURE__ */ jsx16(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs12(
1386
+ ThemeScope2,
1364
1387
  {
1365
1388
  "data-chapter-admin": "",
1366
1389
  theme: brand?.theme,
1390
+ accent: brand?.accent,
1367
1391
  colorScheme: brand?.colorScheme ?? "light",
1368
1392
  children: [
1369
1393
  /* @__PURE__ */ jsx16(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
1370
1394
  props.children
1371
1395
  ]
1372
1396
  }
1373
- );
1397
+ ) });
1374
1398
  }
1375
1399
  function signInRedirect(basePath, workspaces) {
1376
1400
  if (typeof window === "undefined") return basePath;
@@ -1410,8 +1434,9 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
1410
1434
  }
1411
1435
 
1412
1436
  // src/ui/admin.tsx
1413
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1437
+ import { jsx as jsx17, jsxs as jsxs13 } from "preact/jsx-runtime";
1414
1438
  function Authed(props) {
1439
+ const copy = useChapterCopy().admin;
1415
1440
  const {
1416
1441
  workspaces,
1417
1442
  basePath,
@@ -1456,15 +1481,14 @@ function Authed(props) {
1456
1481
  live = false;
1457
1482
  };
1458
1483
  }, [getToken, apiBase, auth]);
1459
- if (state.status === "checking") return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Checking access\u2026" }) });
1484
+ if (state.status === "checking") return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.auth.checking }) });
1460
1485
  if (state.status === "denied") {
1461
1486
  return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsxs13("div", { className: "card", style: S.card, children: [
1462
- /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: "Not authorized" }),
1463
- /* @__PURE__ */ jsxs13("p", { style: S.muted, children: [
1464
- state.email ? `${state.email} isn't` : "This account isn't",
1465
- " on the admin list."
1466
- ] }),
1467
- /* @__PURE__ */ jsx17("button", { className: "btn secondary", onClick: () => signOut(), children: "Sign out" })
1487
+ /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
1488
+ /* @__PURE__ */ jsx17("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
1489
+ account: state.email ?? copy.auth.thisAccount
1490
+ }) }),
1491
+ /* @__PURE__ */ jsx17("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
1468
1492
  ] }) });
1469
1493
  }
1470
1494
  return /* @__PURE__ */ jsx17(
@@ -1487,6 +1511,7 @@ function Authed(props) {
1487
1511
  );
1488
1512
  }
1489
1513
  function ChapterAdmin(props) {
1514
+ const copy = props.chapter?.copy ?? DEFAULT_CHAPTER_COPY;
1490
1515
  const defaults = useMemo(
1491
1516
  () => props.chapter ? defaultAdminWorkspaces(props.chapter) : [],
1492
1517
  [props.chapter]
@@ -1497,7 +1522,7 @@ function ChapterAdmin(props) {
1497
1522
  }, [props.workspaces, props.sections, defaults]);
1498
1523
  const basePath = props.basePath ?? "/admin";
1499
1524
  const brand = props.brand ?? {
1500
- name: props.chapter?.name ?? "Admin",
1525
+ name: props.chapter?.name ?? copy.admin.shell.adminName,
1501
1526
  badge: props.chapter?.brand.badge,
1502
1527
  wordmark: props.chapter?.brand.wordmark
1503
1528
  };
@@ -1517,14 +1542,14 @@ function ChapterAdmin(props) {
1517
1542
  }, [apiBase, auth]);
1518
1543
  let content;
1519
1544
  if (publishableKey === void 0) {
1520
- content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Loading\u2026" }) });
1545
+ content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.loading }) });
1521
1546
  } else if (!publishableKey) {
1522
1547
  content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsxs13("div", { className: "card", style: S.card, children: [
1523
- /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: "Sign-in not configured" }),
1524
- /* @__PURE__ */ jsx17("p", { style: S.muted, children: "No Clerk publishable key is set for this environment yet." })
1548
+ /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
1549
+ /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
1525
1550
  ] }) });
1526
1551
  } else if (workspaces.length === 0) {
1527
- content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "No admin workspaces are configured." }) });
1552
+ content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
1528
1553
  } else {
1529
1554
  content = /* @__PURE__ */ jsxs13(
1530
1555
  ClerkGate,
@@ -1533,7 +1558,7 @@ function ChapterAdmin(props) {
1533
1558
  appearance: clerkAppearanceFromTokens(),
1534
1559
  afterSignOutUrl: "/",
1535
1560
  children: [
1536
- /* @__PURE__ */ jsx17(SignedOut, { children: /* @__PURE__ */ jsx17(Gate, { brand, tagline: "Admin sign-in \u2014 invite only", children: /* @__PURE__ */ jsx17(
1561
+ /* @__PURE__ */ jsx17(SignedOut, { children: /* @__PURE__ */ jsx17(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx17(
1537
1562
  SignIn,
1538
1563
  {
1539
1564
  routing: "hash",
@@ -1570,17 +1595,18 @@ function defaultAdminSections(chapter) {
1570
1595
  }
1571
1596
 
1572
1597
  // src/ui/admin-meetings.tsx
1573
- import { useState as useState12 } from "react";
1598
+ import { useState as useState12 } from "preact/hooks";
1574
1599
  import { Button as Button6 } from "@odla-ai/ui/components";
1575
- import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1600
+ import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "preact/jsx-runtime";
1576
1601
  var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
1577
1602
  function MeetingsBody({ ctx }) {
1603
+ const copy = useChapterCopy().admin.meetings;
1578
1604
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
1579
1605
  const [busy, setBusy] = useState12(null);
1580
1606
  const [note, setNote] = useState12(null);
1581
- if (loading) return /* @__PURE__ */ jsx18(AdminNote, { children: "Loading\u2026" });
1607
+ if (loading) return /* @__PURE__ */ jsx18(AdminNote, { children: copy.loading });
1582
1608
  if (error || !data) return /* @__PURE__ */ jsxs14(AdminNote, { children: [
1583
- "Couldn\u2019t load meetings",
1609
+ copy.loadFailed,
1584
1610
  error ? `: ${error}` : "",
1585
1611
  "."
1586
1612
  ] });
@@ -1597,37 +1623,39 @@ function MeetingsBody({ ctx }) {
1597
1623
  }
1598
1624
  };
1599
1625
  const cancel = (id) => {
1600
- if (!globalThis.confirm?.("Cancel this call? Google notifies the attendee.")) return;
1626
+ if (!globalThis.confirm?.(copy.cancelConfirm)) return;
1601
1627
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/cancel`, { method: "POST" }));
1602
1628
  };
1603
1629
  const reschedule = (id) => {
1604
- const input = globalThis.prompt?.("New start time (e.g. 2026-08-01 14:00):");
1630
+ const input = globalThis.prompt?.(copy.newStartPrompt);
1605
1631
  if (!input) return;
1606
1632
  const startAt = Date.parse(input);
1607
1633
  if (!Number.isFinite(startAt)) {
1608
- setNote("Couldn\u2019t parse that time.");
1634
+ setNote(copy.parseFailed);
1609
1635
  return;
1610
1636
  }
1611
1637
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
1612
1638
  };
1613
1639
  return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(Panel, { title: /* @__PURE__ */ jsxs14(Fragment4, { children: [
1614
- "Agenda ",
1640
+ copy.agenda,
1641
+ " ",
1615
1642
  /* @__PURE__ */ jsxs14("span", { className: "muted", style: { fontWeight: 400 }, children: [
1616
1643
  "\xB7 ",
1617
1644
  data.timezone
1618
1645
  ] })
1619
- ] }), actions: note ? /* @__PURE__ */ jsx18("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: "No meetings." }) : /* @__PURE__ */ jsx18("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs14("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
1646
+ ] }), actions: note ? /* @__PURE__ */ jsx18("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx18("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs14("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
1620
1647
  /* @__PURE__ */ jsx18("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
1621
- /* @__PURE__ */ jsx18("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : "(unknown)" }),
1648
+ /* @__PURE__ */ jsx18("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
1622
1649
  /* @__PURE__ */ jsx18("span", { className: "badge", children: m.status }),
1623
1650
  m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs14("span", { className: "badge", children: [
1624
- "drift: ",
1651
+ copy.drift,
1652
+ ": ",
1625
1653
  m.drift
1626
1654
  ] }) : null,
1627
- m.meetUrl ? /* @__PURE__ */ jsx18("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
1655
+ m.meetUrl ? /* @__PURE__ */ jsx18("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
1628
1656
  m.status === "scheduled" ? /* @__PURE__ */ jsxs14(Fragment4, { children: [
1629
- /* @__PURE__ */ jsx18(Button6, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: "Reschedule" }),
1630
- /* @__PURE__ */ jsx18(Button6, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: "Cancel" })
1657
+ /* @__PURE__ */ jsx18(Button6, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
1658
+ /* @__PURE__ */ jsx18(Button6, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
1631
1659
  ] }) : null
1632
1660
  ] }, m.id)) }) }) });
1633
1661
  }
@@ -1663,4 +1691,4 @@ export {
1663
1691
  defaultAdminSections,
1664
1692
  meetingsSection
1665
1693
  };
1666
- //# sourceMappingURL=chunk-IWW5VAAC.js.map
1694
+ //# sourceMappingURL=chunk-VCC4S6JU.js.map