@odla-ai/chapter 0.32.3 → 0.33.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
@@ -373,6 +373,85 @@ The same renderer can also replace the placeholder chooser inside
373
373
  `redirect_status` and `reschedule` values never choose a UI step. Supply
374
374
  `loadResume` when a host stores the capability elsewhere.
375
375
 
376
+ Studio and automated previews can traverse that same state machine without
377
+ creating an application, loading Stripe, charging a card, or booking a real
378
+ calendar event. Supply deterministic `simulation` boundaries and keep the
379
+ production config, tier selection, copy, render slots, and step transitions:
380
+
381
+ ```tsx
382
+ <JoinIsland
383
+ config={joinConfig}
384
+ simulation={{
385
+ applicationId: "preview-application",
386
+ payment: {
387
+ lineItems: {
388
+ standardCents: 100_000,
389
+ discountCents: 10_000,
390
+ dueTodayCents: 90_000,
391
+ },
392
+ },
393
+ booking: {
394
+ timezone: "America/Los_Angeles",
395
+ slots: [{ startAt: Date.UTC(2026, 8, 3, 17) }],
396
+ },
397
+ }}
398
+ >
399
+ <PreviewApplicationFields />
400
+ </JoinIsland>
401
+ ```
402
+
403
+ Simulation is deliberately a UI/test boundary, not acceptance evidence for a
404
+ live Chapter. A follower still has to prove its real application write, Stripe
405
+ webhook, and calendar booking journey before production.
406
+
407
+ ### Hub-authoritative live signup control
408
+
409
+ When a hub must control the live membership page as well as preview it, use the
410
+ dedicated signup-control contract. Do **not** widen the CRM network route: CRM
411
+ record sharing and operational signup authority are different capabilities.
412
+ The follower opts into one exact source and Stripe mode:
413
+
414
+ ```ts
415
+ const chapter = defineChapter({
416
+ // ...normal chapter config and local bootstrap values...
417
+ signupControl: {
418
+ sourceId: "built-not-found",
419
+ stripeMode: "test",
420
+ // default follower vault key: signup_control_secret
421
+ },
422
+ });
423
+ ```
424
+
425
+ The hub builds a complete `SignupControlDocument`, calls
426
+ `createSignupControlEnvelope(document)`, and sends it with
427
+ `deliverSignupControl` from `@odla-ai/chapter/worker`. The same strong random
428
+ secret is vaulted separately on each side; it is never a Clerk token, ODLA key,
429
+ or CRM network secret.
430
+
431
+ The document carries the source, exact chapter/environment/Stripe-mode target,
432
+ monotonic revision, ordered and available tiers, immutable Stripe Price ids,
433
+ the membership-page renderer document, and explicitly delegated application,
434
+ policy, scheduling, and theme sections. Theme tokens include typography as
435
+ ordinary CSS custom properties such as `--ui-font-display` and
436
+ `--ui-font-body`.
437
+
438
+ The packaged follower receiver validates the signed request timestamp, sender,
439
+ target, environment, Stripe mode, schema, and SHA-256 digest. It rejects a stale
440
+ revision and rejects the same revision with different content; the same revision
441
+ and digest is an idempotent replay. Acceptance transactionally stores immutable
442
+ history, switches the active revision, and materializes the group/tier rows that
443
+ join-config, application tier validation, and checkout already read. Public
444
+ `GET /api/join-config` returns the active revision/digest, renderer document,
445
+ application contract, and theme. Applicant requests therefore use local
446
+ last-known-good state and never depend on the hub being reachable.
447
+
448
+ The hub owns a durable outbox: create/verify immutable Stripe Prices first,
449
+ persist the desired revision, retry signed delivery, and mark it active only
450
+ after the exact revision/digest receipt. Version history is for audit and
451
+ rollback, not a second manual publish gate. A failed delivery leaves the
452
+ follower's prior active revision untouched. Production remains behind the
453
+ normal deployment approval checkpoint.
454
+
376
455
  `MembersArea.renderProvisional` receives the loaded application, authenticated
377
456
  API function, reload callback, apply URL, and `defaultContent`, so the host can
378
457
  wrap or replace the provisional card just as admin workspaces can be composed.
@@ -355,7 +355,8 @@ function PaymentStep(props) {
355
355
  appearance,
356
356
  fonts,
357
357
  renderPriceLines,
358
- children
358
+ children,
359
+ simulation
359
360
  } = props;
360
361
  const copy = props.copy ?? inheritedCopy.join.payment;
361
362
  const [status, setStatus] = useState4("idle");
