@odla-ai/chapter 0.35.0 → 0.37.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.
@@ -4,10 +4,10 @@ import {
4
4
  DEFAULT_CHAPTER_COPY,
5
5
  formatChapterCopy,
6
6
  useChapterCopy
7
- } from "./chunk-JAWTIROV.js";
7
+ } from "./chunk-IAWBE5PX.js";
8
8
 
9
9
  // src/ui/admin.tsx
10
- import { useEffect as useEffect9, useMemo as useMemo2, useRef, useState as useState14 } from "preact/hooks";
10
+ import { useEffect as useEffect10, useMemo as useMemo2, useRef, useState as useState16 } from "preact/hooks";
11
11
  import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
12
12
  import { CrmClient } from "@odla-ai/crm";
13
13
 
@@ -453,7 +453,13 @@ async function adminFetch(getToken, path, init) {
453
453
  if (init?.body) headers.set("content-type", "application/json");
454
454
  const res = await fetch(path, { ...init, headers });
455
455
  const body = await res.json().catch(() => ({}));
456
- if (!res.ok) throw new Error(typeof body.error === "string" ? body.error : `request failed (${res.status})`);
456
+ if (!res.ok) {
457
+ const error = new Error(typeof body.error === "string" ? body.error : `request failed (${res.status})`);
458
+ if (Array.isArray(body.errors) && body.errors.every((item) => typeof item === "string")) {
459
+ error.errors = body.errors;
460
+ }
461
+ throw error;
462
+ }
457
463
  return body;
458
464
  }
459
465
  function useAdminResource(getToken, path) {
@@ -1661,8 +1667,244 @@ function LeaderNetworkOverview({ ctx }) {
1661
1667
  ] });
1662
1668
  }
1663
1669
 
