@odla-ai/chapter 0.41.0 → 0.42.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-IAWBE5PX.js";
4
+ } from "./chunk-JXFYOUDL.js";
5
5
  import {
6
6
  collectFormFields
7
7
  } from "./chunk-IVXVECKR.js";
@@ -27,7 +27,12 @@ function dayLabel(ms, tz) {
27
27
  return new Date(ms).toLocaleDateString(void 0, { timeZone: tz, weekday: "short", month: "short", day: "numeric" });
28
28
  }
29
29
  function timeLabel(ms, tz) {
30
- return new Date(ms).toLocaleTimeString(void 0, { timeZone: tz, hour: "numeric", minute: "2-digit" });
30
+ return new Date(ms).toLocaleTimeString(void 0, {
31
+ timeZone: tz,
32
+ hour: "numeric",
33
+ minute: "2-digit",
34
+ timeZoneName: "short"
35
+ });
31
36
  }
32
37
  function fullLabel(ms, tz) {
33
38
  return new Date(ms).toLocaleString(void 0, {
@@ -105,7 +110,7 @@ function SlotPicker(props) {
105
110
  }
106
111
 
107
112
  // src/ui/members.tsx
108
- import { useEffect as useEffect3, useState as useState7 } from "preact/hooks";
113
+ import { useEffect as useEffect3, useState as useState6 } from "preact/hooks";
109
114
 
110
115
  // src/ui/reschedule.tsx
111
116
  import { useState as useState2 } from "preact/hooks";
@@ -190,7 +195,7 @@ function Rescheduler(props) {
190
195
  }
191
196
 
192
197
  // src/ui/member-gifts.tsx
193
- import { useEffect, useRef as useRef2, useState as useState5 } from "preact/hooks";
198
+ import { useEffect, useRef as useRef2, useState as useState4 } from "preact/hooks";
194
199
 
195
200
  // src/ui/gift-offer-cards.tsx
196
201
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
@@ -208,78 +213,9 @@ function GiftOfferCards(props) {
208
213
  ] }, offer.id)) });
209
214
  }
210
215
 
211
- // src/ui/gift-code-redeemer.tsx
212
- import { InlineActionField } from "@odla-ai/ui/components";
213
- import { useState as useState3 } from "preact/hooks";
214
- import { jsx as jsx4 } from "preact/jsx-runtime";
215
- function GiftCodeRedeemer({ api, copy, id, onRedeemed }) {
216
- const [code, setCode] = useState3("");
217
- const [loading, setLoading] = useState3(false);
218
- const [message, setMessage] = useState3("");
219
- const [messageTone, setMessageTone] = useState3("neutral");
220
- const normalizedCode = code.trim();
221
- const redeem = async () => {
222
- if (!normalizedCode || loading) return;
223
- setLoading(true);
224
- setMessage("");
225
- setMessageTone("neutral");
226
- try {
227
- await api("/api/gifts/redeem", {
228
- method: "POST",
229
- headers: { "content-type": "application/json" },
230
- body: JSON.stringify({ code: normalizedCode })
231
- });
232
- setCode("");
233
- setMessage(copy.redeemed);
234
- setMessageTone("success");
235
- try {
236
- await onRedeemed?.();
237
- } catch {
238
- }
239
- } catch {
240
- setMessage(copy.redeemFailed);
241
- setMessageTone("error");
242
- } finally {
243
- setLoading(false);
244
- }
245
- };
246
- return /* @__PURE__ */ jsx4(
247
- InlineActionField,
248
- {
249
- id,
250
- className: "gift-code-redeemer",
251
- label: copy.code,
252
- value: code,
253
- code: true,
254
- actionLabel: copy.redeem,
255
- loadingLabel: `${copy.redeem}\u2026`,
256
- loading,
257
- actionDisabled: !normalizedCode,
258
- message,
259
- messageTone,
260
- inputProps: {
261
- name: "giftCode",
262
- maxLength: 64,
263
- autoComplete: "one-time-code",
264
- autoCapitalize: "none",
265
- autocorrect: "off",
266
- spellcheck: false
267
- },
268
- onValueChange: (value) => {
269
- setCode(value);
270
- if (message) {
271
- setMessage("");
272
- setMessageTone("neutral");
273
- }
274
- },
275
- onAction: () => void redeem()
276
- }
277
- );
278
- }
279
-
280
216
  // src/ui/payment-step.tsx
281
- import { useRef, useState as useState4 } from "preact/hooks";
282
- import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
217
+ import { useRef, useState as useState3 } from "preact/hooks";
218
+ import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
283
219
  var loader = null;
