@odla-ai/chapter 0.22.1 → 0.24.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,22 +1,19 @@
1
1
  import {
2
- BrandStyle
3
- } from "./chunk-3QKGKUTX.js";
2
+ BrandStyle,
3
+ ChapterCopyProvider,
4
+ DEFAULT_CHAPTER_COPY,
5
+ formatChapterCopy,
6
+ useChapterCopy
7
+ } from "./chunk-WGQO4WDJ.js";
4
8
 
5
9
  // src/ui/admin.tsx
6
- import { useEffect as useEffect7, useMemo, useState as useState11 } from "preact/hooks";
7
- import {
8
- ClerkGate,
9
- SignedIn,
10
- SignedOut,
11
- SignIn,
12
- useClerkAuth,
13
- clerkAppearanceFromTokens
14
- } from "@odla-ai/auth-clerk";
10
+ import { useEffect as useEffect8, useMemo, useRef, useState as useState12 } from "preact/hooks";
11
+ import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
15
12
  import { CrmClient } from "@odla-ai/crm";
16
13
 
17
14
  // src/ui/chrome.tsx
18
15
  import { useCallback, useEffect as useEffect2, useState as useState2 } from "preact/hooks";
19
- import { Tabs, TopBar, TopBarLink } from "@odla-ai/ui/components";
16
+ import { Tabs, ThemeScope, TopBar, TopBarLink } from "@odla-ai/ui/components";
20
17
 
21
18
  // src/ui/admin-layout.tsx
22
19
  import { useEffect, useState } from "preact/hooks";
@@ -301,6 +298,7 @@ function AdminShell({
301
298
  renderAccountMenu,
302
299
  renderWorkspaceNav
303
300
  }) {
301
+ const copy = useChapterCopy().admin;
304
302
  const normalizeRoute = useCallback((candidate) => {
305
303
  if (candidate.viewId) return candidate;
306
304
  const workspace = workspaces.find((item) => item.id === candidate.workspaceId);
@@ -371,10 +369,12 @@ function AdminShell({
371
369
  signOut
372
370
  };
373
371
  const identity = renderAccountMenu?.(accountProps) ?? /* @__PURE__ */ jsxs3(Fragment, { children: [
374
- /* @__PURE__ */ jsx3("span", { style: { ...S.role, color: "inherit", borderColor: "currentColor", background: "transparent" }, children: "admin" }),
372
+ /* @__PURE__ */ jsx3("span", { style: { ...S.role, color: "inherit", borderColor: "currentColor", background: "transparent" }, children: copy.shell.adminRole }),
375
373
  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" })
374
+ /* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: copy.auth.signOut })
377
375
  ] });
376
+ const activeContent = active?.render(context);
377
+ const workspaceContent = active?.accent ? /* @__PURE__ */ jsx3(ThemeScope, { accent: active.accent, children: activeContent }) : activeContent;
378
378
  if (chrome === "topbar") {
379
379
  return /* @__PURE__ */ jsxs3(Fragment, { children: [
380
380
  /* @__PURE__ */ jsxs3(TopBar, { children: [
@@ -382,13 +382,13 @@ function AdminShell({
382
382
  /* @__PURE__ */ jsx3("span", { style: S.badgeSm, children: badgeText(brand) }),
383
383
  brandDisplay(brand)
384
384
  ] }),
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)) }),
385
+ /* @__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
386
  /* @__PURE__ */ jsx3("span", { className: "topbar-spacer" }),
387
387
  /* @__PURE__ */ jsx3("div", { className: "topbar-actions", children: identity })
388
388
  ] }),
389
389
  /* @__PURE__ */ jsxs3("main", { className: "shell-main", style: S.main, children: [
390
390
  /* @__PURE__ */ jsx3(ThemeWarning, {}),
391
- active?.render(context)
391
+ workspaceContent
392
392
  ] })
393
393
  ] });
394
394
  }
@@ -397,7 +397,7 @@ function AdminShell({
397
397
  /* @__PURE__ */ jsx3("span", { style: S.badgeSm, children: badgeText(brand) }),
398
398
  brandDisplay(brand)
399
399
  ] }),
400
- /* @__PURE__ */ jsx3("span", { style: { opacity: 0.68 }, children: "Admin Console" }),
400
+ /* @__PURE__ */ jsx3("span", { style: { opacity: 0.68 }, children: copy.shell.adminConsole }),
401
401
  /* @__PURE__ */ jsx3("span", { style: { flex: 1 } }),
402
402
  /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: identity })
403
403
  ] }) : null;
@@ -407,12 +407,12 @@ function AdminShell({
407
407
  /* @__PURE__ */ jsx3(ThemeWarning, {}),
408
408
  renderWorkspaceNav ? /* @__PURE__ */ jsxs3(Fragment, { children: [
409
409
  /* @__PURE__ */ jsx3("div", { style: S.workspaceTabs, children: renderWorkspaceNav({ workspaces, route, navigate, href }) }),
410
- active?.render(context)
410
+ workspaceContent
411
411
  ] }) : /* @__PURE__ */ jsx3(
412
412
  Tabs,
413
413
  {
414
414
  style: S.workspaceTabs,
415
- ariaLabel: "Admin workspaces",
415
+ ariaLabel: copy.shell.navigationLabel,
416
416
  value: route.workspaceId,
417
417
  mount: "active",
418
418
  onValueChange: (workspaceId) => setRoute(normalizeRoute({ workspaceId })),
@@ -420,7 +420,7 @@ function AdminShell({
420
420
  value: workspace.id,
421
421
  label: workspace.label,
422
422
  href: href(workspace.id),
423
- panel: workspace.id === route.workspaceId ? workspace.render(context) : null
423
+ panel: workspace.id === route.workspaceId ? workspace.accent ? /* @__PURE__ */ jsx3(ThemeScope, { accent: workspace.accent, children: workspace.render(context) }) : workspace.render(context) : null
424
424
  }))
425
425
  }
426
426
  )
@@ -467,17 +467,17 @@ function useAdminResource(getToken, path) {
467
467
 
468
468
  // src/ui/admin-availability.tsx
469
469
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
470
- var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
471
470
  function AvailabilityBody({ ctx }) {
471
+ const copy = useChapterCopy().admin.availability;
472
472
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/scheduling");
473
473
  const [draft, setDraft] = useState4(null);
474
474
  const [busy, setBusy] = useState4(false);
475
475
  const [note, setNote] = useState4(null);
476
476
  const [errors, setErrors] = useState4({});
477
477
  const cfg = draft ?? data?.scheduling ?? null;
478
- if (loading) return /* @__PURE__ */ jsx4(AdminNote, { children: "Loading\u2026" });
478
+ if (loading) return /* @__PURE__ */ jsx4(AdminNote, { children: copy.loading });
479
479
  if (error || !cfg) return /* @__PURE__ */ jsxs4(AdminNote, { children: [
480
- "Couldn\u2019t load availability",
480
+ copy.loadFailed,
481
481
  error ? `: ${error}` : "",
482
482
  "."
483
483
  ] });
@@ -499,7 +499,7 @@ function AvailabilityBody({ ctx }) {
499
499
  }
500
500
  setDraft(null);
501
501
  refresh();
502
- setNote("Saved.");
502
+ setNote(copy.saved);
503
503
  } catch (e) {
504
504
  setNote(e instanceof Error ? e.message : String(e));
505
505
  } finally {
@@ -507,18 +507,18 @@ function AvailabilityBody({ ctx }) {
507
507
  }
508
508
  };
509
509
  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]) }) }) });
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)) }) }),
510
+ return /* @__PURE__ */ jsx4(AdminPage, { children: /* @__PURE__ */ jsxs4(Panel, { title: copy.title, actions: note ? /* @__PURE__ */ jsx4("span", { className: "muted", children: note }) : void 0, children: [
511
+ /* @__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
512
  /* @__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, onInput: (e) => edit({ timezone: e.target.value }) }) })
513
+ numField("startHour", copy.startHour),
514
+ numField("endHour", copy.endHour),
515
+ numField("slotMinutes", copy.slotMinutes),
516
+ numField("minNoticeHours", copy.minNoticeHours),
517
+ numField("windowDays", copy.windowDays),
518
+ /* @__PURE__ */ jsx4(Field, { label: copy.timezone, error: errors.timezone, children: /* @__PURE__ */ jsx4(Input, { value: cfg.timezone, onInput: (e) => edit({ timezone: e.target.value }) }) })
519
519
  ] }),
520
- /* @__PURE__ */ jsx4(Field, { label: "Event summary template", error: errors.summaryTemplate, children: /* @__PURE__ */ jsx4(Input, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
521
- /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(Button, { disabled: !draft || busy, onClick: () => void save(), children: "Save availability" }) })
520
+ /* @__PURE__ */ jsx4(Field, { label: copy.summaryTemplate, error: errors.summaryTemplate, children: /* @__PURE__ */ jsx4(Input, { value: cfg.summaryTemplate, onInput: (e) => edit({ summaryTemplate: e.target.value }) }) }),
521
+ /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(Button, { disabled: !draft || busy, onClick: () => void save(), children: copy.save }) })
522
522
  ] }) });