1670
+ // src/ui/admin-admission.tsx
1671
+ import { useState as useState13 } from "preact/hooks";
1672
+ import { jsx as jsx18, jsxs as jsxs15 } from "preact/jsx-runtime";
1673
+ function AdmissionGrantsWorkspace(props) {
1674
+ const copy = useChapterCopy().admin.admission;
1675
+ const resource = useAdminResource(props.getToken, "/api/admin/admission-grants");
1676
+ const [email, setEmail] = useState13("");
1677
+ const [decision, setDecision] = useState13("waive_interview");
1678
+ const [note, setNote] = useState13("");
1679
+ const [expires, setExpires] = useState13("");
1680
+ const [busy, setBusy] = useState13(false);
1681
+ const [error, setError] = useState13(null);
1682
+ const submit = async (event) => {
1683
+ event.preventDefault();
1684
+ setBusy(true);
1685
+ setError(null);
1686
+ try {
1687
+ await adminFetch(props.getToken, "/api/admin/admission-grants", {
1688
+ method: "POST",
1689
+ body: JSON.stringify({
1690
+ email,
1691
+ decision,
1692
+ ...note.trim() ? { note } : {},
1693
+ ...expires ? { expiresAt: (/* @__PURE__ */ new Date(`${expires}T23:59:59Z`)).getTime() } : {}
1694
+ })
1695
+ });
1696
+ setEmail("");
1697
+ setNote("");
1698
+ setExpires("");
1699
+ resource.refresh();
1700
+ } catch (cause) {
1701
+ setError(cause instanceof Error ? cause.message : String(cause));
1702
+ } finally {
1703
+ setBusy(false);
1704
+ }
1705
+ };
1706
+ const revoke = async (grant) => {
1707
+ setBusy(true);
1708
+ setError(null);
1709
+ try {
1710
+ await adminFetch(props.getToken, `/api/admin/admission-grants/${encodeURIComponent(grant.id)}`, { method: "DELETE" });
1711
+ resource.refresh();
1712
+ } catch (cause) {
1713
+ setError(cause instanceof Error ? cause.message : String(cause));
1714
+ } finally {
1715
+ setBusy(false);
1716
+ }
1717
+ };
1718
+ return /* @__PURE__ */ jsxs15(AdminPage, { children: [
1719
+ /* @__PURE__ */ jsxs15(Panel, { title: copy.title, children: [
1720
+ /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: copy.description }),
1721
+ /* @__PURE__ */ jsxs15("form", { onSubmit: (event) => void submit(event), style: { display: "grid", gap: 12, gridTemplateColumns: "minmax(220px,2fr) minmax(180px,1fr) minmax(160px,1fr)" }, children: [
1722
+ /* @__PURE__ */ jsxs15("label", { children: [
1723
+ copy.email,
1724
+ /* @__PURE__ */ jsx18("input", { required: true, type: "email", value: email, onInput: (event) => setEmail(event.currentTarget.value) })
1725
+ ] }),
1726
+ /* @__PURE__ */ jsxs15("label", { children: [
1727
+ copy.decision,
1728
+ /* @__PURE__ */ jsxs15("select", { value: decision, onChange: (event) => setDecision(event.currentTarget.value), children: [
1729
+ /* @__PURE__ */ jsx18("option", { value: "waive_interview", children: copy.waive }),
1730
+ /* @__PURE__ */ jsx18("option", { value: "require_interview", children: copy.require })
1731
+ ] })
1732
+ ] }),
1733
+ /* @__PURE__ */ jsxs15("label", { children: [
1734
+ copy.expires,
1735
+ /* @__PURE__ */ jsx18("input", { type: "date", value: expires, onInput: (event) => setExpires(event.currentTarget.value) })
1736
+ ] }),
1737
+ /* @__PURE__ */ jsxs15("label", { style: { gridColumn: "1 / -1" }, children: [
1738
+ copy.note,
1739
+ /* @__PURE__ */ jsx18("input", { value: note, maxLength: 2e3, onInput: (event) => setNote(event.currentTarget.value) })
1740
+ ] }),
1741
+ /* @__PURE__ */ jsx18("button", { className: "btn secondary", type: "submit", disabled: busy, children: copy.add })
1742
+ ] }),
1743
+ error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: error }) : null
1744
+ ] }),
1745
+ /* @__PURE__ */ jsxs15(Panel, { title: copy.ledger, children: [
1746
+ resource.loading ? /* @__PURE__ */ jsx18("p", { className: "muted", children: copy.loading }) : resource.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: resource.error }) : null,
1747
+ /* @__PURE__ */ jsx18("div", { style: { display: "grid", gap: 10 }, children: (resource.data?.grants ?? []).map((grant) => {
1748
+ const state = grant.consumedAt ? copy.consumed : grant.revokedAt || !grant.active ? copy.revoked : grant.expiresAt && grant.expiresAt <= Date.now() ? copy.expired : copy.active;
1749
+ return /* @__PURE__ */ jsxs15("article", { className: "card", style: { padding: 14 }, children: [
1750
+ /* @__PURE__ */ jsx18("strong", { children: grant.email }),
1751
+ " \xB7 ",
1752
+ grant.decision === "waive_interview" ? copy.waive : copy.require,
1753
+ /* @__PURE__ */ jsxs15("p", { className: "muted", style: { margin: "6px 0" }, children: [
1754
+ grant.sourceScope === "local" ? copy.local : copy.parent,
1755
+ " \xB7 ",
1756
+ state,
1757
+ grant.note ? ` \xB7 ${grant.note}` : ""
1758
+ ] }),
1759
+ grant.sourceScope === "local" && state === copy.active ? /* @__PURE__ */ jsx18("button", { className: "btn secondary mini", disabled: busy, onClick: () => void revoke(grant), children: copy.revoke }) : null
1760
+ ] }, grant.id);
1761
+ }) })
1762
+ ] })
1763
+ ] });
1764
+ }
1765
+ function admissionGrantsWorkspace(label = "Admission") {
1766
+ return {
1767
+ id: "admission",
1768
+ label,
1769
+ defaultViewId: "grants",
1770
+ render: (ctx) => /* @__PURE__ */ jsx18(AdmissionGrantsWorkspace, { getToken: ctx.getToken })
1771
+ };
1772
+ }
1773
+
1774
+ // src/ui/admin-member-content.tsx
1775
+ import { useEffect as useEffect8, useState as useState14 } from "preact/hooks";
1776
+ import { jsx as jsx19, jsxs as jsxs16 } from "preact/jsx-runtime";
1777
+ var emptyDraft = () => ({ pages: [], upsellOverrides: [] });
1778
+ function accessLabel(page) {
1779
+ return page.audience.kind === "all-members" ? "All members" : `Tiers: ${page.audience.tierIds.join(", ")}`;
1780
+ }
1781
+ function MemberContentAdmin({
1782
+ ctx,
1783
+ initialData
1784
+ }) {
1785
+ const resource = useAdminResource(ctx.getToken, "/api/admin/member-content");
1786
+ const [draft, setDraft] = useState14(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
1787
+ const [saveError, setSaveError] = useState14(null);
1788
+ const [saving, setSaving] = useState14(false);
1789
+ useEffect8(() => {
1790
+ if (!resource.data) return;
1791
+ setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft(), null, 2));
1792
+ }, [resource.data]);
1793
+ const loaded = initialData ?? resource.data;
1794
+ if (!loaded && resource.loading) return /* @__PURE__ */ jsx19(AdminPage, { children: /* @__PURE__ */ jsx19(AdminNote, { children: "Loading member content\u2026" }) });
1795
+ if (resource.error || !loaded) {
1796
+ return /* @__PURE__ */ jsx19(AdminPage, { children: /* @__PURE__ */ jsxs16(AdminNote, { children: [
1797
+ "Member content could not be loaded: ",
1798
+ resource.error ?? "unknown error"
1799
+ ] }) });
1800
+ }
1801
+ const data = loaded;
1802
+ const inherited = data.parent?.document.pages ?? [];
1803
+ const local = data.chapter?.document.pages ?? [];
1804
+ const save = async () => {
1805
+ setSaveError(null);
1806
+ let document2;
1807
+ try {
1808
+ document2 = JSON.parse(draft);
1809
+ } catch (error) {
1810
+ setSaveError([`JSON is invalid: ${error instanceof Error ? error.message : String(error)}`]);
1811
+ return;
1812
+ }
1813
+ setSaving(true);
1814
+ try {
1815
+ await adminFetch(ctx.getToken, "/api/admin/member-content", {
1816
+ method: "PUT",
1817
+ body: JSON.stringify({ expectedRevision: data.chapter?.revision ?? 0, document: document2 })
1818
+ });
1819
+ resource.refresh();
1820
+ } catch (error) {
1821
+ const structured = error;
1822
+ setSaveError(structured.errors ?? [error instanceof Error ? error.message : String(error)]);
1823
+ } finally {
1824
+ setSaving(false);
1825
+ }
1826
+ };
1827
+ return /* @__PURE__ */ jsxs16(AdminPage, { children: [
1828
+ /* @__PURE__ */ jsxs16(Panel, { children: [
1829
+ /* @__PURE__ */ jsx19("div", { className: "card-label", children: "Member content" }),
1830
+ /* @__PURE__ */ jsx19("h2", { children: "One catalog, two clear owners" }),
1831
+ /* @__PURE__ */ jsx19("p", { children: "Parent pages are inherited and read-only here. Chapter pages and delegated upsells are edited in the local document. Access uses exact tier IDs; no tier order is assumed." }),
1832
+ /* @__PURE__ */ jsxs16("div", { className: "stat-band", children: [
1833
+ /* @__PURE__ */ jsxs16("div", { children: [
1834
+ /* @__PURE__ */ jsx19("strong", { children: inherited.length }),
1835
+ /* @__PURE__ */ jsx19("span", { children: "Inherited pages" })
1836
+ ] }),
1837
+ /* @__PURE__ */ jsxs16("div", { children: [
1838
+ /* @__PURE__ */ jsx19("strong", { children: local.length }),
1839
+ /* @__PURE__ */ jsx19("span", { children: "Chapter pages" })
1840
+ ] }),
1841
+ /* @__PURE__ */ jsxs16("div", { children: [
1842
+ /* @__PURE__ */ jsx19("strong", { children: data.tiers.length }),
1843
+ /* @__PURE__ */ jsx19("span", { children: "Declared tiers" })
1844
+ ] })
1845
+ ] })
1846
+ ] }),
1847
+ /* @__PURE__ */ jsxs16(Panel, { children: [
1848
+ /* @__PURE__ */ jsx19("div", { className: "card-label", children: "Inherited \xB7 read only" }),
1849
+ /* @__PURE__ */ jsxs16("h2", { children: [
1850
+ "Parent pages ",
1851
+ data.parent ? `\xB7 revision ${data.parent.revision}` : ""
1852
+ ] }),
1853
+ inherited.length === 0 ? /* @__PURE__ */ jsx19(AdminNote, { children: "No Parent pages have been delivered." }) : /* @__PURE__ */ jsx19("div", { className: "data-list", "data-member-content-inherited": true, children: inherited.map((page) => /* @__PURE__ */ jsxs16("article", { className: "card", children: [
1854
+ /* @__PURE__ */ jsx19("strong", { children: page.title }),
1855
+ /* @__PURE__ */ jsxs16("div", { className: "meeting-note", children: [
1856
+ "/",
1857
+ page.slug,
1858
+ " \xB7 ",
1859
+ page.navigation.section,
1860
+ " \xB7 ",
1861
+ accessLabel(page)
1862
+ ] }),
1863
+ /* @__PURE__ */ jsxs16("div", { className: "meeting-note", children: [
1864
+ "Upsell: ",
1865
+ page.upsell ? `${page.upsell.owner} controlled` : "none"
1866
+ ] })
1867
+ ] }, page.id)) })
1868
+ ] }),
1869
+ /* @__PURE__ */ jsxs16(Panel, { children: [
1870
+ /* @__PURE__ */ jsx19("div", { className: "card-label", children: "Chapter \xB7 editable" }),
1871
+ /* @__PURE__ */ jsxs16("h2", { children: [
1872
+ "Local document \xB7 revision ",
1873
+ data.chapter?.revision ?? 0
1874
+ ] }),
1875
+ /* @__PURE__ */ jsx19("p", { className: "meeting-note", children: "Use the published MemberContentDocument fields. The server supplies schemaVersion, revision, timestamp, and chapter source; unknown fields and undeclared tiers are rejected." }),
1876
+ /* @__PURE__ */ jsxs16("label", { className: "field", children: [
1877
+ /* @__PURE__ */ jsx19("span", { children: "Pages and delegated upsells (JSON)" }),
1878
+ /* @__PURE__ */ jsx19(
1879
+ "textarea",
1880
+ {
1881
+ rows: 24,
1882
+ value: draft,
1883
+ disabled: !data.localAuthoring || saving,
1884
+ onInput: (event) => setDraft(event.currentTarget.value),
1885
+ spellcheck: false
1886
+ }
1887
+ )
1888
+ ] }),
1889
+ saveError ? /* @__PURE__ */ jsxs16("div", { className: "notice danger", role: "alert", children: [
1890
+ /* @__PURE__ */ jsx19("strong", { children: "Fix these items before saving:" }),
1891
+ /* @__PURE__ */ jsx19("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx19("li", { children: error }, error)) })
1892
+ ] }) : null,
1893
+ /* @__PURE__ */ jsx19("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save chapter content" }),
1894
+ !data.localAuthoring ? /* @__PURE__ */ jsx19(AdminNote, { children: "Local authoring is disabled in the checked-in Chapter config." }) : null
1895
+ ] })
1896
+ ] });
1897
+ }
1898
+ function memberContentSection() {
1899
+ return {
1900
+ id: "member-content",
1901
+ label: "Member content",
1902
+ render: (ctx) => /* @__PURE__ */ jsx19(MemberContentAdmin, { ctx })
1903
+ };
1904
+ }
1905
+
1664
1906
  // src/ui/admin-workspaces.tsx
