@odla-ai/chapter 0.20.2 → 0.22.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ email. The host still builds the public pages, routing, and brand presentation;
8
8
  Chapter supplies their application mechanics.
9
9
 
10
10
  ```sh
11
- npm i --save-exact @odla-ai/chapter@0.20.2
11
+ npm i --save-exact @odla-ai/chapter@0.22.0
12
12
  ```
13
13
 
14
14
  > **Agentic experiment.** Built and maintained by AI agents from bounded runbooks
@@ -101,34 +101,38 @@ secrets out of source and require development proof before production.
101
101
  presentational component without the Clerk browser SDK. (A bundle-graph test
102
102
  enforces this.)
103
103
  - `@odla-ai/chapter/ui/admin` — the Clerk-gated console `ChapterAdmin` + the
104
- section catalog. `<ChapterAdmin chapter={chapter}/>` derives the brand and
105
- standard sections automatically: every configured CRM collection plus the
106
- chapter operational surfaces. Pass `sections` only for a deliberate custom
107
- console. This one needs auth-clerk + crm/ui.
104
+ workspace catalog. In chapter mode, `<ChapterAdmin chapter={chapter}/>`
105
+ defaults to just three top-level workspaces: Dashboard, People, and
106
+ Settings. Operational detail belongs in nested page tabs; CRM record
107
+ operations belong in record tabs. Pass `workspaces` to transform or replace
108
+ that information architecture. This entry needs auth-clerk + crm/ui.
108
109
  - `@odla-ai/chapter/ui` — the full barrel, for back-compat.
109
110
 
110
111
  Authored against React, rendered as Preact via `preact/compat` in the reference
111
112
  sites. Brand tokens (`brandTokens`/`<BrandStyle>`) re-skin all of it from
112
113
  `brand` (now light **and** dark, via `brand.palette` + `brand.paletteDark`).
113
114
 
114
- ### Theme tokens (or the console renders broken)
115
+ ### Theme tokens and scoped branding
115
116
 
116
117
  The UI reads its colors from `--ui-*` design tokens whose **values come from a
117
- theme layer, not `@odla-ai/ui/index.css`.** Use the one-import application sheet;
118
- it expands to the token layer, Salt's familiar editorial component treatment,
119
- then the shared component sheet in the required order:
118
+ theme layer, not `@odla-ai/ui/index.css`.** For an admin surface isolated from
119
+ the public site, import one scoped theme, the shared component sheet, and the
120
+ CRM layout:
120
121
 
121
122
  ```ts
122
- import "@odla-ai/ui/themes/salt/app.css";
123
+ import "@odla-ai/ui/fonts/plex.css";
124
+ import "@odla-ai/ui/themes/paper/scope.css";
125
+ import "@odla-ai/ui/index.css";
126
+ import "@odla-ai/crm/ui.css";
123
127
  ```
124
128
 
125
129
  `ChapterAdmin` guards against this at runtime: if a `.panel` has no background
126
130
  (the theme layer is missing), it renders a **loud red banner** at the top of the
127
- console instead of failing silently. To brand it, pass `brand.palette` /
128
- `brand.paletteDark` `brandTokens(brand)` emits both a `:root` block and a
129
- `[data-theme="dark"]` / `prefers-color-scheme: dark` block, so your accent/colors
130
- override the shared visual grammar in both modes. When the `chapter` prop is
131
- present, `ChapterAdmin` installs those brand tokens itself.
131
+ console instead of failing silently. Set `brand.theme`, `brand.colorScheme`,
132
+ and semantic `brand.tokens` / `brand.tokensDark` for normal configuration.
133
+ `brand.palette` / `brand.paletteDark` remain the low-level custom-property
134
+ escape hatch. Chapter scopes these overrides to `[data-chapter-admin]`, so an
135
+ admin brand cannot recolor the document root or vendor sign-in UI.
132
136
 
133
137
  ## API quick start
134
138
 