284
220
  function loadStripe() {
285
221
  const existing = globalThis.Stripe;
@@ -313,9 +249,9 @@ function PaymentStep(props) {
313
249
  simulation
314
250
  } = props;
315
251
  const copy = props.copy ?? inheritedCopy.join.payment;
316
- const [status, setStatus] = useState4("idle");
317
- const [error, setError] = useState4(null);
318
- const [lineItems, setLineItems] = useState4(null);
252
+ const [status, setStatus] = useState3("idle");
253
+ const [error, setError] = useState3(null);
254
+ const [lineItems, setLineItems] = useState3(null);
319
255
  const mountRef = useRef(null);
320
256
  const stripeRef = useRef(null);
321
257
  const elementsRef = useRef(null);
@@ -391,10 +327,10 @@ function PaymentStep(props) {
391
327
  };
392
328
  return /* @__PURE__ */ jsxs4("div", { className: "join-pay", children: [
393
329
  /* @__PURE__ */ jsxs4("div", { className: "compliance-box", children: [
394
- merchantDisclosureText ? /* @__PURE__ */ jsx5("p", { className: "compliance-merchant", children: merchantDisclosureText }) : null,
395
- /* @__PURE__ */ jsx5("p", { className: "compliance-policy", children: refundPolicyText }),
330
+ merchantDisclosureText ? /* @__PURE__ */ jsx4("p", { className: "compliance-merchant", children: merchantDisclosureText }) : null,
331
+ /* @__PURE__ */ jsx4("p", { className: "compliance-policy", children: refundPolicyText }),
396
332
  /* @__PURE__ */ jsxs4("label", { className: "compliance-check", children: [
397
- /* @__PURE__ */ jsx5(
333
+ /* @__PURE__ */ jsx4(
398
334
  "input",
399
335
  {
400
336
  type: "checkbox",
@@ -404,41 +340,41 @@ function PaymentStep(props) {
404
340
  }
405
341
  }
406
342
  ),
407
- copy.consent ? /* @__PURE__ */ jsx5("span", { children: copy.consent }) : null
343
+ copy.consent ? /* @__PURE__ */ jsx4("span", { children: copy.consent }) : null
408
344
  ] })
409
345
  ] }),
410
- status === "loading" ? /* @__PURE__ */ jsx5("p", { className: "pay-status", children: copy.preparing }) : null,
346
+ status === "loading" ? /* @__PURE__ */ jsx4("p", { className: "pay-status", children: copy.preparing }) : null,
411
347
  lineItems && renderPriceLines ? renderPriceLines(lineItems) : null,
412
348
  children,
413
349
  simulation && (status === "ready" || status === "confirming") ? /* @__PURE__ */ jsxs4("fieldset", { className: "join-pay-simulation", children: [
414
- /* @__PURE__ */ jsx5("legend", { children: "Payment details" }),
350
+ /* @__PURE__ */ jsx4("legend", { children: "Payment details" }),
415
351
  /* @__PURE__ */ jsxs4("label", { children: [
416
352
  "Card number",
417
- /* @__PURE__ */ jsx5("input", { name: "simulationCardNumber", inputMode: "numeric", autoComplete: "off", defaultValue: "4242 4242 4242 4242" })
353
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardNumber", inputMode: "numeric", autoComplete: "off", defaultValue: "4242 4242 4242 4242" })
418
354
  ] }),
419
355
  /* @__PURE__ */ jsxs4("label", { children: [
420
356
  "Expiration",
421
- /* @__PURE__ */ jsx5("input", { name: "simulationCardExpiry", inputMode: "numeric", autoComplete: "off", defaultValue: "12 / 34" })
357
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardExpiry", inputMode: "numeric", autoComplete: "off", defaultValue: "12 / 34" })
422
358
  ] }),
423
359
  /* @__PURE__ */ jsxs4("label", { children: [
424
360
  "Security code",
425
- /* @__PURE__ */ jsx5("input", { name: "simulationCardCvc", inputMode: "numeric", autoComplete: "off", defaultValue: "123" })
361
+ /* @__PURE__ */ jsx4("input", { name: "simulationCardCvc", inputMode: "numeric", autoComplete: "off", defaultValue: "123" })
426
362
  ] }),
427
- /* @__PURE__ */ jsx5("p", { children: "This is a simulation. No card data is sent and no charge is created." })
363
+ /* @__PURE__ */ jsx4("p", { children: "This is a simulation. No card data is sent and no charge is created." })
428
364
  ] }) : null,
429
- /* @__PURE__ */ jsx5("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
430
- status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx5("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? copy.processing : copy.payAndContinue }) : null,
431
- error ? /* @__PURE__ */ jsx5("p", { className: "pay-error", children: error }) : null
365
+ /* @__PURE__ */ jsx4("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
366
+ status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx4("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? copy.processing : copy.payAndContinue }) : null,
367
+ error ? /* @__PURE__ */ jsx4("p", { className: "pay-error", children: error }) : null
432
368
  ] });
433
369
  }
434
370
 
435
371
  // src/ui/member-gifts.tsx
436
- import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
437
- function MemberGifts(props) {
438
- const [data, setData] = useState5(null);
439
- const [selected, setSelected] = useState5(null);
440
- const [code, setCode] = useState5("");
441
- const [message, setMessage] = useState5("");
372
+ import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
373
+ function GiftPurchases(props) {
374
+ const [data, setData] = useState4(null);
375
+ const [selected, setSelected] = useState4(null);
376
+ const [code, setCode] = useState4("");
377
+ const [message, setMessage] = useState4("");
442
378
  const purchaseId = useRef2(crypto.randomUUID());
443
379
  const load = async () => setData(await props.api("/api/gifts"));
444
380
  useEffect(() => {
@@ -454,17 +390,18 @@ function MemberGifts(props) {
454
390
  }
455
391
  };
456
392
  if (!data) return null;
393
+ if (data.offers.length === 0 && data.claims.length === 0) return null;
457
394
  const offer = data.offers.find((item) => item.id === selected);
458
395
  return /* @__PURE__ */ jsxs5("section", { className: "member-gifts", children: [
459
- /* @__PURE__ */ jsxs5("div", { className: "card", children: [
460
- /* @__PURE__ */ jsx6("div", { className: "card-label", children: props.copy.title }),
461
- /* @__PURE__ */ jsx6("p", { children: props.copy.body }),
462
- data.offers.length ? /* @__PURE__ */ jsx6(GiftOfferCards, { offers: data.offers, mode: "select", selectedOfferId: selected, onSelect: (id) => {
396
+ data.offers.length ? /* @__PURE__ */ jsxs5("div", { className: "card", children: [
397
+ /* @__PURE__ */ jsx5("div", { className: "card-label", children: props.copy.title }),
398
+ /* @__PURE__ */ jsx5("p", { children: props.copy.body }),
399
+ /* @__PURE__ */ jsx5(GiftOfferCards, { offers: data.offers, mode: "select", selectedOfferId: selected, onSelect: (id) => {
463
400
  setSelected(id);
464
401
  setCode("");
465
402
  purchaseId.current = crypto.randomUUID();
466
- } }) : /* @__PURE__ */ jsx6("p", { children: props.copy.noneAvailable }),
467
- offer ? /* @__PURE__ */ jsx6(
403
+ } }),
404
+ offer ? /* @__PURE__ */ jsx5(
468
405
  PaymentStep,
469
406
  {
470
407
  applicationId: purchaseId.current,
@@ -485,30 +422,27 @@ function MemberGifts(props) {
485
422
  }
486
423
  ) : null,
487
424
  code ? /* @__PURE__ */ jsxs5("div", { className: "gift-code-once", children: [
488
- /* @__PURE__ */ jsx6("output", { children: code }),
489
- /* @__PURE__ */ jsx6("p", { children: props.copy.saveCode })
425
+ /* @__PURE__ */ jsx5("output", { children: code }),
426
+ /* @__PURE__ */ jsx5("p", { children: props.copy.saveCode })
490
427
  ] }) : null
491
- ] }),
428
+ ] }) : null,
492
429
  /* @__PURE__ */ jsxs5("div", { className: "card", children: [
493
- /* @__PURE__ */ jsx6("div", { className: "card-label", children: props.copy.purchases }),
430
+ /* @__PURE__ */ jsx5("div", { className: "card-label", children: props.copy.purchases }),
494
431
  data.claims.length ? data.claims.map((claim) => /* @__PURE__ */ jsxs5("div", { className: "member-row", children: [
495
- /* @__PURE__ */ jsx6("span", { children: claim.offerId }),
496
- /* @__PURE__ */ jsx6("span", { children: props.copy[claim.status] }),
497
- claim.status === "pending" ? /* @__PURE__ */ jsx6("button", { className: "btn secondary mini", onClick: () => void rotate(claim.id), children: props.copy.rotate }) : null
498
- ] }, claim.id)) : /* @__PURE__ */ jsx6("p", { children: props.copy.noPurchases })
499
- ] }),
500
- /* @__PURE__ */ jsxs5("div", { className: "card", children: [
501
- /* @__PURE__ */ jsx6("div", { className: "card-label", children: props.copy.redeemTitle }),
502
- /* @__PURE__ */ jsx6(GiftCodeRedeemer, { api: props.api, copy: props.copy, onRedeemed: load }),
503
- message ? /* @__PURE__ */ jsx6("p", { role: "status", children: message }) : null
432
+ /* @__PURE__ */ jsx5("span", { children: claim.offerId }),
433
+ /* @__PURE__ */ jsx5("span", { children: props.copy[claim.status] }),
434
+ claim.status === "pending" ? /* @__PURE__ */ jsx5("button", { className: "btn secondary mini", onClick: () => void rotate(claim.id), children: props.copy.rotate }) : null
435
+ ] }, claim.id)) : /* @__PURE__ */ jsx5("p", { children: props.copy.noPurchases }),
436
+ message ? /* @__PURE__ */ jsx5("p", { role: "status", children: message }) : null
504
437
  ] })
505
438
  ] });
506
439
  }
440
+ var MemberGifts = GiftPurchases;
507
441
 
508
442
  // src/ui/member-content.tsx
509
- import { useEffect as useEffect2, useMemo as useMemo2, useState as useState6 } from "preact/hooks";
443
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState5 } from "preact/hooks";
510
444
  import { Markdown } from "@odla-ai/ui/components";
511
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
445
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
512
446
  var pageLabel = (page) => `${page.title}${page.access === "locked" ? " \xB7 Locked" : ""}`;
513
447
  function MemberContentArea(props) {
514
448
  const {
@@ -516,12 +450,12 @@ function MemberContentArea(props) {
516
450
  path = "/api/member-content",
517
451
  upsellHref,
518
452
  onUpsell,
519
- loadingContent = /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: "Loading member pages\u2026" }),
520
- emptyContent = /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: "No member pages have been published yet." })
453
+ loadingContent = /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "Loading member pages\u2026" }),
454
+ emptyContent = /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "No member pages have been published yet." })
521
455
  } = props;