1665
- import { jsx as jsx18 } from "preact/jsx-runtime";
1907
+ import { jsx as jsx20 } from "preact/jsx-runtime";
1666
1908
  var adminViewContext = (ctx, viewId, active) => {
1667
1909
  const route = {
1668
1910
  workspaceId: ctx.route.workspaceId,
@@ -1688,7 +1930,7 @@ function DashboardWorkspace({ chapter, ctx }) {
1688
1930
  const active = ctx.route.viewId === "billing" ? "billing" : "overview";
1689
1931
  const overview = dashboardSection({ id: "overview", label: copy.workspaces.overview });
1690
1932
  const billing = billingSection({ label: copy.workspaces.billing });
1691
- return /* @__PURE__ */ jsx18(
1933
+ return /* @__PURE__ */ jsx20(
1692
1934
  Tabs2,
1693
1935
  {
1694
1936
  ariaLabel: copy.workspaces.dashboardViewsLabel,
@@ -1713,10 +1955,11 @@ function DashboardWorkspace({ chapter, ctx }) {
1713
1955
  }
1714
1956
  function SettingsWorkspace({ chapter, ctx }) {
1715
1957
  const copy = chapter.copy.admin;
1716
- const active = ctx.route.viewId === "email" ? "email" : "calendar";
1958
+ const active = ctx.route.viewId === "email" ? "email" : ctx.route.viewId === "member-content" && chapter.memberContent ? "member-content" : "calendar";
1717
1959
  const calendar = availabilitySection({ id: "calendar", label: copy.workspaces.calendar });
1718
1960
  const email = emailSection({ label: copy.workspaces.email });
1719
- return /* @__PURE__ */ jsx18(
1961
+ const content = memberContentSection();
1962
+ return /* @__PURE__ */ jsx20(
1720
1963
  Tabs2,
1721
1964
  {
1722
1965
  ariaLabel: copy.workspaces.settingsViewsLabel,
@@ -1734,7 +1977,13 @@ function SettingsWorkspace({ chapter, ctx }) {
1734
1977
  label: copy.workspaces.email,
1735
1978
  href: ctx.href({ viewId: "email", recordId: null, detailTab: null }),
1736
1979
  panel: email.render(adminViewContext(ctx, "email", active === "email"))
1737
- }
1980
+ },
1981
+ ...chapter.memberContent ? [{
1982
+ value: "member-content",
1983
+ label: "Member content",
1984
+ href: ctx.href({ viewId: "member-content", recordId: null, detailTab: null }),
1985
+ panel: content.render(adminViewContext(ctx, "member-content", active === "member-content"))
1986
+ }] : []
1738
1987
  ]
1739
1988
  }
1740
1989
  );
@@ -1744,7 +1993,7 @@ function PeopleWorkspace({ chapter, ctx }) {
1744
1993
  const fallback = types[0]?.[0] ?? "person";
1745
1994
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
1746
1995
  const networkSharing = chapter.network.targets.length > 0;
1747
- return /* @__PURE__ */ jsx18(
1996
+ return /* @__PURE__ */ jsx20(
1748
1997
  Tabs2,
1749
1998
  {
1750
1999
  ariaLabel: chapter.copy.admin.workspaces.collectionsLabel,
@@ -1774,8 +2023,8 @@ function PortfolioWorkspace({ chapter, ctx }) {
1774
2023
  const types = Object.entries(chapter.crm.config.types).filter(([, def]) => def.workspace === "portfolio");
1775
2024
  const fallback = types[0]?.[0] ?? "";
1776
2025
  const active = types.some(([type]) => type === ctx.route.viewId) ? ctx.route.viewId : fallback;
1777
- if (!active) return /* @__PURE__ */ jsx18(LeaderNetworkOverview, { ctx });
1778
- return /* @__PURE__ */ jsx18(
2026
+ if (!active) return /* @__PURE__ */ jsx20(LeaderNetworkOverview, { ctx });
2027
+ return /* @__PURE__ */ jsx20(
1779
2028
  Tabs2,
1780
2029
  {
1781
2030
  ariaLabel: copy.workspaces.portfolioViewsLabel,
@@ -1799,7 +2048,7 @@ function PortfolioWorkspace({ chapter, ctx }) {
1799
2048
  );
1800
2049
  }
1801
2050
  function LeaderDashboardWorkspace({ chapter, ctx }) {
1802
- return /* @__PURE__ */ jsx18(
2051
+ return /* @__PURE__ */ jsx20(
1803
2052
  Tabs2,
1804
2053
  {
1805
2054
  ariaLabel: chapter.copy.admin.workspaces.dashboardViewsLabel,
@@ -1809,14 +2058,14 @@ function LeaderDashboardWorkspace({ chapter, ctx }) {
1809
2058
  value: "overview",
1810
2059
  label: chapter.copy.admin.workspaces.overview,
1811
2060
  href: ctx.href({ viewId: "overview", recordId: null, detailTab: null }),
1812
- panel: /* @__PURE__ */ jsx18(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
2061
+ panel: /* @__PURE__ */ jsx20(LeaderNetworkOverview, { ctx: adminViewContext(ctx, "overview", true) })
1813
2062
  }]
1814
2063
  }
1815
2064
  );
1816
2065
  }
1817
2066
  function LeaderSettingsWorkspace({ chapter, ctx }) {
1818
2067
  const email = emailSection({ label: chapter.copy.admin.workspaces.email });
1819
- return /* @__PURE__ */ jsx18(
2068
+ return /* @__PURE__ */ jsx20(
1820
2069
  Tabs2,
1821
2070
  {
1822
2071
  ariaLabel: chapter.copy.admin.workspaces.settingsViewsLabel,
@@ -1837,7 +2086,7 @@ function defaultAdminWorkspaces(chapter) {
1837
2086
  id: "people",
1838
2087
  label: copy.workspaces.people,
1839
2088
  defaultViewId: Object.keys(chapter.crm.config.types)[0] ?? "person",
1840
- render: (ctx) => /* @__PURE__ */ jsx18(PeopleWorkspace, { chapter, ctx })
2089
+ render: (ctx) => /* @__PURE__ */ jsx20(PeopleWorkspace, { chapter, ctx })
1841
2090
  };
1842
2091
  if (chapter.mode === "hub" && chapter.network.targets.length === 0) return [people];
1843
2092
  if (chapter.mode === "hub") {
@@ -1847,7 +2096,7 @@ function defaultAdminWorkspaces(chapter) {
1847
2096
  id: "dashboard",
1848
2097
  label: copy.workspaces.dashboard,
1849
2098
  defaultViewId: "overview",
1850
- render: (ctx) => /* @__PURE__ */ jsx18(LeaderDashboardWorkspace, { chapter, ctx })
2099
+ render: (ctx) => /* @__PURE__ */ jsx20(LeaderDashboardWorkspace, { chapter, ctx })
1851
2100
  },
1852
2101
  networkRecordsWorkspace(chapter),
1853
2102
  people,
@@ -1855,13 +2104,13 @@ function defaultAdminWorkspaces(chapter) {
1855
2104
  id: "portfolio",
1856
2105
  label: copy.workspaces.portfolio,
1857
2106
  defaultViewId: portfolioTypes[0]?.[0],
1858
- render: (ctx) => /* @__PURE__ */ jsx18(PortfolioWorkspace, { chapter, ctx })
2107
+ render: (ctx) => /* @__PURE__ */ jsx20(PortfolioWorkspace, { chapter, ctx })
1859
2108
  },
1860
2109
  {
1861
2110
  id: "settings",
1862
2111
  label: copy.workspaces.settings,
1863
2112
  defaultViewId: "email",
1864
- render: (ctx) => /* @__PURE__ */ jsx18(LeaderSettingsWorkspace, { chapter, ctx })
2113
+ render: (ctx) => /* @__PURE__ */ jsx20(LeaderSettingsWorkspace, { chapter, ctx })
1865
2114
  }
1866
2115
  ];
1867
2116
  }
@@ -1870,24 +2119,25 @@ function defaultAdminWorkspaces(chapter) {
1870
2119
  id: "dashboard",
1871
2120
  label: copy.workspaces.dashboard,
1872
2121
  defaultViewId: "overview",
1873
- render: (ctx) => /* @__PURE__ */ jsx18(DashboardWorkspace, { chapter, ctx })
2122
+ render: (ctx) => /* @__PURE__ */ jsx20(DashboardWorkspace, { chapter, ctx })
1874
2123
  },
1875
2124
  people,
2125
+ admissionGrantsWorkspace(copy.workspaces.admission),
1876
2126
  {
1877
2127
  id: "settings",
1878
2128
  label: copy.workspaces.settings,
1879
2129
  defaultViewId: "calendar",
1880
- render: (ctx) => /* @__PURE__ */ jsx18(SettingsWorkspace, { chapter, ctx })
2130
+ render: (ctx) => /* @__PURE__ */ jsx20(SettingsWorkspace, { chapter, ctx })
1881
2131
  }
1882
2132
  ];
1883
2133
  }
1884
2134
 
1885
2135
  // src/ui/admin-frame.tsx
1886
2136
  import { ThemeScope as ThemeScope2 } from "@odla-ai/ui/components";
1887
- import { jsx as jsx19, jsxs as jsxs15 } from "preact/jsx-runtime";
2137
+ import { jsx as jsx21, jsxs as jsxs17 } from "preact/jsx-runtime";
1888
2138
  function AdminTheme(props) {
1889
2139
  const brand = props.chapter?.brand;
1890
- return /* @__PURE__ */ jsx19(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs15(
2140
+ return /* @__PURE__ */ jsx21(ChapterCopyProvider, { copy: props.chapter?.copy, children: /* @__PURE__ */ jsxs17(
1891
2141
  ThemeScope2,
1892
2142
  {
1893
2143
  ref: props.scopeRef,
@@ -1896,7 +2146,7 @@ function AdminTheme(props) {
1896
2146
  accent: brand?.accent,
1897
2147
  colorScheme: brand?.colorScheme ?? "light",
1898
2148
  children: [
1899
- /* @__PURE__ */ jsx19(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
2149
+ /* @__PURE__ */ jsx21(BrandStyle, { brand, selector: "[data-chapter-admin]" }),
1900
2150
  props.children
1901
2151
  ]
1902
2152
  }
@@ -1916,11 +2166,11 @@ function signInRedirect(basePath, workspaces) {
1916
2166
  }
1917
2167
 
1918
2168
  // src/ui/admin-appearance.ts
1919
- import { useEffect as useEffect8, useState as useState13 } from "preact/hooks";
2169
+ import { useEffect as useEffect9, useState as useState15 } from "preact/hooks";
1920
2170
  import { clerkAppearanceFromTokens } from "@odla-ai/auth-clerk";
1921
2171
  function useScopedClerkAppearance(scope) {
1922
- const [appearance, setAppearance] = useState13();
1923
- useEffect8(() => {
2172
+ const [appearance, setAppearance] = useState15();
2173
+ useEffect9(() => {
1924
2174
  setAppearance(clerkAppearanceFromTokens(scope.current ?? void 0));
1925
2175
  }, [scope]);
1926
2176
  return appearance;
@@ -1951,7 +2201,7 @@ async function loadChapterAdminUser(adapter, apiBase, getToken) {
1951
2201
  }
1952
2202
 
1953
2203
  // src/ui/admin.tsx
1954
- import { jsx as jsx20, jsxs as jsxs16 } from "preact/jsx-runtime";
2204
+ import { jsx as jsx22, jsxs as jsxs18 } from "preact/jsx-runtime";
1955
2205
  function Authed(props) {
1956
2206
  const copy = useChapterCopy().admin;
1957
2207
  const {
@@ -1978,8 +2228,8 @@ function Authed(props) {
1978
2228
  }),
1979
2229
  [getToken, crmBasePath]
1980
2230
  );
1981
- const [state, setState] = useState14({ status: "checking" });
1982
- useEffect9(() => {
2231
+ const [state, setState] = useState16({ status: "checking" });
2232
+ useEffect10(() => {
1983
2233
  let live = true;
1984
2234
  void (async () => {
1985
2235
  try {
@@ -1998,17 +2248,17 @@ function Authed(props) {
1998
2248
  live = false;
1999
2249
  };
2000
2250
  }, [getToken, apiBase, auth]);
2001
- if (state.status === "checking") return /* @__PURE__ */ jsx20(Gate, { brand, children: /* @__PURE__ */ jsx20("p", { style: S.muted, children: copy.auth.checking }) });
2251
+ if (state.status === "checking") return /* @__PURE__ */ jsx22(Gate, { brand, children: /* @__PURE__ */ jsx22("p", { style: S.muted, children: copy.auth.checking }) });
2002
2252
  if (state.status === "denied") {
2003
- return /* @__PURE__ */ jsx20(Gate, { brand, children: /* @__PURE__ */ jsxs16("div", { className: "card", style: S.card, children: [
2004
- /* @__PURE__ */ jsx20("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
2005
- /* @__PURE__ */ jsx20("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
2253
+ return /* @__PURE__ */ jsx22(Gate, { brand, children: /* @__PURE__ */ jsxs18("div", { className: "card", style: S.card, children: [
2254
+ /* @__PURE__ */ jsx22("h2", { style: { marginTop: 0 }, children: copy.auth.notAuthorized }),
2255
+ /* @__PURE__ */ jsx22("p", { style: S.muted, children: formatChapterCopy(copy.auth.accountNotAuthorized, {
2006
2256
  account: state.email ?? copy.auth.thisAccount
2007
2257
  }) }),
2008
- /* @__PURE__ */ jsx20("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
2258
+ /* @__PURE__ */ jsx22("button", { className: "btn secondary", onClick: () => signOut(), children: copy.auth.signOut })
2009
2259
  ] }) });
2010
2260
  }
2011
- return /* @__PURE__ */ jsx20(
2261
+ return /* @__PURE__ */ jsx22(
2012
2262
  AdminShell,
2013
2263
  {
2014
2264
  workspaces,
@@ -2049,10 +2299,10 @@ function ChapterAdmin(props) {
2049
2299
  const chrome = props.chrome ?? "embedded";
2050
2300
  const auth = props.auth ?? DEFAULT_ADMIN_AUTH;
2051
2301
  const redirect = signInRedirect(basePath, workspaces);
2052
- const [publishableKey, setPublishableKey] = useState14(void 0);
2302
+ const [publishableKey, setPublishableKey] = useState16(void 0);
2053
2303
  const scopeRef = useRef(null);
2054
2304
  const appearance = useScopedClerkAppearance(scopeRef);
2055
- useEffect9(() => {
2305
+ useEffect10(() => {
2056
2306
  let live = true;
2057
2307
  void loadChapterAdminConfig(auth, apiBase).then((config) => live && setPublishableKey(config.publishableKey ?? null)).catch(() => live && setPublishableKey(null));
2058
2308
  return () => {
@@ -2061,23 +2311,23 @@ function ChapterAdmin(props) {
2061
2311
  }, [apiBase, auth]);
2062
2312
  let content;
2063
2313
  if (publishableKey === void 0 || publishableKey && !appearance) {
2064
- content = /* @__PURE__ */ jsx20(Gate, { brand, children: /* @__PURE__ */ jsx20("p", { style: S.muted, children: copy.admin.auth.loading }) });
2314
+ content = /* @__PURE__ */ jsx22(Gate, { brand, children: /* @__PURE__ */ jsx22("p", { style: S.muted, children: copy.admin.auth.loading }) });
2065
2315
  } else if (!publishableKey) {
2066
- content = /* @__PURE__ */ jsx20(Gate, { brand, children: /* @__PURE__ */ jsxs16("div", { className: "card", style: S.card, children: [
2067
- /* @__PURE__ */ jsx20("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
2068
- /* @__PURE__ */ jsx20("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
2316
+ content = /* @__PURE__ */ jsx22(Gate, { brand, children: /* @__PURE__ */ jsxs18("div", { className: "card", style: S.card, children: [
2317
+ /* @__PURE__ */ jsx22("h2", { style: { marginTop: 0 }, children: copy.admin.auth.signInNotConfigured }),
2318
+ /* @__PURE__ */ jsx22("p", { style: S.muted, children: copy.admin.auth.missingPublishableKey })
2069
2319
  ] }) });
2070
2320
  } else if (workspaces.length === 0) {
2071
- content = /* @__PURE__ */ jsx20(Gate, { brand, children: /* @__PURE__ */ jsx20("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
2321
+ content = /* @__PURE__ */ jsx22(Gate, { brand, children: /* @__PURE__ */ jsx22("p", { style: S.muted, children: copy.admin.auth.noWorkspaces }) });
2072
2322
  } else {
2073
- content = /* @__PURE__ */ jsxs16(
2323
+ content = /* @__PURE__ */ jsxs18(
2074
2324
  ClerkGate,
2075
2325
  {
2076
2326
  publishableKey,
2077
2327
  appearance,
2078
2328
  afterSignOutUrl: "/",
2079
2329
  children: [
2080
- /* @__PURE__ */ jsx20(SignedOut, { children: /* @__PURE__ */ jsx20(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx20(
2330
+ /* @__PURE__ */ jsx22(SignedOut, { children: /* @__PURE__ */ jsx22(Gate, { brand, tagline: copy.admin.auth.signInTagline, children: /* @__PURE__ */ jsx22(
2081
2331
  SignIn,
2082
2332
  {
2083
2333
  routing: "hash",
@@ -2085,7 +2335,7 @@ function ChapterAdmin(props) {
2085
2335
  signUpForceRedirectUrl: redirect
2086
2336
  }
2087
2337
  ) }) }),
2088
- /* @__PURE__ */ jsx20(SignedIn, { children: /* @__PURE__ */ jsx20(
2338
+ /* @__PURE__ */ jsx22(SignedIn, { children: /* @__PURE__ */ jsx22(
2089
2339
  Authed,
2090
2340
  {
2091
2341
  workspaces,
@@ -2105,7 +2355,7 @@ function ChapterAdmin(props) {
2105
2355
  }
2106
2356
  );
2107
2357
  }
2108
- return /* @__PURE__ */ jsx20(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
2358
+ return /* @__PURE__ */ jsx22(AdminTheme, { chapter: props.chapter, scopeRef, children: content });
2109
2359
  }
2110
2360
 
2111
2361
  // src/ui/admin-defaults.ts
@@ -2114,17 +2364,17 @@ function defaultAdminSections(chapter) {
2114
2364
  }
2115
2365
 
2116
2366
  // src/ui/admin-meetings.tsx
2117
- import { useState as useState15 } from "preact/hooks";
2367
+ import { useState as useState17 } from "preact/hooks";
2118
2368
  import { Button as Button7 } from "@odla-ai/ui/components";
2119
- import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs17 } from "preact/jsx-runtime";
2369
+ import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs19 } from "preact/jsx-runtime";
2120
2370
  var when2 = (t) => new Date(t).toLocaleString([], { dateStyle: "medium", timeStyle: "short" });
2121
2371
  function MeetingsBody({ ctx }) {
2122
2372
  const copy = useChapterCopy().admin.meetings;
2123
2373
  const { data, loading, error, refresh } = useAdminResource(ctx.getToken, "/api/admin/meetings?all=1");
2124
- const [busy, setBusy] = useState15(null);
2125
- const [note, setNote] = useState15(null);
2126
- if (loading) return /* @__PURE__ */ jsx21(AdminNote, { children: copy.loading });
2127
- if (error || !data) return /* @__PURE__ */ jsxs17(AdminNote, { children: [
2374
+ const [busy, setBusy] = useState17(null);
2375
+ const [note, setNote] = useState17(null);
2376
+ if (loading) return /* @__PURE__ */ jsx23(AdminNote, { children: copy.loading });
2377
+ if (error || !data) return /* @__PURE__ */ jsxs19(AdminNote, { children: [
2128
2378
  copy.loadFailed,
2129
2379
  error ? `: ${error}` : "",
2130
2380
  "."
@@ -2155,32 +2405,32 @@ function MeetingsBody({ ctx }) {
2155
2405
  }
2156
2406
  void run(id, () => adminFetch(ctx.getToken, `/api/admin/meetings/${id}/reschedule`, { method: "POST", body: JSON.stringify({ startAt }) }));
2157
2407
  };
2158
- return /* @__PURE__ */ jsx21(AdminPage, { children: /* @__PURE__ */ jsx21(Panel, { title: /* @__PURE__ */ jsxs17(Fragment4, { children: [
2408
+ return /* @__PURE__ */ jsx23(AdminPage, { children: /* @__PURE__ */ jsx23(Panel, { title: /* @__PURE__ */ jsxs19(Fragment4, { children: [
2159
2409
  copy.agenda,
2160
2410
  " ",
2161
- /* @__PURE__ */ jsxs17("span", { className: "muted", style: { fontWeight: 400 }, children: [
2411
+ /* @__PURE__ */ jsxs19("span", { className: "muted", style: { fontWeight: 400 }, children: [
2162
2412
  "\xB7 ",
2163
2413
  data.timezone
2164
2414
  ] })
2165
- ] }), actions: note ? /* @__PURE__ */ jsx21("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx21("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs17("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
2166
- /* @__PURE__ */ jsx21("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
2167
- /* @__PURE__ */ jsx21("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
2168
- /* @__PURE__ */ jsx21("span", { className: "badge", children: m.status }),
2169
- m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs17("span", { className: "badge", children: [
2415
+ ] }), actions: note ? /* @__PURE__ */ jsx23("span", { className: "muted", children: note }) : void 0, children: data.meetings.length === 0 ? /* @__PURE__ */ jsx23("p", { className: "muted", style: { margin: 0 }, children: copy.empty }) : /* @__PURE__ */ jsx23("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 12 }, children: data.meetings.map((m) => /* @__PURE__ */ jsxs19("li", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", opacity: m.status === "cancelled" ? 0.55 : 1 }, children: [
2416
+ /* @__PURE__ */ jsx23("strong", { style: { minWidth: 170 }, children: when2(m.startAt) }),
2417
+ /* @__PURE__ */ jsx23("span", { style: { flex: 1 }, children: m.applicant ? `${m.applicant.name} \xB7 ${m.applicant.email}` : copy.unknown }),
2418
+ /* @__PURE__ */ jsx23("span", { className: "badge", children: m.status }),
2419
+ m.drift && m.drift !== "none" ? /* @__PURE__ */ jsxs19("span", { className: "badge", children: [
2170
2420
  copy.drift,
2171
2421
  ": ",
2172
2422
  m.drift
2173
2423
  ] }) : null,
2174
- m.meetUrl ? /* @__PURE__ */ jsx21("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
2175
- m.status === "scheduled" ? /* @__PURE__ */ jsxs17(Fragment4, { children: [
2176
- /* @__PURE__ */ jsx21(Button7, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
2177
- /* @__PURE__ */ jsx21(Button7, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
2424
+ m.meetUrl ? /* @__PURE__ */ jsx23("a", { href: m.meetUrl, target: "_blank", rel: "noreferrer", children: copy.meet }) : null,
2425
+ m.status === "scheduled" ? /* @__PURE__ */ jsxs19(Fragment4, { children: [
2426
+ /* @__PURE__ */ jsx23(Button7, { variant: "secondary", mini: true, disabled: busy === m.id, onClick: () => reschedule(m.id), children: copy.reschedule }),
2427
+ /* @__PURE__ */ jsx23(Button7, { variant: "danger", mini: true, disabled: busy === m.id, onClick: () => cancel(m.id), children: copy.cancel })
2178
2428
  ] }) : null
2179
2429
  ] }, m.id)) }) }) });
2180
2430
  }
2181
2431
  function meetingsSection(options = {}) {
2182
2432
  const { id = "meetings", label = "Meetings" } = options;
2183
- return { id, label, render: (ctx) => /* @__PURE__ */ jsx21(MeetingsBody, { ctx }) };
2433
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx23(MeetingsBody, { ctx }) };
2184
2434
  }
2185
2435
 
2186
2436
  export {
@@ -2206,6 +2456,10 @@ export {
2206
2456
  NetworkRecordsWorkspace,
2207
2457
  networkRecordsWorkspace,
2208
2458
  LeaderNetworkOverview,
2459
+ AdmissionGrantsWorkspace,
2460
+ admissionGrantsWorkspace,
2461
+ MemberContentAdmin,
2462
+ memberContentSection,
2209
2463
  defaultAdminWorkspaces,
2210
2464
  loadChapterAdminConfig,
2211
2465
  loadChapterAdminUser,
@@ -2213,4 +2467,4 @@ export {
2213
2467
  defaultAdminSections,
2214
2468
  meetingsSection
2215
2469
  };
2216
- //# sourceMappingURL=chunk-TXE5ZDI4.js.map
2470
+ //# sourceMappingURL=chunk-SI3JHSEX.js.map