@odla-ai/chapter 0.35.0 → 0.36.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 useEffect2, useState as useState5 } 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,242 @@ 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/members.tsx
456
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
457
+ function card(copy, kicker, body) {
458
+ return /* @__PURE__ */ jsxs6("div", { className: "card", children: [
459
+ /* @__PURE__ */ jsx6("div", { className: "card-label", children: copy.provisional.cardLabel }),
460
+ /* @__PURE__ */ jsxs6("div", { className: "meeting-block", children: [
461
+ /* @__PURE__ */ jsx6("div", { className: "meeting-kicker", children: kicker }),
462
+ body
463
+ ] })
464
+ ] });
465
+ }
466
+ function ProvisionalCard(props) {
467
+ const { api, application, applyHref, copy, onReschedule } = props;
468
+ if (!application) {
469
+ return card(
470
+ copy,
471
+ copy.provisional.applicationNeeded,
472
+ /* @__PURE__ */ jsxs6(Fragment2, { children: [
473
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
474
+ /* @__PURE__ */ jsx6("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
475
+ ] })
476
+ );
477
+ }
478
+ if (application.status === "refunded") {
479
+ return card(
480
+ copy,
481
+ copy.provisional.refunded,
482
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: copy.provisional.refundedBody })
483
+ );
484
+ }
485
+ const membership = application.paid ? /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: application.renewalAt ? formatChapterCopy(copy.provisional.renews, { date: fmtDate(application.renewalAt) }) : copy.provisional.active }) : null;
486
+ if (application.meetingAt) {
487
+ return card(
488
+ copy,
489
+ copy.provisional.introductionCall,
490
+ /* @__PURE__ */ jsxs6(Fragment2, { children: [
491
+ /* @__PURE__ */ jsx6("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
492
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
493
+ application.meetUrl ? /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: /* @__PURE__ */ jsx6("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
494
+ /* @__PURE__ */ jsx6(
495
+ Rescheduler,
496
+ {
497
+ api,
498
+ applicationId: application.id,
499
+ timezone: application.timezone,
500
+ copy: copy.reschedule,
501
+ onRescheduled: onReschedule
502
+ }
503
+ ),
504
+ membership
505
+ ] })
506
+ );
507
+ }
508
+ return card(
509
+ copy,
510
+ copy.provisional.bookCall,
511
+ /* @__PURE__ */ jsxs6(Fragment2, { children: [
512
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
513
+ /* @__PURE__ */ jsx6(
514
+ Rescheduler,
515
+ {
516
+ api,
517
+ applicationId: application.id,
518
+ timezone: application.timezone,
519
+ copy: copy.reschedule,
520
+ onRescheduled: onReschedule,
521
+ label: copy.provisional.chooseTime
522
+ }
523
+ ),
524
+ membership
525
+ ] })
526
+ );
527
+ }
528
+ function MembersArea(props) {
529
+ const inheritedCopy = useChapterCopy();
530
+ const {
531
+ api,
532
+ signOut,
533
+ adminHref = "/admin/",
534
+ applyHref = "/join.html",
535
+ memberContent,
536
+ renderProvisional
537
+ } = props;
538
+ const copy = props.copy ?? inheritedCopy.members;
539
+ const [me, setMe] = useState5(null);
540
+ const [error, setError] = useState5(false);
541
+ const reload = async () => {
542
+ try {
543
+ setMe(await api("/api/me"));
544
+ } catch {
545
+ setError(true);
546
+ }
547
+ };
548
+ useEffect2(() => {
549
+ void reload();
550
+ }, []);
551
+ if (error) return /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: copy.loadFailed });
552
+ if (!me) return /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: inheritedCopy.common.loading });
553
+ const role = me.role || "provisional";
554
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
555
+ /* @__PURE__ */ jsxs6("div", { className: "card", children: [
556
+ /* @__PURE__ */ jsxs6("div", { className: "member-row", children: [
557
+ /* @__PURE__ */ jsx6("span", { className: "member-email", children: me.email }),
558
+ /* @__PURE__ */ jsx6("span", { className: "role-badge " + role, children: role })
559
+ ] }),
560
+ /* @__PURE__ */ jsxs6("div", { className: "account-actions", children: [
561
+ /* @__PURE__ */ jsx6("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
562
+ role === "admin" ? /* @__PURE__ */ jsx6("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
563
+ ] })
564
+ ] }),
565
+ role === "provisional" ? (() => {
566
+ const defaultContent = /* @__PURE__ */ jsx6(
567
+ ProvisionalCard,
568
+ {
569
+ api,
570
+ application: me.application,
571
+ applyHref,
572
+ copy,
573
+ onReschedule: reload
574
+ }
575
+ );
576
+ return renderProvisional?.({
577
+ api,
578
+ application: me.application,
579
+ applyHref,
580
+ reload,
581
+ defaultContent
582
+ }) ?? defaultContent;
583
+ })() : memberContent ?? /* @__PURE__ */ jsx6("div", { className: "card", children: /* @__PURE__ */ jsx6("div", { className: "card-label", children: copy.full.welcome }) }),
584
+ /* @__PURE__ */ jsx6(MemberGifts, { api, copy: copy.gifts, paymentCopy: inheritedCopy.join.payment })
585
+ ] });
586
+ }
587
+
588
+ // src/ui/join.tsx
589
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState9 } from "preact/hooks";
590
+
591
+ // src/ui/join-booking.tsx
592
+ import { useEffect as useEffect3, useState as useState6 } from "preact/hooks";
593
+ import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
477
594
  function JoinBooking(props) {
478
595
  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);
