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