523
523
  }
524
524
  function availabilitySection(options = {}) {
@@ -531,35 +531,36 @@ import { DataTable, StatBand } from "@odla-ai/ui/components";
531
531
  import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
532
532
  var money = (cents) => `$${(cents / 100).toLocaleString()}`;
533
533
  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 }
534
+ var columns = (copy) => [
535
+ { key: "name", header: copy.name, cell: (r) => r.name, sortAs: "string" },
536
+ { key: "email", header: copy.email, cell: (r) => r.email },
537
+ { key: "applicationStatus", header: copy.application, cell: (r) => r.applicationStatus },
538
+ { key: "subscriptionStatus", header: copy.subscription, cell: (r) => r.subscriptionStatus ? `${r.subscriptionStatus}${r.cancelAtPeriodEnd ? ` \xB7 ${copy.cancelling}` : ""}` : "\u2014" },
539
+ { key: "amountCents", header: copy.amount, cell: (r) => `${money(r.amountCents)}/${r.interval === "month" ? "mo" : "yr"}`, sortAs: "number", sortValue: (r) => r.amountCents },
540
+ { key: "renewalAt", header: copy.renews, cell: (r) => date(r.renewalAt), sortAs: "number", sortValue: (r) => r.renewalAt ?? 0 }
541
541
  ];
542
542
  function BillingBody({ ctx }) {
543
+ const copy = useChapterCopy().admin.billing;
543
544
  const { data, loading, error } = useAdminResource(ctx.getToken, "/api/admin/billing");
544
- if (loading) return /* @__PURE__ */ jsx5(AdminNote, { children: "Loading\u2026" });
545
+ if (loading) return /* @__PURE__ */ jsx5(AdminNote, { children: copy.loading });
545
546
  if (error || !data) return /* @__PURE__ */ jsxs5(AdminNote, { children: [
546
- "Couldn\u2019t load billing",
547
+ copy.loadFailed,
547
548
  error ? `: ${error}` : "",
548
549
  "."
549
550
  ] });
550
- if (!data.billingReady) return /* @__PURE__ */ jsx5(AdminNote, { children: "Billing isn\u2019t configured \u2014 no Stripe key is vaulted for this group." });
551
+ if (!data.billingReady) return /* @__PURE__ */ jsx5(AdminNote, { children: copy.notConfigured });
551
552
  const s = data.summary;
552
553
  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" }
554
+ { value: s.activeCount, label: copy.active },
555
+ { value: money(s.annualizedCents), label: copy.annualized },
556
+ { value: s.renewingSoonCount, label: copy.renewingSoon },
557
+ { value: s.pastDueCount, label: copy.pastDue }
557
558
  ] : [];
558
559
  return /* @__PURE__ */ jsxs5(AdminPage, { children: [
559
560
  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 })
561
+ /* @__PURE__ */ jsxs5(Panel, { title: copy.subscriptions, actions: data.testMode ? /* @__PURE__ */ jsx5("span", { className: "badge", children: copy.testMode }) : void 0, children: [
562
+ data.truncated ? /* @__PURE__ */ jsx5("p", { className: "badge", children: copy.truncated }) : null,
563
+ /* @__PURE__ */ jsx5(DataTable, { rows: data.rows, columns: columns(copy), rowKey: (r) => r.id })
563
564
  ] })
564
565
  ] });
565
566
  }
@@ -575,20 +576,21 @@ var number = (value) => Math.round(value).toLocaleString();
575
576
  var money2 = (cents) => `$${Math.round(cents / 100).toLocaleString()}`;
576
577
  var when = (time) => new Date(time).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