522
- const [data, setData] = useState6(props.initialData ?? null);
523
- const [error, setError] = useState6(null);
524
- const [selectedId, setSelectedId] = useState6(
456
+ const [data, setData] = useState5(props.initialData ?? null);
457
+ const [error, setError] = useState5(null);
458
+ const [selectedId, setSelectedId] = useState5(
525
459
  props.initialPageId ?? props.initialData?.pages[0]?.id ?? ""
526
460
  );
527
461
  useEffect2(() => {
@@ -550,14 +484,14 @@ function MemberContentArea(props) {
550
484
  "Member pages could not be loaded: ",
551
485
  error
552
486
  ] });
553
- if (!data) return /* @__PURE__ */ jsx7(Fragment2, { children: loadingContent });
554
- if (data.pages.length === 0) return /* @__PURE__ */ jsx7(Fragment2, { children: emptyContent });
487
+ if (!data) return /* @__PURE__ */ jsx6(Fragment2, { children: loadingContent });
488
+ if (data.pages.length === 0) return /* @__PURE__ */ jsx6(Fragment2, { children: emptyContent });
555
489
  const selected = data.pages.find((page) => page.id === selectedId) ?? data.pages[0];
556
490
  const href = selected.upsell && upsellHref ? upsellHref(selected.upsell.tierId, selected) : void 0;
557
491
  return /* @__PURE__ */ jsxs6("div", { className: "chapter-member-content", "data-member-content": true, children: [
558
- /* @__PURE__ */ jsx7("nav", { className: "chapter-member-content-nav", "aria-label": "Member pages", children: sections.map(([section, pages]) => /* @__PURE__ */ jsxs6("section", { className: "chapter-member-content-section", children: [
559
- /* @__PURE__ */ jsx7("h2", { children: section }),
560
- /* @__PURE__ */ jsx7("ul", { children: pages.map((page) => /* @__PURE__ */ jsx7("li", { "data-parent-page": page.navigation.parentId ?? void 0, children: /* @__PURE__ */ jsx7(
492
+ /* @__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: [
493
+ /* @__PURE__ */ jsx6("h2", { children: section }),
494
+ /* @__PURE__ */ jsx6("ul", { children: pages.map((page) => /* @__PURE__ */ jsx6("li", { "data-parent-page": page.navigation.parentId ?? void 0, children: /* @__PURE__ */ jsx6(
561
495
  "button",
562
496
  {
563
497
  type: "button",
@@ -574,16 +508,16 @@ function MemberContentArea(props) {
574
508
  " \xB7 ",
575
509
  selected.access
576
510
  ] }),
577
- /* @__PURE__ */ jsx7("h1", { children: selected.title }),
578
- selected.summary ? /* @__PURE__ */ jsx7("p", { children: selected.summary }) : null
511
+ /* @__PURE__ */ jsx6("h1", { children: selected.title }),
512
+ selected.summary ? /* @__PURE__ */ jsx6("p", { children: selected.summary }) : null
579
513
  ] }),
580
- selected.access === "allowed" && selected.bodyMarkdown ? /* @__PURE__ */ jsx7(Markdown, { source: selected.bodyMarkdown }) : /* @__PURE__ */ jsxs6("div", { className: "chapter-member-content-locked", children: [
581
- selected.teaserMarkdown ? /* @__PURE__ */ jsx7(Markdown, { source: selected.teaserMarkdown }) : /* @__PURE__ */ jsx7("p", { children: "This page is not included in your current membership." }),
514
+ selected.access === "allowed" && selected.bodyMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.bodyMarkdown }) : /* @__PURE__ */ jsxs6("div", { className: "chapter-member-content-locked", children: [
515
+ selected.teaserMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.teaserMarkdown }) : /* @__PURE__ */ jsx6("p", { children: "This page is not included in your current membership." }),
582
516
  selected.upsell ? /* @__PURE__ */ jsxs6("aside", { className: "chapter-member-content-upsell", "aria-label": selected.upsell.label ?? "Membership option", children: [
583
- /* @__PURE__ */ jsx7("h2", { children: selected.upsell.label ?? selected.upsell.tierName }),
584
- /* @__PURE__ */ jsx7("p", { children: fmtMoney(selected.upsell.priceCents) }),
585
- selected.upsell.bodyMarkdown ? /* @__PURE__ */ jsx7(Markdown, { source: selected.upsell.bodyMarkdown }) : null,
586
- href ? /* @__PURE__ */ jsx7("a", { className: "btn primary", href, children: selected.upsell.ctaLabel ?? `Explore ${selected.upsell.tierName}` }) : onUpsell ? /* @__PURE__ */ jsx7("button", { className: "btn primary", type: "button", onClick: () => onUpsell(selected.upsell.tierId, selected), children: selected.upsell.ctaLabel ?? `Explore ${selected.upsell.tierName}` }) : null
517
+ /* @__PURE__ */ jsx6("h2", { children: selected.upsell.label ?? selected.upsell.tierName }),
518
+ /* @__PURE__ */ jsx6("p", { children: fmtMoney(selected.upsell.priceCents) }),
519
+ selected.upsell.bodyMarkdown ? /* @__PURE__ */ jsx6(Markdown, { source: selected.upsell.bodyMarkdown }) : null,
520
+ 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
587
521
  ] }) : null
588
522
  ] })
589
523
  ] })
@@ -591,12 +525,12 @@ function MemberContentArea(props) {
591
525
  }
592
526
 
593
527
  // src/ui/members.tsx
594
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
528
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
595
529
  function card(copy, kicker, body) {
596
530
  return /* @__PURE__ */ jsxs7("div", { className: "card", children: [
597
- /* @__PURE__ */ jsx8("div", { className: "card-label", children: copy.provisional.cardLabel }),
531
+ /* @__PURE__ */ jsx7("div", { className: "card-label", children: copy.provisional.cardLabel }),
598
532
  /* @__PURE__ */ jsxs7("div", { className: "meeting-block", children: [
599
- /* @__PURE__ */ jsx8("div", { className: "meeting-kicker", children: kicker }),
533
+ /* @__PURE__ */ jsx7("div", { className: "meeting-kicker", children: kicker }),
600
534
  body
601
535
  ] })
602
536
  ] });
@@ -608,8 +542,8 @@ function ProvisionalCard(props) {
608
542
  copy,
609
543
  copy.provisional.applicationNeeded,
610
544
  /* @__PURE__ */ jsxs7(Fragment3, { children: [
611
- /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
612
- /* @__PURE__ */ jsx8("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
545
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
546
+ /* @__PURE__ */ jsx7("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
613
547
  ] })
614
548
  );
615
549
  }
@@ -617,19 +551,19 @@ function ProvisionalCard(props) {
617
551
  return card(
618
552
  copy,
619
553
  copy.provisional.refunded,
620
- /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: copy.provisional.refundedBody })
554
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.refundedBody })
621
555
  );
622
556
  }
623
- const membership = application.paid ? /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: application.renewalAt ? formatChapterCopy(copy.provisional.renews, { date: fmtDate(application.renewalAt) }) : copy.provisional.active }) : null;
557
+ 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;
624
558
  if (application.meetingAt) {
625
559
  return card(
626
560
  copy,
627
561
  copy.provisional.introductionCall,
628
562
  /* @__PURE__ */ jsxs7(Fragment3, { children: [
629
- /* @__PURE__ */ jsx8("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
630
- /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
631
- application.meetUrl ? /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: /* @__PURE__ */ jsx8("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
632
- /* @__PURE__ */ jsx8(
563
+ /* @__PURE__ */ jsx7("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
564
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
565
+ application.meetUrl ? /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: /* @__PURE__ */ jsx7("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
566
+ /* @__PURE__ */ jsx7(
633
567
  Rescheduler,
634
568
  {
635
569
  api,
@@ -647,8 +581,8 @@ function ProvisionalCard(props) {
647
581
  copy,
648
582
  copy.provisional.bookCall,
649
583
  /* @__PURE__ */ jsxs7(Fragment3, { children: [
650
- /* @__PURE__ */ jsx8("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
651
- /* @__PURE__ */ jsx8(
584
+ /* @__PURE__ */ jsx7("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
585
+ /* @__PURE__ */ jsx7(
652
586
  Rescheduler,
653
587
  {
654
588
  api,
@@ -675,8 +609,8 @@ function MembersArea(props) {
675
609
  renderProvisional
676
610
  } = props;
677
611
  const copy = props.copy ?? inheritedCopy.members;
678
- const [me, setMe] = useState7(null);
679
- const [error, setError] = useState7(false);
612
+ const [me, setMe] = useState6(null);
613
+ const [error, setError] = useState6(false);
680
614
  const reload = async () => {
681
615
  try {
682
616
  setMe(await api("/api/me"));
@@ -687,22 +621,22 @@ function MembersArea(props) {
687
621
  useEffect3(() => {
688
622
  void reload();
689
623
  }, []);
690
- if (error) return /* @__PURE__ */ jsx8("p", { className: "meeting-note", children: copy.loadFailed });
691
- if (!me) return /* @__PURE__ */ jsx8("p", { className: "meeting-note", children: inheritedCopy.common.loading });
624
+ if (error) return /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: copy.loadFailed });
625
+ if (!me) return /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: inheritedCopy.common.loading });
692
626
  const role = me.role || "provisional";
693
627
  return /* @__PURE__ */ jsxs7(Fragment3, { children: [
694
628
  /* @__PURE__ */ jsxs7("div", { className: "card", children: [
695
629
  /* @__PURE__ */ jsxs7("div", { className: "member-row", children: [
696
- /* @__PURE__ */ jsx8("span", { className: "member-email", children: me.email }),
697
- /* @__PURE__ */ jsx8("span", { className: "role-badge " + role, children: role })
630
+ /* @__PURE__ */ jsx7("span", { className: "member-email", children: me.email }),
631
+ /* @__PURE__ */ jsx7("span", { className: "role-badge " + role, children: role })
698
632
  ] }),
699
633
  /* @__PURE__ */ jsxs7("div", { className: "account-actions", children: [
700
- /* @__PURE__ */ jsx8("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
701
- role === "admin" ? /* @__PURE__ */ jsx8("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
634
+ /* @__PURE__ */ jsx7("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
635
+ role === "admin" ? /* @__PURE__ */ jsx7("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
702
636
  ] })
703
637
  ] }),
704
638
  role === "provisional" ? (() => {
705
- const defaultContent = /* @__PURE__ */ jsx8(
639
+ const defaultContent = /* @__PURE__ */ jsx7(
706
640
  ProvisionalCard,
707
641
  {
708
642
  api,
@@ -719,14 +653,14 @@ function MembersArea(props) {
719
653
  reload,
720
654
  defaultContent
721
655
  }) ?? defaultContent;
722
- })() : memberContent ?? (memberPages ? /* @__PURE__ */ jsx8(
656
+ })() : memberContent ?? (memberPages ? /* @__PURE__ */ jsx7(
723
657
  MemberContentArea,
724
658
  {
725
659
  api,
726
660
  ...memberPages === true ? {} : memberPages
727
661
  }
728
- ) : /* @__PURE__ */ jsx8("div", { className: "card", children: /* @__PURE__ */ jsx8("div", { className: "card-label", children: copy.full.welcome }) })),
729
- /* @__PURE__ */ jsx8(MemberGifts, { api, copy: copy.gifts, paymentCopy: inheritedCopy.join.payment })
662
+ ) : /* @__PURE__ */ jsx7("div", { className: "card", children: /* @__PURE__ */ jsx7("div", { className: "card-label", children: copy.full.welcome }) })),
663
+ /* @__PURE__ */ jsx7(GiftPurchases, { api, copy: copy.gifts, paymentCopy: inheritedCopy.join.payment })
730
664
  ] });
731
665
  }
732
666
 
@@ -734,14 +668,14 @@ function MembersArea(props) {
734
668
  import { useEffect as useEffect5, useRef as useRef3, useState as useState11 } from "preact/hooks";
735
669
 
736
670
  // src/ui/join-booking.tsx
737
- import { useEffect as useEffect4, useState as useState8 } from "preact/hooks";
738
- import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
671
+ import { useEffect as useEffect4, useState as useState7 } from "preact/hooks";
672
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
739
673
  function JoinBooking(props) {
740
674
  const { applicationId, copy, onBooked, simulation } = props;
741
- const [state, setState] = useState8(null);
742
- const [msg, setMsg] = useState8(null);
743
- const [busy, setBusy] = useState8(false);
744
- const [selected, setSelected] = useState8(null);
675
+ const [state, setState] = useState7(null);
676
+ const [msg, setMsg] = useState7(null);
677
+ const [busy, setBusy] = useState7(false);
678
+ const [selected, setSelected] = useState7(null);
745
679
  const load = async () => {
746
680
  setMsg(null);
747
681
  try {
@@ -798,9 +732,9 @@ function JoinBooking(props) {
798
732
  setBusy(false);
799
733
  }
800
734
  };
801
- if (!state) return /* @__PURE__ */ jsx9("p", { className: "slots-status", children: msg ?? copy.loading });
735
+ if (!state) return /* @__PURE__ */ jsx8("p", { className: "slots-status", children: msg ?? copy.loading });
802
736
  return /* @__PURE__ */ jsxs8("div", { className: "join-book", children: [
803
- /* @__PURE__ */ jsx9(
737
+ /* @__PURE__ */ jsx8(
804
738
  SlotPicker,
805
739
  {
806
740
  slots: state.slots,
@@ -811,28 +745,28 @@ function JoinBooking(props) {
811
745
  }
812
746
  ),
813
747
  /* @__PURE__ */ jsxs8("div", { className: "slot-confirm", hidden: !selected, children: [
814
- /* @__PURE__ */ jsx9("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
815
- msg ? /* @__PURE__ */ jsx9("p", { className: "step2-note", children: msg }) : null
748
+ /* @__PURE__ */ jsx8("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
749
+ msg ? /* @__PURE__ */ jsx8("p", { className: "step2-note", children: msg }) : null
816
750
  ] })
817
751
  ] });
818
752
  }
819
753
 
820
754
  // src/ui/join-accepted.tsx
821
- import { jsx as jsx10, jsxs as jsxs9 } from "preact/jsx-runtime";
755
+ import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
822
756
  function JoinAccepted(props) {
823
757
  return /* @__PURE__ */ jsxs9("div", { className: "join-done card", children: [
824
- /* @__PURE__ */ jsx10("div", { className: "card-label", children: props.copy.accepted.label }),
825
- /* @__PURE__ */ jsx10("p", { className: "meeting-note", children: props.copy.accepted.body }),
826
- /* @__PURE__ */ jsx10("a", { className: "apply-link", href: props.membersHref, children: props.copy.done.memberArea })
758
+ /* @__PURE__ */ jsx9("div", { className: "card-label", children: props.copy.accepted.label }),
759
+ /* @__PURE__ */ jsx9("p", { className: "meeting-note", children: props.copy.accepted.body }),
760
+ /* @__PURE__ */ jsx9("a", { className: "apply-link", href: props.membersHref, children: props.copy.done.memberArea })
827
761
  ] });
828
762
  }
829
763
 
830
764
  // src/ui/gift-claim-card.tsx
831
- import { useState as useState9 } from "preact/hooks";
832
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs10 } from "preact/jsx-runtime";
765
+ import { useState as useState8 } from "preact/hooks";
766
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
833
767
  function GiftClaimCard(props) {
834
- const [code, setCode] = useState9(props.initialCode ?? "");
835
- const [error, setError] = useState9(false);
768
+ const [code, setCode] = useState8(props.initialCode ?? "");
769
+ const [error, setError] = useState8(false);
836
770
  const rotate = async () => {
837
771
  if (!props.claimId) return;
838
772
  setError(false);
@@ -846,20 +780,165 @@ function GiftClaimCard(props) {
846
780
  else setError(true);
847
781
  };
848
782
  return /* @__PURE__ */ jsxs10("div", { className: "join-done card", children: [
849
- /* @__PURE__ */ jsx11("div", { className: "card-label", children: props.copy.label }),
850
- /* @__PURE__ */ jsx11("p", { className: "meeting-note", children: props.copy.body }),
783
+ /* @__PURE__ */ jsx10("div", { className: "card-label", children: props.copy.label }),
784
+ /* @__PURE__ */ jsx10("p", { className: "meeting-note", children: props.copy.body }),
851
785
  code ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
852
- /* @__PURE__ */ jsx11("output", { className: "gift-code", children: code }),
853
- /* @__PURE__ */ jsx11("p", { children: props.copy.save })
786
+ /* @__PURE__ */ jsx10("output", { className: "gift-code", children: code }),
787
+ /* @__PURE__ */ jsx10("p", { children: props.copy.save })
854
788
  ] }) : null,
855
- !code && props.claimId ? /* @__PURE__ */ jsx11("button", { type: "button", className: "btn secondary", onClick: () => void rotate(), children: props.copy.rotate }) : null,
856
- error ? /* @__PURE__ */ jsx11("p", { role: "alert", children: props.copy.rotateFailed }) : null
789
+ !code && props.claimId ? /* @__PURE__ */ jsx10("button", { type: "button", className: "btn secondary", onClick: () => void rotate(), children: props.copy.rotate }) : null,
790
+ error ? /* @__PURE__ */ jsx10("p", { role: "alert", children: props.copy.rotateFailed }) : null
791
+ ] });
792
+ }
793
+
794
+ // src/ui/join-application-response.ts
795
+ function joinStateFromApplicationResponse(data, fallbackNeedsPayment, submittedGiftPurchase) {
796
+ if (!data.id) return null;
797
+ if (data.state === "accepted") return { step: "accepted", applicationId: data.id };
798
+ if (data.state === "paymentPending") return { step: "paymentPending", applicationId: data.id };
799
+ if (data.state === "booking") return { step: "booking", applicationId: data.id };
800
+ if (data.state === "payment") return {
801
+ step: "payment",
802
+ applicationId: data.id,
803
+ ...data.giftPurchase || submittedGiftPurchase ? { giftPurchase: true } : {}
804
+ };
805
+ return fallbackNeedsPayment ? { step: "payment", applicationId: data.id, ...submittedGiftPurchase ? { giftPurchase: true } : {} } : { step: "booking", applicationId: data.id };
806
+ }
807
+
808
+ // src/ui/gift-code-redeemer.tsx
809
+ import { InlineActionField } from "@odla-ai/ui/components";
810
+ import { useState as useState9 } from "preact/hooks";
811
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
812
+ var GIFT_CODE_REDEEMER_CSS = `
813
+ .gift-code-redeemer{container-type:inline-size;display:grid;gap:var(--ui-space-1,.35rem);margin:0}
814
+ .gift-code-redeemer .inline-action-field-label{display:block;color:var(--ui-text-muted,#66777f);font-size:var(--ui-text-xs,.72rem);font-weight:var(--ui-weight-strong,700);letter-spacing:var(--ui-tracking-caps,.08em);text-transform:uppercase}
815
+ .gift-code-redeemer .inline-action-field-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--ui-space-2,.65rem);align-items:stretch}
816
+ .gift-code-redeemer .inline-action-field-input,.gift-code-redeemer .inline-action-field-action{min-height:var(--ui-inline-action-control-height,38px)}
817
+ .gift-code-redeemer .inline-action-field-input.code{font-family:var(--ui-font-mono,ui-monospace,monospace);font-variant-ligatures:none;letter-spacing:var(--ui-tracking-wide,.04em)}
818
+ .gift-code-redeemer .inline-action-field-input:focus-visible{outline:none;border-color:var(--ui-accent,#168c84);box-shadow:var(--ui-focus,0 0 0 3px rgb(22 140 132 / .18))}
819
+ .gift-code-redeemer .inline-action-field-action{white-space:nowrap}
820
+ .gift-code-redeemer .inline-action-field-status{display:block;min-block-size:1.4em;margin:0;color:var(--ui-text-muted,#66777f);font-size:var(--ui-text-sm,.8rem);line-height:1.4}
821
+ .gift-code-redeemer .inline-action-field-status.success{color:var(--ui-good,#1f7a52)}
822
+ .gift-code-redeemer .inline-action-field-status.error{color:var(--ui-danger,#b42318)}
823
+ @container (max-width:26rem){.gift-code-redeemer .inline-action-field-row{grid-template-columns:minmax(0,1fr)}.gift-code-redeemer .inline-action-field-action{width:100%}}
824
+ `;
825
+ function GiftCodeRedeemer({ api, applicationId, copy, id, onRedeemed }) {
826
+ const [code, setCode] = useState9("");
827
+ const [loading, setLoading] = useState9(false);
828
+ const [message, setMessage] = useState9("");
829
+ const [messageTone, setMessageTone] = useState9("neutral");
830
+ const normalizedCode = code.trim();
831
+ const redeem = async () => {
832
+ if (!normalizedCode || loading) return;
833
+ setLoading(true);
834
+ setMessage("");
835
+ setMessageTone("neutral");
836
+ try {
837
+ const result = await api("/api/gifts/redeem", {
838
+ method: "POST",
839
+ headers: { "content-type": "application/json" },
840
+ body: JSON.stringify({ code: normalizedCode, applicationId })
841
+ });
842
+ setCode("");
843
+ setMessage(copy.redeemed);
844
+ setMessageTone("success");
845
+ try {
846
+ await onRedeemed?.(result);
847
+ } catch {
848
+ }
849
+ } catch {
850
+ setMessage(copy.redeemFailed);
851
+ setMessageTone("error");
852
+ } finally {
853
+ setLoading(false);
854
+ }
855
+ };
856
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
857
+ /* @__PURE__ */ jsx11("style", { children: GIFT_CODE_REDEEMER_CSS }),
858
+ /* @__PURE__ */ jsx11(
859
+ InlineActionField,
860
+ {
861
+ id,
862
+ className: "gift-code-redeemer",
863
+ label: copy.code,
864
+ value: code,
865
+ code: true,
866
+ actionLabel: copy.redeem,
867
+ loadingLabel: `${copy.redeem}\u2026`,
868
+ loading,
869
+ actionDisabled: !normalizedCode,
870
+ message,
871
+ messageTone,
872
+ inputProps: {
873
+ name: "giftCode",
874
+ maxLength: 64,
875
+ autoComplete: "one-time-code",
876
+ autoCapitalize: "none",
877
+ autocorrect: "off",
878
+ spellcheck: false
879
+ },
880
+ onValueChange: (value) => {
881
+ setCode(value);
882
+ if (message) {
883
+ setMessage("");
884
+ setMessageTone("neutral");
885
+ }
886
+ },
887
+ onAction: () => void redeem()
888
+ }
889
+ )
890
+ ] });
891
+ }
892
+
893
+ // src/ui/join-resume.ts
894
+ async function loadJoinResume(applicationId, fetcher = fetch) {
895
+ const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);
896
+ const data = await res.json();
897
+ if (!res.ok) throw new Error(data.error ?? "The application could not be resumed.");
898
+ if (data.state === "done" && data.booked) return { step: "done", applicationId: data.applicationId, booked: data.booked };
899
+ if (data.state === "paymentPending") return { step: "paymentPending", applicationId: data.applicationId };
900
+ if (data.state === "booking") return { step: "booking", applicationId: data.applicationId };
901
+ if (data.state === "accepted") return { step: "accepted", applicationId: data.applicationId };
902
+ if (data.state === "gift") return { step: "gift", applicationId: data.applicationId, ...data.claimId ? { claimId: data.claimId } : {} };
903
+ return {
904
+ step: "payment",
905
+ applicationId: data.applicationId,
906
+ ...data.giftPurchase ? { giftPurchase: true } : {}
907
+ };
908
+ }
909
+
910
+ // src/ui/join-gift-claim.tsx
911
+ import { jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
912
+ function JoinGiftClaim(props) {
913
+ return /* @__PURE__ */ jsxs12("section", { className: "join-gift-claim card", children: [
914
+ /* @__PURE__ */ jsx12("div", { className: "card-label", children: props.copy.claimTitle }),
915
+ /* @__PURE__ */ jsx12("p", { children: props.copy.claimBody }),
916
+ /* @__PURE__ */ jsx12(
917
+ GiftCodeRedeemer,
918
+ {
919
+ api: props.api,
920
+ applicationId: props.applicationId,
921
+ copy: props.copy,
922
+ onRedeemed: async (result) => {
923
+ if (result.state === "booking" || result.state === "accepted") {
924
+ props.onState({ step: result.state, applicationId: result.applicationId });
925
+ return;
926
+ }
927
+ try {
928
+ const resumed = props.loadResume ? await props.loadResume() : await loadJoinResume(result.applicationId);
929
+ if (resumed) props.onState(resumed);
930
+ } catch {
931
+ props.onError();
932
+ }
933
+ }
934
+ }
935
+ )
857
936
  ] });
858
937
  }
859
938
 
860
939
  // src/ui/join-tiers.tsx
861
940
  import { useState as useState10 } from "preact/hooks";
862
- import { jsx as jsx12, jsxs as jsxs11 } from "preact/jsx-runtime";
941
+ import { jsx as jsx13, jsxs as jsxs13 } from "preact/jsx-runtime";
863
942
  function useTierSelection(tiers, initialTierId) {
864
943
  const [selectedTierId, setSelectedTierId] = useState10(() => {
865
944
  const preset = tiers.find((tier) => tier.id === initialTierId);
@@ -876,10 +955,10 @@ function useTierSelection(tiers, initialTierId) {
876
955
  };
877
956
  }
878
957
  function TierPlaceholder(context) {
879
- return /* @__PURE__ */ jsxs11("fieldset", { "data-chapter-placeholder": "tiers", children: [
880
- /* @__PURE__ */ jsx12("legend", { children: "Membership (placeholder \u2014 pass renderTiers to style this)" }),
881
- context.tiers.map((tier) => /* @__PURE__ */ jsxs11("label", { children: [
882
- /* @__PURE__ */ jsx12(
958
+ return /* @__PURE__ */ jsxs13("fieldset", { "data-chapter-placeholder": "tiers", children: [
959
+ /* @__PURE__ */ jsx13("legend", { children: "Membership (placeholder \u2014 pass renderTiers to style this)" }),
960
+ context.tiers.map((tier) => /* @__PURE__ */ jsxs13("label", { children: [
961
+ /* @__PURE__ */ jsx13(
883
962
  "input",
884
963
  {
885
964
  type: "radio",
@@ -897,21 +976,8 @@ function tierNeedsPayment(selected, paymentsReady) {
897
976
  return selected ? !selected.free && paymentsReady : paymentsReady;
898
977
  }
899
978
 
900
- // src/ui/join-resume.ts
901
- async function loadJoinResume(applicationId, fetcher = fetch) {
902
- const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);
903
- const data = await res.json();
904
- if (!res.ok) throw new Error(data.error ?? "The application could not be resumed.");
905
- if (data.state === "done" && data.booked) return { step: "done", applicationId: data.applicationId, booked: data.booked };
906
- if (data.state === "paymentPending") return { step: "paymentPending", applicationId: data.applicationId };
907
- if (data.state === "booking") return { step: "booking", applicationId: data.applicationId };
908
- if (data.state === "accepted") return { step: "accepted", applicationId: data.applicationId };
909
- if (data.state === "gift") return { step: "gift", applicationId: data.applicationId, ...data.claimId ? { claimId: data.claimId } : {} };
910
- return { step: "payment", applicationId: data.applicationId };
911
- }
912
-
913
979
  // src/ui/join.tsx
914
- import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs12 } from "preact/jsx-runtime";
980
+ import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs14 } from "preact/jsx-runtime";
915
981
  function JoinIsland(props) {
916
982
  const inheritedCopy = useChapterCopy();
917
983
  const {
@@ -1004,22 +1070,11 @@ function JoinIsland(props) {
1004
1070
  });
1005
1071
  const data = await res.json();
1006
1072
  if (!res.ok || !data.id) throw new Error(data.error ?? copy.form.submitFailed);
1007
- if (data.state === "accepted") {
1008
- setState({ step: "accepted", applicationId: data.id });
1009
- return;
1010
- }
1011
- if (data.state === "paymentPending") {
1012
- setState({ step: "paymentPending", applicationId: data.id });
1013
- return;
1014
- }
1015
- if (data.state === "payment" || data.state === "booking") {
1016
- setState({ step: data.state, applicationId: data.id });
1017
- return;
1018
- }
1019
- setState({
1020
- step: tierNeedsPayment(selectedTier, config.paymentsReady) ? "payment" : "booking",
1021
- applicationId: data.id
1022
- });
1073
+ setState(joinStateFromApplicationResponse(
1074
+ data,
1075
+ tierNeedsPayment(selectedTier, config.paymentsReady),
1076
+ Boolean(giftOfferId)
1077
+ ));
1023
1078
  } catch (err) {
1024
1079
  setError(err instanceof Error ? err.message : copy.form.unexpectedFailure);
1025
1080
  } finally {
@@ -1031,35 +1086,35 @@ function JoinIsland(props) {
1031
1086
  ..."applicationId" in state ? { applicationId: state.applicationId } : {}
1032
1087
  });
1033
1088
  if (state.step === "done") {
1034
- const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs12("div", { className: "join-done card", children: [
1035
- /* @__PURE__ */ jsx13("div", { className: "card-label", children: copy.done.label }),
1036
- /* @__PURE__ */ jsx13("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
1037
- /* @__PURE__ */ jsx13("p", { className: "meeting-note", children: copy.done.calendarInvite }),
1038
- /* @__PURE__ */ jsx13("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
1089
+ const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs14("div", { className: "join-done card", children: [
1090
+ /* @__PURE__ */ jsx14("div", { className: "card-label", children: copy.done.label }),
1091
+ /* @__PURE__ */ jsx14("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
1092
+ /* @__PURE__ */ jsx14("p", { className: "meeting-note", children: copy.done.calendarInvite }),
1093
+ /* @__PURE__ */ jsx14("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
1039
1094
  ] });
1040
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1095
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1041
1096
  header,
1042
1097
  body
1043
1098
  ] });
1044
1099
  }
1045
1100
  if (state.step === "accepted") {
1046
- const body = renderAccepted?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsx13(JoinAccepted, { copy, membersHref });
1047
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1101
+ const body = renderAccepted?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsx14(JoinAccepted, { copy, membersHref });
1102
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1048
1103
  header,
1049
1104
  body
1050
1105
  ] });
1051
1106
  }
1052
1107
  if (state.step === "gift") {
1053
- const body = renderGift?.(state) ?? /* @__PURE__ */ jsx13(GiftClaimCard, { applicationId: state.applicationId, claimId: state.claimId, initialCode: state.code, copy: copy.gift });
1054
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1108
+ const body = renderGift?.(state) ?? /* @__PURE__ */ jsx14(GiftClaimCard, { applicationId: state.applicationId, claimId: state.claimId, initialCode: state.code, copy: copy.gift });
1109
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1055
1110
  header,
1056
1111
  body
1057
1112
  ] });
1058
1113
  }
1059
1114
  if (state.step === "booking") {
1060
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1115
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1061
1116
  header,
1062
- /* @__PURE__ */ jsx13(
1117
+ /* @__PURE__ */ jsx14(
1063
1118
  JoinBooking,
1064
1119
  {
1065
1120
  applicationId: state.applicationId,
@@ -1073,9 +1128,20 @@ function JoinIsland(props) {
1073
1128
  ] });
1074
1129
  }
1075
1130
  if (state.step === "payment") {
1076
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1131
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1077
1132
  header,
1078
- /* @__PURE__ */ jsx13(
1133
+ props.giftClaimApi && !state.giftPurchase ? /* @__PURE__ */ jsx14(
1134
+ JoinGiftClaim,
1135
+ {
1136
+ api: props.giftClaimApi,
1137
+ applicationId: state.applicationId,
1138
+ copy: copy.gift,
1139
+ loadResume: props.loadResume,
1140
+ onState: setState,
1141
+ onError: () => setError(copy.form.unexpectedFailure)
1142
+ }
1143
+ ) : null,
1144
+ /* @__PURE__ */ jsx14(
1079
1145
  PaymentStep,
1080
1146
  {
1081
1147
  applicationId: state.applicationId,
@@ -1103,25 +1169,25 @@ function JoinIsland(props) {
1103
1169
  ] });
1104
1170
  }
1105
1171
  if (state.step === "paymentPending") {
1106
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1172
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1107
1173
  header,
1108
- /* @__PURE__ */ jsx13("p", { className: "pay-status", role: "status", children: copy.payment.pending })
1174
+ /* @__PURE__ */ jsx14("p", { className: "pay-status", role: "status", children: copy.payment.pending })
1109
1175
  ] });
1110
1176
  }
1111
1177
  const submitDisabled = submitting || props.submitDisabled === true;
1112
1178
  const tierChooser = tiers.length > 1 ? props.renderTiers?.(tierSelection) ?? TierPlaceholder(tierSelection) : null;
1113
- return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1179
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
1114
1180
  header,
1115
- /* @__PURE__ */ jsxs12("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
1116
- (config.giftOffers?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs12("fieldset", { children: [
1117
- /* @__PURE__ */ jsx13("legend", { children: copy.gift.optionsLabel }),
1118
- /* @__PURE__ */ jsx13("button", { type: "button", className: "btn secondary", "aria-pressed": !giftOfferId, onClick: () => setGiftOfferId(null), children: copy.gift.chooseSelf }),
1119
- /* @__PURE__ */ jsx13(GiftOfferCards, { offers: config.giftOffers ?? [], mode: "select", selectedOfferId: giftOfferId, onSelect: setGiftOfferId })
1181
+ /* @__PURE__ */ jsxs14("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
1182
+ (config.giftOffers?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs14("fieldset", { children: [
1183
+ /* @__PURE__ */ jsx14("legend", { children: copy.gift.optionsLabel }),
1184
+ /* @__PURE__ */ jsx14("button", { type: "button", className: "btn secondary", "aria-pressed": !giftOfferId, onClick: () => setGiftOfferId(null), children: copy.gift.chooseSelf }),
1185
+ /* @__PURE__ */ jsx14(GiftOfferCards, { offers: config.giftOffers ?? [], mode: "select", selectedOfferId: giftOfferId, onSelect: setGiftOfferId })
1120
1186
  ] }) : null,
1121
1187
  tierChooser,
1122
1188
  children,
1123
- error ? /* @__PURE__ */ jsx13("p", { className: "join-error", children: error }) : null,
1124
- renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx13("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
1189
+ error ? /* @__PURE__ */ jsx14("p", { className: "join-error", children: error }) : null,
1190
+ renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx14("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
1125
1191
  ] })
1126
1192
  ] });
1127
1193
  }
@@ -1298,7 +1364,7 @@ function membershipPageDocument(input, options = {}) {
1298
1364
  }
1299
1365
 
1300
1366
  // src/ui/membership-cards.tsx
1301
- import { jsx as jsx14, jsxs as jsxs13 } from "preact/jsx-runtime";
1367
+ import { jsx as jsx15, jsxs as jsxs15 } from "preact/jsx-runtime";
1302
1368
  var MEMBERSHIP_CARD_TOKEN_DEFAULTS = Object.freeze({
1303
1369
  "--ui-membership-grid-gap": "1.4rem",
1304
1370
  "--ui-membership-card-padding": "2rem 1.7rem 1.8rem",
@@ -1367,67 +1433,67 @@ function membershipApplicationHref(applicationPath, tierId) {
1367
1433
  }
1368
1434
  function TierAction(props) {
1369
1435
  if (props.mode === "select") {
1370
- return /* @__PURE__ */ jsxs13("button", { type: "button", className: "chapter-membership-action", "aria-current": props.selected ? "true" : void 0, onClick: () => props.onSelectTier?.(props.tierId), children: [
1436
+ return /* @__PURE__ */ jsxs15("button", { type: "button", className: "chapter-membership-action", "aria-current": props.selected ? "true" : void 0, onClick: () => props.onSelectTier?.(props.tierId), children: [
1371
1437
  props.label,
1372
- /* @__PURE__ */ jsx14("span", { "aria-hidden": "true", children: "\u2192" })
1438
+ /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u2192" })
1373
1439
  ] });
1374
1440
  }
1375
1441
  if (props.mode === "preview") {
1376
- return /* @__PURE__ */ jsxs13("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1442
+ return /* @__PURE__ */ jsxs15("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1377
1443
  props.label,
1378
- /* @__PURE__ */ jsx14("span", { "aria-hidden": "true", children: "\u2192" })
1444
+ /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u2192" })
1379
1445
  ] });
1380
1446
  }
1381
- return /* @__PURE__ */ jsxs13("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1447
+ return /* @__PURE__ */ jsxs15("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1382
1448
  props.label,
1383
- /* @__PURE__ */ jsx14("span", { "aria-hidden": "true", children: "\u2192" })
1449
+ /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u2192" })
1384
1450
  ] });
1385
1451
  }
1386
1452
  function TierCard(props) {
1387
1453
  const { tier, presentation } = props;
1388
1454
  const summary = presentation.summaryMarkdown ?? tier.blurb;
1389
1455
  const action = presentation.ctaLabel ?? (tier.free ? "Join for free" : `Choose ${tier.name}`);
1390
- return /* @__PURE__ */ jsxs13("article", { className: "chapter-membership-card", "data-tier-id": tier.id, "data-variant": presentation.variant ?? "standard", children: [
1391
- presentation.badge ? /* @__PURE__ */ jsx14("div", { className: "chapter-membership-badge", children: presentation.badge }) : null,
1392
- /* @__PURE__ */ jsx14("h3", { className: "chapter-membership-name", children: tier.name }),
1393
- /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-offer", "data-with-capacity": presentation.capacity ? "true" : void 0, children: [
1394
- presentation.capacity ? /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-capacity", "aria-label": `${presentation.capacity.total} ${presentation.capacity.label ?? "available"}`, children: [
1395
- /* @__PURE__ */ jsx14("strong", { children: presentation.capacity.total.toLocaleString("en-US") }),
1396
- /* @__PURE__ */ jsx14("small", { children: presentation.capacity.label ?? "available" })
1456
+ return /* @__PURE__ */ jsxs15("article", { className: "chapter-membership-card", "data-tier-id": tier.id, "data-variant": presentation.variant ?? "standard", children: [
1457
+ presentation.badge ? /* @__PURE__ */ jsx15("div", { className: "chapter-membership-badge", children: presentation.badge }) : null,
1458
+ /* @__PURE__ */ jsx15("h3", { className: "chapter-membership-name", children: tier.name }),
1459
+ /* @__PURE__ */ jsxs15("div", { className: "chapter-membership-offer", "data-with-capacity": presentation.capacity ? "true" : void 0, children: [
1460
+ presentation.capacity ? /* @__PURE__ */ jsxs15("div", { className: "chapter-membership-capacity", "aria-label": `${presentation.capacity.total} ${presentation.capacity.label ?? "available"}`, children: [
1461
+ /* @__PURE__ */ jsx15("strong", { children: presentation.capacity.total.toLocaleString("en-US") }),
1462
+ /* @__PURE__ */ jsx15("small", { children: presentation.capacity.label ?? "available" })
1397
1463
  ] }) : null,
1398
- /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-price-stack", children: [
1399
- presentation.priceEyebrow ? /* @__PURE__ */ jsx14("p", { className: "chapter-membership-price-eyebrow", children: presentation.priceEyebrow }) : null,
1400
- /* @__PURE__ */ jsxs13("div", { className: "chapter-membership-price-row", children: [
1401
- presentation.compareAtPriceCents !== void 0 ? /* @__PURE__ */ jsx14("span", { className: "chapter-membership-compare", children: fmtMoney(presentation.compareAtPriceCents) }) : null,
1402
- /* @__PURE__ */ jsx14("span", { className: "chapter-membership-price", children: tier.free ? "Free" : fmtMoney(tier.priceCents) }),
1403
- !tier.free && presentation.billingLabel ? /* @__PURE__ */ jsx14("span", { className: "chapter-membership-billing", children: presentation.billingLabel }) : null
1464
+ /* @__PURE__ */ jsxs15("div", { className: "chapter-membership-price-stack", children: [
1465
+ presentation.priceEyebrow ? /* @__PURE__ */ jsx15("p", { className: "chapter-membership-price-eyebrow", children: presentation.priceEyebrow }) : null,
1466
+ /* @__PURE__ */ jsxs15("div", { className: "chapter-membership-price-row", children: [
1467
+ presentation.compareAtPriceCents !== void 0 ? /* @__PURE__ */ jsx15("span", { className: "chapter-membership-compare", children: fmtMoney(presentation.compareAtPriceCents) }) : null,
1468
+ /* @__PURE__ */ jsx15("span", { className: "chapter-membership-price", children: tier.free ? "Free" : fmtMoney(tier.priceCents) }),
1469
+ !tier.free && presentation.billingLabel ? /* @__PURE__ */ jsx15("span", { className: "chapter-membership-billing", children: presentation.billingLabel }) : null
1404
1470
  ] })
1405
1471
  ] })
1406
1472
  ] }),
1407
- presentation.priceNoteMarkdown ? /* @__PURE__ */ jsx14(Markdown2, { className: "chapter-membership-price-note", source: presentation.priceNoteMarkdown }) : null,
1408
- summary ? /* @__PURE__ */ jsx14(Markdown2, { className: "chapter-membership-summary", source: summary }) : null,
1409
- presentation.benefits?.length ? /* @__PURE__ */ jsx14("ul", { className: "chapter-membership-benefits", children: presentation.benefits.map((benefit, index) => /* @__PURE__ */ jsxs13("li", { children: [
1410
- /* @__PURE__ */ jsx14("span", { className: "chapter-membership-check", "aria-hidden": "true", children: "\u2713" }),
1411
- /* @__PURE__ */ jsx14(Markdown2, { className: "chapter-membership-benefit", source: benefit })
1473
+ presentation.priceNoteMarkdown ? /* @__PURE__ */ jsx15(Markdown2, { className: "chapter-membership-price-note", source: presentation.priceNoteMarkdown }) : null,
1474
+ summary ? /* @__PURE__ */ jsx15(Markdown2, { className: "chapter-membership-summary", source: summary }) : null,
1475
+ presentation.benefits?.length ? /* @__PURE__ */ jsx15("ul", { className: "chapter-membership-benefits", children: presentation.benefits.map((benefit, index) => /* @__PURE__ */ jsxs15("li", { children: [
1476
+ /* @__PURE__ */ jsx15("span", { className: "chapter-membership-check", "aria-hidden": "true", children: "\u2713" }),
1477
+ /* @__PURE__ */ jsx15(Markdown2, { className: "chapter-membership-benefit", source: benefit })
1412
1478
  ] }, `${tier.id}-benefit-${index}`)) }) : null,
1413
- presentation.callout ? /* @__PURE__ */ jsxs13("aside", { className: "chapter-membership-callout", children: [
1414
- presentation.callout.label ? /* @__PURE__ */ jsx14("p", { className: "chapter-membership-callout-label", children: presentation.callout.label }) : null,
1415
- /* @__PURE__ */ jsx14(Markdown2, { source: presentation.callout.bodyMarkdown })
1479
+ presentation.callout ? /* @__PURE__ */ jsxs15("aside", { className: "chapter-membership-callout", children: [
1480
+ presentation.callout.label ? /* @__PURE__ */ jsx15("p", { className: "chapter-membership-callout-label", children: presentation.callout.label }) : null,
1481
+ /* @__PURE__ */ jsx15(Markdown2, { source: presentation.callout.bodyMarkdown })
1416
1482
  ] }) : null,
1417
- /* @__PURE__ */ jsx14(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1483
+ /* @__PURE__ */ jsx15(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1418
1484
  ] });
1419
1485
  }
1420
1486
  function MembershipCards(props) {
1421
1487
  const document2 = membershipPageDocument(props.document, { tiers: props.tiers });
1422
1488
  const byId = new Map(props.tiers.map((tier) => [tier.id, tier]));
1423
1489
  const mode = props.mode ?? "link";
1424
- 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: [
1425
- /* @__PURE__ */ jsx14("style", { children: MEMBERSHIP_CARDS_CSS }),
1426
- /* @__PURE__ */ jsx14("div", { className: "chapter-membership-grid", style: { "--chapter-membership-columns": String(Math.min(document2.tiers.length, 3)) }, children: document2.tiers.map((presentation) => {
1490
+ return /* @__PURE__ */ jsxs15("section", { className: ["chapter-membership", props.className].filter(Boolean).join(" "), "data-chapter-membership": MEMBERSHIP_PAGE_RENDERER, "aria-label": props.ariaLabel ?? "Membership options", children: [
1491
+ /* @__PURE__ */ jsx15("style", { children: MEMBERSHIP_CARDS_CSS }),
1492
+ /* @__PURE__ */ jsx15("div", { className: "chapter-membership-grid", style: { "--chapter-membership-columns": String(Math.min(document2.tiers.length, 3)) }, children: document2.tiers.map((presentation) => {
1427
1493
  const tier = byId.get(presentation.tierId);
1428
- return /* @__PURE__ */ jsx14(TierCard, { tier, presentation, mode, selected: props.selectedTierId === tier.id, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier }, tier.id);
1494
+ return /* @__PURE__ */ jsx15(TierCard, { tier, presentation, mode, selected: props.selectedTierId === tier.id, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier }, tier.id);
1429
1495
  }) }),
1430
- document2.footnoteMarkdown ? /* @__PURE__ */ jsx14(Markdown2, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1496
+ document2.footnoteMarkdown ? /* @__PURE__ */ jsx15(Markdown2, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1431
1497
  ] });
1432
1498
  }
1433
1499
 
@@ -1443,18 +1509,19 @@ export {
1443
1509
  SlotPicker,
1444
1510
  Rescheduler,
1445
1511
  GiftOfferCards,
1446
- GiftCodeRedeemer,
1447
1512
  PaymentStep,
1513
+ GiftPurchases,
1448
1514
  MemberGifts,
1449
1515
  MemberContentArea,
1450
1516
  MembersArea,
1517
+ GiftCodeRedeemer,
1518
+ loadJoinResume,
1451
1519
  useTierSelection,
1452
1520
  TierPlaceholder,
1453
1521
  tierNeedsPayment,
1454
- loadJoinResume,
1455
1522
  JoinIsland,
1456
1523
  MEMBERSHIP_CARD_TOKEN_DEFAULTS,
1457
1524
  membershipApplicationHref,
1458
1525
  MembershipCards
1459
1526
  };
1460
- //# sourceMappingURL=chunk-JLLRZO7B.js.map
1527
+ //# sourceMappingURL=chunk-S7CND3R5.js.map