596
+ const [state, setState] = useState6(null);
597
+ const [msg, setMsg] = useState6(null);
598
+ const [busy, setBusy] = useState6(false);
599
+ const [selected, setSelected] = useState6(null);
483
600
  const load = async () => {
484
601
  setMsg(null);
485
602
  try {
@@ -504,7 +621,7 @@ function JoinBooking(props) {
504
621
  setMsg(copy.loadFailed);
505
622
  }
506
623
  };
507
- useEffect2(() => {
624
+ useEffect3(() => {
508
625
  void load();
509
626
  }, [simulation]);
510
627
  const book = async () => {
@@ -536,9 +653,9 @@ function JoinBooking(props) {
536
653
  setBusy(false);
537
654
  }
538
655
  };
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(
656
+ if (!state) return /* @__PURE__ */ jsx7("p", { className: "slots-status", children: msg ?? copy.loading });
657
+ return /* @__PURE__ */ jsxs7("div", { className: "join-book", children: [
658
+ /* @__PURE__ */ jsx7(
542
659
  SlotPicker,
543
660
  {
544
661
  slots: state.slots,
@@ -548,18 +665,58 @@ function JoinBooking(props) {
548
665
  onDayChange: () => setSelected(null)
549
666
  }
550
667
  ),
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
668
+ /* @__PURE__ */ jsxs7("div", { className: "slot-confirm", hidden: !selected, children: [
669
+ /* @__PURE__ */ jsx7("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
670
+ msg ? /* @__PURE__ */ jsx7("p", { className: "step2-note", children: msg }) : null
554
671
  ] })
555
672
  ] });
556
673
  }
557
674
 
675
+ // src/ui/join-accepted.tsx
676
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
677
+ function JoinAccepted(props) {
678
+ return /* @__PURE__ */ jsxs8("div", { className: "join-done card", children: [
679
+ /* @__PURE__ */ jsx8("div", { className: "card-label", children: props.copy.accepted.label }),
680
+ /* @__PURE__ */ jsx8("p", { className: "meeting-note", children: props.copy.accepted.body }),
681
+ /* @__PURE__ */ jsx8("a", { className: "apply-link", href: props.membersHref, children: props.copy.done.memberArea })
682
+ ] });
683
+ }
684
+
685
+ // src/ui/gift-claim-card.tsx
686
+ import { useState as useState7 } from "preact/hooks";
687
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
688
+ function GiftClaimCard(props) {
689
+ const [code, setCode] = useState7(props.initialCode ?? "");
690
+ const [error, setError] = useState7(false);
691
+ const rotate = async () => {
692
+ if (!props.claimId) return;
693
+ setError(false);
694
+ const response = await fetch(`/api/gifts/${encodeURIComponent(props.claimId)}/rotate`, {
695
+ method: "POST",
696
+ headers: { "content-type": "application/json" },
697
+ body: JSON.stringify({ applicationId: props.applicationId })
698
+ });
699
+ const body = await response.json();
700
+ if (response.ok && body.code) setCode(body.code);
701
+ else setError(true);
702
+ };
703
+ return /* @__PURE__ */ jsxs9("div", { className: "join-done card", children: [
704
+ /* @__PURE__ */ jsx9("div", { className: "card-label", children: props.copy.label }),
705
+ /* @__PURE__ */ jsx9("p", { className: "meeting-note", children: props.copy.body }),
706
+ code ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
707
+ /* @__PURE__ */ jsx9("output", { className: "gift-code", children: code }),
708
+ /* @__PURE__ */ jsx9("p", { children: props.copy.save })
709
+ ] }) : null,
710
+ !code && props.claimId ? /* @__PURE__ */ jsx9("button", { type: "button", className: "btn secondary", onClick: () => void rotate(), children: props.copy.rotate }) : null,
711
+ error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: props.copy.rotateFailed }) : null
712
+ ] });
713
+ }
714
+
558
715
  // 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";