577
578
  function DashboardBody({ ctx }) {
579
+ const copy = useChapterCopy().admin.dashboard;
578
580
  const { data, loading, error } = useAdminResource(
579
581
  ctx.getToken,
580
582
  "/api/admin/dashboard"
581
583
  );
582
- if (loading) return /* @__PURE__ */ jsx6(AdminNote, { children: "Loading dashboard\u2026" });
584
+ if (loading) return /* @__PURE__ */ jsx6(AdminNote, { children: copy.loading });
583
585
  if (error || !data) return /* @__PURE__ */ jsxs6(AdminNote, { children: [
584
- "Couldn\u2019t load the dashboard",
586
+ copy.loadFailed,
585
587
  error ? `: ${error}` : "",
586
588
  "."
587
589
  ] });
588
590
  const pipelineSteps = Object.entries(data.pipeline).filter(([stage]) => !["declined", "refunded"].includes(stage)).map(([stage, count]) => ({
589
591
  icon: /* @__PURE__ */ jsx6("span", { style: { fontSize: 22, fontWeight: 700 }, children: count }),
590
592
  title: stage.replaceAll("_", " "),
591
- description: data.pipelineDelta[stage] ? `+${data.pipelineDelta[stage]} this week` : "no change"
593
+ description: data.pipelineDelta[stage] ? formatChapterCopy(copy.thisWeek, { count: data.pipelineDelta[stage] }) : copy.noChange
592
594
  }));
593
595
  const exceptions = ["declined", "refunded"].filter((stage) => stage in data.pipeline).map((stage) => `${stage} ${data.pipeline[stage]}`).join(" \xB7 ");
594
596
  return /* @__PURE__ */ jsxs6(AdminPage, { children: [
@@ -596,54 +598,54 @@ function DashboardBody({ ctx }) {
596
598
  Panel,
597
599
  {
598
600
  title: /* @__PURE__ */ jsxs6(Fragment2, { children: [
599
- "Upcoming calls",
601
+ copy.upcomingCalls,
600
602
  " ",
601
603
  /* @__PURE__ */ jsxs6("span", { className: "muted", style: { fontWeight: 400 }, children: [
602
604
  "\xB7 ",
603
605
  data.timezone
604
606
  ] })
605
607
  ] }),
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: [
608
+ actions: data.calls.needsAttention ? /* @__PURE__ */ jsx6("span", { className: "badge", children: formatChapterCopy(copy.callsNeedAttention, { count: data.calls.needsAttention }) }) : void 0,
609
+ 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
610
  /* @__PURE__ */ jsx6("strong", { style: { minWidth: 170 }, children: when(meeting.startAt) }),
612
611
  /* @__PURE__ */ jsxs6("span", { style: { flex: 1 }, children: [
613
612
  meeting.name,
614
613
  meeting.email ? ` \xB7 ${meeting.email}` : ""
615
614
  ] }),
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
615
+ meeting.drift && meeting.drift !== "none" ? /* @__PURE__ */ jsx6("span", { className: "badge", children: copy.drift }) : null,
616
+ meeting.htmlLink ? /* @__PURE__ */ jsx6("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.open }) : null,
617
+ meeting.meetUrl ? /* @__PURE__ */ jsx6("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null
619
618
  ] }, meeting.id)) })
620
619
  }
621
620
  ),
622
621
  /* @__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." }) })
622
+ /* @__PURE__ */ jsx6(MetricWidget, { label: copy.applications, data: data.applicationsSeries, format: number }),
623
+ 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
624
  ] }),
626
625
  data.revenueSeries && data.revenue.billingReady ? /* @__PURE__ */ jsxs6("div", { children: [
627
626
  /* @__PURE__ */ jsx6(
628
627
  MetricWidget,
629
628
  {
630
- label: "Revenue \xB7 annual run rate added",
629
+ label: copy.revenueAdded,
631
630
  data: data.revenueSeries,
632
631
  format: money2,
633
632
  size: "large"
634
633
  }
635
634
  ),
636
635
  /* @__PURE__ */ jsxs6("p", { className: "muted", children: [
637
- "Active memberships: ",
636
+ copy.activeMemberships,
637
+ ": ",
638
638
  data.revenue.activeCount ?? 0,
639
- " \xB7 Annual run rate:",
639
+ " \xB7 ",
640
+ copy.annualRunRate,
641
+ ":",
640
642
  " ",
641
643
  money2(data.revenue.annualRunRateCents ?? 0),
642
- data.revenue.testMode ? " \xB7 test mode" : ""
644
+ data.revenue.testMode ? ` \xB7 ${copy.testMode}` : ""
643
645
  ] })
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 }),
646
+ ] }) : /* @__PURE__ */ jsx6(Panel, { title: copy.revenue, children: /* @__PURE__ */ jsx6("p", { className: "muted", children: copy.revenueUnavailable }) }),
647
+ /* @__PURE__ */ jsxs6(Panel, { title: copy.pipeline, children: [
648
+ /* @__PURE__ */ jsx6(StepPipeline, { ariaLabel: copy.pipelineLabel, steps: pipelineSteps }),
647
649
  exceptions ? /* @__PURE__ */ jsx6("p", { className: "muted", style: { margin: 0 }, children: exceptions }) : null
648
650
  ] })
649
651
  ] });
@@ -657,22 +659,23 @@ function dashboardSection(options = {}) {
657
659
  import { useState as useState5 } from "preact/hooks";
658
660
  import { Button as Button2, Checkbox as Checkbox2, DataTable as DataTable2, Field as Field2, Input as Input2, Textarea } from "@odla-ai/ui/components";
659
661
  import { jsx as jsx7, jsxs as jsxs7 } from "preact/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" }
