@odla-ai/chapter 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  formatChapterCopy,
3
3
  useChapterCopy
4
- } from "./chunk-JAWTIROV.js";
4
+ } from "./chunk-IAWBE5PX.js";
5
5
  import {
6
6
  collectFormFields
7
7
  } from "./chunk-IVXVECKR.js";
@@ -105,7 +105,7 @@ function SlotPicker(props) {
105
105
  }
106
106
 
107
107
  // src/ui/members.tsx
108
- import { useEffect, useState as useState3 } from "preact/hooks";
108
+ import { useEffect as useEffect3, useState as useState6 } from "preact/hooks";
109
109
 
110
110
  // src/ui/reschedule.tsx
111
111
  import { useState as useState2 } from "preact/hooks";
@@ -189,143 +189,27 @@ function Rescheduler(props) {
189
189
  ] });
190
190
  }
191
191
 
192
- // src/ui/members.tsx
193
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
194
- function card(copy, kicker, body) {
195
- return /* @__PURE__ */ jsxs3("div", { className: "card", children: [
196
- /* @__PURE__ */ jsx3("div", { className: "card-label", children: copy.provisional.cardLabel }),
197
- /* @__PURE__ */ jsxs3("div", { className: "meeting-block", children: [
198
- /* @__PURE__ */ jsx3("div", { className: "meeting-kicker", children: kicker }),
199
- body
200
- ] })
201
- ] });
202
- }
203
- function ProvisionalCard(props) {
204
- const { api, application, applyHref, copy, onReschedule } = props;
205
- if (!application) {
206
- return card(
207
- copy,
208
- copy.provisional.applicationNeeded,
209
- /* @__PURE__ */ jsxs3(Fragment2, { children: [
210
- /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
211
- /* @__PURE__ */ jsx3("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
212
- ] })
213
- );
214
- }
215
- if (application.status === "refunded") {
216
- return card(
217
- copy,
218
- copy.provisional.refunded,
219
- /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.refundedBody })
220
- );
221
- }
222
- const membership = application.paid ? /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: application.renewalAt ? formatChapterCopy(copy.provisional.renews, { date: fmtDate(application.renewalAt) }) : copy.provisional.active }) : null;
223
- if (application.meetingAt) {
224
- return card(
225
- copy,
226
- copy.provisional.introductionCall,
227
- /* @__PURE__ */ jsxs3(Fragment2, { children: [
228
- /* @__PURE__ */ jsx3("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
229
- /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
230
- application.meetUrl ? /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: /* @__PURE__ */ jsx3("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
231
- /* @__PURE__ */ jsx3(
232
- Rescheduler,
233
- {
234
- api,
235
- applicationId: application.id,
236
- timezone: application.timezone,
237
- copy: copy.reschedule,
238
- onRescheduled: onReschedule
239
- }
240
- ),
241
- membership
242
- ] })
243
- );
244
- }
245
- return card(
246
- copy,
247
- copy.provisional.bookCall,
248
- /* @__PURE__ */ jsxs3(Fragment2, { children: [
249
- /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
250
- /* @__PURE__ */ jsx3(
251
- Rescheduler,
252
- {
253
- api,
254
- applicationId: application.id,
255
- timezone: application.timezone,
256
- copy: copy.reschedule,
257
- onRescheduled: onReschedule,
258
- label: copy.provisional.chooseTime
259
- }
260
- ),
261
- membership
262
- ] })
263
- );
264
- }
265
- function MembersArea(props) {
266
- const inheritedCopy = useChapterCopy();
267
- const {
268
- api,
269
- signOut,
270
- adminHref = "/admin/",
271
- applyHref = "/join.html",
272
- memberContent,
273
- renderProvisional
274
- } = props;
275
- const copy = props.copy ?? inheritedCopy.members;
276
- const [me, setMe] = useState3(null);
277
- const [error, setError] = useState3(false);
278
- const reload = async () => {
279
- try {
280
- setMe(await api("/api/me"));
281
- } catch {
282
- setError(true);
283
- }
284
- };
285
- useEffect(() => {
286
- void reload();
287
- }, []);
288
- if (error) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children: copy.loadFailed });
289
- if (!me) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children: inheritedCopy.common.loading });
290
- const role = me.role || "provisional";
291
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
292
- /* @__PURE__ */ jsxs3("div", { className: "card", children: [
293
- /* @__PURE__ */ jsxs3("div", { className: "member-row", children: [
294
- /* @__PURE__ */ jsx3("span", { className: "member-email", children: me.email }),
295
- /* @__PURE__ */ jsx3("span", { className: "role-badge " + role, children: role })
296
- ] }),
297
- /* @__PURE__ */ jsxs3("div", { className: "account-actions", children: [
298
- /* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
299
- role === "admin" ? /* @__PURE__ */ jsx3("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
300
- ] })
192
+ // src/ui/member-gifts.tsx
193
+ import { useEffect, useRef as useRef2, useState as useState4 } from "preact/hooks";
194
+
195
+ // src/ui/gift-offer-cards.tsx
196
+ import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
197
+ var price = (cents, currency) => currency === "usd" ? fmtMoney(cents) : new Intl.NumberFormat(void 0, { style: "currency", currency: currency.toUpperCase() }).format(cents / 100);
198
+ function GiftOfferCards(props) {
199
+ const mode = props.mode ?? "preview";
200
+ return /* @__PURE__ */ jsx3("section", { className: "chapter-gift-offers", "aria-label": props.ariaLabel ?? "Gift options", children: props.offers.map((offer) => /* @__PURE__ */ jsxs3("article", { className: "card chapter-gift-offer", "data-offer-id": offer.id, children: [
201
+ /* @__PURE__ */ jsx3("div", { className: "card-label", children: offer.title }),
202
+ /* @__PURE__ */ jsx3("p", { children: offer.description }),
203
+ /* @__PURE__ */ jsxs3("p", { className: "meeting-date", children: [
204
+ price(offer.priceCents, offer.currency),
205
+ offer.billingMode === "subscription" ? " recurring" : ""
301
206
  ] }),
302
- role === "provisional" ? (() => {
303
- const defaultContent = /* @__PURE__ */ jsx3(
304
- ProvisionalCard,
305
- {
306
- api,
307
- application: me.application,
308
- applyHref,
309
- copy,
310
- onReschedule: reload
311
- }
312
- );
313
- return renderProvisional?.({
314
- api,
315
- application: me.application,
316
- applyHref,
317
- reload,
318
- defaultContent
319
- }) ?? defaultContent;
320
- })() : memberContent ?? /* @__PURE__ */ jsx3("div", { className: "card", children: /* @__PURE__ */ jsx3("div", { className: "card-label", children: copy.full.welcome }) })
321
- ] });
207
+ mode === "select" ? /* @__PURE__ */ jsx3("button", { type: "button", className: "btn secondary", "aria-pressed": props.selectedOfferId === offer.id, onClick: () => props.onSelect?.(offer.id), children: offer.ctaLabel }) : /* @__PURE__ */ jsx3("span", { className: "btn secondary", "aria-disabled": "true", children: offer.ctaLabel })
208
+ ] }, offer.id)) });
322
209
  }
323
210
 
324
- // src/ui/join.tsx
325
- import { useEffect as useEffect3, useRef as useRef2, useState as useState7 } from "preact/hooks";
326
-
327
211
  // src/ui/payment-step.tsx
328
- import { useRef, useState as useState4 } from "preact/hooks";
212
+ import { useRef, useState as useState3 } from "preact/hooks";
329
213
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
330
214
  var loader = null;
331
215
  function loadStripe() {
@@ -359,12 +243,13 @@ function PaymentStep(props) {
359
243
  simulation
360
244
  } = props;
361
245
  const copy = props.copy ?? inheritedCopy.join.payment;
362
- const [status, setStatus] = useState4("idle");
363
- const [error, setError] = useState4(null);
364
- const [lineItems, setLineItems] = useState4(null);
246
+ const [status, setStatus] = useState3("idle");
247
+ const [error, setError] = useState3(null);
248
+ const [lineItems, setLineItems] = useState3(null);
365
249
  const mountRef = useRef(null);
366
250
  const stripeRef = useRef(null);
367
251
  const elementsRef = useRef(null);
252
+ const completionRef = useRef({ gift: false });
368
253
  const begin = async () => {
369
254
  setStatus("loading");
370
255
  setError(null);
@@ -374,13 +259,17 @@ function PaymentStep(props) {
374
259
  setStatus("ready");
375
260
  return;
376
261
  }
377
- const res = await fetch("/api/payments/subscription", {
378
- method: "POST",
379
- headers: { "content-type": "application/json" },
380
- body: JSON.stringify({ applicationId, refundPolicyAck: true })
381
- });
382
- const data = await res.json();
383
- if (!res.ok || !data.clientSecret || !data.publishableKey) throw new Error(copy.setupFailed);
262
+ const data = props.startCheckout ? await props.startCheckout() : await (async () => {
263
+ const res = await fetch("/api/payments/subscription", {
264
+ method: "POST",
265
+ headers: { "content-type": "application/json" },
266
+ body: JSON.stringify({ applicationId, refundPolicyAck: true })
267
+ });
268
+ const body = await res.json();
269
+ if (!res.ok) throw new Error(copy.setupFailed);
270
+ return body;
271
+ })();
272
+ if (!data.clientSecret || !data.publishableKey) throw new Error(copy.setupFailed);
384
273
  const stripe = (await loadStripe())(data.publishableKey);
385
274
  const elements = stripe.elements({
386
275
  clientSecret: data.clientSecret,
@@ -392,6 +281,7 @@ function PaymentStep(props) {
392
281
  stripeRef.current = stripe;
393
282
  elementsRef.current = elements;
394
283
  setLineItems(data.lineItems ?? null);
284
+ completionRef.current = { gift: data.gift === true, ...data.giftCode ? { giftCode: data.giftCode } : {} };
395
285
  setStatus("ready");
396
286
  } catch (e) {
397
287
  setStatus("idle");
@@ -401,7 +291,7 @@ function PaymentStep(props) {
401
291
  const pay = async () => {
402
292
  if (simulation) {
403
293
  setStatus("confirming");
404
- onPaid();
294
+ onPaid({ gift: false });
405
295
  return;
406
296
  }
407
297
  const stripe = stripeRef.current;
@@ -423,7 +313,7 @@ function PaymentStep(props) {
423
313
  return;
424
314
  }
425
315
  const paid = result.paymentIntent?.status;
426
- if (paid === "succeeded" || paid === "processing") onPaid();
316
+ if (paid === "succeeded" || paid === "processing") onPaid(completionRef.current);
427
317
  else {
428
318
  setError(copy.incomplete);
429
319
  setStatus("ready");
@@ -471,15 +361,334 @@ function PaymentStep(props) {
471
361
  ] });
472
362
  }
473
363
 
474
- // src/ui/join-booking.tsx
475
- import { useEffect as useEffect2, useState as useState5 } from "preact/hooks";
364
+ // src/ui/member-gifts.tsx
476
365
  import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
366
+ function MemberGifts(props) {
367
+ const [data, setData] = useState4(null);
368
+ const [selected, setSelected] = useState4(null);
369
+ const [code, setCode] = useState4("");
370
+ const [redeemCode, setRedeemCode] = useState4("");
371
+ const [message, setMessage] = useState4("");
372
+ const purchaseId = useRef2(crypto.randomUUID());
373
+ const load = async () => setData(await props.api("/api/gifts"));
374
+ useEffect(() => {
375
+ void load().catch(() => setData({ offers: [], claims: [] }));
376
+ }, []);
377
+ const rotate = async (claimId) => {
378
+ try {
379
+ const result = await props.api(`/api/gifts/${encodeURIComponent(claimId)}/rotate`, { method: "POST" });
380
+ setCode(result.code);
381
+ setMessage(props.copy.rotated);
382
+ } catch {
383
+ setMessage(props.copy.rotateFailed);
384
+ }
385
+ };
386
+ const redeem = async () => {
387
+ try {
388
+ await props.api("/api/gifts/redeem", {
389
+ method: "POST",
390
+ headers: { "content-type": "application/json" },
391
+ body: JSON.stringify({ code: redeemCode })
392
+ });
393
+ setRedeemCode("");
394
+ setMessage(props.copy.redeemed);
395
+ await load();
396
+ } catch {
397
+ setMessage(props.copy.redeemFailed);
398
+ }
399
+ };
400
+ if (!data) return null;
401
+ const offer = data.offers.find((item) => item.id === selected);
402
+ return /* @__PURE__ */ jsxs5("section", { className: "member-gifts", children: [
403
+ /* @__PURE__ */ jsxs5("div", { className: "card", children: [
404
+ /* @__PURE__ */ jsx5("div", { className: "card-label", children: props.copy.title }),
405
+ /* @__PURE__ */ jsx5("p", { children: props.copy.body }),
406
+ data.offers.length ? /* @__PURE__ */ jsx5(GiftOfferCards, { offers: data.offers, mode: "select", selectedOfferId: selected, onSelect: (id) => {
407
+ setSelected(id);
408
+ setCode("");
409
+ purchaseId.current = crypto.randomUUID();
410
+ } }) : /* @__PURE__ */ jsx5("p", { children: props.copy.noneAvailable }),
411
+ offer ? /* @__PURE__ */ jsx5(
412
+ PaymentStep,
413
+ {
414
+ applicationId: purchaseId.current,
415
+ refundPolicyText: data.refundPolicyText ?? "",
416
+ copy: props.paymentCopy,
417
+ startCheckout: () => props.api("/api/gifts/checkout", {
418
+ method: "POST",
419
+ headers: { "content-type": "application/json" },
420
+ body: JSON.stringify({ offerId: offer.id, purchaseId: purchaseId.current })
421
+ }),
422
+ onPaid: (result) => {
423
+ if (result.giftCode) setCode(result.giftCode);
424
+ setMessage(props.copy.paid);
425
+ setSelected(null);
426
+ setTimeout(() => void load(), 1500);
427
+ }
428
+ }
429
+ ) : null,
430
+ code ? /* @__PURE__ */ jsxs5("div", { className: "gift-code-once", children: [
431
+ /* @__PURE__ */ jsx5("output", { children: code }),
432
+ /* @__PURE__ */ jsx5("p", { children: props.copy.saveCode })
433
+ ] }) : null
434
+ ] }),
435
+ /* @__PURE__ */ jsxs5("div", { className: "card", children: [
436
+ /* @__PURE__ */ jsx5("div", { className: "card-label", children: props.copy.purchases }),
437
+ data.claims.length ? data.claims.map((claim) => /* @__PURE__ */ jsxs5("div", { className: "member-row", children: [
438
+ /* @__PURE__ */ jsx5("span", { children: claim.offerId }),
439
+ /* @__PURE__ */ jsx5("span", { children: props.copy[claim.status] }),
440
+ claim.status === "pending" ? /* @__PURE__ */ jsx5("button", { className: "btn secondary mini", onClick: () => void rotate(claim.id), children: props.copy.rotate }) : null
441
+ ] }, claim.id)) : /* @__PURE__ */ jsx5("p", { children: props.copy.noPurchases })
442
+ ] }),
443
+ /* @__PURE__ */ jsxs5("div", { className: "card", children: [
444
+ /* @__PURE__ */ jsx5("div", { className: "card-label", children: props.copy.redeemTitle }),
445
+ /* @__PURE__ */ jsxs5("label", { children: [
446
+ props.copy.code,
447
+ /* @__PURE__ */ jsx5("input", { value: redeemCode, maxLength: 64, onInput: (event) => setRedeemCode(event.currentTarget.value) })
448
+ ] }),
449
+ /* @__PURE__ */ jsx5("button", { className: "btn secondary", disabled: !redeemCode, onClick: () => void redeem(), children: props.copy.redeem }),
450
+ message ? /* @__PURE__ */ jsx5("p", { role: "status", children: message }) : null
451
+ ] })
452
+ ] });
453
+ }
454
+
455
+ // src/ui/member-content.tsx
456
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState5 } from "preact/hooks";
457
+ import { Markdown } from "@odla-ai/ui/components";
458
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
459
+ var pageLabel = (page) => `${page.title}${page.access === "locked" ? " \xB7 Locked" : ""}`;
460
+ function MemberContentArea(props) {
461
+ const {
462
+ api,
463
+ path = "/api/member-content",
464
+ upsellHref,
465
+ onUpsell,
466
+ loadingContent = /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "Loading member pages\u2026" }),
467
+ emptyContent = /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "No member pages have been published yet." })
468
+ } = props;
469
+ const [data, setData] = useState5(props.initialData ?? null);
470
+ const [error, setError] = useState5(null);
471
+ const [selectedId, setSelectedId] = useState5(
472
+ props.initialPageId ?? props.initialData?.pages[0]?.id ?? ""
473
+ );
474
+ useEffect2(() => {
475
+ if (props.initialData) return;
476
+ let live = true;
477
+ setError(null);
478
+ void api(path).then((value) => {
479
+ if (!live) return;
480
+ setData(value);
481
+ setSelectedId((current) => value.pages.some((page) => page.id === current) ? current : value.pages[0]?.id ?? "");
482
+ }).catch((reason) => live && setError(reason instanceof Error ? reason.message : String(reason)));
483
+ return () => {
484
+ live = false;
485
+ };
486
+ }, [api, path, props.initialData]);
487
+ const sections = useMemo2(() => {
488
+ const result = /* @__PURE__ */ new Map();
489
+ for (const page of data?.pages ?? []) {
490
+ const list = result.get(page.navigation.section) ?? [];
491
+ list.push(page);
492
+ result.set(page.navigation.section, list);
493
+ }
494
+ return [...result.entries()];
495
+ }, [data]);
496
+ if (error) return /* @__PURE__ */ jsxs6("p", { className: "meeting-note", role: "alert", children: [
497
+ "Member pages could not be loaded: ",
498
+ error
499
+ ] });
500
+ if (!data) return /* @__PURE__ */ jsx6(Fragment2, { children: loadingContent });
501
+ if (data.pages.length === 0) return /* @__PURE__ */ jsx6(Fragment2, { children: emptyContent });
502
+ const selected = data.pages.find((page) => page.id === selectedId) ?? data.pages[0];
503
+ const href = selected.upsell && upsellHref ? upsellHref(selected.upsell.tierId, selected) : void 0;
504
+ return /* @__PURE__ */ jsxs6("div", { className: "chapter-member-content", "data-member-content": true, children: [
505
+ /* @__PURE__ */ jsx6("nav", { className: "chapter-member-content-nav", "aria-label": "Member pages", children: sections.map(([section, pages]) => /* @__PURE__ */ jsxs6("section", { className: "chapter-member-content-section", children: [
506
+ /* @__PURE__ */ jsx6("h2", { children: section }),
507
+ /* @__PURE__ */ jsx6("ul", { children: pages.map((page) => /* @__PURE__ */ jsx6("li", { "data-parent-page": page.navigation.parentId ?? void 0, children: /* @__PURE__ */ jsx6(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ "aria-current": page.id === selected.id ? "page" : void 0,
512
+ onClick: () => setSelectedId(page.id),
513
+ children: pageLabel(page)
514
+ }
515
+ ) }, page.id)) })
516
+ ] }, section)) }),
517
+ /* @__PURE__ */ jsxs6("article", { className: "chapter-member-content-page", "data-access": selected.access, children: [
518
+ /* @__PURE__ */ jsxs6("header", { children: [
519
+ /* @__PURE__ */ jsxs6("div", { className: "card-label", children: [
520
+ selected.source.type === "parent" ? "Parent" : "Chapter",
521
+ " \xB7 ",
522
+ selected.access
523
+ ] }),
524
+ /* @__PURE__ */ jsx6("h1", { children: selected.title }),
525
+ selected.summary ? /* @__PURE__ */ jsx6("p", { children: selected.summary }) : null
526
+ ] }),
527
+ selected.access === "allowed" && selected.bodyMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.bodyMarkdown }) : /* @__PURE__ */ jsxs6("div", { className: "chapter-member-content-locked", children: [
528
+ selected.teaserMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.teaserMarkdown }) : /* @__PURE__ */ jsx6("p", { children: "This page is not included in your current membership." }),
529
+ selected.upsell ? /* @__PURE__ */ jsxs6("aside", { className: "chapter-member-content-upsell", "aria-label": selected.upsell.label ?? "Membership option", children: [
530
+ /* @__PURE__ */ jsx6("h2", { children: selected.upsell.label ?? selected.upsell.tierName }),
531
+ /* @__PURE__ */ jsx6("p", { children: fmtMoney(selected.upsell.priceCents) }),
532
+ selected.upsell.bodyMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.upsell.bodyMarkdown }) : null,
533
+ href ? /* @__PURE__ */ jsx6("a", { className: "btn primary", href, children: selected.upsell.ctaLabel ?? `Explore ${selected.upsell.tierName}` }) : onUpsell ? /* @__PURE__ */ jsx6("button", { className: "btn primary", type: "button", onClick: () => onUpsell(selected.upsell.tierId, selected), children: selected.upsell.ctaLabel ?? `Explore ${selected.upsell.tierName}` }) : null
534
+ ] }) : null
535
+ ] })
536
+ ] })
537
+ ] });
538
+ }
539
+
540
+ // src/ui/members.tsx
541
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
542
+ function card(copy, kicker, body) {
543
+ return /* @__PURE__ */ jsxs7("div", { className: "card", children: [
544
+ /* @__PURE__ */ jsx7("div", { className: "card-label", children: copy.provisional.cardLabel }),
545
+ /* @__PURE__ */ jsxs7("div", { className: "meeting-block", children: [
546
+ /* @__PURE__ */ jsx7("div", { className: "meeting-kicker", children: kicker }),
547
+ body
548
+ ] })
549
+ ] });
550
+ }
551
+ function ProvisionalCard(props) {
552
+ const { api, application, applyHref, copy, onReschedule } = props;
553
+ if (!application) {
554
+ return card(
555
+ copy,
556
+ copy.provisional.applicationNeeded,
557
+ /* @__PURE__ */ jsxs7(Fragment3, { children: [
558
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
559
+ /* @__PURE__ */ jsx7("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
560
+ ] })
561
+ );
562
+ }
563
+ if (application.status === "refunded") {
564
+ return card(
565
+ copy,
566
+ copy.provisional.refunded,
567
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.refundedBody })
568
+ );
569
+ }
570
+ const membership = application.paid ? /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: application.renewalAt ? formatChapterCopy(copy.provisional.renews, { date: fmtDate(application.renewalAt) }) : copy.provisional.active }) : null;
571
+ if (application.meetingAt) {
572
+ return card(
573
+ copy,
574
+ copy.provisional.introductionCall,
575
+ /* @__PURE__ */ jsxs7(Fragment3, { children: [
576
+ /* @__PURE__ */ jsx7("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
577
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
578
+ application.meetUrl ? /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: /* @__PURE__ */ jsx7("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
579
+ /* @__PURE__ */ jsx7(
580
+ Rescheduler,
581
+ {
582
+ api,
583
+ applicationId: application.id,
584
+ timezone: application.timezone,
585
+ copy: copy.reschedule,
586
+ onRescheduled: onReschedule
587
+ }
588
+ ),
589
+ membership
590
+ ] })
591
+ );
592
+ }
593
+ return card(
594
+ copy,
595
+ copy.provisional.bookCall,
596
+ /* @__PURE__ */ jsxs7(Fragment3, { children: [
597
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
598
+ /* @__PURE__ */ jsx7(
599
+ Rescheduler,
600
+ {
601
+ api,
602
+ applicationId: application.id,
603
+ timezone: application.timezone,
604
+ copy: copy.reschedule,
605
+ onRescheduled: onReschedule,
606
+ label: copy.provisional.chooseTime
607
+ }
608
+ ),
609
+ membership
610
+ ] })
611
+ );
612
+ }
613
+ function MembersArea(props) {
614
+ const inheritedCopy = useChapterCopy();
615
+ const {
616
+ api,
617
+ signOut,
618
+ adminHref = "/admin/",
619
+ applyHref = "/join.html",
620
+ memberContent,
621
+ memberPages,
622
+ renderProvisional
623
+ } = props;
624
+ const copy = props.copy ?? inheritedCopy.members;
625
+ const [me, setMe] = useState6(null);
626
+ const [error, setError] = useState6(false);
627
+ const reload = async () => {
628
+ try {
629
+ setMe(await api("/api/me"));
630
+ } catch {
631
+ setError(true);
632
+ }
633
+ };
634
+ useEffect3(() => {
635
+ void reload();
636
+ }, []);
637
+ if (error) return /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: copy.loadFailed });
638
+ if (!me) return /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: inheritedCopy.common.loading });
639
+ const role = me.role || "provisional";
640
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
641
+ /* @__PURE__ */ jsxs7("div", { className: "card", children: [
642
+ /* @__PURE__ */ jsxs7("div", { className: "member-row", children: [
643
+ /* @__PURE__ */ jsx7("span", { className: "member-email", children: me.email }),
644
+ /* @__PURE__ */ jsx7("span", { className: "role-badge " + role, children: role })
645
+ ] }),
646
+ /* @__PURE__ */ jsxs7("div", { className: "account-actions", children: [
647
+ /* @__PURE__ */ jsx7("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
648
+ role === "admin" ? /* @__PURE__ */ jsx7("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
649
+ ] })
650
+ ] }),
651
+ role === "provisional" ? (() => {
652
+ const defaultContent = /* @__PURE__ */ jsx7(
653
+ ProvisionalCard,
654
+ {
655
+ api,
656
+ application: me.application,
657
+ applyHref,
658
+ copy,
659
+ onReschedule: reload
660
+ }
661
+ );
662
+ return renderProvisional?.({
663
+ api,
664
+ application: me.application,
665
+ applyHref,
666
+ reload,
667
+ defaultContent
668
+ }) ?? defaultContent;
669
+ })() : memberContent ?? (memberPages ? /* @__PURE__ */ jsx7(
670
+ MemberContentArea,
671
+ {
672
+ api,
673
+ ...memberPages === true ? {} : memberPages
674
+ }
675
+ ) : /* @__PURE__ */ jsx7("div", { className: "card", children: /* @__PURE__ */ jsx7("div", { className: "card-label", children: copy.full.welcome }) })),
676
+ /* @__PURE__ */ jsx7(MemberGifts, { api, copy: copy.gifts, paymentCopy: inheritedCopy.join.payment })
677
+ ] });
678
+ }
679
+
680
+ // src/ui/join.tsx
681
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState10 } from "preact/hooks";
682
+
683
+ // src/ui/join-booking.tsx
684
+ import { useEffect as useEffect4, useState as useState7 } from "preact/hooks";
685
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
477
686
  function JoinBooking(props) {
478
687
  const { applicationId, copy, onBooked, simulation } = props;
479
- const [state, setState] = useState5(null);
480
- const [msg, setMsg] = useState5(null);
481
- const [busy, setBusy] = useState5(false);
482
- const [selected, setSelected] = useState5(null);
688
+ const [state, setState] = useState7(null);
689
+ const [msg, setMsg] = useState7(null);
690
+ const [busy, setBusy] = useState7(false);
691
+ const [selected, setSelected] = useState7(null);
483
692
  const load = async () => {
484
693
  setMsg(null);
485
694
  try {
@@ -504,7 +713,7 @@ function JoinBooking(props) {
504
713
  setMsg(copy.loadFailed);
505
714
  }
506
715
  };
507
- useEffect2(() => {
716
+ useEffect4(() => {
508
717
  void load();
509
718
  }, [simulation]);
510
719
  const book = async () => {
@@ -536,9 +745,9 @@ function JoinBooking(props) {
536
745
  setBusy(false);
537
746
  }
538
747
  };
539
- if (!state) return /* @__PURE__ */ jsx5("p", { className: "slots-status", children: msg ?? copy.loading });
540
- return /* @__PURE__ */ jsxs5("div", { className: "join-book", children: [
541
- /* @__PURE__ */ jsx5(
748
+ if (!state) return /* @__PURE__ */ jsx8("p", { className: "slots-status", children: msg ?? copy.loading });
749
+ return /* @__PURE__ */ jsxs8("div", { className: "join-book", children: [
750
+ /* @__PURE__ */ jsx8(
542
751
  SlotPicker,
543
752
  {
544
753
  slots: state.slots,
@@ -548,18 +757,58 @@ function JoinBooking(props) {
548
757
  onDayChange: () => setSelected(null)
549
758
  }
550
759
  ),
551
- /* @__PURE__ */ jsxs5("div", { className: "slot-confirm", hidden: !selected, children: [
552
- /* @__PURE__ */ jsx5("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
553
- msg ? /* @__PURE__ */ jsx5("p", { className: "step2-note", children: msg }) : null
760
+ /* @__PURE__ */ jsxs8("div", { className: "slot-confirm", hidden: !selected, children: [
761
+ /* @__PURE__ */ jsx8("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
762
+ msg ? /* @__PURE__ */ jsx8("p", { className: "step2-note", children: msg }) : null
554
763
  ] })
555
764
  ] });
556
765
  }
557
766
 
767
+ // src/ui/join-accepted.tsx
768
+ import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
769
+ function JoinAccepted(props) {
770
+ return /* @__PURE__ */ jsxs9("div", { className: "join-done card", children: [
771
+ /* @__PURE__ */ jsx9("div", { className: "card-label", children: props.copy.accepted.label }),
772
+ /* @__PURE__ */ jsx9("p", { className: "meeting-note", children: props.copy.accepted.body }),
773
+ /* @__PURE__ */ jsx9("a", { className: "apply-link", href: props.membersHref, children: props.copy.done.memberArea })
774
+ ] });
775
+ }
776
+
777
+ // src/ui/gift-claim-card.tsx
778
+ import { useState as useState8 } from "preact/hooks";
779
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
780
+ function GiftClaimCard(props) {
781
+ const [code, setCode] = useState8(props.initialCode ?? "");
782
+ const [error, setError] = useState8(false);
783
+ const rotate = async () => {
784
+ if (!props.claimId) return;
785
+ setError(false);
786
+ const response = await fetch(`/api/gifts/${encodeURIComponent(props.claimId)}/rotate`, {
787
+ method: "POST",
788
+ headers: { "content-type": "application/json" },
789
+ body: JSON.stringify({ applicationId: props.applicationId })
790
+ });
791
+ const body = await response.json();
792
+ if (response.ok && body.code) setCode(body.code);
793
+ else setError(true);
794
+ };
795
+ return /* @__PURE__ */ jsxs10("div", { className: "join-done card", children: [
796
+ /* @__PURE__ */ jsx10("div", { className: "card-label", children: props.copy.label }),
797
+ /* @__PURE__ */ jsx10("p", { className: "meeting-note", children: props.copy.body }),
798
+ code ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
799
+ /* @__PURE__ */ jsx10("output", { className: "gift-code", children: code }),
800
+ /* @__PURE__ */ jsx10("p", { children: props.copy.save })
801
+ ] }) : null,
802
+ !code && props.claimId ? /* @__PURE__ */ jsx10("button", { type: "button", className: "btn secondary", onClick: () => void rotate(), children: props.copy.rotate }) : null,
803
+ error ? /* @__PURE__ */ jsx10("p", { role: "alert", children: props.copy.rotateFailed }) : null
804
+ ] });
805
+ }
806
+
558
807
  // src/ui/join-tiers.tsx
559
- import { useState as useState6 } from "preact/hooks";
560
- import { jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
808
+ import { useState as useState9 } from "preact/hooks";
809
+ import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
561
810
  function useTierSelection(tiers, initialTierId) {
562
- const [selectedTierId, setSelectedTierId] = useState6(() => {
811
+ const [selectedTierId, setSelectedTierId] = useState9(() => {
563
812
  const preset = tiers.find((tier) => tier.id === initialTierId);
564
813
  if (preset) return preset.id;
565
814
  return tiers.length === 1 ? tiers[0].id : null;
@@ -574,10 +823,10 @@ function useTierSelection(tiers, initialTierId) {
574
823
  };
575
824
  }
576
825
  function TierPlaceholder(context) {
577
- return /* @__PURE__ */ jsxs6("fieldset", { "data-chapter-placeholder": "tiers", children: [
578
- /* @__PURE__ */ jsx6("legend", { children: "Membership (placeholder \u2014 pass renderTiers to style this)" }),
579
- context.tiers.map((tier) => /* @__PURE__ */ jsxs6("label", { children: [
580
- /* @__PURE__ */ jsx6(
826
+ return /* @__PURE__ */ jsxs11("fieldset", { "data-chapter-placeholder": "tiers", children: [
827
+ /* @__PURE__ */ jsx11("legend", { children: "Membership (placeholder \u2014 pass renderTiers to style this)" }),
828
+ context.tiers.map((tier) => /* @__PURE__ */ jsxs11("label", { children: [
829
+ /* @__PURE__ */ jsx11(
581
830
  "input",
582
831
  {
583
832
  type: "radio",
@@ -595,23 +844,21 @@ function tierNeedsPayment(selected, paymentsReady) {
595
844
  return selected ? !selected.free && paymentsReady : paymentsReady;
596
845
  }
597
846
 
598
- // src/ui/join.tsx
599
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
847
+ // src/ui/join-resume.ts
600
848
  async function loadJoinResume(applicationId, fetcher = fetch) {
601
849
  const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);
602
850
  const data = await res.json();
603
851
  if (!res.ok) throw new Error(data.error ?? "The application could not be resumed.");
604
- if (data.state === "done" && data.booked) {
605
- return { step: "done", applicationId: data.applicationId, booked: data.booked };
606
- }
607
- if (data.state === "paymentPending") {
608
- return { step: "paymentPending", applicationId: data.applicationId };
609
- }
610
- if (data.state === "booking") {
611
- return { step: "booking", applicationId: data.applicationId };
612
- }
852
+ if (data.state === "done" && data.booked) return { step: "done", applicationId: data.applicationId, booked: data.booked };
853
+ if (data.state === "paymentPending") return { step: "paymentPending", applicationId: data.applicationId };
854
+ if (data.state === "booking") return { step: "booking", applicationId: data.applicationId };
855
+ if (data.state === "accepted") return { step: "accepted", applicationId: data.applicationId };
856
+ if (data.state === "gift") return { step: "gift", applicationId: data.applicationId, ...data.claimId ? { claimId: data.claimId } : {} };
613
857
  return { step: "payment", applicationId: data.applicationId };
614
858
  }
859
+
860
+ // src/ui/join.tsx
861
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
615
862
  function JoinIsland(props) {
616
863
  const inheritedCopy = useChapterCopy();
617
864
  const {
@@ -620,15 +867,18 @@ function JoinIsland(props) {
620
867
  membersHref = "/members/",
621
868
  renderStepHeader,
622
869
  renderDone,
870
+ renderAccepted,
871
+ renderGift,
623
872
  renderSubmit
624
873
  } = props;
625
874
  const copy = props.copy ?? config.copy ?? inheritedCopy.join;
626
875
  const tiers = config.tiers ?? [];
627
876
  const tierSelection = useTierSelection(tiers, props.initialTierId);
628
877
  const { selectedTierId, selected: selectedTier } = tierSelection;
629
- const [state, setState] = useState7(props.initialState ?? { step: "form" });
630
- const [error, setError] = useState7(null);
631
- const [submitting, setSubmitting] = useState7(false);
878
+ const [state, setState] = useState10(props.initialState ?? { step: "form" });
879
+ const [error, setError] = useState10(null);
880
+ const [submitting, setSubmitting] = useState10(false);
881
+ const [giftOfferId, setGiftOfferId] = useState10(props.initialGiftOfferId ?? null);
632
882
  const simulatedPayment = props.simulation ? props.simulation.payment ?? {
633
883
  lineItems: {
634
884
  standardCents: selectedTier?.priceCents ?? 0,
@@ -637,8 +887,8 @@ function JoinIsland(props) {
637
887
  }
638
888
  } : void 0;
639
889
  const simulatedBooking = props.simulation ? props.simulation.booking ?? { slots: [], timezone: "UTC" } : void 0;
640
- const submissionId = useRef2(null);
641
- useEffect3(() => {
890
+ const submissionId = useRef3(null);
891
+ useEffect5(() => {
642
892
  if (props.simulation) return;
643
893
  const resolver = props.loadResume ?? (() => {
644
894
  if (typeof window === "undefined") return Promise.resolve(null);
@@ -655,7 +905,7 @@ function JoinIsland(props) {
655
905
  live = false;
656
906
  };
657
907
  }, [props.loadResume, props.simulation]);
658
- useEffect3(() => {
908
+ useEffect5(() => {
659
909
  if (state.step !== "paymentPending") return;
660
910
  let live = true;
661
911
  let timer;
@@ -683,12 +933,13 @@ function JoinIsland(props) {
683
933
  try {
684
934
  const fields = collectFormFields(new FormData(e.currentTarget));
685
935
  fields.submissionId = submissionId.current ??= crypto.randomUUID();
686
- if (selectedTierId) fields.tierId = selectedTierId;
936
+ if (giftOfferId) fields.giftOfferId = giftOfferId;
937
+ else if (selectedTierId) fields.tierId = selectedTierId;
687
938
  if (props.simulation) {
688
939
  setState({
689
940
  // Preview the intended paid journey even before live Stripe wiring is
690
941
  // ready. Production still uses the server-authoritative readiness bit.
691
- step: tierNeedsPayment(selectedTier, true) ? "payment" : "booking",
942
+ step: giftOfferId || tierNeedsPayment(selectedTier, true) ? "payment" : "booking",
692
943
  applicationId: props.simulation.applicationId ?? "simulation-application"
693
944
  });
694
945
  return;
@@ -700,6 +951,18 @@ function JoinIsland(props) {
700
951
  });
701
952
  const data = await res.json();
702
953
  if (!res.ok || !data.id) throw new Error(data.error ?? copy.form.submitFailed);
954
+ if (data.state === "accepted") {
955
+ setState({ step: "accepted", applicationId: data.id });
956
+ return;
957
+ }
958
+ if (data.state === "paymentPending") {
959
+ setState({ step: "paymentPending", applicationId: data.id });
960
+ return;
961
+ }
962
+ if (data.state === "payment" || data.state === "booking") {
963
+ setState({ step: data.state, applicationId: data.id });
964
+ return;
965
+ }
703
966
  setState({
704
967
  step: tierNeedsPayment(selectedTier, config.paymentsReady) ? "payment" : "booking",
705
968
  applicationId: data.id
@@ -715,21 +978,35 @@ function JoinIsland(props) {
715
978
  ..."applicationId" in state ? { applicationId: state.applicationId } : {}
716
979
  });
717
980
  if (state.step === "done") {
718
- const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs7("div", { className: "join-done card", children: [
719
- /* @__PURE__ */ jsx7("div", { className: "card-label", children: copy.done.label }),
720
- /* @__PURE__ */ jsx7("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
721
- /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: copy.done.calendarInvite }),
722
- /* @__PURE__ */ jsx7("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
981
+ const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs12("div", { className: "join-done card", children: [
982
+ /* @__PURE__ */ jsx12("div", { className: "card-label", children: copy.done.label }),
983
+ /* @__PURE__ */ jsx12("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
984
+ /* @__PURE__ */ jsx12("p", { className: "meeting-note", children: copy.done.calendarInvite }),
985
+ /* @__PURE__ */ jsx12("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
723
986
  ] });
724
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
987
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
988
+ header,
989
+ body
990
+ ] });
991
+ }
992
+ if (state.step === "accepted") {
993
+ const body = renderAccepted?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsx12(JoinAccepted, { copy, membersHref });
994
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
995
+ header,
996
+ body
997
+ ] });
998
+ }
999
+ if (state.step === "gift") {
1000
+ const body = renderGift?.(state) ?? /* @__PURE__ */ jsx12(GiftClaimCard, { applicationId: state.applicationId, claimId: state.claimId, initialCode: state.code, copy: copy.gift });
1001
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
725
1002
  header,
726
1003
  body
727
1004
  ] });
728
1005
  }
729
1006
  if (state.step === "booking") {
730
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1007
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
731
1008
  header,
732
- /* @__PURE__ */ jsx7(
1009
+ /* @__PURE__ */ jsx12(
733
1010
  JoinBooking,
734
1011
  {
735
1012
  applicationId: state.applicationId,
@@ -743,9 +1020,9 @@ function JoinIsland(props) {
743
1020
  ] });
744
1021
  }
745
1022
  if (state.step === "payment") {
746
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1023
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
747
1024
  header,
748
- /* @__PURE__ */ jsx7(
1025
+ /* @__PURE__ */ jsx12(
749
1026
  PaymentStep,
750
1027
  {
751
1028
  applicationId: state.applicationId,
@@ -755,33 +1032,48 @@ function JoinIsland(props) {
755
1032
  fonts: props.payment?.fonts,
756
1033
  renderPriceLines: props.payment?.renderPriceLines,
757
1034
  simulation: simulatedPayment,
758
- onPaid: () => setState({ step: "booking", applicationId: state.applicationId }),
1035
+ onPaid: (result) => {
1036
+ if (props.simulation) {
1037
+ setState(giftOfferId ? { step: "gift", applicationId: state.applicationId, code: "gift1_simulation_only_not_a_real_claim_code" } : { step: "booking", applicationId: state.applicationId });
1038
+ return;
1039
+ }
1040
+ if (result.gift && result.giftCode) {
1041
+ setState({ step: "gift", applicationId: state.applicationId, code: result.giftCode });
1042
+ return;
1043
+ }
1044
+ setState({ step: "paymentPending", applicationId: state.applicationId });
1045
+ },
759
1046
  children: props.payment?.children
760
1047
  }
761
1048
  )
762
1049
  ] });
763
1050
  }
764
1051
  if (state.step === "paymentPending") {
765
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1052
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
766
1053
  header,
767
- /* @__PURE__ */ jsx7("p", { className: "pay-status", role: "status", children: copy.payment.pending })
1054
+ /* @__PURE__ */ jsx12("p", { className: "pay-status", role: "status", children: copy.payment.pending })
768
1055
  ] });
769
1056
  }
770
1057
  const submitDisabled = submitting || props.submitDisabled === true;
771
1058
  const tierChooser = tiers.length > 1 ? props.renderTiers?.(tierSelection) ?? TierPlaceholder(tierSelection) : null;
772
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1059
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
773
1060
  header,
774
- /* @__PURE__ */ jsxs7("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
1061
+ /* @__PURE__ */ jsxs12("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
1062
+ (config.giftOffers?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs12("fieldset", { children: [
1063
+ /* @__PURE__ */ jsx12("legend", { children: copy.gift.optionsLabel }),
1064
+ /* @__PURE__ */ jsx12("button", { type: "button", className: "btn secondary", "aria-pressed": !giftOfferId, onClick: () => setGiftOfferId(null), children: copy.gift.chooseSelf }),
1065
+ /* @__PURE__ */ jsx12(GiftOfferCards, { offers: config.giftOffers ?? [], mode: "select", selectedOfferId: giftOfferId, onSelect: setGiftOfferId })
1066
+ ] }) : null,
775
1067
  tierChooser,
776
1068
  children,
777
- error ? /* @__PURE__ */ jsx7("p", { className: "join-error", children: error }) : null,
778
- renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx7("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
1069
+ error ? /* @__PURE__ */ jsx12("p", { className: "join-error", children: error }) : null,
1070
+ renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx12("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
779
1071
  ] })
780
1072
  ] });
781
1073
  }
782
1074
 
783
1075
  // src/ui/membership-cards.tsx
784
- import { Markdown } from "@odla-ai/ui/components";
1076
+ import { Markdown as Markdown2 } from "@odla-ai/ui/components";
785
1077
 
786
1078
  // src/membership-page.ts
787
1079
  var MEMBERSHIP_PAGE_RENDERER = "membership-cards-v1";
@@ -952,7 +1244,7 @@ function membershipPageDocument(input, options = {}) {
952
1244
  }
953
1245
 
954
1246
  // src/ui/membership-cards.tsx
955
- import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
1247
+ import { jsx as jsx13, jsxs as jsxs13 } from "preact/jsx-runtime";
956
1248
  var MEMBERSHIP_CARD_TOKEN_DEFAULTS = Object.freeze({
957
1249
  "--ui-membership-grid-gap": "1.4rem",
958
1250
  "--ui-membership-card-padding": "2rem 1.7rem 1.8rem",
@@ -1021,67 +1313,67 @@ function membershipApplicationHref(applicationPath, tierId) {
1021
1313
  }
1022
1314
  function TierAction(props) {
1023
1315
  if (props.mode === "select") {
1024
- return /* @__PURE__ */ jsxs8("button", { type: "button", className: "chapter-membership-action", "aria-current": props.selected ? "true" : void 0, onClick: () => props.onSelectTier?.(props.tierId), children: [
1316
+ return /* @__PURE__ */ jsxs13("button", { type: "button", className: "chapter-membership-action", "aria-current": props.selected ? "true" : void 0, onClick: () => props.onSelectTier?.(props.tierId), children: [
1025
1317
  props.label,
1026
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1318
+ /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\u2192" })
1027
1319
  ] });
1028
1320
  }
1029
1321
  if (props.mode === "preview") {
1030
- return /* @__PURE__ */ jsxs8("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1322
+ return /* @__PURE__ */ jsxs13("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1031
1323
  props.label,
1032
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1324
+ /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\u2192" })
1033
1325
  ] });
1034
1326
  }
1035
- return /* @__PURE__ */ jsxs8("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1327
+ return /* @__PURE__ */ jsxs13("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1036
1328
  props.label,
1037
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1329
+ /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\u2192" })
1038
1330
  ] });
1039
1331
  }
1040
1332
  function TierCard(props) {
1041
1333
  const { tier, presentation } = props;
1042
1334
  const summary = presentation.summaryMarkdown ?? tier.blurb;
1043
1335
  const action = presentation.ctaLabel ?? (tier.free ? "Join for free" : `Choose ${tier.name}`);
1044
- return /* @__PURE__ */ jsxs8("article", { className: "chapter-membership-card", "data-tier-id": tier.id, "data-variant": presentation.variant ?? "standard", children: [
1045
- presentation.badge ? /* @__PURE__ */ jsx8("div", { className: "chapter-membership-badge", children: presentation.badge }) : null,
1046
- /* @__PURE__ */ jsx8("h3", { className: "chapter-membership-name", children: tier.name }),
1047
- /* @__PURE__ */ jsxs8("div", { className: "chapter-membership-offer", "data-with-capacity": presentation.capacity ? "true" : void 0, children: [
1048
- presentation.capacity ? /* @__PURE__ */ jsxs8("div", { className: "chapter-membership-capacity", "aria-label": `${presentation.capacity.total} ${presentation.capacity.label ?? "available"}`, children: [
1049
- /* @__PURE__ */ jsx8("strong", { children: presentation.capacity.total.toLocaleString("en-US") }),
1050
- /* @__PURE__ */ jsx8("small", { children: presentation.capacity.label ?? "available" })
1336
+ return /* @__PURE__ */ jsxs13("article", { className: "chapter-membership-card", "data-tier-id": tier.id, "data-variant": presentation.variant ?? "standard", children: [
1337
+ presentation.badge ? /* @__PURE__ */ jsx13("div", { className: "chapter-membership-badge", children: presentation.badge }) : null,
1338
+ /* @__PURE__ */ jsx13("h3", { className: "chapter-membership-name", children: tier.name }),
1339
+ /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-offer", "data-with-capacity": presentation.capacity ? "true" : void 0, children: [
1340
+ presentation.capacity ? /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-capacity", "aria-label": `${presentation.capacity.total} ${presentation.capacity.label ?? "available"}`, children: [
1341
+ /* @__PURE__ */ jsx13("strong", { children: presentation.capacity.total.toLocaleString("en-US") }),
1342
+ /* @__PURE__ */ jsx13("small", { children: presentation.capacity.label ?? "available" })
1051
1343
  ] }) : null,
1052
- /* @__PURE__ */ jsxs8("div", { className: "chapter-membership-price-stack", children: [
1053
- presentation.priceEyebrow ? /* @__PURE__ */ jsx8("p", { className: "chapter-membership-price-eyebrow", children: presentation.priceEyebrow }) : null,
1054
- /* @__PURE__ */ jsxs8("div", { className: "chapter-membership-price-row", children: [
1055
- presentation.compareAtPriceCents !== void 0 ? /* @__PURE__ */ jsx8("span", { className: "chapter-membership-compare", children: fmtMoney(presentation.compareAtPriceCents) }) : null,
1056
- /* @__PURE__ */ jsx8("span", { className: "chapter-membership-price", children: tier.free ? "Free" : fmtMoney(tier.priceCents) }),
1057
- !tier.free && presentation.billingLabel ? /* @__PURE__ */ jsx8("span", { className: "chapter-membership-billing", children: presentation.billingLabel }) : null
1344
+ /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-price-stack", children: [
1345
+ presentation.priceEyebrow ? /* @__PURE__ */ jsx13("p", { className: "chapter-membership-price-eyebrow", children: presentation.priceEyebrow }) : null,
1346
+ /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-price-row", children: [
1347
+ presentation.compareAtPriceCents !== void 0 ? /* @__PURE__ */ jsx13("span", { className: "chapter-membership-compare", children: fmtMoney(presentation.compareAtPriceCents) }) : null,
1348
+ /* @__PURE__ */ jsx13("span", { className: "chapter-membership-price", children: tier.free ? "Free" : fmtMoney(tier.priceCents) }),
1349
+ !tier.free && presentation.billingLabel ? /* @__PURE__ */ jsx13("span", { className: "chapter-membership-billing", children: presentation.billingLabel }) : null
1058
1350
  ] })
1059
1351
  ] })
1060
1352
  ] }),
1061
- presentation.priceNoteMarkdown ? /* @__PURE__ */ jsx8(Markdown, { className: "chapter-membership-price-note", source: presentation.priceNoteMarkdown }) : null,
1062
- summary ? /* @__PURE__ */ jsx8(Markdown, { className: "chapter-membership-summary", source: summary }) : null,
1063
- presentation.benefits?.length ? /* @__PURE__ */ jsx8("ul", { className: "chapter-membership-benefits", children: presentation.benefits.map((benefit, index) => /* @__PURE__ */ jsxs8("li", { children: [
1064
- /* @__PURE__ */ jsx8("span", { className: "chapter-membership-check", "aria-hidden": "true", children: "\u2713" }),
1065
- /* @__PURE__ */ jsx8(Markdown, { className: "chapter-membership-benefit", source: benefit })
1353
+ presentation.priceNoteMarkdown ? /* @__PURE__ */ jsx13(Markdown2, { className: "chapter-membership-price-note", source: presentation.priceNoteMarkdown }) : null,
1354
+ summary ? /* @__PURE__ */ jsx13(Markdown2, { className: "chapter-membership-summary", source: summary }) : null,
1355
+ presentation.benefits?.length ? /* @__PURE__ */ jsx13("ul", { className: "chapter-membership-benefits", children: presentation.benefits.map((benefit, index) => /* @__PURE__ */ jsxs13("li", { children: [
1356
+ /* @__PURE__ */ jsx13("span", { className: "chapter-membership-check", "aria-hidden": "true", children: "\u2713" }),
1357
+ /* @__PURE__ */ jsx13(Markdown2, { className: "chapter-membership-benefit", source: benefit })
1066
1358
  ] }, `${tier.id}-benefit-${index}`)) }) : null,
1067
- presentation.callout ? /* @__PURE__ */ jsxs8("aside", { className: "chapter-membership-callout", children: [
1068
- presentation.callout.label ? /* @__PURE__ */ jsx8("p", { className: "chapter-membership-callout-label", children: presentation.callout.label }) : null,
1069
- /* @__PURE__ */ jsx8(Markdown, { source: presentation.callout.bodyMarkdown })
1359
+ presentation.callout ? /* @__PURE__ */ jsxs13("aside", { className: "chapter-membership-callout", children: [
1360
+ presentation.callout.label ? /* @__PURE__ */ jsx13("p", { className: "chapter-membership-callout-label", children: presentation.callout.label }) : null,
1361
+ /* @__PURE__ */ jsx13(Markdown2, { source: presentation.callout.bodyMarkdown })
1070
1362
  ] }) : null,
1071
- /* @__PURE__ */ jsx8(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1363
+ /* @__PURE__ */ jsx13(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1072
1364
  ] });
1073
1365
  }
1074
1366
  function MembershipCards(props) {
1075
1367
  const document2 = membershipPageDocument(props.document, { tiers: props.tiers });
1076
1368
  const byId = new Map(props.tiers.map((tier) => [tier.id, tier]));
1077
1369
  const mode = props.mode ?? "link";
1078
- return /* @__PURE__ */ jsxs8("section", { className: ["chapter-membership", props.className].filter(Boolean).join(" "), "data-chapter-membership": MEMBERSHIP_PAGE_RENDERER, "aria-label": props.ariaLabel ?? "Membership options", children: [
1079
- /* @__PURE__ */ jsx8("style", { children: MEMBERSHIP_CARDS_CSS }),
1080
- /* @__PURE__ */ jsx8("div", { className: "chapter-membership-grid", style: { "--chapter-membership-columns": String(Math.min(document2.tiers.length, 3)) }, children: document2.tiers.map((presentation) => {
1370
+ return /* @__PURE__ */ jsxs13("section", { className: ["chapter-membership", props.className].filter(Boolean).join(" "), "data-chapter-membership": MEMBERSHIP_PAGE_RENDERER, "aria-label": props.ariaLabel ?? "Membership options", children: [
1371
+ /* @__PURE__ */ jsx13("style", { children: MEMBERSHIP_CARDS_CSS }),
1372
+ /* @__PURE__ */ jsx13("div", { className: "chapter-membership-grid", style: { "--chapter-membership-columns": String(Math.min(document2.tiers.length, 3)) }, children: document2.tiers.map((presentation) => {
1081
1373
  const tier = byId.get(presentation.tierId);
1082
- return /* @__PURE__ */ jsx8(TierCard, { tier, presentation, mode, selected: props.selectedTierId === tier.id, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier }, tier.id);
1374
+ return /* @__PURE__ */ jsx13(TierCard, { tier, presentation, mode, selected: props.selectedTierId === tier.id, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier }, tier.id);
1083
1375
  }) }),
1084
- document2.footnoteMarkdown ? /* @__PURE__ */ jsx8(Markdown, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1376
+ document2.footnoteMarkdown ? /* @__PURE__ */ jsx13(Markdown2, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1085
1377
  ] });
1086
1378
  }
1087
1379
 
@@ -1096,8 +1388,11 @@ export {
1096
1388
  groupSlotsByDay,
1097
1389
  SlotPicker,
1098
1390
  Rescheduler,
1099
- MembersArea,
1391
+ GiftOfferCards,
1100
1392
  PaymentStep,
1393
+ MemberGifts,
1394
+ MemberContentArea,
1395
+ MembersArea,
1101
1396
  useTierSelection,
1102
1397
  TierPlaceholder,
1103
1398
  tierNeedsPayment,
@@ -1107,4 +1402,4 @@ export {
1107
1402
  membershipApplicationHref,
1108
1403
  MembershipCards
1109
1404
  };
1110
- //# sourceMappingURL=chunk-UJIVBGL5.js.map
1405
+ //# sourceMappingURL=chunk-BIYWPPH6.js.map