@@ -144,7 +148,13 @@ export const chapter = defineChapter({
144
148
  badge: "EX",
145
149
  wordmark: "Example Chapter",
146
150
  tagline: "Capital and craft for durable local businesses.",
147
- palette: { "--ui-accent": "#2f6f4f" },
151
+ theme: "paper",
152
+ colorScheme: "light",
153
+ tokens: {
154
+ accent: "#2f6f4f",
155
+ accentStrong: "#244f3b",
156
+ accentSoft: "#dfece4",
157
+ },
148
158
  fonts: { display: "GT Sectra" },
149
159
  },
150
160
  prices: { standardCents: 100000, foundingDiscountCents: 10000 },
@@ -160,8 +170,11 @@ export default chapterWorker({ chapter });
160
170
  ```
161
171
 
162
172
  ```tsx
163
- // src/app/admin.tsx — brand + familiar sections come from the same config.
164
- import "@odla-ai/ui/themes/salt/app.css";
173
+ // src/app/admin.tsx — brand + familiar workspaces come from the same config.
174
+ import "@odla-ai/ui/fonts/plex.css";
175
+ import "@odla-ai/ui/themes/paper/scope.css";
176
+ import "@odla-ai/ui/index.css";
177
+ import "@odla-ai/crm/ui.css";
165
178
  import { render } from "preact";
166
179
  import { ChapterAdmin } from "@odla-ai/chapter/ui/admin";
167
180
  import { chapter } from "../chapter.config.mjs";
@@ -274,24 +287,24 @@ records, not `crm_link` relation rows; create or curate `works_at` links locally
274
287
 
275
288
  #### Custom leader consoles must mount the sharing UI
276
289
 
277
- Automatic “Share with …” actions come from chapter's standard collection
278
- sections. They are present when the console uses
279
- `<ChapterAdmin chapter={chapter} />`. Passing an explicit `sections` array
280
- replaces that catalog, so a custom console must deliberately compose it:
290
+ Automatic “Share with …” actions come from Chapter's standard People workspace.
291
+ They are present when the console uses `<ChapterAdmin chapter={chapter} />`.
292
+ Passing a `workspaces` array replaces that catalog, so a custom console must
293
+ deliberately compose it:
281
294
 
282
295
  ```tsx
283
296
  import {
284
297
  ChapterAdmin,
285
- defaultAdminSections,
298
+ defaultAdminWorkspaces,
286
299
  } from "@odla-ai/chapter/ui/admin";
287
300
 
288
- const sections = [
289
- customOverviewSection,
290
- ...defaultAdminSections(chapter),
301
+ const workspaces = [
302
+ customOperationsWorkspace,
303
+ ...defaultAdminWorkspaces(chapter),
291
304
  ];
292
305
 
293
306
  render(
294
- <ChapterAdmin chapter={chapter} sections={sections} />,
307
+ <ChapterAdmin chapter={chapter} workspaces={workspaces} />,
295
308
  document.getElementById("admin-root"),
296
309
  );
297
310
  ```
@@ -333,10 +346,112 @@ Only after this contract passes against development origins should the leader
333
346
  target be changed to a production follower origin and the matching production
334
347
  vault values be installed.
335
348
 
336
- Section navigation defaults to `/admin/?tab=people`, not
337
- `/admin/people`. Query routing deliberately works in both leader-style SPAs and
338
- ordinary static subdirectories. Legacy path links still open, and
339
- `routing="path"` remains available only for hosts with an explicit SPA fallback.
349
+ Admin navigation defaults to link-backed fragments:
350
+ `/admin/#people/person/record-id/profile`. The four segments are workspace,
351
+ nested view, selected record, and record-detail tab. This keeps meaningful,
352
+ reloadable URLs without requiring a server-side SPA fallback, and it avoids
353
+ competing with Clerk's sign-in hash: while signed out, Chapter carries the
354
+ requested state through the redirect query and canonicalizes it after sign-in.
355
+ Legacy `?tab=` and path links still open, while `routing="query"` and
356
+ `routing="path"` remain compatibility modes.
357
+
358
+ ### Admin information architecture and migration
359
+
360
+ The default chapter console deliberately has only three top-level workspaces:
361
+
362
+ - **Dashboard** — Overview and Billing page tabs.
363
+ - **People** — configured CRM collections as page tabs, then a master/detail
364
+ record view with Stage, Profile, communications, scheduling, Billing, Notes,
365
+ Connections, Access, and Sharing record tabs when those capabilities apply.
366
+ - **Settings** — Calendar and Email page tabs.
367
+
368
+ The default `chrome="embedded"` assumes the host supplies the site header.
369
+ Use `chrome="standalone"` for Chapter's packaged masthead or `renderHeader` for
370
+ a custom header rendered inside the Chapter theme boundary. `editorial`,
371
+ `none`, `topbar`, and `sections` remain compatibility APIs. Do not add
372
+ operational detail back to the global site navigation.
373
+
374
+ Only the active workspace, page tab, and record tab mount their content.
375
+ Inactive operational panels therefore do not fetch, subscribe, or run effects.
376
+ Their native anchors remain in the document, so every level is still
377
+ reloadable and participates in browser back/forward navigation.
378
+
379
+ #### Preserve an existing People structure
380
+
381
+ The responsive state and data loading belong to `@odla-ai/crm`; the branded
382
+ summary, master rail, record heading, and specialized panels can remain
383
+ host-owned:
384
+
385
+ ```tsx
386
+ const people = collectionSection({
387
+ crm: chapter.crm,
388
+ type: "person",
389
+ lifecycle: true,
390
+ renderSummary: ({ query }) => (
391
+ <PeopleSummary total={query.page?.total ?? 0} />
392
+ ),
393
+ renderMaster: ({ defaultMaster }) => (
394
+ <PeopleRail>{defaultMaster}</PeopleRail>
395
+ ),
396
+ renderDetailHeader: ({ detail }) => (
397
+ <RecordHeading record={detail.record} />
398
+ ),
399
+ extendRecordTabs: (tabs, context) => replaceRecordPanels(tabs, context),
400
+ });
401
+ ```
402
+
403
+ `renderMaster` receives the loaded query, selection helpers, native record-link
404
+ builder, and `defaultMaster`, so a host may wrap the standard list or replace
405
+ its presentation without taking over fetching. `extendRecordTabs` supports
406
+ per-record `visible` and `disabled` predicates. Hidden panels do not mount.
407
+
408
+ For `lifecycle: true`, Chapter routes stage transitions through the
409
+ application-authoritative approve/refund/manual-transition endpoints and
410
+ requires that adapter. A custom operational collection can pass
411
+ `lifecycleAdapter`; generic CRM collections continue to use the CRM mutation.
412
+
413
+ #### Adapt existing authentication routes
414
+
415
+ An existing site does not need to rename its config or current-user endpoints:
416
+
417
+ ```tsx
418
+ <ChapterAdmin
419
+ chapter={chapter}
420
+ auth={{
421
+ configPath: "/api/auth/config",
422
+ mapConfig: (body) => ({
423
+ publishableKey: typeof body.publishableKey === "string"
424
+ ? body.publishableKey
425
+ : null,
426
+ }),
427
+ mapCurrentUser: (body) => ({
428
+ ...body,
429
+ authorized: body.role === "admin" || body.superAdmin === true,
430
+ }),
431
+ }}
432
+ />
433
+ ```
434
+
435
+ `loadConfig` and `loadCurrentUser` may replace fetching entirely. The normalized
436
+ current-user object is also available to workspace renderers for presentation
437
+ decisions; server authorization remains authoritative.
438
+
439
+ When upgrading a flat console:
440
+
441
+ 1. Import the scoped UI theme, `@odla-ai/ui/index.css`, and
442
+ `@odla-ai/crm/ui.css`.
443
+ 2. Remove top-level Billing, Email, Calendar, or collection links that duplicate
444
+ the standard nested tabs.
445
+ 3. Replace `sections` with `workspaces`, or omit it to accept the defaults.
446
+ 4. Move host-owned summaries, master rails, and record headings into
447
+ `collectionSection` render slots; compose specialized operations with
448
+ `extendRecordTabs`.
449
+ 5. Connect application-backed stage changes through `lifecycleAdapter`; never
450
+ replace an operational transition with a raw CRM stage write.
451
+ 6. Change generated links to `adminRouteHref`; keep legacy query/path URLs only
452
+ as inbound compatibility links.
453
+ 7. Verify refresh, back/forward navigation, keyboard tab behavior, mobile
454
+ list/detail switching, and brand containment before deleting old routes.
340
455
 
341
456
  ## Adopting into an existing site
342
457
 
@@ -467,7 +582,7 @@ These bite silently — a smoke test won't catch them:
467
582
  invitation mail. Importing the full `@odla-ai/chapter/ui` barrel pulls the admin
468
583
  half, so prefer the narrower entry.
469
584
  - **Known-good application set** (installs clean, no flags):
470
- `@odla-ai/chapter` 0.20.2, `@odla-ai/ui` 0.10.0, `@odla-ai/crm` 0.1.4,
585
+ `@odla-ai/chapter` 0.22.0, `@odla-ai/ui` 0.12.0, `@odla-ai/crm` 0.3.0,
471
586
  `@odla-ai/db` 0.6.6,
472
587
  `@odla-ai/calendar` 0.2.0, `@odla-ai/email` 0.3.1,
473
588
  `@odla-ai/auth-clerk` 0.4.0, `@odla-ai/o11y` 2.2.2, `jose` 6.2.3, React
@@ -502,4 +502,4 @@ export {
502
502
  PaymentStep,
503
503
  JoinIsland
504
504
  };
505
- //# sourceMappingURL=chunk-LME2IHY5.js.map
505
+ //# sourceMappingURL=chunk-ILGUJXSB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/slot-picker.tsx","../src/ui/datetime.ts","../src/ui/members.tsx","../src/ui/reschedule.tsx","../src/ui/join.tsx","../src/ui/payment-step.tsx"],"sourcesContent":["// SlotPicker — a pure presentational day-chips + time-grid picker, shared by the\n// join booking step and the member-area reschedule. Ported from the proven\n// reference Preact island to React; behaviour and DOM shape are unchanged.\n//\n// It holds only the active-day UI state; slot data, selection, and booking are\n// the caller's (the picker is `onPick`-driven). `classes` lets the composing\n// island supply its own CSS contract (join styles `slot-*`, reschedule\n// `msched-*`); the defaults work standalone against those class names.\nimport { useMemo, useState } from \"react\";\nimport { dayLabel, groupSlotsByDay, timeLabel } from \"./datetime.js\";\nimport type { Slot } from \"./datetime.js\";\n\n/** CSS class names for the four picker parts — overridable so one component\n * serves several differently-styled surfaces. */\nexport interface SlotPickerClasses {\n days: string;\n day: string;\n times: string;\n time: string;\n}\n\nconst DEFAULT_CLASSES: SlotPickerClasses = {\n days: \"slot-days\",\n day: \"slot-day\",\n times: \"slot-grid\",\n time: \"slot-time\",\n};\n\n/** Props for {@link SlotPicker}. Generic over the slot type so a caller's richer\n * slot object survives through `onPick`. */\nexport interface SlotPickerProps<T extends Slot = Slot> {\n /** Bookable slots, ideally already chronological. */\n slots: readonly T[];\n /** The group's scheduling timezone; all labels render in it. */\n timezone: string;\n /** The currently-selected slot's start, for the pressed state. */\n selectedStartAt?: number;\n /** Called with the full slot object when a time is chosen. */\n onPick: (slot: T) => void;\n /** Called when the active day changes (callers clear their selection). */\n onDayChange?: () => void;\n classes?: SlotPickerClasses;\n}\n\n/** A day-chips + time-grid slot picker. Presentational and `onPick`-driven: it\n * owns only the active-day selection; the caller owns slot data, the chosen\n * slot, and booking. Shared by the join booking step and the member reschedule. */\nexport function SlotPicker<T extends Slot = Slot>(props: SlotPickerProps<T>) {\n const { slots, timezone, selectedStartAt, onPick, onDayChange, classes = DEFAULT_CLASSES } = props;\n const byDay = useMemo(() => groupSlotsByDay(slots, timezone), [slots, timezone]);\n const dayKeys = [...byDay.keys()];\n const [activeDay, setActiveDay] = useState<string | undefined>(dayKeys[0]);\n\n // Keep the active day valid if the slot set changed under us (e.g. a reload\n // after a 409): fall back to the first available day.\n const day = activeDay !== undefined && byDay.has(activeDay) ? activeDay : dayKeys[0];\n const times = (day !== undefined ? byDay.get(day) : undefined) ?? [];\n\n return (\n <>\n <div className={classes.days}>\n {dayKeys.map((key) => {\n const first = byDay.get(key)?.[0];\n return (\n <button\n key={key}\n type=\"button\"\n className={classes.day}\n aria-pressed={key === day}\n onClick={() => {\n setActiveDay(key);\n onDayChange?.();\n }}\n >\n {first ? dayLabel(first.startAt, timezone) : key}\n </button>\n );\n })}\n </div>\n <div className={classes.times}>\n {times.map((s) => (\n <button\n key={s.startAt}\n type=\"button\"\n className={classes.time}\n aria-pressed={selectedStartAt === s.startAt}\n onClick={() => onPick(s)}\n >\n {timeLabel(s.startAt, timezone)}\n </button>\n ))}\n </div>\n </>\n );\n}\n","// Date/timezone formatting for the chapter member islands (join booking, member\n// area). Ported from proven production helpers. Every time renders in the\n// group's SCHEDULING timezone with an explicit abbreviation — never the viewer's\n// unlabeled local zone — so an applicant always sees the time the chapter meant.\n// Pure and framework-free, so it is unit-testable without a DOM.\n\n/** A bookable slot: a start instant in epoch milliseconds. */\nexport interface Slot {\n startAt: number;\n}\n\n/** The timezone's short abbreviation (e.g. \"PST\"); falls back to the id if the\n * runtime can't resolve it. */\nexport function tzShort(tz: string): string {\n try {\n const parts = new Intl.DateTimeFormat(undefined, { timeZone: tz, timeZoneName: \"short\" }).formatToParts(\n new Date(),\n );\n return parts.find((p) => p.type === \"timeZoneName\")?.value ?? tz;\n } catch {\n return tz;\n }\n}\n\n/** DST-safe day bucket key (ISO date in the target zone). Groups slots by the\n * calendar day a viewer in `tz` would see, not by UTC midnight. */\nexport function dayKey(ms: number, tz: string): string {\n return new Date(ms).toLocaleDateString(\"en-CA\", { timeZone: tz });\n}\n\n/** Short day label for a day chip, e.g. \"Mon, Jun 3\". */\nexport function dayLabel(ms: number, tz: string): string {\n return new Date(ms).toLocaleDateString(undefined, { timeZone: tz, weekday: \"short\", month: \"short\", day: \"numeric\" });\n}\n\n/** Time-of-day label for a slot button, e.g. \"2:30 PM\". */\nexport function timeLabel(ms: number, tz: string): string {\n return new Date(ms).toLocaleTimeString(undefined, { timeZone: tz, hour: \"numeric\", minute: \"2-digit\" });\n}\n\n/** Full, human confirmation label, e.g. \"Monday, June 3, 2:30 PM PST\". */\nexport function fullLabel(ms: number, tz: string): string {\n return new Date(ms).toLocaleString(undefined, {\n timeZone: tz,\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n timeZoneName: \"short\",\n });\n}\n\n/** Currency label from integer cents, e.g. 100000 → \"$1,000\". */\nexport function fmtMoney(cents: number): string {\n return \"$\" + Math.round(cents / 100).toLocaleString();\n}\n\n/** Short date label, e.g. \"Jun 3, 2026\" (used for renewal/refund copy). */\nexport function fmtDate(ms: number): string {\n return new Date(ms).toLocaleDateString(undefined, { month: \"short\", day: \"numeric\", year: \"numeric\" });\n}\n\n/** Bucket slots into an insertion-ordered `Map<dayKey, Slot[]>` for the picker.\n * Input order is preserved within each day, so a server that returns slots\n * chronologically yields chronological chips + times. */\nexport function groupSlotsByDay<T extends Slot>(slots: readonly T[], tz: string): Map<string, T[]> {\n const byDay = new Map<string, T[]>();\n for (const s of slots) {\n const k = dayKey(s.startAt, tz);\n const bucket = byDay.get(k);\n if (bucket) bucket.push(s);\n else byDay.set(k, [s]);\n }\n return byDay;\n}\n","// The member area island. Reads GET /api/me (role + the member's own\n// application, folded server-side) and renders the account header plus, for a\n// provisional member, their application card with book/reschedule; a full member\n// sees the caller-supplied `memberContent`. Ported from the reference members island to\n// React, driven by the injected `api` (no Clerk SDK dependency).\nimport { useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Rescheduler } from \"./reschedule.js\";\nimport { fmtDate, fullLabel } from \"./datetime.js\";\nimport type { ApiFn } from \"./api.js\";\nimport type { MemberApplication } from \"../session.js\";\n\ninterface Me {\n email: string | null;\n role: string;\n application: MemberApplication | null;\n}\n\n/** Props for {@link MembersArea}. */\nexport interface MembersAreaProps {\n api: ApiFn;\n /** Sign the member out (the host owns Clerk's signOut). */\n signOut: () => void;\n /** Where the \"Admin console\" link points for admins. Default \"/admin/\". */\n adminHref?: string;\n /** Where an accountless member goes to apply. Default \"/join.html\". */\n applyHref?: string;\n /** What a full (non-provisional) member sees below the account header. */\n memberContent?: ReactNode;\n}\n\nfunction card(kicker: string, body: ReactNode): ReactNode {\n return (\n <div className=\"card\">\n <div className=\"card-label\">Your Application</div>\n <div className=\"meeting-block\">\n <div className=\"meeting-kicker\">{kicker}</div>\n {body}\n </div>\n </div>\n );\n}\n\nfunction ProvisionalCard(props: {\n api: ApiFn;\n application: MemberApplication | null;\n applyHref: string;\n onReschedule: () => void | Promise<void>;\n}): ReactNode {\n const { api, application, applyHref, onReschedule } = props;\n if (!application) {\n return card(\n \"One step remains\",\n <>\n <div className=\"meeting-note\">Your account is ready, and the application that completes it takes a few minutes.</div>\n <a className=\"apply-link\" href={applyHref}>\n Apply for membership\n </a>\n </>,\n );\n }\n if (application.status === \"refunded\") {\n return card(\"Membership refunded\", <div className=\"meeting-note\">Your fee has been refunded in full and your membership is canceled.</div>);\n }\n const membership = application.paid ? (\n <div className=\"meeting-note\">Your membership is active{application.renewalAt ? ` and renews ${fmtDate(application.renewalAt)}` : \"\"}.</div>\n ) : null;\n if (application.meetingAt) {\n return card(\n \"Your introduction call\",\n <>\n <div className=\"meeting-date\">{fullLabel(application.meetingAt, application.timezone)}</div>\n <div className=\"meeting-note\">A calendar invitation with the video call link is in your email.</div>\n {application.meetUrl ? (\n <div className=\"meeting-note\">\n <a href={application.meetUrl} target=\"_blank\" rel=\"noopener\">\n Join the video call\n </a>\n </div>\n ) : null}\n <Rescheduler api={api} applicationId={application.id} timezone={application.timezone} onRescheduled={onReschedule} />\n {membership}\n </>,\n );\n }\n return card(\n \"Book your introduction call\",\n <>\n <div className=\"meeting-note\">Your application is in. Choose a time below, and a calendar invitation will reach your email.</div>\n <Rescheduler api={api} applicationId={application.id} timezone={application.timezone} onRescheduled={onReschedule} label=\"Choose a time\" />\n {membership}\n </>,\n );\n}\n\n/** The signed-in member area. Loads /api/me on mount and renders the account\n * header plus the provisional application card or the full-member content. */\nexport function MembersArea(props: MembersAreaProps) {\n const { api, signOut, adminHref = \"/admin/\", applyHref = \"/join.html\", memberContent } = props;\n const [me, setMe] = useState<Me | null>(null);\n const [error, setError] = useState(false);\n\n const reload = async () => {\n try {\n setMe(await api<Me>(\"/api/me\"));\n } catch {\n setError(true);\n }\n };\n useEffect(() => {\n void reload();\n }, []);\n\n if (error) return <p className=\"meeting-note\">Sign in is briefly unavailable. Please refresh.</p>;\n if (!me) return <p className=\"meeting-note\">Loading…</p>;\n const role = me.role || \"provisional\";\n\n return (\n <>\n <div className=\"card\">\n <div className=\"member-row\">\n <span className=\"member-email\">{me.email}</span>\n <span className={\"role-badge \" + role}>{role}</span>\n </div>\n <div className=\"account-actions\">\n <button className=\"btn secondary mini\" onClick={signOut}>\n Sign out\n </button>\n {role === \"admin\" ? (\n <a className=\"admin-console-link\" href={adminHref}>\n Admin console\n </a>\n ) : null}\n </div>\n </div>\n {role === \"provisional\" ? (\n <ProvisionalCard api={api} application={me.application} applyHref={applyHref} onReschedule={reload} />\n ) : (\n (memberContent ?? (\n <div className=\"card\">\n <div className=\"card-label\">Welcome back.</div>\n </div>\n ))\n )}\n </>\n );\n}\n","// In-place reschedule: open a SlotPicker, rebook via /api/schedule/book (the same\n// capability the join flow uses; the application id is the credential). Ported\n// from the reference member-area Rescheduler to React, driven by the injected `api`.\nimport { useState } from \"react\";\nimport { SlotPicker } from \"./slot-picker.js\";\nimport type { Slot } from \"./datetime.js\";\nimport type { ApiFn } from \"./api.js\";\n\ninterface SlotsResponse {\n schedulingReady?: boolean;\n slots?: Slot[];\n timezone?: string;\n}\n\n/** Props for {@link Rescheduler}. */\nexport interface RescheduleProps {\n api: ApiFn;\n applicationId: string;\n timezone: string;\n /** Called after a successful (re)booking so the caller can refresh /api/me. */\n onRescheduled: () => void | Promise<void>;\n /** Link text to open the picker (e.g. \"Choose a time\" for a first booking). */\n label?: string;\n}\n\n/** A collapsed \"change your time\" link that expands into a {@link SlotPicker} and\n * rebooks on pick. Degrades to a message when scheduling is unavailable. */\nexport function Rescheduler(props: RescheduleProps) {\n const { api, applicationId, timezone, onRescheduled, label = \"Change your time\" } = props;\n const [open, setOpen] = useState(false);\n const [slots, setSlots] = useState<Slot[] | null>(null);\n const [tz, setTz] = useState(timezone);\n const [busy, setBusy] = useState(false);\n const [msg, setMsg] = useState<string | null>(null);\n\n const start = async () => {\n setOpen(true);\n setSlots(null);\n setMsg(null);\n try {\n const r = await api<SlotsResponse>(\"/api/schedule/slots\");\n if (r.schedulingReady === false) {\n setSlots([]);\n setMsg(\"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\");\n return;\n }\n setSlots(r.slots ?? []);\n if (r.timezone) setTz(r.timezone);\n } catch {\n setSlots([]);\n setMsg(\"Times are briefly unavailable. Please try again.\");\n }\n };\n\n const pick = async (slot: Slot) => {\n setBusy(true);\n setMsg(null);\n try {\n await api(\"/api/schedule/book\", { method: \"POST\", body: JSON.stringify({ applicationId, startAt: slot.startAt }) });\n setOpen(false);\n await onRescheduled();\n } catch (e) {\n setMsg(e instanceof Error ? e.message : \"That time is no longer available. Please pick another.\");\n } finally {\n setBusy(false);\n }\n };\n\n if (!open) {\n return (\n <p className=\"meeting-note\">\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n void start();\n }}\n >\n {label}\n </a>\n </p>\n );\n }\n return (\n <div className=\"msched\">\n {slots === null ? (\n <p className=\"meeting-note\">Loading available times…</p>\n ) : slots.length === 0 ? (\n <p className=\"meeting-note\">{msg ?? \"No open times right now. Please check back soon.\"}</p>\n ) : (\n <SlotPicker\n slots={slots}\n timezone={tz}\n classes={{ days: \"msched-days\", day: \"msched-day\", times: \"msched-times\", time: \"msched-time\" }}\n onPick={(s) => void pick(s)}\n />\n )}\n {busy ? <p className=\"meeting-note\">Rescheduling…</p> : null}\n {msg && slots && slots.length > 0 ? <p className=\"meeting-note error\">{msg}</p> : null}\n <p className=\"meeting-note\">\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n setOpen(false);\n }}\n >\n Keep my current time\n </a>\n </p>\n </div>\n );\n}\n","// The join island — the signup flow orchestrator. The SITE provides the form\n// fields (as children); this owns the flow: submit → /api/applications →\n// (paymentsReady) the Stripe PaymentStep → (schedulingReady) the SlotPicker\n// booking → confirmation. Faithful to the reference join flow, but the flow +\n// payment + booking are packaged instead of hand-rolled per site.\nimport { useEffect, useState } from \"react\";\nimport type { FormEvent, ReactNode } from \"react\";\nimport { SlotPicker } from \"./slot-picker.js\";\nimport { PaymentStep } from \"./payment-step.js\";\nimport { fullLabel } from \"./datetime.js\";\nimport type { Slot } from \"./datetime.js\";\n\n/** The public join config (the shape `GET /api/join-config` returns). */\nexport interface JoinConfig {\n id: string;\n name: string;\n paymentsReady: boolean;\n refundPolicyText?: string;\n}\n\n/** Props for {@link JoinIsland}. */\nexport interface JoinIslandProps {\n config: JoinConfig;\n /** The site's application form fields — inputs with `name` attributes; their\n * values are collected via FormData and posted to /api/applications. */\n children: ReactNode;\n /** Where the confirmation links after booking. Default \"/members/\". */\n membersHref?: string;\n}\n\ntype Step = \"form\" | \"pay\" | \"book\" | \"done\";\n\ninterface SlotsResponse {\n schedulingReady?: boolean;\n slots?: Slot[];\n timezone?: string;\n}\n\nfunction JoinBooking(props: { applicationId: string; onBooked: (b: { startAt: number; timezone: string }) => void }): ReactNode {\n const { applicationId, onBooked } = props;\n const [state, setState] = useState<{ slots: Slot[]; timezone: string } | null>(null);\n const [msg, setMsg] = useState<string | null>(null);\n const [busy, setBusy] = useState(false);\n const [selected, setSelected] = useState<Slot | null>(null);\n\n const load = async () => {\n setMsg(null);\n try {\n const res = await fetch(\"/api/schedule/slots\");\n const data = (await res.json()) as SlotsResponse;\n if (data.schedulingReady === false || !data.slots?.length) {\n setState(null);\n setMsg(\"Scheduling is briefly unavailable — we'll reach out by email to arrange your call.\");\n return;\n }\n setState({ slots: data.slots, timezone: data.timezone ?? \"UTC\" });\n } catch {\n setMsg(\"Times are briefly unavailable. Please try again.\");\n }\n };\n useEffect(() => {\n void load();\n }, []);\n\n const book = async () => {\n if (!selected) return;\n setBusy(true);\n setMsg(null);\n try {\n const res = await fetch(\"/api/schedule/book\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ applicationId, startAt: selected.startAt }),\n });\n const data = (await res.json()) as { startAt?: number; error?: string; code?: string };\n if (res.status === 409 && data.code === \"calendar_slot_unavailable\") {\n setSelected(null);\n setMsg(\"That time was just taken. Here are the current openings.\");\n await load();\n return;\n }\n if (!res.ok) throw new Error(data.error ?? \"The booking could not be completed.\");\n onBooked({ startAt: data.startAt ?? selected.startAt, timezone: state?.timezone ?? \"UTC\" });\n } catch (e) {\n setMsg(e instanceof Error ? e.message : \"The booking could not be completed.\");\n } finally {\n setBusy(false);\n }\n };\n\n if (!state) return <p className=\"slots-status\">{msg ?? \"Loading available times…\"}</p>;\n return (\n <div className=\"join-book\">\n <SlotPicker\n slots={state.slots}\n timezone={state.timezone}\n selectedStartAt={selected?.startAt}\n onPick={setSelected}\n onDayChange={() => setSelected(null)}\n />\n <div className=\"slot-confirm\" hidden={!selected}>\n <button className=\"submit-btn\" disabled={busy} onClick={() => void book()}>\n {busy ? \"Booking…\" : \"Book this time\"}\n </button>\n {msg ? <p className=\"step2-note\">{msg}</p> : null}\n </div>\n </div>\n );\n}\n\n/** The signup island. Renders the site's form, then drives payment (when the\n * chapter charges) and booking (when a calendar is connected) to confirmation. */\nexport function JoinIsland(props: JoinIslandProps) {\n const { config, children, membersHref = \"/members/\" } = props;\n const [step, setStep] = useState<Step>(\"form\");\n const [applicationId, setApplicationId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [submitting, setSubmitting] = useState(false);\n const [booked, setBooked] = useState<{ startAt: number; timezone: string } | null>(null);\n\n const submit = async (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n setSubmitting(true);\n setError(null);\n try {\n const fields: Record<string, unknown> = {};\n for (const [k, v] of new FormData(e.currentTarget).entries()) fields[k] = v;\n fields.submissionId = crypto.randomUUID();\n const res = await fetch(\"/api/applications\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(fields),\n });\n const data = (await res.json()) as { id?: string; error?: string };\n if (!res.ok || !data.id) throw new Error(data.error ?? \"Your application could not be submitted.\");\n setApplicationId(data.id);\n setStep(config.paymentsReady ? \"pay\" : \"book\");\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Something went wrong. Please try again.\");\n } finally {\n setSubmitting(false);\n }\n };\n\n if (step === \"done\" && booked) {\n return (\n <div className=\"join-done card\">\n <div className=\"card-label\">You're booked</div>\n <p className=\"meeting-date\">{fullLabel(booked.startAt, booked.timezone)}</p>\n <p className=\"meeting-note\">A calendar invitation with the video call link is on its way to your email.</p>\n <a className=\"apply-link\" href={membersHref}>\n Go to your member area\n </a>\n </div>\n );\n }\n if (step === \"book\" && applicationId) {\n return (\n <JoinBooking\n applicationId={applicationId}\n onBooked={(b) => {\n setBooked(b);\n setStep(\"done\");\n }}\n />\n );\n }\n if (step === \"pay\" && applicationId) {\n return <PaymentStep applicationId={applicationId} refundPolicyText={config.refundPolicyText ?? \"\"} onPaid={() => setStep(\"book\")} />;\n }\n return (\n <form className=\"join-form\" onSubmit={(e) => void submit(e)}>\n {children}\n {error ? <p className=\"join-error\">{error}</p> : null}\n <button className=\"submit-btn\" type=\"submit\" disabled={submitting}>\n {submitting ? \"Submitting…\" : \"Submit application\"}\n </button>\n </form>\n );\n}\n","// The join flow's payment step. Ports the reference card entry into the island:\n// dynamically loads js.stripe.com (NO @stripe/react-stripe-js dep), creates a\n// subscription server-side, and mounts a Stripe Payment Element. The refund-policy\n// checkbox is the gate — checking it starts the (money-creating) subscription\n// call. Client success is advisory only; the webhook is the\n// authoritative writer of paid state (see payments.ts).\nimport { useRef, useState } from \"react\";\n\n// Minimal shapes for the dynamically-loaded js.stripe.com global.\ninterface StripeElementsApi {\n create(type: string): { mount(target: HTMLElement): void };\n}\ninterface StripeApi {\n elements(opts: { clientSecret: string }): StripeElementsApi;\n confirmPayment(opts: {\n elements: StripeElementsApi;\n confirmParams?: { return_url?: string };\n redirect?: \"if_required\";\n }): Promise<{ error?: { message?: string }; paymentIntent?: { status?: string } }>;\n}\ntype StripeFactory = (publishableKey: string) => StripeApi;\n\nlet loader: Promise<StripeFactory> | null = null;\n\n/** Load js.stripe.com once and resolve the `Stripe` global (browser only). */\nfunction loadStripe(): Promise<StripeFactory> {\n const existing = (globalThis as { Stripe?: StripeFactory }).Stripe;\n if (existing) return Promise.resolve(existing);\n if (!loader) {\n loader = new Promise<StripeFactory>((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = \"https://js.stripe.com/v3/\";\n s.onload = () => {\n const fn = (globalThis as { Stripe?: StripeFactory }).Stripe;\n if (fn) resolve(fn);\n else reject(new Error(\"stripe.js unavailable\"));\n };\n s.onerror = () => reject(new Error(\"stripe.js failed to load\"));\n document.head.appendChild(s);\n });\n }\n return loader;\n}\n\n/** Props for {@link PaymentStep}. */\nexport interface PaymentStepProps {\n /** The application to attach the subscription to (the capability). */\n applicationId: string;\n /** Refund-policy copy the applicant must acknowledge to start payment. */\n refundPolicyText: string;\n /** Called once the payment succeeds (or is processing) — advance to booking. */\n onPaid: () => void;\n}\n\ninterface SubResponse {\n clientSecret?: string;\n publishableKey?: string | null;\n}\n\n/** The card-entry step: acknowledge the refund policy → create the subscription\n * → mount Stripe Elements → confirm. */\nexport function PaymentStep(props: PaymentStepProps) {\n const { applicationId, refundPolicyText, onPaid } = props;\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"ready\" | \"confirming\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n const mountRef = useRef<HTMLDivElement | null>(null);\n const stripeRef = useRef<StripeApi | null>(null);\n const elementsRef = useRef<StripeElementsApi | null>(null);\n\n const begin = async () => {\n setStatus(\"loading\");\n setError(null);\n try {\n const res = await fetch(\"/api/payments/subscription\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ applicationId, refundPolicyAck: true }),\n });\n const data = (await res.json()) as SubResponse;\n if (!res.ok || !data.clientSecret || !data.publishableKey) throw new Error(\"Payment could not be set up. Please try again.\");\n const stripe = (await loadStripe())(data.publishableKey);\n const elements = stripe.elements({ clientSecret: data.clientSecret });\n const element = elements.create(\"payment\");\n if (mountRef.current) element.mount(mountRef.current);\n stripeRef.current = stripe;\n elementsRef.current = elements;\n setStatus(\"ready\");\n } catch (e) {\n setStatus(\"idle\");\n setError(e instanceof Error ? e.message : \"Payment could not be set up. Please try again.\");\n }\n };\n\n const pay = async () => {\n const stripe = stripeRef.current;\n const elements = elementsRef.current;\n if (!stripe || !elements) return;\n setStatus(\"confirming\");\n setError(null);\n const returnUrl =\n typeof window !== \"undefined\" ? `${window.location.origin}${window.location.pathname}?redirect_status=succeeded` : undefined;\n const result = await stripe.confirmPayment({ elements, confirmParams: { return_url: returnUrl }, redirect: \"if_required\" });\n if (result.error) {\n setError(result.error.message ?? \"The payment could not be completed.\");\n setStatus(\"ready\");\n return;\n }\n const paid = result.paymentIntent?.status;\n if (paid === \"succeeded\" || paid === \"processing\") onPaid();\n else {\n setError(\"The payment did not complete. Please try again.\");\n setStatus(\"ready\");\n }\n };\n\n return (\n <div className=\"join-pay\">\n <label className=\"compliance-box\">\n <input\n type=\"checkbox\"\n disabled={status !== \"idle\"}\n onChange={(e) => {\n if (e.currentTarget.checked) void begin();\n }}\n />\n <span>{refundPolicyText}</span>\n </label>\n {status === \"loading\" ? <p className=\"pay-status\">Preparing secure payment…</p> : null}\n <div ref={mountRef} hidden={status === \"idle\" || status === \"loading\"} />\n {status === \"ready\" || status === \"confirming\" ? (\n <button className=\"submit-btn\" disabled={status === \"confirming\"} onClick={() => void pay()}>\n {status === \"confirming\" ? \"Processing…\" : \"Pay and continue\"}\n </button>\n ) : null}\n {error ? <p className=\"pay-error\">{error}</p> : null}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useState","jsx","jsxs","useState","Fragment","jsx","jsxs","useState","useEffect","useState","useState","jsx","jsxs","jsx","jsxs","useState","useEffect"]}