662
+ var logColumns = (copy) => [
663
+ { key: "sentAt", header: copy.sentColumn, cell: (r) => new Date(r.sentAt).toLocaleString(), sortAs: "number", sortValue: (r) => r.sentAt },
664
+ { key: "template", header: copy.templateColumn, cell: (r) => r.template },
665
+ { key: "to", header: copy.toColumn, cell: (r) => r.to },
666
+ { 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
667
  ];
666
668
  function EmailBody({ ctx }) {
669
+ const copy = useChapterCopy().admin.email;
667
670
  const cfg = useAdminResource(ctx.getToken, "/api/admin/group/email");
668
671
  const log = useAdminResource(ctx.getToken, "/api/admin/email/log");
669
672
  const [draft, setDraft] = useState5(null);
670
673
  const [busy, setBusy] = useState5(null);
671
674
  const [note, setNote] = useState5(null);
672
675
  const data = draft ?? cfg.data;
673
- if (cfg.loading) return /* @__PURE__ */ jsx7(AdminNote, { children: "Loading\u2026" });
676
+ if (cfg.loading) return /* @__PURE__ */ jsx7(AdminNote, { children: copy.loading });
674
677
  if (cfg.error || !data) return /* @__PURE__ */ jsxs7(AdminNote, { children: [
675
- "Couldn\u2019t load email config",
678
+ copy.loadFailed,
676
679
  cfg.error ? `: ${cfg.error}` : "",
677
680
  "."
678
681
  ] });
@@ -693,18 +696,22 @@ function EmailBody({ ctx }) {
693
696
  await adminFetch(ctx.getToken, "/api/admin/group/email", { method: "PUT", body: JSON.stringify(data) });
694
697
  setDraft(null);
695
698
  cfg.refresh();
696
- setNote("Saved.");
699
+ setNote(copy.saved);
697
700
  });
698
701
  const test = (template) => run(`test:${template}`, async () => {
699
702
  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.");
703
+ setNote(r.ok ? formatChapterCopy(copy.sent, {
704
+ template,
705
+ address: r.to,
706
+ redirected: r.redirected ? ` (${copy.redirected})` : ""
707
+ }) : copy.testFailed);
701
708
  log.refresh();
702
709
  });
703
710
  return /* @__PURE__ */ jsxs7(AdminPage, { children: [
704
711
  /* @__PURE__ */ jsxs7(
705
712
  Panel,
706
713
  {
707
- title: "Delivery",
714
+ title: copy.delivery,
708
715
  actions: /* @__PURE__ */ jsxs7("span", { className: "muted", children: [
709
716
  data.envName,
710
717
  " \xB7 ",
@@ -712,22 +719,22 @@ function EmailBody({ ctx }) {
712
719
  data.fromEmail ? ` \xB7 ${data.fromEmail}` : ""
713
720
  ] }),
714
721
  children: [
715
- /* @__PURE__ */ jsx7(Field2, { label: "Notification address", children: /* @__PURE__ */ jsx7(Input2, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
716
- /* @__PURE__ */ jsx7(Field2, { label: "Reply-to", children: /* @__PURE__ */ jsx7(Input2, { value: data.replyTo, onInput: (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, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
722
+ /* @__PURE__ */ jsx7(Field2, { label: copy.notificationAddress, children: /* @__PURE__ */ jsx7(Input2, { value: data.notificationEmail, onInput: (e) => edit({ notificationEmail: e.target.value }) }) }),
723
+ /* @__PURE__ */ jsx7(Field2, { label: copy.replyTo, children: /* @__PURE__ */ jsx7(Input2, { value: data.replyTo, onInput: (e) => edit({ replyTo: e.target.value }) }) }),
724
+ /* @__PURE__ */ jsx7(Field2, { label: copy.debugInbox, hint: copy.debugInboxHint, children: /* @__PURE__ */ jsx7(Input2, { value: data.debugEmail, onInput: (e) => edit({ debugEmail: e.target.value }) }) })
718
725
  ]
719
726
  }
720
727
  ),
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, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
724
- /* @__PURE__ */ jsx7(Field2, { label: "Body", children: /* @__PURE__ */ jsx7(Textarea, { prose: true, rows: 5, value: t.text, onInput: (e) => editTemplate(key, { text: e.target.value }) }) })
728
+ 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: [
729
+ /* @__PURE__ */ jsx7(Checkbox2, { label: copy.enabled, checked: t.enabled, onChange: (e) => editTemplate(key, { enabled: e.target.checked }) }),
730
+ /* @__PURE__ */ jsx7(Field2, { label: copy.subject, children: /* @__PURE__ */ jsx7(Input2, { value: t.subject, onInput: (e) => editTemplate(key, { subject: e.target.value }) }) }),
731
+ /* @__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
732
  ] }, key)),
726
733
  /* @__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" }),
734
+ /* @__PURE__ */ jsx7(Button2, { disabled: !draft || busy === "save", onClick: () => void save(), children: copy.save }),
728
735
  note ? /* @__PURE__ */ jsx7("span", { className: "muted", children: note }) : null
729
736
  ] }),
730
- /* @__PURE__ */ jsx7(Panel, { title: "Send log", children: /* @__PURE__ */ jsx7(DataTable2, { rows: log.data?.sends ?? [], columns: LOG_COLUMNS, rowKey: (r) => r.id }) })
737
+ /* @__PURE__ */ jsx7(Panel, { title: copy.sendLog, children: /* @__PURE__ */ jsx7(DataTable2, { rows: log.data?.sends ?? [], columns: logColumns(copy), rowKey: (r) => r.id }) })
731
738
  ] });
732
739
  }
733
740
  function emailSection(options = {}) {
@@ -743,6 +750,7 @@ import { useEffect as useEffect4, useState as useState6 } from "preact/hooks";
743
750
  import { Button as Button3, Field as Field3, Select } from "@odla-ai/ui/components";
744
751
  import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
745
752
  function RecordActions({ getToken, record, roles, onChanged }) {
753
+ const copy = useChapterCopy().admin.records;
746
754
  const userId = typeof record.clerkUserId === "string" ? record.clerkUserId : null;
747
755
  const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
748
756
  const [access, setAccess] = useState6(null);
@@ -772,13 +780,13 @@ function RecordActions({ getToken, record, roles, onChanged }) {
772
780
  setBusy(null);
773
781
  }
774
782
  };
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.");
783
+ const setRole = (role) => act("role", () => adminFetch(getToken, "/api/admin/people/role", { method: "POST", body: JSON.stringify({ userId, role }) }), formatChapterCopy(copy.roleSet, { role }));
784
+ const approve = () => act("approve", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/approve`, { method: "POST" }), copy.approved);
785
+ const refund = () => act("refund", () => adminFetch(getToken, `/api/admin/applications/${applicationId}/refund`, { method: "POST" }), copy.refunded);
778
786
  if (!userId && !applicationId) return null;
779
787
  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(
788
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: 16 }, children: copy.lifecycle }),
789
+ userId ? /* @__PURE__ */ jsx8(Field3, { label: copy.role, hint: access?.superAdmin ? copy.superAdminHint : void 0, children: /* @__PURE__ */ jsx8(
782
790
  Select,
783
791
  {
784
792
  value: access?.role ?? "",
@@ -786,10 +794,10 @@ function RecordActions({ getToken, record, roles, onChanged }) {
786
794
  options: roles.map((r) => ({ value: r, label: r })),
787
795
  onChange: (e) => void setRole(e.target.value)
788
796
  }
789
- ) }) : /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: "No linked account yet \u2014 role is set once they sign in." }),
797
+ ) }) : /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: copy.noAccount }),
790
798
  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" })
799
+ /* @__PURE__ */ jsx8(Button3, { variant: "secondary", mini: true, disabled: busy === "approve", onClick: () => void approve(), children: copy.approve }),
800
+ /* @__PURE__ */ jsx8(Button3, { variant: "danger", mini: true, disabled: busy === "refund", onClick: () => void refund(), children: copy.refund })
793
801
  ] }) : null,
794
802
  note ? /* @__PURE__ */ jsx8("p", { className: "muted", style: { margin: 0 }, children: note }) : null
795
803
  ] });
@@ -799,6 +807,7 @@ function RecordActions({ getToken, record, roles, onChanged }) {
799
807
  import { useState as useState7 } from "preact/hooks";
800
808
  import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
801
809
  function NetworkShareActions(props) {
810
+ const copy = useChapterCopy().admin.network;
802
811
  const { recordId, recordType, getToken } = props;
803
812
  const targets = useAdminResource(getToken, "/api/admin/network/targets");
804
813
  const [busy, setBusy] = useState7(null);
@@ -814,19 +823,19 @@ function NetworkShareActions(props) {
814
823
  body: JSON.stringify({ recordId, targetIds: [target.id] })
815
824
  });
816
825
  const delivery = result.results[0];
817
- setMessage(delivery?.ok ? `Shared with ${target.name}.` : delivery?.error ?? `Could not share with ${target.name}.`);
826
+ setMessage(delivery?.ok ? formatChapterCopy(copy.shared, { target: target.name }) : delivery?.error ?? formatChapterCopy(copy.shareFailed, { target: target.name }));
818
827
  } catch (err) {
819
- setMessage(err instanceof Error ? err.message : "Delivery failed.");
828
+ setMessage(err instanceof Error ? err.message : copy.deliveryFailed);
820
829
  } finally {
821
830
  setBusy(null);
822
831
  }
823
832
  };
824
833
  return /* @__PURE__ */ jsxs9("section", { className: "panel", style: { marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
825
834
  /* @__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." })
835
+ /* @__PURE__ */ jsx9("h3", { style: { margin: 0, fontSize: 16 }, children: copy.title }),
836
+ /* @__PURE__ */ jsx9("p", { className: "muted", style: { margin: "4px 0 0", fontSize: 13 }, children: copy.allowlistDescription })
828
837
  ] }),
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)) }),
838
+ /* @__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
839
  message ? /* @__PURE__ */ jsx9("p", { role: "status", style: { margin: 0, fontSize: 13 }, children: message }) : null
831
840
  ] });
832
841
  }
@@ -877,6 +886,7 @@ import { useCallback as useCallback4, useEffect as useEffect6, useState as useSt
877
886
  import { Button as Button4, Field as Field4, Select as Select2 } from "@odla-ai/ui/components";
878
887
  import { jsx as jsx11, jsxs as jsxs10 } from "preact/jsx-runtime";
879
888
  function RecordCommsPanel(props) {
889
+ const copy = useChapterCopy().admin.records;
880
890
  const { client, recordId, getToken } = props;
881
891
  const config = useAdminResource(getToken, "/api/admin/group/email");
882
892
  const templates = Object.entries(config.data?.emailTemplates ?? {}).filter(([, value]) => value.enabled);
@@ -889,32 +899,33 @@ function RecordCommsPanel(props) {
889
899
  setNote(null);
890
900
  try {
891
901
  const result = await client.sendEmail(recordId, { templateId: template });
892
- setNote(result.sent ? "Message sent." : result.reason ?? "Message was not sent.");
902
+ setNote(result.sent ? copy.messageSent : result.reason ?? copy.messageNotSent);
893
903
  } catch (failure) {
894
904
  setNote(failure instanceof Error ? failure.message : String(failure));
895
905
  } finally {
896
906
  setBusy(false);
897
907
  }
898
908
  };
899
- if (config.loading) return /* @__PURE__ */ jsx11("p", { className: "muted", children: "Loading templates\u2026" });
909
+ if (config.loading) return /* @__PURE__ */ jsx11("p", { className: "muted", children: copy.loadingTemplates });
900
910
  if (config.error) return /* @__PURE__ */ jsx11("p", { role: "alert", className: "muted", children: config.error });
901
911
  return /* @__PURE__ */ jsxs10("div", { style: { display: "grid", gap: 14, maxWidth: 560 }, children: [
902
- /* @__PURE__ */ jsx11(Field4, { label: "Template", children: /* @__PURE__ */ jsx11(
912
+ /* @__PURE__ */ jsx11(Field4, { label: copy.template, children: /* @__PURE__ */ jsx11(
903
913
  Select2,
904
914
  {
905
915
  value: template,
906
916
  onChange: (event) => setTemplate(event.currentTarget.value),
907
917
  options: [
908
- { value: "", label: "Choose a template\u2026" },
918
+ { value: "", label: copy.chooseTemplate },
909
919
  ...templates.map(([id, value]) => ({ value: id, label: value.subject || id }))
910
920
  ]
911
921
  }
912
922
  ) }),
913
- /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsx11(Button4, { disabled: !template || busy, onClick: () => void send(), children: busy ? "Sending\u2026" : "Send" }) }),
923
+ /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsx11(Button4, { disabled: !template || busy, onClick: () => void send(), children: busy ? copy.sending : copy.send }) }),
914
924
  note ? /* @__PURE__ */ jsx11("p", { role: "status", className: "muted", children: note }) : null
915
925
  ] });
916
926
  }
917
927
  function RecordCommsHistory(props) {
928
+ const copy = useChapterCopy().admin.records;
918
929
  const { client, recordId } = props;
919
930
  const [rows, setRows] = useState9([]);
920
931
  const [error, setError] = useState9(null);
@@ -929,9 +940,9 @@ function RecordCommsHistory(props) {
929
940
  }, [client, recordId]);
930
941
  useEffect6(() => void load(), [load]);
931
942
  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." });
943
+ if (rows.length === 0) return /* @__PURE__ */ jsx11("p", { className: "muted", children: copy.noMessages });
933
944
  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") }),
945
+ /* @__PURE__ */ jsx11("strong", { children: String(row["templateId"] ?? row["template"] ?? copy.message) }),
935
946
  /* @__PURE__ */ jsxs10("div", { className: "muted", children: [
936
947
  row["to"] ? String(row["to"]) : "",
937
948
  row["sentAt"] ? ` \xB7 ${new Date(Number(row["sentAt"])).toLocaleString()}` : ""
@@ -944,6 +955,7 @@ import { useState as useState10 } from "preact/hooks";
944
955
  import { Button as Button5 } from "@odla-ai/ui/components";
945
956
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "preact/jsx-runtime";
946
957
  function RecordSchedulingPanel(props) {
958
+ const copy = useChapterCopy().admin.records;
947
959
  const { getToken, applicationId } = props;
948
960
  const resource = useAdminResource(
949
961
  getToken,
@@ -955,32 +967,32 @@ function RecordSchedulingPanel(props) {
955
967
  const run = async (meeting, action) => {
956
968
  let body;
957
969
  if (action === "reschedule") {
958
- const input = globalThis.prompt?.("New start time:");
970
+ const input = globalThis.prompt?.(copy.newStartPrompt);
959
971
  if (!input) return;
960
972
  const startAt = Date.parse(input);
961
973
  if (!Number.isFinite(startAt)) {
962
- setNote("Couldn\u2019t parse that time.");
974
+ setNote(copy.parseFailed);
963
975
  return;
964
976
  }
965
977
  body = JSON.stringify({ startAt });
966
- } else if (!globalThis.confirm?.("Cancel this call?")) {
978
+ } else if (!globalThis.confirm?.(copy.cancelConfirm)) {
967
979
  return;
968
980
  }
969
981
  setBusy(meeting.id);
970
982
  try {
971
983
  await adminFetch(getToken, `/api/admin/meetings/${meeting.id}/${action}`, { method: "POST", body });
972
984
  resource.refresh();
973
- setNote(action === "cancel" ? "Call cancelled." : "Call rescheduled.");
985
+ setNote(action === "cancel" ? copy.callCancelled : copy.callRescheduled);
974
986
  } catch (failure) {
975
987
  setNote(failure instanceof Error ? failure.message : String(failure));
976
988
  } finally {
977
989
  setBusy(null);
978
990
  }
979
991
  };
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" });
992
+ if (!applicationId) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.noApplication });
993
+ if (resource.loading) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.loadingMeetings });
982
994
  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." });
995
+ if (meetings.length === 0) return /* @__PURE__ */ jsx12("p", { className: "muted", children: copy.noMeetings });
984
996
  return /* @__PURE__ */ jsxs11("div", { style: { display: "grid", gap: 12 }, children: [
985
997
  meetings.map((meeting) => /* @__PURE__ */ jsxs11("section", { className: "panel", children: [
986
998
  /* @__PURE__ */ jsx12("strong", { children: new Date(meeting.startAt).toLocaleString() }),
@@ -990,11 +1002,11 @@ function RecordSchedulingPanel(props) {
990
1002
  resource.data?.timezone
991
1003
  ] }),
992
1004
  /* @__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,
1005
+ meeting.meetUrl ? /* @__PURE__ */ jsx12("a", { href: meeting.meetUrl, target: "_blank", rel: "noreferrer", children: copy.joinMeeting }) : null,
1006
+ meeting.htmlLink ? /* @__PURE__ */ jsx12("a", { href: meeting.htmlLink, target: "_blank", rel: "noreferrer", children: copy.openCalendar }) : null,
995
1007
  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" })
1008
+ /* @__PURE__ */ jsx12(Button5, { variant: "secondary", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "reschedule"), children: copy.reschedule }),
1009
+ /* @__PURE__ */ jsx12(Button5, { variant: "danger", mini: true, disabled: busy === meeting.id, onClick: () => void run(meeting, "cancel"), children: copy.cancel })
998
1010
  ] }) : null
999
1011
  ] })
1000
1012
  ] }, meeting.id)),
@@ -1004,12 +1016,12 @@ function RecordSchedulingPanel(props) {
1004
1016
 
1005
1017
  // src/ui/admin-people-workspace.tsx
1006
1018
  import { jsx as jsx13 } from "preact/jsx-runtime";
1007
- function applicationLifecycleAdapter(ctx) {
1019
+ function applicationLifecycleAdapter(ctx, copy = DEFAULT_CHAPTER_COPY.admin.records) {
1008
1020
  return {
1009
1021
  transitionStage: async ({ record, to }) => {
1010
1022
  const applicationId = typeof record.fields?.applicationId === "string" ? record.fields.applicationId : null;
1011
1023
  if (!applicationId) {
1012
- throw new Error("This record is not linked to an application workflow.");
1024
+ throw new Error(copy.workflowMissing);
1013
1025
  }
1014
1026
  const path = `/api/admin/applications/${encodeURIComponent(applicationId)}`;
1015
1027
  if (to === "approved") {
@@ -1026,6 +1038,7 @@ function applicationLifecycleAdapter(ctx) {
1026
1038
  };
1027
1039
  }
1028
1040
  function PeopleBody(props) {
1041
+ const copy = useChapterCopy().admin.records;
1029
1042
  const {
1030
1043
  crm,
1031
1044
  client,
@@ -1045,7 +1058,7 @@ function PeopleBody(props) {
1045
1058
  } = props;
1046
1059
  const def = crm.type(type);
1047
1060
  const recordId = ctx.route?.recordId ?? null;
1048
- const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx) : void 0;
1061
+ const builtInLifecycle = lifecycle ? applicationLifecycleAdapter(ctx, copy) : void 0;
1049
1062
  const resolvedLifecycle = typeof lifecycleAdapter === "function" ? lifecycleAdapter(ctx) : lifecycleAdapter ?? builtInLifecycle;
1050
1063
  const workspaceClient = client;
1051
1064
  return /* @__PURE__ */ jsx13(AdminPage, { children: /* @__PURE__ */ jsx13(
@@ -1081,35 +1094,35 @@ function PeopleBody(props) {
1081
1094
  if (def.facets?.email !== false) {
1082
1095
  tabs.push({
1083
1096
  id: "comms",
1084
- label: "Comms",
1097
+ label: copy.comms,
1085
1098
  panel: /* @__PURE__ */ jsx13(RecordCommsPanel, { client, recordId: record.id, getToken: ctx.getToken })
1086
1099
  });
1087
1100
  tabs.push({
1088
1101
  id: "history",
1089
- label: "Comms history",
1102
+ label: copy.commsHistory,
1090
1103
  panel: /* @__PURE__ */ jsx13(RecordCommsHistory, { client, recordId: record.id })
1091
1104
  });
1092
1105
  }
1093
1106
  if (lifecycle) {
1094
1107
  tabs.push({
1095
1108
  id: "scheduling",
1096
- label: "Scheduling",
1109
+ label: copy.scheduling,
1097
1110
  panel: /* @__PURE__ */ jsx13(RecordSchedulingPanel, { getToken: ctx.getToken, applicationId })
1098
1111
  });
1099
- tabs.push({ id: "billing", label: "Billing", panel: /* @__PURE__ */ jsx13(BillingCard, { record }) });
1112
+ tabs.push({ id: "billing", label: copy.billing, panel: /* @__PURE__ */ jsx13(BillingCard, { record }) });
1100
1113
  } else {
1101
1114
  add("billing");
1102
1115
  }
1103
1116
  tabs.push({
1104
1117
  id: "notes",
1105
- label: "Notes",
1118
+ label: copy.notes,
1106
1119
  panel: /* @__PURE__ */ jsx13(RecordActivityPanel, { client, recordId: record.id })
1107
1120
  });
1108
1121
  add("connections");
1109
1122
  if (lifecycle) {
1110
1123
  tabs.push({
1111
1124
  id: "access",
1112
- label: "Access",
1125
+ label: copy.access,
1113
1126
  panel: /* @__PURE__ */ jsx13(
1114
1127
  RecordActions,
1115
1128
  {
@@ -1128,7 +1141,7 @@ function PeopleBody(props) {
1128
1141
  if (networkSharing) {
1129
1142
  tabs.push({
1130
1143
  id: "sharing",
1131
- label: "Sharing",
1144
+ label: copy.sharing,
1132
1145
  panel: /* @__PURE__ */ jsx13(NetworkShareActions, { recordId: record.id, recordType: record.type, getToken: ctx.getToken })
1133
1146
  });
1134
1147
  }
@@ -1245,26 +1258,27 @@ var adminViewContext = (ctx, viewId, active) => {
1245
1258
  href: (target) => ctx.href(targetFromView(target))
1246
1259
  };
1247
1260
  };
1248
- function DashboardWorkspace({ ctx }) {
1261
+ function DashboardWorkspace({ chapter, ctx }) {
1262
+ const copy = chapter.copy.admin;
1249
1263
  const active = ctx.route.viewId === "billing" ? "billing" : "overview";
1250
- const overview = dashboardSection({ id: "overview", label: "Overview" });
1251
- const billing = billingSection();
1264
+ const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
1265
+ const billing = billingSection({ label: copy.workspaces.billing });
1252
1266
  return /* @__PURE__ */ jsx15(
1253
1267
  Tabs2,
1254
1268
  {
1255
- ariaLabel: "Dashboard views",
1269
+ ariaLabel: copy.workspaces.dashboardViewsLabel,
1256
1270
  value: active,
1257
1271
  mount: "active",
1258
1272
  items: [
1259
1273
  {
1260
1274
  value: "overview",
1261
- label: "Overview",
1275
+ label: copy.workspaces.overview,
1262
1276
  href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
1263
1277
  panel: overview.render(adminViewContext(ctx, "overview", active === "overview"))
1264
1278
  },
1265
1279
  {
1266
1280
  value: "billing",
1267
- label: "Billing",
1281
+ label: copy.workspaces.billing,
1268
1282
  href: ctx.href({ viewId: "billing", recordId: null, detailTab: null }),
1269
1283
  panel: billing.render(adminViewContext(ctx, "billing", active === "billing"))
1270
1284
  }
@@ -1272,26 +1286,27 @@ function DashboardWorkspace({ ctx }) {
1272
1286
  }
1273
1287
  );
1274
1288
  }
1275
- function SettingsWorkspace({ ctx }) {
1289
+ function SettingsWorkspace({ chapter, ctx }) {
1290
+ const copy = chapter.copy.admin;
1276
1291
  const active = ctx.route.viewId === "email" ? "email" : "calendar";
1277
- const calendar = availabilitySection({ id: "calendar", label: "Calendar" });
1278
- const email = emailSection();
1292
+ const calendar = availabilitySection({ id: "calendar", label: copy.workspaces.calendar });
1293
+ const email = emailSection({ label: copy.workspaces.email });
1279
1294
  return /* @__PURE__ */ jsx15(
1280
1295
  Tabs2,
1281
1296
  {
1282
- ariaLabel: "Settings views",
1297
+ ariaLabel: copy.workspaces.settingsViewsLabel,
1283
1298
  value: active,
1284
1299
  mount: "active",
1285
1300
  items: [
1286
1301
  {
1287
1302
  value: "calendar",
1288
- label: "Calendar",
1303
+ label: copy.workspaces.calendar,
1289
1304
  href: ctx.href({ viewId: "calendar", recordId: null, detailTab: null }),
1290
1305
  panel: calendar.render(adminViewContext(ctx, "calendar", active === "calendar"))
1291
1306
  },
1292
1307
  {
1293
1308
  value: "email",
1294
- label: "Email",
1309
+ label: copy.workspaces.email,
1295
1310
  href: ctx.href({ viewId: "email", recordId: null, detailTab: null }),
1296
1311
  panel: email.render(adminViewContext(ctx, "email", active === "email"))
1297
1312
  }
@@ -1307,7 +1322,7 @@ function PeopleWorkspace({ chapter, ctx }) {
1307
1322
  return /* @__PURE__ */ jsx15(
1308
1323
  Tabs2,
1309
1324
  {
1310
- ariaLabel: "CRM collections",
1325
+ ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
1311
1326
  value: active,
1312
1327
  variant: "pill",
1313
1328
  mount: "active",
@@ -1330,9 +1345,10 @@ function PeopleWorkspace({ chapter, ctx }) {
1330
1345
  );
1331
1346
  }
1332
1347
  function defaultAdminWorkspaces(chapter) {
1348
+ const copy = chapter.copy.admin;
1333
1349
  const people = {
1334
1350
  id: "people",
1335
- label: "People",
1351
+ label: copy.workspaces.people,
1336
1352
  defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
1337
1353
  render: (ctx) => /* @__PURE__ */ jsx15(PeopleWorkspace, { chapter, ctx })
1338
1354
  };
@@ -1340,37 +1356,39 @@ function defaultAdminWorkspaces(chapter) {
1340
1356
  return [
1341
1357
  {
1342
1358
  id: "dashboard",
1343
- label: "Dashboard",
1359
+ label: copy.workspaces.dashboard,
1344
1360
  defaultViewId: "overview",
1345
- render: (ctx) => /* @__PURE__ */ jsx15(DashboardWorkspace, { ctx })
1361
+ render: (ctx) => /* @__PURE__ */ jsx15(DashboardWorkspace, { chapter, ctx })
1346
1362
  },
1347
1363
  people,
1348
1364
  {
1349
1365
  id: "settings",
1350
- label: "Settings",
1366
+ label: copy.workspaces.settings,
1351
1367
  defaultViewId: "calendar",
1352
- render: (ctx) => /* @__PURE__ */ jsx15(SettingsWorkspace, { ctx })
1368
+ render: (ctx) => /* @__PURE__ */ jsx15(SettingsWorkspace, { chapter, ctx })
1353
1369
  }
1354
1370
  ];
1355
1371
  }
1356
1372
 
1357
1373
  // src/ui/admin-frame.tsx
1358
- import { ThemeScope } from "@odla-ai/ui/components";
1374
+ import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
1359
1375
  import { jsx as jsx16, jsxs as jsxs12 } from "preact/jsx-runtime";
1360
1376
  function AdminTheme(props) {
1361
1377
  const brand = props.chapter?.brand;
1362
- return /* @__PURE__ */ jsxs12(
1363
- ThemeScope,
1378
+ return /* @__PURE__ */ jsx16(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs12(
1379
+ ThemeScope2,
1364
1380
  {
1381
+ ref: props.scopeRef,
1365
1382
  "data-chapter-admin": "",
1366
1383
  theme: brand?.theme,
1384
+ accent: brand?.accent,
1367
1385
  colorScheme: brand?.colorScheme ?? "light",
1368
1386
  children: [
1369
1387
  /* @__PURE__ */ jsx16(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
1370
1388
  props.children
1371
1389
  ]
1372
1390
  }
1373
- );
1391
+ ) });
1374
1392
  }
1375
1393
  function signInRedirect(basePath, workspaces) {
1376
1394
  if (typeof window === "undefined") return basePath;
@@ -1385,6 +1403,17 @@ function signInRedirect(basePath, workspaces) {
1385
1403
  return adminRouteHref(current, basePath, route, "query");
1386
1404
  }
1387
1405
 
1406
+ // src/ui/admin-appearance.ts
1407
+ import { useEffect as useEffect7, useState as useState11 } from "preact/hooks";
1408
+ import { clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
1409
+ function useScopedClerkAppearance(scope) {
1410
+ const [appearance, setAppearance] = useState11();
1411
+ useEffect7(() => {
1412
+ setAppearance(clerkAppearanceFromTokens(scope.current ?? void 0));
1413
+ }, [scope]);
1414
+ return appearance;
1415
+ }
1416
+
1388
1417
  // src/ui/admin-auth.ts
1389
1418
  var apiUrl = (apiBase, path) => `${apiBase.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
1390
1419
  var DEFAULT_ADMIN_AUTH = {};
@@ -1412,6 +1441,7 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
1412
1441
  // src/ui/admin.tsx
1413
1442
  import { jsx as jsx17, jsxs as jsxs13 } from "preact/jsx-runtime";
1414
1443
  function Authed(props) {
1444
+ const copy = useChapterCopy().admin;
1415
1445
  const {
1416
1446
  workspaces,
1417
1447
  basePath,
@@ -1436,8 +1466,8 @@ function Authed(props) {
1436
1466
  }),
1437
1467
  [getToken, crmBasePath]
1438
1468
  );
1439
- const [state, setState] = useState11({ status: "checking" });
1440
- useEffect7(() => {
1469
+ const [state, setState] = useState12({ status: "checking" });
1470
+ useEffect8(() => {
1441
1471
  let live = true;
1442
1472
  void (async () => {
1443
1473
  try {
@@ -1456,15 +1486,14 @@ function Authed(props) {
1456
1486
  live = false;
1457
1487
  };
1458
1488
  }, [getToken, apiBase, auth]);
1459
- if (state.status === "checking") return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Checking access\u2026" }) });
1489
+ if (state.status === "checking") return /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.auth.checking }) });
1460
1490
  if (state.status === "denied") {
1461
1491
  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" })
1492
+ /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
1493
+ /* @__PURE__ */ jsx17("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
1494
+ account: state.email ?? copy.auth.thisAccount
1495
+ }) }),
1496
+ /* @__PURE__ */ jsx17("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
1468
1497
  ] }) });
1469
1498
  }
1470
1499
  return /* @__PURE__ */ jsx17(
@@ -1487,6 +1516,7 @@ function Authed(props) {
1487
1516
  );
1488
1517
  }
1489
1518
  function ChapterAdmin(props) {
1519
+ const copy = props.chapter?.copy ?? DEFAULT_CHAPTER_COPY;
1490
1520
  const defaults = useMemo(
1491
1521
  () => props.chapter ? defaultAdminWorkspaces(props.chapter) : [],
1492
1522
  [props.chapter]
@@ -1497,7 +1527,7 @@ function ChapterAdmin(props) {
1497
1527
  }, [props.workspaces, props.sections, defaults]);
1498
1528
  const basePath = props.basePath ?? "/admin";
1499
1529
  const brand = props.brand ?? {
1500
- name: props.chapter?.name ?? "Admin",
1530
+ name: props.chapter?.name ?? copy.admin.shell.adminName,
1501
1531
  badge: props.chapter?.brand.badge,
1502
1532
  wordmark: props.chapter?.brand.wordmark
1503
1533
  };
@@ -1507,8 +1537,10 @@ function ChapterAdmin(props) {
1507
1537
  const chrome = props.chrome ?? "embedded";
1508
1538
  const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
1509
1539
  const redirect = signInRedirect(basePath, workspaces);
1510
- const [publishableKey, setPublishableKey] = useState11(void 0);
1511
- useEffect7(() => {
1540
+ const [publishableKey, setPublishableKey] = useState12(void 0);
1541
+ const scopeRef = useRef(null);
1542
+ const appearance = useScopedClerkAppearance(scopeRef);
1543
+ useEffect8(() => {
1512
1544
  let live = true;
1513
1545
  void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
1514
1546
  return () => {
@@ -1516,24 +1548,24 @@ function ChapterAdmin(props) {
1516
1548
  };
1517
1549
  }, [apiBase, auth]);
1518
1550
  let content;
1519
- if (publishableKey === void 0) {
1520
- content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "Loading\u2026" }) });
1551
+ if (publishableKey === void 0 || publishableKey && !appearance) {
1552
+ content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.loading }) });
1521
1553
  } else if (!publishableKey) {
1522
1554
  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." })
1555
+ /* @__PURE__ */ jsx17("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
1556
+ /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
1525
1557
  ] }) });