716
+ import { useState as useState8 } from "preact/hooks";
717
+ import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
561
718
  function useTierSelection(tiers, initialTierId) {
562
- const [selectedTierId, setSelectedTierId] = useState6(() => {
719
+ const [selectedTierId, setSelectedTierId] = useState8(() => {
563
720
  const preset = tiers.find((tier) => tier.id === initialTierId);
564
721
  if (preset) return preset.id;
565
722
  return tiers.length === 1 ? tiers[0].id : null;
@@ -574,10 +731,10 @@ function useTierSelection(tiers, initialTierId) {
574
731
  };
575
732
  }
576
733
  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(
734
+ return /* @__PURE__ */ jsxs10("fieldset", { "data-chapter-placeholder": "tiers", children: [
735
+ /* @__PURE__ */ jsx10("legend", { children: "Membership (placeholder \u2014 pass renderTiers to style this)" }),
736
+ context.tiers.map((tier) => /* @__PURE__ */ jsxs10("label", { children: [
737
+ /* @__PURE__ */ jsx10(
581
738
  "input",
582
739
  {
583
740
  type: "radio",
@@ -595,23 +752,21 @@ function tierNeedsPayment(selected, paymentsReady) {
595
752
  return selected ? !selected.free && paymentsReady : paymentsReady;
596
753
  }
597
754
 
598
- // src/ui/join.tsx
599
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
755
+ // src/ui/join-resume.ts
600
756
  async function loadJoinResume(applicationId, fetcher = fetch) {
601
757
  const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);
602
758
  const data = await res.json();
603
759
  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
- }
760
+ if (data.state === "done" && data.booked) return { step: "done", applicationId: data.applicationId, booked: data.booked };
761
+ if (data.state === "paymentPending") return { step: "paymentPending", applicationId: data.applicationId };
762
+ if (data.state === "booking") return { step: "booking", applicationId: data.applicationId };
763
+ if (data.state === "accepted") return { step: "accepted", applicationId: data.applicationId };
764
+ if (data.state === "gift") return { step: "gift", applicationId: data.applicationId, ...data.claimId ? { claimId: data.claimId } : {} };
613
765
  return { step: "payment", applicationId: data.applicationId };
614
766
  }
767
+
768
+ // src/ui/join.tsx
769
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
615
770
  function JoinIsland(props) {
616
771
  const inheritedCopy = useChapterCopy();
617
772
  const {
@@ -620,15 +775,18 @@ function JoinIsland(props) {
620
775
  membersHref = "/members/",
621
776
  renderStepHeader,
622
777
  renderDone,
778
+ renderAccepted,
779
+ renderGift,
623
780
  renderSubmit
624
781
  } = props;
625
782
  const copy = props.copy ?? config.copy ?? inheritedCopy.join;
626
783
  const tiers = config.tiers ?? [];
627
784
  const tierSelection = useTierSelection(tiers, props.initialTierId);
628
785
  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);
786
+ const [state, setState] = useState9(props.initialState ?? { step: "form" });
787
+ const [error, setError] = useState9(null);
788
+ const [submitting, setSubmitting] = useState9(false);
789
+ const [giftOfferId, setGiftOfferId] = useState9(props.initialGiftOfferId ?? null);
632
790
  const simulatedPayment = props.simulation ? props.simulation.payment ?? {
633
791
  lineItems: {
634
792
  standardCents: selectedTier?.priceCents ?? 0,
@@ -637,8 +795,8 @@ function JoinIsland(props) {
637
795
  }
638
796
  } : void 0;
639
797
  const simulatedBooking = props.simulation ? props.simulation.booking ?? { slots: [], timezone: "UTC" } : void 0;
640
- const submissionId = useRef2(null);
641
- useEffect3(() => {
798
+ const submissionId = useRef3(null);
799
+ useEffect4(() => {
642
800
  if (props.simulation) return;
643
801
  const resolver = props.loadResume ?? (() => {
644
802
  if (typeof window === "undefined") return Promise.resolve(null);
@@ -655,7 +813,7 @@ function JoinIsland(props) {
655
813
  live = false;
656
814
  };
657
815
  }, [props.loadResume, props.simulation]);
658
- useEffect3(() => {
816
+ useEffect4(() => {
659
817
  if (state.step !== "paymentPending") return;
660
818
  let live = true;
661
819
  let timer;
@@ -683,12 +841,13 @@ function JoinIsland(props) {
683
841
  try {
684
842
  const fields = collectFormFields(new FormData(e.currentTarget));
685
843
  fields.submissionId = submissionId.current ??= crypto.randomUUID();
686
- if (selectedTierId) fields.tierId = selectedTierId;
844
+ if (giftOfferId) fields.giftOfferId = giftOfferId;
845
+ else if (selectedTierId) fields.tierId = selectedTierId;
687
846
  if (props.simulation) {
688
847
  setState({
689
848
  // Preview the intended paid journey even before live Stripe wiring is
690
849
  // ready. Production still uses the server-authoritative readiness bit.
691
- step: tierNeedsPayment(selectedTier, true) ? "payment" : "booking",
850
+ step: giftOfferId || tierNeedsPayment(selectedTier, true) ? "payment" : "booking",
692
851
  applicationId: props.simulation.applicationId ?? "simulation-application"
693
852
  });
694
853
  return;
@@ -700,6 +859,18 @@ function JoinIsland(props) {
700
859
  });
701
860
  const data = await res.json();
702
861
  if (!res.ok || !data.id) throw new Error(data.error ?? copy.form.submitFailed);
862
+ if (data.state === "accepted") {
863
+ setState({ step: "accepted", applicationId: data.id });
864
+ return;
865
+ }
866
+ if (data.state === "paymentPending") {
867
+ setState({ step: "paymentPending", applicationId: data.id });
868
+ return;
869
+ }
870
+ if (data.state === "payment" || data.state === "booking") {
871
+ setState({ step: data.state, applicationId: data.id });
872
+ return;
873
+ }
703
874
  setState({
704
875
  step: tierNeedsPayment(selectedTier, config.paymentsReady) ? "payment" : "booking",
705
876
  applicationId: data.id
@@ -715,21 +886,35 @@ function JoinIsland(props) {
715
886
  ..."applicationId" in state ? { applicationId: state.applicationId } : {}
716
887
  });
717
888
  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 })
889
+ const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs11("div", { className: "join-done card", children: [
890
+ /* @__PURE__ */ jsx11("div", { className: "card-label", children: copy.done.label }),
891
+ /* @__PURE__ */ jsx11("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
892
+ /* @__PURE__ */ jsx11("p", { className: "meeting-note", children: copy.done.calendarInvite }),
893
+ /* @__PURE__ */ jsx11("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
723
894
  ] });
724
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
895
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
896
+ header,
897
+ body
898
+ ] });
899
+ }
900
+ if (state.step === "accepted") {
901
+ const body = renderAccepted?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsx11(JoinAccepted, { copy, membersHref });
902
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
903
+ header,
904
+ body
905
+ ] });
906
+ }
907
+ if (state.step === "gift") {
908
+ const body = renderGift?.(state) ?? /* @__PURE__ */ jsx11(GiftClaimCard, { applicationId: state.applicationId, claimId: state.claimId, initialCode: state.code, copy: copy.gift });
909
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
725
910
  header,
726
911
  body
727
912
  ] });
728
913
  }
729
914
  if (state.step === "booking") {
730
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
915
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
731
916
  header,
732
- /* @__PURE__ */ jsx7(
917
+ /* @__PURE__ */ jsx11(
733
918
  JoinBooking,
734
919
  {
735
920
  applicationId: state.applicationId,
@@ -743,9 +928,9 @@ function JoinIsland(props) {
743
928
  ] });
744
929
  }
745
930
  if (state.step === "payment") {
746
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
931
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
747
932
  header,
748
- /* @__PURE__ */ jsx7(
933
+ /* @__PURE__ */ jsx11(
749
934
  PaymentStep,
750
935
  {
751
936
  applicationId: state.applicationId,
@@ -755,27 +940,42 @@ function JoinIsland(props) {
755
940
  fonts: props.payment?.fonts,
756
941
  renderPriceLines: props.payment?.renderPriceLines,
757
942
  simulation: simulatedPayment,
758
- onPaid: () => setState({ step: "booking", applicationId: state.applicationId }),
943
+ onPaid: (result) => {
944
+ if (props.simulation) {
945
+ setState(giftOfferId ? { step: "gift", applicationId: state.applicationId, code: "gift1_simulation_only_not_a_real_claim_code" } : { step: "booking", applicationId: state.applicationId });
946
+ return;
947
+ }
948
+ if (result.gift && result.giftCode) {
949
+ setState({ step: "gift", applicationId: state.applicationId, code: result.giftCode });
950
+ return;
951
+ }
952
+ setState({ step: "paymentPending", applicationId: state.applicationId });
953
+ },
759
954
  children: props.payment?.children
760
955
  }
761
956
  )
762
957
  ] });
763
958
  }
764
959
  if (state.step === "paymentPending") {
765
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
960
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
766
961
  header,
767
- /* @__PURE__ */ jsx7("p", { className: "pay-status", role: "status", children: copy.payment.pending })
962
+ /* @__PURE__ */ jsx11("p", { className: "pay-status", role: "status", children: copy.payment.pending })
768
963
  ] });
769
964
  }
770
965
  const submitDisabled = submitting || props.submitDisabled === true;
771
966
  const tierChooser = tiers.length > 1 ? props.renderTiers?.(tierSelection) ?? TierPlaceholder(tierSelection) : null;
772
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
967
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
773
968
  header,
774
- /* @__PURE__ */ jsxs7("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
969
+ /* @__PURE__ */ jsxs11("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
970
+ (config.giftOffers?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs11("fieldset", { children: [
971
+ /* @__PURE__ */ jsx11("legend", { children: copy.gift.optionsLabel }),
972
+ /* @__PURE__ */ jsx11("button", { type: "button", className: "btn secondary", "aria-pressed": !giftOfferId, onClick: () => setGiftOfferId(null), children: copy.gift.chooseSelf }),
973
+ /* @__PURE__ */ jsx11(GiftOfferCards, { offers: config.giftOffers ?? [], mode: "select", selectedOfferId: giftOfferId, onSelect: setGiftOfferId })
974
+ ] }) : null,
775
975
  tierChooser,
776
976
  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 })
977
+ error ? /* @__PURE__ */ jsx11("p", { className: "join-error", children: error }) : null,
978
+ renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx11("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
779
979
  ] })
780
980
  ] });
781
981
  }
@@ -952,7 +1152,7 @@ function membershipPageDocument(input, options = {}) {
952
1152
  }
953
1153
 
954
1154
  // src/ui/membership-cards.tsx
955
- import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
1155
+ import { jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
956
1156
  var MEMBERSHIP_CARD_TOKEN_DEFAULTS = Object.freeze({
957
1157
  "--ui-membership-grid-gap": "1.4rem",
958
1158
  "--ui-membership-card-padding": "2rem 1.7rem 1.8rem",
@@ -1021,67 +1221,67 @@ function membershipApplicationHref(applicationPath, tierId) {
1021
1221
  }
1022
1222
  function TierAction(props) {
1023
1223
  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: [
1224
+ return /* @__PURE__ */ jsxs12("button", { type: "button", className: "chapter-membership-action", "aria-current": props.selected ? "true" : void 0, onClick: () => props.onSelectTier?.(props.tierId), children: [
1025
1225
  props.label,
1026
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1226
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": "true", children: "\u2192" })
1027
1227
  ] });
1028
1228
  }
1029
1229
  if (props.mode === "preview") {
1030
- return /* @__PURE__ */ jsxs8("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1230
+ return /* @__PURE__ */ jsxs12("span", { className: "chapter-membership-action", "aria-disabled": "true", children: [
1031
1231
  props.label,
1032
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1232
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": "true", children: "\u2192" })
1033
1233
  ] });
1034
1234
  }
1035
- return /* @__PURE__ */ jsxs8("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1235
+ return /* @__PURE__ */ jsxs12("a", { className: "chapter-membership-action", href: membershipApplicationHref(props.applicationPath, props.tierId), children: [
1036
1236
  props.label,
1037
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2192" })
1237
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": "true", children: "\u2192" })
1038
1238
  ] });
1039
1239
  }
1040
1240
  function TierCard(props) {
1041
1241
  const { tier, presentation } = props;
1042
1242
  const summary = presentation.summaryMarkdown ?? tier.blurb;
1043
1243
  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" })
1244
+ return /* @__PURE__ */ jsxs12("article", { className: "chapter-membership-card", "data-tier-id": tier.id, "data-variant": presentation.variant ?? "standard", children: [
1245
+ presentation.badge ? /* @__PURE__ */ jsx12("div", { className: "chapter-membership-badge", children: presentation.badge }) : null,
1246
+ /* @__PURE__ */ jsx12("h3", { className: "chapter-membership-name", children: tier.name }),
1247
+ /* @__PURE__ */ jsxs12("div", { className: "chapter-membership-offer", "data-with-capacity": presentation.capacity ? "true" : void 0, children: [
1248
+ presentation.capacity ? /* @__PURE__ */ jsxs12("div", { className: "chapter-membership-capacity", "aria-label": `${presentation.capacity.total} ${presentation.capacity.label ?? "available"}`, children: [
1249
+ /* @__PURE__ */ jsx12("strong", { children: presentation.capacity.total.toLocaleString("en-US") }),
1250
+ /* @__PURE__ */ jsx12("small", { children: presentation.capacity.label ?? "available" })
1051
1251
  ] }) : 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
1252
+ /* @__PURE__ */ jsxs12("div", { className: "chapter-membership-price-stack", children: [
1253
+ presentation.priceEyebrow ? /* @__PURE__ */ jsx12("p", { className: "chapter-membership-price-eyebrow", children: presentation.priceEyebrow }) : null,
1254
+ /* @__PURE__ */ jsxs12("div", { className: "chapter-membership-price-row", children: [
1255
+ presentation.compareAtPriceCents !== void 0 ? /* @__PURE__ */ jsx12("span", { className: "chapter-membership-compare", children: fmtMoney(presentation.compareAtPriceCents) }) : null,
1256
+ /* @__PURE__ */ jsx12("span", { className: "chapter-membership-price", children: tier.free ? "Free" : fmtMoney(tier.priceCents) }),
1257
+ !tier.free && presentation.billingLabel ? /* @__PURE__ */ jsx12("span", { className: "chapter-membership-billing", children: presentation.billingLabel }) : null
1058
1258
  ] })
1059
1259
  ] })
1060
1260
  ] }),
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 })
1261
+ presentation.priceNoteMarkdown ? /* @__PURE__ */ jsx12(Markdown, { className: "chapter-membership-price-note", source: presentation.priceNoteMarkdown }) : null,
1262
+ summary ? /* @__PURE__ */ jsx12(Markdown, { className: "chapter-membership-summary", source: summary }) : null,
1263
+ presentation.benefits?.length ? /* @__PURE__ */ jsx12("ul", { className: "chapter-membership-benefits", children: presentation.benefits.map((benefit, index) => /* @__PURE__ */ jsxs12("li", { children: [
1264
+ /* @__PURE__ */ jsx12("span", { className: "chapter-membership-check", "aria-hidden": "true", children: "\u2713" }),
1265
+ /* @__PURE__ */ jsx12(Markdown, { className: "chapter-membership-benefit", source: benefit })
1066
1266
  ] }, `${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 })
1267
+ presentation.callout ? /* @__PURE__ */ jsxs12("aside", { className: "chapter-membership-callout", children: [
1268
+ presentation.callout.label ? /* @__PURE__ */ jsx12("p", { className: "chapter-membership-callout-label", children: presentation.callout.label }) : null,
1269
+ /* @__PURE__ */ jsx12(Markdown, { source: presentation.callout.bodyMarkdown })
1070
1270
  ] }) : null,
1071
- /* @__PURE__ */ jsx8(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1271
+ /* @__PURE__ */ jsx12(TierAction, { tierId: tier.id, label: action, mode: props.mode, selected: props.selected, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier })
1072
1272
  ] });
1073
1273
  }
1074
1274
  function MembershipCards(props) {
1075
1275
  const document2 = membershipPageDocument(props.document, { tiers: props.tiers });
1076
1276
  const byId = new Map(props.tiers.map((tier) => [tier.id, tier]));
1077
1277
  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) => {
1278
+ return /* @__PURE__ */ jsxs12("section", { className: ["chapter-membership", props.className].filter(Boolean).join(" "), "data-chapter-membership": MEMBERSHIP_PAGE_RENDERER, "aria-label": props.ariaLabel ?? "Membership options", children: [
1279
+ /* @__PURE__ */ jsx12("style", { children: MEMBERSHIP_CARDS_CSS }),
1280
+ /* @__PURE__ */ jsx12("div", { className: "chapter-membership-grid", style: { "--chapter-membership-columns": String(Math.min(document2.tiers.length, 3)) }, children: document2.tiers.map((presentation) => {
1081
1281
  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);
1282
+ return /* @__PURE__ */ jsx12(TierCard, { tier, presentation, mode, selected: props.selectedTierId === tier.id, applicationPath: props.applicationPath, onSelectTier: props.onSelectTier }, tier.id);
1083
1283
  }) }),
1084
- document2.footnoteMarkdown ? /* @__PURE__ */ jsx8(Markdown, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1284
+ document2.footnoteMarkdown ? /* @__PURE__ */ jsx12(Markdown, { className: "chapter-membership-footnote", source: document2.footnoteMarkdown }) : null
1085
1285
  ] });
1086
1286
  }
1087
1287
 
@@ -1096,8 +1296,10 @@ export {
1096
1296
  groupSlotsByDay,
1097
1297
  SlotPicker,
1098
1298
  Rescheduler,
1099
- MembersArea,
1299
+ GiftOfferCards,
1100
1300
  PaymentStep,
1301
+ MemberGifts,
1302
+ MembersArea,
1101
1303
  useTierSelection,
1102
1304
  TierPlaceholder,
1103
1305
  tierNeedsPayment,
@@ -1107,4 +1309,4 @@ export {
1107
1309
  membershipApplicationHref,
1108
1310
  MembershipCards
1109
1311
  };
1110
- //# sourceMappingURL=chunk-UJIVBGL5.js.map
1312
+ //# sourceMappingURL=chunk-I45VYBUV.js.map