@@ -368,6 +369,11 @@ function PaymentStep(props) {
368
369
  setStatus("loading");
369
370
  setError(null);
370
371
  try {
372
+ if (simulation) {
373
+ setLineItems(simulation.lineItems);
374
+ setStatus("ready");
375
+ return;
376
+ }
371
377
  const res = await fetch("/api/payments/subscription", {
372
378
  method: "POST",
373
379
  headers: { "content-type": "application/json" },
@@ -393,6 +399,11 @@ function PaymentStep(props) {
393
399
  }
394
400
  };
395
401
  const pay = async () => {
402
+ if (simulation) {
403
+ setStatus("confirming");
404
+ onPaid();
405
+ return;
406
+ }
396
407
  const stripe = stripeRef.current;
397
408
  const elements = elementsRef.current;
398
409
  if (!stripe || !elements) return;
@@ -438,6 +449,22 @@ function PaymentStep(props) {
438
449
  status === "loading" ? /* @__PURE__ */ jsx4("p", { className: "pay-status", children: copy.preparing }) : null,
439
450
  lineItems && renderPriceLines ? renderPriceLines(lineItems) : null,
440
451
  children,
452
+ simulation && (status === "ready" || status === "confirming") ? /* @__PURE__ */ jsxs4("fieldset", { className: "join-pay-simulation", children: [
453
+ /* @__PURE__ */ jsx4("legend", { children: "Payment details" }),
454
+ /* @__PURE__ */ jsxs4("label", { children: [
455
+ "Card number",
456
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardNumber", inputMode: "numeric", autoComplete: "off", defaultValue: "4242 4242 4242 4242" })
457
+ ] }),
458
+ /* @__PURE__ */ jsxs4("label", { children: [
459
+ "Expiration",
460
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardExpiry", inputMode: "numeric", autoComplete: "off", defaultValue: "12 / 34" })
461
+ ] }),
462
+ /* @__PURE__ */ jsxs4("label", { children: [
463
+ "Security code",
464
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardCvc", inputMode: "numeric", autoComplete: "off", defaultValue: "123" })
465
+ ] }),
466
+ /* @__PURE__ */ jsx4("p", { children: "This is a simulation. No card data is sent and no charge is created." })
467
+ ] }) : null,
441
468
  /* @__PURE__ */ jsx4("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
442
469
  status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx4("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? copy.processing : copy.payAndContinue }) : null,
443
470
  error ? /* @__PURE__ */ jsx4("p", { className: "pay-error", children: error }) : null
@@ -448,7 +475,7 @@ function PaymentStep(props) {
448
475
  import { useEffect as useEffect2, useState as useState5 } from "preact/hooks";
449
476
  import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
450
477
  function JoinBooking(props) {
451
- const { applicationId, copy, onBooked } = props;
478
+ const { applicationId, copy, onBooked, simulation } = props;
452
479
  const [state, setState] = useState5(null);
453
480
  const [msg, setMsg] = useState5(null);
454
481
  const [busy, setBusy] = useState5(false);
@@ -456,6 +483,15 @@ function JoinBooking(props) {
456
483
  const load = async () => {
457
484
  setMsg(null);
458
485
  try {
486
+ if (simulation) {
487
+ if (!simulation.slots.length) {
488
+ setState(null);
489
+ setMsg(copy.unavailable);
490
+ return;
491
+ }
492
+ setState({ slots: [...simulation.slots], timezone: simulation.timezone });
493
+ return;
494
+ }
459
495
  const res = await fetch("/api/schedule/slots");
460
496
  const data = await res.json();
461
497
  if (data.schedulingReady === false || !data.slots?.length) {
@@ -470,12 +506,16 @@ function JoinBooking(props) {
470
506
  };
471
507
  useEffect2(() => {
472
508
  void load();
473
- }, []);
509
+ }, [simulation]);
474
510
  const book = async () => {
475
511
  if (!selected) return;
476
512
  setBusy(true);
477
513
  setMsg(null);
478
514
  try {
515
+ if (simulation) {
516
+ onBooked({ startAt: selected.startAt, timezone: simulation.timezone });
517
+ return;
518
+ }
479
519
  const res = await fetch("/api/schedule/book", {
480
520
  method: "POST",
481
521
  headers: { "content-type": "application/json" },
@@ -589,8 +629,17 @@ function JoinIsland(props) {
589
629
  const [state, setState] = useState7(props.initialState ?? { step: "form" });
590
630
  const [error, setError] = useState7(null);
591
631
  const [submitting, setSubmitting] = useState7(false);
632
+ const simulatedPayment = props.simulation ? props.simulation.payment ?? {
633
+ lineItems: {
634
+ standardCents: selectedTier?.priceCents ?? 0,
635
+ discountCents: 0,
636
+ dueTodayCents: selectedTier?.priceCents ?? 0
637
+ }
638
+ } : void 0;
639
+ const simulatedBooking = props.simulation ? props.simulation.booking ?? { slots: [], timezone: "UTC" } : void 0;
592
640
  const submissionId = useRef2(null);
593
641
  useEffect3(() => {
642
+ if (props.simulation) return;
594
643
  const resolver = props.loadResume ?? (() => {
595
644
  if (typeof window === "undefined") return Promise.resolve(null);
596
645
  const applicationId = new URL(window.location.href).searchParams.get("application");
@@ -605,7 +654,7 @@ function JoinIsland(props) {
605
654
  return () => {
606
655
  live = false;
607
656
  };
608
- }, [props.loadResume]);
657
+ }, [props.loadResume, props.simulation]);
609
658
  useEffect3(() => {
610
659
  if (state.step !== "paymentPending") return;
611
660
  let live = true;
@@ -635,6 +684,15 @@ function JoinIsland(props) {
635
684
  const fields = collectFormFields(new FormData(e.currentTarget));
636
685
  fields.submissionId = submissionId.current ??= crypto.randomUUID();
637
686
  if (selectedTierId) fields.tierId = selectedTierId;
687
+ if (props.simulation) {
688
+ setState({
689
+ // Preview the intended paid journey even before live Stripe wiring is
690
+ // ready. Production still uses the server-authoritative readiness bit.
691
+ step: tierNeedsPayment(selectedTier, true) ? "payment" : "booking",
692
+ applicationId: props.simulation.applicationId ?? "simulation-application"
693
+ });
694
+ return;
695
+ }
638
696
  const res = await fetch("/api/applications", {
639
697
  method: "POST",
640
698
  headers: { "content-type": "application/json" },
@@ -676,6 +734,7 @@ function JoinIsland(props) {
676
734
  {
677
735
  applicationId: state.applicationId,
678
736
  copy: copy.booking,
737
+ simulation: simulatedBooking,
679
738
  onBooked: (booked) => {
680
739
  setState({ step: "done", applicationId: state.applicationId, booked });
681
740
  }
@@ -695,6 +754,7 @@ function JoinIsland(props) {
695
754
  appearance: props.payment?.appearance,
696
755
  fonts: props.payment?.fonts,
697
756
  renderPriceLines: props.payment?.renderPriceLines,
757
+ simulation: simulatedPayment,
698
758
  onPaid: () => setState({ step: "booking", applicationId: state.applicationId }),
699
759
  children: props.payment?.children
700
760
  }
@@ -1047,4 +1107,4 @@ export {
1047
1107
  membershipApplicationHref,
1048
1108
  MembershipCards
1049
1109
  };
1050
- //# sourceMappingURL=chunk-NYMLKPQF.js.map
1110
+ //# sourceMappingURL=chunk-UJIVBGL5.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","../src/ui/join-booking.tsx","../src/ui/join-tiers.tsx","../src/ui/membership-cards.tsx","../src/membership-page.ts"],"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; 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 \"preact/hooks\";\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// Preact, driven by the injected `api` (no Clerk SDK dependency).\nimport type { ComponentChildren } from \"preact\";\nimport { useEffect, useState } from \"preact/hooks\";\nimport { Rescheduler } from \"./reschedule.js\";\nimport { fmtDate, fullLabel } from \"./datetime.js\";\nimport type { ApiFn } from \"./api.js\";\nimport type { MemberApplication } from \"../session.js\";\nimport { formatChapterCopy, type ChapterCopy } from \"../copy.js\";\nimport { useChapterCopy } from \"./copy-context.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?: ComponentChildren;\n /** Resolved member-area copy. */\n copy?: ChapterCopy[\"members\"];\n /** Replace the provisional application card without replacing auth/data load. */\n renderProvisional?: (context: MemberProvisionalRenderContext) => ComponentChildren;\n}\n\n/** Data and actions supplied to a site's provisional-member render slot. */\nexport interface MemberProvisionalRenderContext {\n api: ApiFn;\n application: MemberApplication | null;\n applyHref: string;\n reload: () => Promise<void>;\n defaultContent: ComponentChildren;\n}\n\nfunction card(copy: ChapterCopy[\"members\"], kicker: string, body: ComponentChildren): ComponentChildren {\n return (\n <div className=\"card\">\n <div className=\"card-label\">{copy.provisional.cardLabel}</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 copy: ChapterCopy[\"members\"];\n onReschedule: () => void | Promise<void>;\n}): ComponentChildren {\n const { api, application, applyHref, copy, onReschedule } = props;\n if (!application) {\n return card(\n copy,\n copy.provisional.applicationNeeded,\n <>\n <div className=\"meeting-note\">{copy.provisional.applicationNeededBody}</div>\n <a className=\"apply-link\" href={applyHref}>\n {copy.provisional.apply}\n </a>\n </>,\n );\n }\n if (application.status === \"refunded\") {\n return card(\n copy,\n copy.provisional.refunded,\n <div className=\"meeting-note\">{copy.provisional.refundedBody}</div>,\n );\n }\n const membership = application.paid ? (\n <div className=\"meeting-note\">\n {application.renewalAt\n ? formatChapterCopy(copy.provisional.renews, { date: fmtDate(application.renewalAt) })\n : copy.provisional.active}\n </div>\n ) : null;\n if (application.meetingAt) {\n return card(\n copy,\n copy.provisional.introductionCall,\n <>\n <div className=\"meeting-date\">{fullLabel(application.meetingAt, application.timezone)}</div>\n <div className=\"meeting-note\">{copy.provisional.calendarInvite}</div>\n {application.meetUrl ? (\n <div className=\"meeting-note\">\n <a href={application.meetUrl} target=\"_blank\" rel=\"noopener\">\n {copy.provisional.joinCall}\n </a>\n </div>\n ) : null}\n <Rescheduler\n api={api}\n applicationId={application.id}\n timezone={application.timezone}\n copy={copy.reschedule}\n onRescheduled={onReschedule}\n />\n {membership}\n </>,\n );\n }\n return card(\n copy,\n copy.provisional.bookCall,\n <>\n <div className=\"meeting-note\">{copy.provisional.bookCallBody}</div>\n <Rescheduler\n api={api}\n applicationId={application.id}\n timezone={application.timezone}\n copy={copy.reschedule}\n onRescheduled={onReschedule}\n label={copy.provisional.chooseTime}\n />\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 inheritedCopy = useChapterCopy();\n const {\n api,\n signOut,\n adminHref = \"/admin/\",\n applyHref = \"/join.html\",\n memberContent,\n renderProvisional,\n } = props;\n const copy = props.copy ?? inheritedCopy.members;\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\">{copy.loadFailed}</p>;\n if (!me) return <p className=\"meeting-note\">{inheritedCopy.common.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 {copy.account.signOut}\n </button>\n {role === \"admin\" ? (\n <a className=\"admin-console-link\" href={adminHref}>\n {copy.account.adminConsole}\n </a>\n ) : null}\n </div>\n </div>\n {role === \"provisional\" ? (() => {\n const defaultContent = (\n <ProvisionalCard\n api={api}\n application={me.application}\n applyHref={applyHref}\n copy={copy}\n onReschedule={reload}\n />\n );\n return renderProvisional?.({\n api,\n application: me.application,\n applyHref,\n reload,\n defaultContent,\n }) ?? defaultContent;\n })() : (\n (memberContent ?? (\n <div className=\"card\">\n <div className=\"card-label\">{copy.full.welcome}</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, driven by the injected `api`.\nimport { useState } from \"preact/hooks\";\nimport { SlotPicker } from \"./slot-picker.js\";\nimport type { Slot } from \"./datetime.js\";\nimport type { ApiFn } from \"./api.js\";\nimport type { ChapterCopy } from \"../copy.js\";\nimport { useChapterCopy } from \"./copy-context.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 /** Resolved rescheduling copy. */\n copy?: ChapterCopy[\"members\"][\"reschedule\"];\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 inheritedCopy = useChapterCopy();\n const copy = props.copy ?? inheritedCopy.members.reschedule;\n const { api, applicationId, timezone, onRescheduled, label = copy.changeTime } = 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(copy.unavailable);\n return;\n }\n setSlots(r.slots ?? []);\n if (r.timezone) setTz(r.timezone);\n } catch {\n setSlots([]);\n setMsg(copy.loadFailed);\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 : copy.slotGone);\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\">{copy.loading}</p>\n ) : slots.length === 0 ? (\n <p className=\"meeting-note\">{msg ?? copy.noTimes}</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\">{copy.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 {copy.keepTime}\n </a>\n </p>\n </div>\n );\n}\n","// The join island owns form → application → payment → booking → confirmation.\n// The site provides fields and presentation; Chapter packages the mechanics.\nimport type { JSX } from \"preact\";\nimport { useEffect, useRef, useState } from \"preact/hooks\";\nimport { PaymentStep } from \"./payment-step.js\";\nimport { fullLabel } from \"./datetime.js\";\nimport { useChapterCopy } from \"./copy-context.js\";\nimport { collectFormFields } from \"./form-fields.js\";\nimport { JoinBooking } from \"./join-booking.js\";\nimport type { JoinSimulation } from \"./join-simulation.js\";\nimport { TierPlaceholder, tierNeedsPayment, useTierSelection } from \"./join-tiers.js\";\nimport type { JoinFlowState, JoinIslandProps } from \"./join-types.js\";\n\nexport type { JoinTier, JoinTierRenderContext } from \"./join-tiers.js\";\nexport type { JoinSimulation } from \"./join-simulation.js\";\nexport type { JoinConfig, JoinFlowState, JoinIslandProps, JoinStepRenderContext, JoinSubmitRenderContext } from \"./join-types.js\";\n\ninterface JoinResumeResponse {\n state: \"payment\" | \"paymentPending\" | \"booking\" | \"done\";\n applicationId: string;\n booked?: { startAt: number; timezone: string };\n}\n\n/** Load canonical server state for a capability-bearing application id. */\nexport async function loadJoinResume(\n applicationId: string,\n fetcher: typeof fetch = fetch,\n): Promise<JoinFlowState> {\n const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);\n const data = (await res.json()) as JoinResumeResponse & { error?: string };\n if (!res.ok) throw new Error(data.error ?? \"The application could not be resumed.\");\n if (data.state === \"done\" && data.booked) {\n return { step: \"done\", applicationId: data.applicationId, booked: data.booked };\n }\n if (data.state === \"paymentPending\") {\n return { step: \"paymentPending\", applicationId: data.applicationId };\n }\n if (data.state === \"booking\") {\n return { step: \"booking\", applicationId: data.applicationId };\n }\n return { step: \"payment\", applicationId: data.applicationId };\n}\n\n/** The signup island. Renders the site's form, keeps one application\n * `submissionId` across ambiguous retries, then drives payment (when the\n * chapter charges) and booking (when a calendar is connected) to confirmation. */\nexport function JoinIsland(props: JoinIslandProps) {\n const inheritedCopy = useChapterCopy();\n const {\n config,\n children,\n membersHref = \"/members/\",\n renderStepHeader,\n renderDone,\n renderSubmit,\n } = props;\n const copy = props.copy ?? config.copy ?? inheritedCopy.join;\n const tiers = config.tiers ?? [];\n const tierSelection = useTierSelection(tiers, props.initialTierId);\n const { selectedTierId, selected: selectedTier } = tierSelection;\n const [state, setState] = useState<JoinFlowState>(props.initialState ?? { step: \"form\" });\n const [error, setError] = useState<string | null>(null);\n const [submitting, setSubmitting] = useState(false);\n // Simulation fails closed at runtime even for an untyped/older caller: once\n // the flag exists, no step may silently fall through to a live adapter.\n const simulatedPayment = props.simulation\n ? props.simulation.payment ?? {\n lineItems: {\n standardCents: selectedTier?.priceCents ?? 0,\n discountCents: 0,\n dueTodayCents: selectedTier?.priceCents ?? 0,\n },\n }\n : undefined;\n const simulatedBooking = props.simulation\n ? props.simulation.booking ?? { slots: [], timezone: \"UTC\" }\n : undefined;\n // One logical form journey owns one idempotency key. Keep it across explicit\n // errors and ambiguous lost responses so a retry cannot create another row.\n const submissionId = useRef<string | null>(null);\n\n useEffect(() => {\n if (props.simulation) return;\n const resolver = props.loadResume ?? (() => {\n if (typeof window === \"undefined\") return Promise.resolve(null);\n const applicationId = new URL(window.location.href).searchParams.get(\"application\");\n return applicationId ? loadJoinResume(applicationId) : Promise.resolve(null);\n });\n let live = true;\n void resolver()\n .then((resumed) => {\n if (live && resumed) setState(resumed);\n })\n .catch(() => {\n if (live) setError(copy.form.unexpectedFailure);\n });\n return () => {\n live = false;\n };\n }, [props.loadResume, props.simulation]);\n\n useEffect(() => {\n if (state.step !== \"paymentPending\") return;\n let live = true;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const poll = async () => {\n try {\n const resumed = props.loadResume\n ? await props.loadResume()\n : await loadJoinResume(state.applicationId);\n if (!live || !resumed) return;\n setState(resumed);\n if (resumed.step === \"paymentPending\") timer = setTimeout(() => void poll(), 1500);\n } catch {\n if (live) timer = setTimeout(() => void poll(), 2500);\n }\n };\n timer = setTimeout(() => void poll(), 1000);\n return () => {\n live = false;\n if (timer) clearTimeout(timer);\n };\n }, [state, props.loadResume]);\n\n const submit = async (e: JSX.TargetedSubmitEvent<HTMLFormElement>) => {\n e.preventDefault();\n if (submitting || props.submitDisabled === true) return;\n setSubmitting(true);\n setError(null);\n try {\n const fields = collectFormFields(new FormData(e.currentTarget));\n fields.submissionId = submissionId.current ??= crypto.randomUUID();\n if (selectedTierId) fields.tierId = selectedTierId;\n if (props.simulation) {\n setState({\n // Preview the intended paid journey even before live Stripe wiring is\n // ready. Production still uses the server-authoritative readiness bit.\n step: tierNeedsPayment(selectedTier, true) ? \"payment\" : \"booking\",\n applicationId: props.simulation.applicationId ?? \"simulation-application\",\n });\n return;\n }\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 ?? copy.form.submitFailed);\n // The server reaches the same conclusion independently on resume.\n setState({\n step: tierNeedsPayment(selectedTier, config.paymentsReady) ? \"payment\" : \"booking\",\n applicationId: data.id,\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : copy.form.unexpectedFailure);\n } finally {\n setSubmitting(false);\n }\n };\n\n const header = renderStepHeader?.({\n state,\n ...(\"applicationId\" in state ? { applicationId: state.applicationId } : {}),\n });\n if (state.step === \"done\") {\n const body = renderDone?.({ ...state, membersHref }) ?? (\n <div className=\"join-done card\">\n <div className=\"card-label\">{copy.done.label}</div>\n <p className=\"meeting-date\">{fullLabel(state.booked.startAt, state.booked.timezone)}</p>\n <p className=\"meeting-note\">{copy.done.calendarInvite}</p>\n <a className=\"apply-link\" href={membersHref}>\n {copy.done.memberArea}\n </a>\n </div>\n );\n return <>{header}{body}</>;\n }\n if (state.step === \"booking\") {\n return (\n <>\n {header}\n <JoinBooking\n applicationId={state.applicationId}\n copy={copy.booking}\n simulation={simulatedBooking}\n onBooked={(booked) => {\n setState({ step: \"done\", applicationId: state.applicationId, booked });\n }}\n />\n </>\n );\n }\n if (state.step === \"payment\") {\n return (\n <>\n {header}\n <PaymentStep\n applicationId={state.applicationId}\n refundPolicyText={config.refundPolicyText ?? \"\"}\n copy={copy.payment}\n appearance={props.payment?.appearance}\n fonts={props.payment?.fonts}\n renderPriceLines={props.payment?.renderPriceLines}\n simulation={simulatedPayment}\n onPaid={() => setState({ step: \"booking\", applicationId: state.applicationId })}\n >\n {props.payment?.children}\n </PaymentStep>\n </>\n );\n }\n if (state.step === \"paymentPending\") {\n return (\n <>\n {header}\n <p className=\"pay-status\" role=\"status\">{copy.payment.pending}</p>\n </>\n );\n }\n const submitDisabled = submitting || props.submitDisabled === true;\n // Presentation belongs to the site; the placeholder is a fallback, not a\n // design. One tier needs no chooser at all.\n const tierChooser = tiers.length > 1\n ? props.renderTiers?.(tierSelection) ?? TierPlaceholder(tierSelection)\n : null;\n return (\n <>\n {header}\n <form className=\"join-form\" onSubmit={(e) => void submit(e)}>\n {tierChooser}\n {children}\n {error ? <p className=\"join-error\">{error}</p> : null}\n {renderSubmit?.({ submitting, disabled: submitDisabled }) ?? (\n <button className=\"submit-btn\" type=\"submit\" disabled={submitDisabled}>\n {submitting ? copy.form.submitting : copy.form.submit}\n </button>\n )}\n </form>\n </>\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 type { ComponentChildren } from \"preact\";\nimport { useRef, useState } from \"preact/hooks\";\nimport type { ChapterCopy } from \"../copy.js\";\nimport { useChapterCopy } from \"./copy-context.js\";\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: {\n clientSecret: string;\n appearance?: Record<string, unknown>;\n fonts?: readonly Record<string, unknown>[];\n }): 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 /** Stripe Elements appearance passed through without Chapter reinterpretation. */\n appearance?: Record<string, unknown>;\n /** Stripe Elements custom-font descriptors. */\n fonts?: readonly Record<string, unknown>[];\n /** Resolved payment-step copy. */\n copy?: ChapterCopy[\"join\"][\"payment\"];\n /** Render the server-returned membership price breakdown. */\n renderPriceLines?: (lines: PaymentPriceLines) => ComponentChildren;\n /** Site-owned trust, security, or explanatory content. */\n children?: ComponentChildren;\n /**\n * Side-effect-free payment data for a Studio or test journey. When present,\n * this component never calls the subscription route or loads Stripe.js; it\n * presents an inert card form and advances only the local flow.\n */\n simulation?: PaymentStepSimulation;\n}\n\n/** Server-authoritative membership amounts supplied to the payment price slot. */\nexport interface PaymentPriceLines {\n standardCents: number;\n discountCents: number;\n dueTodayCents: number;\n}\n\n/** Deterministic, non-charging inputs for a simulated payment step. */\nexport interface PaymentStepSimulation {\n lineItems: PaymentPriceLines;\n}\n\ninterface SubResponse {\n clientSecret?: string;\n publishableKey?: string | null;\n lineItems?: PaymentPriceLines;\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 inheritedCopy = useChapterCopy();\n const {\n applicationId,\n refundPolicyText,\n onPaid,\n appearance,\n fonts,\n renderPriceLines,\n children,\n simulation,\n } = props;\n const copy = props.copy ?? inheritedCopy.join.payment;\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"ready\" | \"confirming\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n const [lineItems, setLineItems] = useState<PaymentPriceLines | 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 if (simulation) {\n setLineItems(simulation.lineItems);\n setStatus(\"ready\");\n return;\n }\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(copy.setupFailed);\n const stripe = (await loadStripe())(data.publishableKey);\n const elements = stripe.elements({\n clientSecret: data.clientSecret,\n ...(appearance ? { appearance } : {}),\n ...(fonts ? { fonts } : {}),\n });\n const element = elements.create(\"payment\");\n if (mountRef.current) element.mount(mountRef.current);\n stripeRef.current = stripe;\n elementsRef.current = elements;\n setLineItems(data.lineItems ?? null);\n setStatus(\"ready\");\n } catch (e) {\n setStatus(\"idle\");\n setError(e instanceof Error ? e.message : copy.setupFailed);\n }\n };\n\n const pay = async () => {\n if (simulation) {\n setStatus(\"confirming\");\n onPaid();\n return;\n }\n const stripe = stripeRef.current;\n const elements = elementsRef.current;\n if (!stripe || !elements) return;\n setStatus(\"confirming\");\n setError(null);\n let returnUrl: string | undefined;\n if (typeof window !== \"undefined\") {\n const next = new URL(window.location.href);\n next.searchParams.set(\"application\", applicationId);\n next.searchParams.set(\"redirect_status\", \"succeeded\");\n returnUrl = next.toString();\n }\n const result = await stripe.confirmPayment({ elements, confirmParams: { return_url: returnUrl }, redirect: \"if_required\" });\n if (result.error) {\n setError(result.error.message ?? copy.incomplete);\n setStatus(\"ready\");\n return;\n }\n const paid = result.paymentIntent?.status;\n if (paid === \"succeeded\" || paid === \"processing\") onPaid();\n else {\n setError(copy.incomplete);\n setStatus(\"ready\");\n }\n };\n\n return (\n <div className=\"join-pay\">\n {/*\n The policy is a block of its own and the checkbox carries the\n affirmative sentence, so what the member agrees to is a statement of\n agreement rather than the policy text itself. A site that wants the\n checkbox labelled only by the policy sets `copy.join.payment.consent`\n to an empty string.\n */}\n <div className=\"compliance-box\">\n <p className=\"compliance-policy\">{refundPolicyText}</p>\n <label className=\"compliance-check\">\n <input\n type=\"checkbox\"\n disabled={status !== \"idle\"}\n onChange={(e) => {\n if (e.currentTarget.checked) void begin();\n }}\n />\n {copy.consent ? <span>{copy.consent}</span> : null}\n </label>\n </div>\n {status === \"loading\" ? <p className=\"pay-status\">{copy.preparing}</p> : null}\n {lineItems && renderPriceLines ? renderPriceLines(lineItems) : null}\n {children}\n {simulation && (status === \"ready\" || status === \"confirming\") ? (\n <fieldset className=\"join-pay-simulation\">\n <legend>Payment details</legend>\n <label>\n Card number\n <input name=\"simulationCardNumber\" inputMode=\"numeric\" autoComplete=\"off\" defaultValue=\"4242 4242 4242 4242\" />\n </label>\n <label>\n Expiration\n <input name=\"simulationCardExpiry\" inputMode=\"numeric\" autoComplete=\"off\" defaultValue=\"12 / 34\" />\n </label>\n <label>\n Security code\n <input name=\"simulationCardCvc\" inputMode=\"numeric\" autoComplete=\"off\" defaultValue=\"123\" />\n </label>\n <p>This is a simulation. No card data is sent and no charge is created.</p>\n </fieldset>\n ) : 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\" ? copy.processing : copy.payAndContinue}\n </button>\n ) : null}\n {error ? <p className=\"pay-error\">{error}</p> : null}\n </div>\n );\n}\n","import type { ComponentChildren } from \"preact\";\nimport { useEffect, useState } from \"preact/hooks\";\nimport type { ChapterCopy } from \"../copy.js\";\nimport type { Slot } from \"./datetime.js\";\nimport { SlotPicker } from \"./slot-picker.js\";\n\ninterface SlotsResponse {\n schedulingReady?: boolean;\n slots?: Slot[];\n timezone?: string;\n}\n\n/** Deterministic, side-effect-free availability for a simulated join flow. */\nexport interface JoinBookingSimulation {\n slots: readonly Slot[];\n timezone: string;\n}\n\nexport function JoinBooking(props: {\n applicationId: string;\n copy: ChapterCopy[\"join\"][\"booking\"];\n onBooked: (b: { startAt: number; timezone: string }) => void;\n simulation?: JoinBookingSimulation;\n}): ComponentChildren {\n const { applicationId, copy, onBooked, simulation } = 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 if (simulation) {\n if (!simulation.slots.length) {\n setState(null);\n setMsg(copy.unavailable);\n return;\n }\n setState({ slots: [...simulation.slots], timezone: simulation.timezone });\n return;\n }\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(copy.unavailable);\n return;\n }\n setState({ slots: data.slots, timezone: data.timezone ?? \"UTC\" });\n } catch {\n setMsg(copy.loadFailed);\n }\n };\n useEffect(() => {\n void load();\n }, [simulation]);\n\n const book = async () => {\n if (!selected) return;\n setBusy(true);\n setMsg(null);\n try {\n if (simulation) {\n onBooked({ startAt: selected.startAt, timezone: simulation.timezone });\n return;\n }\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(copy.slotTaken);\n await load();\n return;\n }\n if (!res.ok) throw new Error(data.error ?? copy.failed);\n onBooked({ startAt: data.startAt ?? selected.startAt, timezone: state?.timezone ?? \"UTC\" });\n } catch (e) {\n setMsg(e instanceof Error ? e.message : copy.failed);\n } finally {\n setBusy(false);\n }\n };\n\n if (!state) return <p className=\"slots-status\">{msg ?? copy.loading}</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 ? copy.booking : copy.book}\n </button>\n {msg ? <p className=\"step2-note\">{msg}</p> : null}\n </div>\n </div>\n );\n}\n","// The tier chooser seam. The package owns which tier is selected, whether it\n// needs paying for, and what gets posted; a site owns entirely what a tier\n// looks like. Everything here exists so the site can replace the visuals\n// without reimplementing the flow.\nimport type { ComponentChildren } from \"preact\";\nimport { useState } from \"preact/hooks\";\n\n/** One membership tier as the public join config exposes it. */\nexport interface JoinTier {\n id: string;\n name: string;\n priceCents: number;\n blurb: string;\n /** True when the tier costs nothing, so the flow skips the payment step. */\n free: boolean;\n}\n\n/**\n * What a site needs to render its own tier chooser.\n *\n * Pass `renderTiers` to take over presentation entirely — the built-in output\n * is an unstyled placeholder, not a design.\n */\nexport interface JoinTierRenderContext {\n /** Offered tiers, in display order. */\n tiers: readonly JoinTier[];\n /** The chosen tier, or null while nothing is chosen. */\n selected: JoinTier | null;\n selectedTierId: string | null;\n /** Choose a tier. Unknown ids are ignored. */\n selectTier: (tierId: string) => void;\n}\n\n/**\n * Track the selected tier.\n *\n * A single offered tier is selected implicitly: the choice is unambiguous, so\n * a chooser would be furniture. Nothing is preselected when several are on\n * offer, because defaulting silently picks a price on the applicant's behalf.\n */\nexport function useTierSelection(\n tiers: readonly JoinTier[],\n initialTierId?: string,\n): JoinTierRenderContext {\n const [selectedTierId, setSelectedTierId] = useState<string | null>(() => {\n const preset = tiers.find((tier) => tier.id === initialTierId);\n if (preset) return preset.id;\n return tiers.length === 1 ? tiers[0]!.id : null;\n });\n return {\n tiers,\n selected: tiers.find((tier) => tier.id === selectedTierId) ?? null,\n selectedTierId,\n selectTier: (tierId: string) => {\n if (tiers.some((tier) => tier.id === tierId)) setSelectedTierId(tierId);\n },\n };\n}\n\n/**\n * The unstyled fallback chooser.\n *\n * Deliberately plain and self-labelling: the flow stays usable before a site\n * has designed anything, and it is obvious on sight that this was never meant\n * to ship. Sites replace it wholesale via `renderTiers`; the `data-*` hook is\n * there so a build can assert no placeholder survives into production.\n */\nexport function TierPlaceholder(context: JoinTierRenderContext): ComponentChildren {\n return (\n <fieldset data-chapter-placeholder=\"tiers\">\n <legend>Membership (placeholder — pass renderTiers to style this)</legend>\n {context.tiers.map((tier) => (\n <label key={tier.id}>\n <input\n type=\"radio\"\n name=\"__chapterTier\"\n value={tier.id}\n checked={context.selectedTierId === tier.id}\n onChange={() => context.selectTier(tier.id)}\n />\n {tier.name}\n </label>\n ))}\n </fieldset>\n );\n}\n\n/**\n * Whether the flow should charge for this submission.\n *\n * A free tier skips payment even on a chapter otherwise wired for it. With no\n * tier in play the chapter-wide readiness decides, which is how every chapter\n * behaved before tiers existed.\n */\nexport function tierNeedsPayment(\n selected: JoinTier | null,\n paymentsReady: boolean,\n): boolean {\n return selected ? !selected.free && paymentsReady : paymentsReady;\n}\n","import type { JSX } from \"preact\";\nimport { Markdown } from \"@odla-ai/ui/components\";\nimport {\n MEMBERSHIP_PAGE_RENDERER,\n membershipPageDocument,\n type MembershipPageDocument,\n type MembershipTierPresentation,\n} from \"../membership-page.js\";\nimport { fmtMoney } from \"./datetime.js\";\nimport type { JoinTier } from \"./join-tiers.js\";\n\n/** Link on public pages, select inside JoinIsland, or remain inert in a CMS preview. */\nexport type MembershipCardsMode = \"link\" | \"select\" | \"preview\";\n\n/** Inputs for the canonical membership-card renderer. */\nexport interface MembershipCardsProps {\n /** Server-authoritative tiers, including the exact amount checkout charges. */\n tiers: readonly JoinTier[];\n /** Reviewed presentation only; it cannot override tier price or identity. */\n document: MembershipPageDocument;\n /** Link to the Chapter application flow. Only the tier query is appended. */\n applicationPath?: string;\n /** Links for a marketing page, buttons for JoinIsland, inert actions in CMS preview. */\n mode?: MembershipCardsMode;\n selectedTierId?: string | null;\n onSelectTier?: (tierId: string) => void;\n className?: string;\n ariaLabel?: string;\n}\n\n/** Editable renderer defaults. A CMS can expose these exact tokens without duplicating the contract. */\nexport const MEMBERSHIP_CARD_TOKEN_DEFAULTS = Object.freeze({\n \"--ui-membership-grid-gap\": \"1.4rem\",\n \"--ui-membership-card-padding\": \"2rem 1.7rem 1.8rem\",\n \"--ui-membership-card-radius\": \"1rem\",\n \"--ui-membership-badge-size\": \".66rem\",\n \"--ui-membership-name-size\": \"1.5rem\",\n \"--ui-membership-price-size\": \"1.85rem\",\n \"--ui-membership-summary-size\": \".83rem\",\n \"--ui-membership-body-size\": \".93rem\",\n \"--ui-membership-line-height\": \"1.5\",\n \"--ui-membership-capacity-size\": \"3.625rem\",\n} as const);\n\n/** One of the supported Chapter membership renderer design-token names. */\nexport type MembershipCardTokenName = keyof typeof MEMBERSHIP_CARD_TOKEN_DEFAULTS;\n\nconst MEMBERSHIP_CARDS_CSS = `\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}]{font-family:var(--ui-font-sans,ui-sans-serif,system-ui,sans-serif);color:var(--ui-text,#17252d)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-grid{display:grid;grid-template-columns:1fr;gap:var(--ui-membership-grid-gap,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-grid-gap\"]});align-items:stretch}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-card{position:relative;display:flex;min-width:0;flex-direction:column;padding:var(--ui-membership-card-padding,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-card-padding\"]});border:1px solid var(--ui-border,#d9e1e1);border-radius:var(--ui-membership-card-radius,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-card-radius\"]});background:var(--ui-surface,#fff);color:var(--ui-membership-body,var(--ui-text-muted,#66777f));box-shadow:var(--ui-membership-card-shadow,0 1px 2px rgb(13 40 66 / .05))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-card[data-variant=featured]{border-color:var(--ui-membership-featured-border,var(--ui-accent,#168c84));box-shadow:var(--ui-membership-featured-shadow,0 2px 8px rgb(13 40 66 / .08),0 18px 40px rgb(13 40 66 / .13))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-badge{position:absolute;top:-.75rem;left:1.25rem;padding:.4rem .85rem;border-radius:999px;background:var(--ui-membership-featured-badge,var(--ui-accent,#168c84));color:var(--ui-on-accent,#fff);font-family:var(--ui-font-mono,ui-monospace,monospace);font-size:var(--ui-membership-badge-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-badge-size\"]});font-weight:700;letter-spacing:.1em;text-transform:uppercase}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] [data-variant=supporting] .chapter-membership-badge{background:var(--ui-accent-strong,#0d3b52)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-name{margin:.3rem 0 0;font-family:var(--ui-font-display,ui-serif,Georgia,serif);font-size:var(--ui-membership-name-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-name-size\"]});font-weight:var(--ui-font-weight-display,400);letter-spacing:-.01em;line-height:1.08;color:var(--ui-membership-heading,var(--ui-text,#17252d))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-offer{margin:.7rem 0 0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-offer[data-with-capacity=true]{display:flex;align-items:center;gap:.9rem;margin-top:1rem}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price-stack{min-width:0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price-row{display:flex;flex-wrap:wrap;align-items:end;gap:.4rem;margin:0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price{font-family:var(--ui-font-numeral,var(--ui-font-display,ui-serif,Georgia,serif));font-size:var(--ui-membership-price-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-price-size\"]});font-variant-numeric:tabular-nums;line-height:1;color:var(--ui-membership-heading,var(--ui-text,#17252d))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] [data-variant=featured] .chapter-membership-price{font-size:var(--ui-membership-featured-price-size,1.75rem)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-compare{font-size:var(--ui-membership-compare-size,1.05rem);color:var(--ui-membership-summary,var(--ui-text-muted,#66777f));text-decoration:line-through}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-billing{padding-bottom:.1rem;color:var(--ui-membership-body,var(--ui-text-muted,#66777f));font-size:var(--ui-membership-billing-size,.85rem)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price-eyebrow{margin:0 0 .3rem;font-family:var(--ui-font-mono,ui-monospace,monospace);font-size:var(--ui-membership-badge-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-badge-size\"]});font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--ui-accent-strong,#0d3b52)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-summary{margin:.6rem 0 0;color:var(--ui-membership-summary,var(--ui-text-muted,#66777f));font-size:var(--ui-membership-summary-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-summary-size\"]});line-height:var(--ui-membership-line-height,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-line-height\"]})}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-benefit.ui-markdown,[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout .ui-markdown{font:inherit;color:inherit}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price-note{margin:.7rem 0 0;font-family:var(--ui-font-display,ui-serif,Georgia,serif);font-size:var(--ui-membership-price-note-size,.9rem);font-style:italic;line-height:1.45;color:var(--ui-membership-body,var(--ui-text-muted,#66777f))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-price-note strong{font-style:normal;font-weight:500;color:var(--ui-membership-action,var(--ui-accent,#168c84))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-capacity{display:grid;place-items:center;flex:0 0 auto;width:var(--ui-membership-capacity-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-capacity-size\"]});height:var(--ui-membership-capacity-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-capacity-size\"]});border:2px solid var(--ui-membership-capacity-border,var(--ui-accent,#168c84));border-radius:50%;background:var(--ui-membership-capacity-bg,var(--ui-surface-2,#f1f6fa));color:var(--ui-membership-action,var(--ui-accent-strong,#0d3b52))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-capacity strong{font-family:var(--ui-font-numeral,var(--ui-font-display,ui-serif,Georgia,serif));font-size:1.35rem;font-weight:400;line-height:1}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-capacity small{font-family:var(--ui-font-mono,ui-monospace,monospace);font-size:.55rem;letter-spacing:.1em;text-transform:uppercase}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-benefits{display:grid;gap:0;margin:1.3rem 0 0;padding:0;list-style:none;font-size:var(--ui-membership-body-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-body-size\"]});line-height:var(--ui-membership-line-height,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-line-height\"]})}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-benefits li{display:grid;grid-template-columns:15px minmax(0,1fr);gap:.6rem;align-items:start;padding:.36rem 0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-check{display:grid;place-items:center;width:15px;height:15px;margin-top:.24rem;border-radius:50%;background:var(--ui-accent-soft,#edf7fa);color:var(--ui-membership-check,var(--ui-accent-strong,#0d3b52));font-size:.62rem;font-weight:800}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout{margin:1.3rem 0 0;padding:1rem 1.1rem;border-radius:var(--ui-radius-md,.75rem);background:var(--ui-membership-callout-bg,var(--ui-surface-2,#f1f6fa));color:var(--ui-membership-body,var(--ui-text-muted,#66777f));font-size:var(--ui-membership-callout-body-size,.87rem);line-height:1.5}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout-label{margin:0;font-family:var(--ui-font-mono,ui-monospace,monospace);font-size:var(--ui-membership-badge-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-badge-size\"]});font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--ui-membership-action,var(--ui-accent,#168c84))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout .ui-markdown>:first-child{margin:.45rem 0 0;font-family:var(--ui-font-display,ui-serif,Georgia,serif);font-size:var(--ui-membership-callout-quote-size,1.05rem);font-style:italic;line-height:1.35;color:var(--ui-membership-heading,var(--ui-text,#17252d))}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout .ui-markdown>:last-child:not(:first-child){margin:.5rem 0 0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-callout strong{color:var(--ui-membership-heading,var(--ui-text,#17252d));font-weight:500}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-action{display:inline-flex;align-items:center;justify-content:flex-start;gap:.4rem;margin-top:auto;padding:1.6rem 0 0;border:0;background:transparent;color:var(--ui-membership-action,var(--ui-accent,#168c84));font:inherit;font-size:var(--ui-membership-action-size,.95rem);font-weight:500;text-decoration:none;cursor:pointer}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-action[aria-current=true]{color:var(--ui-accent,#168c84)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-action:focus-visible{outline:none;box-shadow:var(--ui-focus,0 0 0 3px var(--ui-accent-soft,#dff5f1));border-radius:var(--ui-radius-sm,.2rem)}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-footnote{margin:1.5rem auto 0;max-width:70ch;color:var(--ui-text-muted,#66777f);font-size:var(--ui-membership-summary-size,${MEMBERSHIP_CARD_TOKEN_DEFAULTS[\"--ui-membership-summary-size\"]});text-align:center}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .ui-markdown>:first-child{margin-top:0}\n[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .ui-markdown>:last-child{margin-bottom:0}\n@media (min-width:56rem){[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-grid{grid-template-columns:repeat(var(--chapter-membership-columns,3),minmax(0,1fr))}}\n@media (prefers-reduced-motion:no-preference){[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-action{transition:color .16s ease,transform .16s ease}[data-chapter-membership=${MEMBERSHIP_PAGE_RENDERER}] .chapter-membership-action:hover{transform:translateX(.2rem)}}\n`;\n\nconst safeApplicationPath = (value: string | undefined): string => {\n if (typeof value !== \"string\" || !/^\\/(?!\\/)[^\\s#\\\\]*$/.test(value)) return \"/apply\";\n try {\n const resolved = new URL(value, \"https://chapter.invalid\");\n return resolved.origin === \"https://chapter.invalid\" ? `${resolved.pathname}${resolved.search}` : \"/apply\";\n } catch {\n return \"/apply\";\n }\n};\n\n/** Derive a safe same-origin application link for one authoritative tier id. */\nexport function membershipApplicationHref(applicationPath: string | undefined, tierId: string): string {\n const path = safeApplicationPath(applicationPath);\n return `${path}${path.includes(\"?\") ? \"&\" : \"?\"}tier=${encodeURIComponent(tierId)}`;\n}\n\nfunction TierAction(props: {\n tierId: string;\n label: string;\n mode: MembershipCardsMode;\n selected: boolean;\n applicationPath?: string;\n onSelectTier?: (tierId: string) => void;\n}): JSX.Element {\n if (props.mode === \"select\") {\n return <button type=\"button\" className=\"chapter-membership-action\" aria-current={props.selected ? \"true\" : undefined} onClick={() => props.onSelectTier?.(props.tierId)}>{props.label}<span aria-hidden=\"true\">→</span></button>;\n }\n if (props.mode === \"preview\") {\n return <span className=\"chapter-membership-action\" aria-disabled=\"true\">{props.label}<span aria-hidden=\"true\">→</span></span>;\n }\n return <a className=\"chapter-membership-action\" href={membershipApplicationHref(props.applicationPath, props.tierId)}>{props.label}<span aria-hidden=\"true\">→</span></a>;\n}\n\nfunction TierCard(props: {\n tier: JoinTier;\n presentation: MembershipTierPresentation;\n mode: MembershipCardsMode;\n selected: boolean;\n applicationPath?: string;\n onSelectTier?: (tierId: string) => void;\n}): JSX.Element {\n const { tier, presentation } = props;\n const summary = presentation.summaryMarkdown ?? tier.blurb;\n const action = presentation.ctaLabel ?? (tier.free ? \"Join for free\" : `Choose ${tier.name}`);\n return (\n <article className=\"chapter-membership-card\" data-tier-id={tier.id} data-variant={presentation.variant ?? \"standard\"}>\n {presentation.badge ? <div className=\"chapter-membership-badge\">{presentation.badge}</div> : null}\n <h3 className=\"chapter-membership-name\">{tier.name}</h3>\n <div className=\"chapter-membership-offer\" data-with-capacity={presentation.capacity ? \"true\" : undefined}>\n {presentation.capacity ? <div className=\"chapter-membership-capacity\" aria-label={`${presentation.capacity.total} ${presentation.capacity.label ?? \"available\"}`}><strong>{presentation.capacity.total.toLocaleString(\"en-US\")}</strong><small>{presentation.capacity.label ?? \"available\"}</small></div> : null}\n <div className=\"chapter-membership-price-stack\">\n {presentation.priceEyebrow ? <p className=\"chapter-membership-price-eyebrow\">{presentation.priceEyebrow}</p> : null}\n <div className=\"chapter-membership-price-row\">\n {presentation.compareAtPriceCents !== undefined ? <span className=\"chapter-membership-compare\">{fmtMoney(presentation.compareAtPriceCents)}</span> : null}\n <span className=\"chapter-membership-price\">{tier.free ? \"Free\" : fmtMoney(tier.priceCents)}</span>\n {!tier.free && presentation.billingLabel ? <span className=\"chapter-membership-billing\">{presentation.billingLabel}</span> : null}\n </div>\n </div>\n </div>\n {presentation.priceNoteMarkdown ? <Markdown className=\"chapter-membership-price-note\" source={presentation.priceNoteMarkdown} /> : null}\n {summary ? <Markdown className=\"chapter-membership-summary\" source={summary} /> : null}\n {presentation.benefits?.length ? <ul className=\"chapter-membership-benefits\">{presentation.benefits.map((benefit, index) => <li key={`${tier.id}-benefit-${index}`}><span className=\"chapter-membership-check\" aria-hidden=\"true\">✓</span><Markdown className=\"chapter-membership-benefit\" source={benefit} /></li>)}</ul> : null}\n {presentation.callout ? <aside className=\"chapter-membership-callout\">{presentation.callout.label ? <p className=\"chapter-membership-callout-label\">{presentation.callout.label}</p> : null}<Markdown source={presentation.callout.bodyMarkdown} /></aside> : null}\n <TierAction tierId={tier.id} label={action} mode={props.mode} selected={props.selected} applicationPath={props.applicationPath} onSelectTier={props.onSelectTier} />\n </article>\n );\n}\n\n/**\n * Render the reviewed membership document against live Chapter tiers.\n *\n * The document controls order and bounded presentation. Names, prices,\n * free/paid state, and application tier ids always come from `tiers`, making\n * the component safe for both a follower's live page and a hub's exact preview.\n */\nexport function MembershipCards(props: MembershipCardsProps): JSX.Element {\n const document = membershipPageDocument(props.document, { tiers: props.tiers });\n const byId = new Map(props.tiers.map((tier) => [tier.id, tier]));\n const mode = props.mode ?? \"link\";\n return (\n <section className={[\"chapter-membership\", props.className].filter(Boolean).join(\" \")} data-chapter-membership={MEMBERSHIP_PAGE_RENDERER} aria-label={props.ariaLabel ?? \"Membership options\"}>\n <style>{MEMBERSHIP_CARDS_CSS}</style>\n <div className=\"chapter-membership-grid\" style={{ \"--chapter-membership-columns\": String(Math.min(document.tiers.length, 3)) }}>\n {document.tiers.map((presentation) => {\n const tier = byId.get(presentation.tierId)!;\n return <TierCard key={tier.id} tier={tier} presentation={presentation} mode={mode} selected={props.selectedTierId === tier.id} applicationPath={props.applicationPath} onSelectTier={props.onSelectTier} />;\n })}\n </div>\n {document.footnoteMarkdown ? <Markdown className=\"chapter-membership-footnote\" source={document.footnoteMarkdown} /> : null}\n </section>\n );\n}\n","/** The first stable, shared Chapter membership-page renderer contract. */\nexport const MEMBERSHIP_PAGE_RENDERER = \"membership-cards-v1\" as const;\n\n/** Fixed visual emphasis choices supported by the versioned card renderer. */\nexport type MembershipTierVariant = \"standard\" | \"featured\" | \"supporting\";\n\n/** A bounded, token-rendered aside within one membership card. */\nexport interface MembershipTierCallout {\n label?: string;\n bodyMarkdown: string;\n}\n\n/** Scarcity copy displayed without changing tier availability or checkout. */\nexport interface MembershipTierCapacity {\n total: number;\n label?: string;\n}\n\n/**\n * Presentation attached to one server-authoritative membership tier.\n *\n * Price, free/paid state, identity, and application targeting deliberately do\n * not live here. They are resolved from the public Chapter join config so\n * authored marketing copy can never change what checkout charges.\n */\nexport interface MembershipTierPresentation {\n tierId: string;\n variant?: MembershipTierVariant;\n badge?: string;\n summaryMarkdown?: string;\n priceEyebrow?: string;\n compareAtPriceCents?: number;\n billingLabel?: string;\n priceNoteMarkdown?: string;\n capacity?: MembershipTierCapacity;\n benefits?: readonly string[];\n callout?: MembershipTierCallout;\n ctaLabel?: string;\n}\n\n/** A bounded document that can be reviewed once and rendered by any Chapter host. */\nexport interface MembershipPageDocument {\n schemaVersion: 1;\n renderer: typeof MEMBERSHIP_PAGE_RENDERER;\n tiers: readonly MembershipTierPresentation[];\n footnoteMarkdown?: string;\n}\n\n/** Minimal server-authoritative tier identity and charged amount used during validation. */\nexport interface MembershipTierAuthority {\n id: string;\n priceCents: number;\n}\n\n/** Live tier authority used to reject missing, unknown, or misleading presentation. */\nexport interface MembershipPageValidationOptions {\n /** When supplied, every live tier must occur exactly once in the document. */\n tiers?: readonly MembershipTierAuthority[];\n}\n\n/** A normalized document or the complete actionable list of validation failures. */\nexport type MembershipPageValidationResult =\n | { ok: true; value: MembershipPageDocument }\n | { ok: false; errors: readonly string[] };\n\nconst TIER_ID = /^[a-z][a-z0-9-]{0,79}$/;\nconst VARIANTS = new Set<MembershipTierVariant>([\"standard\", \"featured\", \"supporting\"]);\nconst ROOT_KEYS = new Set([\"schemaVersion\", \"renderer\", \"tiers\", \"footnoteMarkdown\"]);\nconst TIER_KEYS = new Set([\n \"tierId\", \"variant\", \"badge\", \"summaryMarkdown\", \"priceEyebrow\",\n \"compareAtPriceCents\", \"billingLabel\", \"priceNoteMarkdown\", \"capacity\",\n \"benefits\", \"callout\", \"ctaLabel\",\n]);\nconst CALLOUT_KEYS = new Set([\"label\", \"bodyMarkdown\"]);\nconst CAPACITY_KEYS = new Set([\"total\", \"label\"]);\nconst MAX_TIERS = 20;\nconst MAX_BENEFITS = 24;\nconst MAX_MARKDOWN = 65_536;\nconst MAX_LABEL = 160;\n\nconst objectRecord = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n\nfunction rejectUnknownKeys(\n value: Record<string, unknown>,\n allowed: ReadonlySet<string>,\n path: string,\n errors: string[],\n): void {\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) errors.push(`${path}.${key} is not supported`);\n }\n}\n\nfunction boundedString(\n value: unknown,\n path: string,\n errors: string[],\n max: number,\n required = false,\n): string | undefined {\n if (value === undefined) {\n if (required) errors.push(`${path} is required`);\n return undefined;\n }\n if (typeof value !== \"string\") {\n errors.push(`${path} must be a string`);\n return undefined;\n }\n const text = value.trim();\n if (required && !text) errors.push(`${path} is required`);\n if (text.length > max) errors.push(`${path} must be at most ${max.toLocaleString(\"en-US\")} characters`);\n return text;\n}\n\n/**\n * Validate and normalize a membership-page document without evaluating code,\n * accepting HTML, or allowing authored links/styles. Markdown slots are later\n * rendered by @odla-ai/ui's bounded safe Markdown component.\n */\nexport function validateMembershipPageDocument(\n input: unknown,\n options: MembershipPageValidationOptions = {},\n): MembershipPageValidationResult {\n const errors: string[] = [];\n const root = objectRecord(input);\n if (!root) return { ok: false, errors: [\"document must be an object\"] };\n rejectUnknownKeys(root, ROOT_KEYS, \"document\", errors);\n if (root.schemaVersion !== 1) errors.push(\"document.schemaVersion must be 1\");\n if (root.renderer !== MEMBERSHIP_PAGE_RENDERER) {\n errors.push(`document.renderer must be \\\"${MEMBERSHIP_PAGE_RENDERER}\\\"`);\n }\n if (!Array.isArray(root.tiers)) errors.push(\"document.tiers must be an array\");\n const rawTiers = Array.isArray(root.tiers) ? root.tiers : [];\n if (Array.isArray(root.tiers) && rawTiers.length === 0) errors.push(\"document.tiers must include at least one tier\");\n if (rawTiers.length > MAX_TIERS) errors.push(`document.tiers supports at most ${MAX_TIERS} entries`);\n\n const authority = new Map((options.tiers ?? []).map((tier) => [tier.id, tier]));\n const seen = new Set<string>();\n const tiers: MembershipTierPresentation[] = [];\n for (const [index, raw] of rawTiers.slice(0, MAX_TIERS).entries()) {\n const path = `document.tiers[${index}]`;\n const item = objectRecord(raw);\n if (!item) {\n errors.push(`${path} must be an object`);\n continue;\n }\n rejectUnknownKeys(item, TIER_KEYS, path, errors);\n const tierId = boundedString(item.tierId, `${path}.tierId`, errors, 80, true) ?? \"\";\n if (tierId && !TIER_ID.test(tierId)) errors.push(`${path}.tierId must be a lowercase kebab-case id`);\n if (seen.has(tierId)) errors.push(`${path}.tierId duplicates \\\"${tierId}\\\"`);\n seen.add(tierId);\n if (authority.size && !authority.has(tierId)) errors.push(`${path}.tierId \\\"${tierId}\\\" is not an offered tier`);\n\n const variant = item.variant;\n if (variant !== undefined && (typeof variant !== \"string\" || !VARIANTS.has(variant as MembershipTierVariant))) {\n errors.push(`${path}.variant must be standard, featured, or supporting`);\n }\n const compareAt = item.compareAtPriceCents;\n if (compareAt !== undefined && (!Number.isInteger(compareAt) || Number(compareAt) < 0 || Number(compareAt) > 100_000_000)) {\n errors.push(`${path}.compareAtPriceCents must be an integer from 0 to 100,000,000`);\n }\n const actual = authority.get(tierId)?.priceCents;\n if (Number.isInteger(compareAt) && actual !== undefined && Number(compareAt) <= actual) {\n errors.push(`${path}.compareAtPriceCents must be greater than the authoritative tier price`);\n }\n\n let benefits: string[] | undefined;\n if (item.benefits !== undefined) {\n if (!Array.isArray(item.benefits)) errors.push(`${path}.benefits must be an array`);\n else {\n if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);\n benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {\n const text = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2_000, true);\n return text ? [text] : [];\n });\n }\n }\n\n let callout: MembershipTierCallout | undefined;\n if (item.callout !== undefined) {\n const rawCallout = objectRecord(item.callout);\n if (!rawCallout) errors.push(`${path}.callout must be an object`);\n else {\n rejectUnknownKeys(rawCallout, CALLOUT_KEYS, `${path}.callout`, errors);\n const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN, true);\n const label = boundedString(rawCallout.label, `${path}.callout.label`, errors, MAX_LABEL);\n if (bodyMarkdown) callout = { ...(label ? { label } : {}), bodyMarkdown };\n }\n }\n\n let capacity: MembershipTierCapacity | undefined;\n if (item.capacity !== undefined) {\n const rawCapacity = objectRecord(item.capacity);\n if (!rawCapacity) errors.push(`${path}.capacity must be an object`);\n else {\n rejectUnknownKeys(rawCapacity, CAPACITY_KEYS, `${path}.capacity`, errors);\n const total = rawCapacity.total;\n if (!Number.isInteger(total) || Number(total) < 1 || Number(total) > 1_000_000) {\n errors.push(`${path}.capacity.total must be an integer from 1 to 1,000,000`);\n }\n const label = boundedString(rawCapacity.label, `${path}.capacity.label`, errors, MAX_LABEL);\n if (Number.isInteger(total) && Number(total) >= 1 && Number(total) <= 1_000_000) {\n capacity = { total: Number(total), ...(label ? { label } : {}) };\n }\n }\n }\n\n const badge = boundedString(item.badge, `${path}.badge`, errors, MAX_LABEL);\n const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN);\n const priceEyebrow = boundedString(item.priceEyebrow, `${path}.priceEyebrow`, errors, MAX_LABEL);\n const billingLabel = boundedString(item.billingLabel, `${path}.billingLabel`, errors, MAX_LABEL);\n const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN);\n const ctaLabel = boundedString(item.ctaLabel, `${path}.ctaLabel`, errors, MAX_LABEL);\n tiers.push({\n tierId,\n ...(typeof variant === \"string\" && VARIANTS.has(variant as MembershipTierVariant) ? { variant: variant as MembershipTierVariant } : {}),\n ...(badge ? { badge } : {}),\n ...(summaryMarkdown ? { summaryMarkdown } : {}),\n ...(priceEyebrow ? { priceEyebrow } : {}),\n ...(Number.isInteger(compareAt) ? { compareAtPriceCents: Number(compareAt) } : {}),\n ...(billingLabel ? { billingLabel } : {}),\n ...(priceNoteMarkdown ? { priceNoteMarkdown } : {}),\n ...(capacity ? { capacity } : {}),\n ...(benefits ? { benefits } : {}),\n ...(callout ? { callout } : {}),\n ...(ctaLabel ? { ctaLabel } : {}),\n });\n }\n if (authority.size) {\n for (const tierId of authority.keys()) {\n if (!seen.has(tierId)) errors.push(`document.tiers is missing offered tier \\\"${tierId}\\\"`);\n }\n }\n const footnoteMarkdown = boundedString(root.footnoteMarkdown, \"document.footnoteMarkdown\", errors, MAX_MARKDOWN);\n if (errors.length) return { ok: false, errors };\n return {\n ok: true,\n value: {\n schemaVersion: 1,\n renderer: MEMBERSHIP_PAGE_RENDERER,\n tiers,\n ...(footnoteMarkdown ? { footnoteMarkdown } : {}),\n },\n };\n}\n\n/** Validate a document or fail closed with all actionable validation errors. */\nexport function membershipPageDocument(\n input: unknown,\n options: MembershipPageValidationOptions = {},\n): MembershipPageDocument {\n const result = validateMembershipPageDocument(input, options);\n if (!result.ok) throw new TypeError(`Invalid membership page document: ${result.errors.join(\"; \")}`);\n return result.value;\n}\n"],"mappings":"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","names":["useState","useState","jsx","jsxs","useState","Fragment","jsx","jsxs","useState","useEffect","useRef","useState","useState","jsx","jsxs","useState","useEffect","useState","jsx","jsxs","useState","useEffect","useState","jsx","jsxs","Fragment","jsx","jsxs","useState","useRef","useEffect","jsx","jsxs","document"]}
@@ -73,6 +73,22 @@ type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T ex
73
73
  /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
74
74
  type ChapterCopyInput = DeepPartial<ChapterCopy>;
75
75
 
76
+ /** Explicit authority a chapter grants to one Built Not Found-style hub. */
77
+ interface ChapterSignupControl {
78
+ /** Stable app id expected in the signed delivery envelope. */
79
+ sourceId: string;
80
+ /** Follower-vault key holding the dedicated edge secret. */
81
+ secretName?: string;
82
+ /** Stripe mode this deployed environment accepts. */
83
+ stripeMode: "test" | "live";
84
+ }
85
+ /** Validated signup-control authority carried on a resolved Chapter. */
86
+ interface ResolvedSignupControl {
87
+ sourceId: string;
88
+ secretName: string;
89
+ stripeMode: "test" | "live";
90
+ }
91
+
76
92
  /** One follower site a leader can push CRM records to. The secret VALUE never
77
93
  * lives in config: `secretName` names the leader tenant's vaulted copy of the
78
94
  * follower's `network_share_secret`. */
@@ -442,6 +458,8 @@ interface ChapterConfig {
442
458
  /** Signed network edges: leader delivery targets and follower-owned,
443
459
  * privacy-bounded record readers. Secret values stay in tenant vaults. */
444
460
  network?: ChapterNetwork;
461
+ /** A separate signed edge that may control this chapter's complete signup. */
462
+ signupControl?: ChapterSignupControl;
445
463
  /** Enable a public, host-rendered formation application workflow. */
446
464
  formation?: LeaderFormationConfig;
447
465
  thesis?: unknown;
@@ -536,6 +554,8 @@ interface Chapter {
536
554
  copy: ChapterCopy;
537
555
  /** Validated follower targets for leader-driven record pushes. */
538
556
  network: ResolvedNetwork;
557
+ /** Null unless this follower explicitly delegates signup authority. */
558
+ signupControl: ResolvedSignupControl | null;
539
559
  /** Resolved public formation intake contract (disabled when omitted). */
540
560
  formation: ResolvedLeaderFormation;
541
561
  /** Resolved CRM engine (from `defineCrm`). */