1526
1558
  } else if (workspaces.length === 0) {
1527
- content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: "No admin workspaces are configured." }) });
1559
+ content = /* @__PURE__ */ jsx17(Gate, { brand, children: /* @__PURE__ */ jsx17("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
1528
1560
  } else {
1529
1561
  content = /* @__PURE__ */ jsxs13(
1530
1562
  ClerkGate,
1531
1563
  {
1532
1564
  publishableKey,
1533
- appearance: clerkAppearanceFromTokens(),
1565
+ appearance,
1534
1566
  afterSignOutUrl: "/",
1535
1567
  children: [
1536
- /* @__PURE__ */ jsx17(SignedOut, { children: /* @__PURE__ */ jsx17(Gate, { brand, tagline: "Admin sign-in \u2014 invite only", children: /* @__PURE__ */ jsx17(
1568
+ /* @__PURE__ */ jsx17(SignedOut, { children: /* @__PURE__ */ jsx17(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx17(
1537
1569
  SignIn,
1538
1570
  {
1539
1571
  routing: "hash",
@@ -1561,7 +1593,7 @@ function ChapterAdmin(props) {
1561
1593
  }
1562
1594
  );
1563
1595
  }
1564
- return /* @__PURE__ */ jsx17(AdminTheme, { chapter: props.chapter, children: content });
1596
+ return /* @__PURE__ */ jsx17(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
1565
1597
  }
1566
1598
 
1567
1599
  // src/ui/admin-defaults.ts
@@ -1570,17 +1602,18 @@ function defaultAdminSections(chapter) {
1570
1602
  }
1571
1603
 
1572
1604
  // src/ui/admin-meetings.tsx
1573
- import { useState as useState12 } from "preact/hooks";
1605
+ import { useState as useState13 } from "preact/hooks";
1574
1606
  import { Button as Button6 } from "@odla-ai/ui/components";
1575
1607
  import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "preact/jsx-runtime";
1576
1608
  var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
1577
1609
  function MeetingsBody({ ctx }) {
1610
+ const copy = useChapterCopy().admin.meetings;
1578
1611
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
1579
- const [busy, setBusy] = useState12(null);
1580
- const [note, setNote] = useState12(null);
1581
- if (loading) return /* @__PURE__ */ jsx18(AdminNote, { children: "Loading\u2026" });
1612
+ const [busy, setBusy] = useState13(null);
1613
+ const [note, setNote] = useState13(null);
1614
+ if (loading) return /* @__PURE__ */ jsx18(AdminNote, { children: copy.loading });
1582
1615
  if (error || !data) return /* @__PURE__ */ jsxs14(AdminNote, { children: [
1583
- "Couldn\u2019t load meetings",
1616
+ copy.loadFailed,
1584
1617
  error ? `: ${error}` : "",
1585
1618
  "."
1586
1619
  ] });
@@ -1597,37 +1630,39 @@ function MeetingsBody({ ctx }) {
1597
1630
  }
1598
1631
  };
1599
1632
  const cancel = (id) => {
1600
- if (!globalThis.confirm?.("Cancel this call? Google notifies the attendee.")) return;
1633
+ if (!globalThis.confirm?.(copy.cancelConfirm)) return;
1601
1634
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/cancel`, { method: "POST" }));
1602
1635
  };
1603
1636
  const reschedule = (id) => {
1604
- const input = globalThis.prompt?.("New start time (e.g. 2026-08-01 14:00):");
1637
+ const input = globalThis.prompt?.(copy.newStartPrompt);
1605
1638
  if (!input) return;
1606
1639
  const startAt = Date.parse(input);
1607
1640
  if (!Number.isFinite(startAt)) {
1608
- setNote("Couldn\u2019t parse that time.");
1641
+ setNote(copy.parseFailed);
1609
1642
  return;
1610
1643
  }
1611
1644
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
1612
1645
  };
1613
1646
  return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(Panel, { title: /* @__PURE__ */ jsxs14(Fragment4, { children: [
1614
- "Agenda ",
1647
+ copy.agenda,
1648
+ " ",
1615
1649
  /* @__PURE__ */ jsxs14("span", { className: "muted", style: { fontWeight: 400 }, children: [
1616
1650
  "\xB7 ",
1617
1651
  data.timezone
1618
1652
  ] })
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: [
1653
+ ] }), 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
1654
  /* @__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)" }),
1655
+ /* @__PURE__ */ jsx18("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
1622
1656
  /* @__PURE__ */ jsx18("span", { className: "badge", children: m.status }),
1623
1657
  m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs14("span", { className: "badge", children: [
1624
- "drift: ",
1658
+ copy.drift,
1659
+ ": ",
1625
1660
  m.drift
1626
1661
  ] }) : null,
1627
- m.meetUrl ? /* @__PURE__ */ jsx18("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: "Meet" }) : null,
1662
+ m.meetUrl ? /* @__PURE__ */ jsx18("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
1628
1663
  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" })
1664
+ /* @__PURE__ */ jsx18(Button6, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
1665
+ /* @__PURE__ */ jsx18(Button6, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
1631
1666
  ] }) : null
1632
1667
  ] }, m.id)) }) }) });
1633
1668
  }
@@ -1663,4 +1698,4 @@ export {
1663
1698
  defaultAdminSections,
1664
1699
  meetingsSection
1665
1700
  };
1666
- //# sourceMappingURL=chunk-C4XQSRW7.js.map
1701
+ //# sourceMappingURL=chunk-ZPAJL7KG.js.map