@odla-ai/chapter 0.22.0 → 0.23.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 +112 -41
- package/dist/chunk-NTZSXAFT.js +411 -0
- package/dist/chunk-NTZSXAFT.js.map +1 -0
- package/dist/{chunk-ILGUJXSB.js → chunk-OYXNI6VR.js} +275 -93
- package/dist/chunk-OYXNI6VR.js.map +1 -0
- package/dist/{chunk-IWW5VAAC.js → chunk-VCC4S6JU.js} +230 -202
- package/dist/chunk-VCC4S6JU.js.map +1 -0
- package/dist/{types-CgWUQ2MH.d.ts → copy-context-pDogTR4x.d.ts} +78 -20
- package/dist/index.cjs +322 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -24
- package/dist/index.d.ts +90 -24
- package/dist/index.js +322 -3
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +31 -28
- package/dist/ui/admin/index.js +8 -3
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +11 -5
- package/dist/ui/member/index.d.ts +128 -12
- package/dist/ui/member/index.js +10 -4
- package/dist/worker/index.cjs +180 -130
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +67 -19
- package/dist/worker/index.d.ts +67 -19
- package/dist/worker/index.js +180 -130
- package/dist/worker/index.js.map +1 -1
- package/package.json +11 -11
- package/runbooks/adopt-existing.md +23 -5
- package/runbooks/greenfield.md +95 -15
- package/dist/chunk-ILGUJXSB.js.map +0 -1
- package/dist/chunk-IWW5VAAC.js.map +0 -1
- package/dist/chunk-VZPBTTO3.js +0 -91
- package/dist/chunk-VZPBTTO3.js.map +0 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatChapterCopy,
|
|
3
|
+
useChapterCopy
|
|
4
|
+
} from "./chunk-NTZSXAFT.js";
|
|
5
|
+
|
|
1
6
|
// src/ui/slot-picker.tsx
|
|
2
|
-
import { useMemo, useState } from "
|
|
7
|
+
import { useMemo, useState } from "preact/hooks";
|
|
3
8
|
|
|
4
9
|
// src/ui/datetime.ts
|
|
5
10
|
function tzShort(tz) {
|
|
@@ -50,7 +55,7 @@ function groupSlotsByDay(slots, tz) {
|
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
// src/ui/slot-picker.tsx
|
|
53
|
-
import { Fragment, jsx, jsxs } from "
|
|
58
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
54
59
|
var DEFAULT_CLASSES = {
|
|
55
60
|
days: "slot-days",
|
|
56
61
|
day: "slot-day",
|
|
@@ -97,13 +102,15 @@ function SlotPicker(props) {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
// src/ui/members.tsx
|
|
100
|
-
import { useEffect, useState as useState3 } from "
|
|
105
|
+
import { useEffect, useState as useState3 } from "preact/hooks";
|
|
101
106
|
|
|
102
107
|
// src/ui/reschedule.tsx
|
|
103
|
-
import { useState as useState2 } from "
|
|
104
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "
|
|
108
|
+
import { useState as useState2 } from "preact/hooks";
|
|
109
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
105
110
|
function Rescheduler(props) {
|
|
106
|
-
const
|
|
111
|
+
const inheritedCopy = useChapterCopy();
|
|
112
|
+
const copy = props.copy ?? inheritedCopy.members.reschedule;
|
|
113
|
+
const { api, applicationId, timezone, onRescheduled, label = copy.changeTime } = props;
|
|
107
114
|
const [open, setOpen] = useState2(false);
|
|
108
115
|
const [slots, setSlots] = useState2(null);
|
|
109
116
|
const [tz, setTz] = useState2(timezone);
|
|
@@ -117,14 +124,14 @@ function Rescheduler(props) {
|
|
|
117
124
|
const r = await api("/api/schedule/slots");
|
|
118
125
|
if (r.schedulingReady === false) {
|
|
119
126
|
setSlots([]);
|
|
120
|
-
setMsg(
|
|
127
|
+
setMsg(copy.unavailable);
|
|
121
128
|
return;
|
|
122
129
|
}
|
|
123
130
|
setSlots(r.slots ?? []);
|
|
124
131
|
if (r.timezone) setTz(r.timezone);
|
|
125
132
|
} catch {
|
|
126
133
|
setSlots([]);
|
|
127
|
-
setMsg(
|
|
134
|
+
setMsg(copy.loadFailed);
|
|
128
135
|
}
|
|
129
136
|
};
|
|
130
137
|
const pick = async (slot) => {
|
|
@@ -135,7 +142,7 @@ function Rescheduler(props) {
|
|
|
135
142
|
setOpen(false);
|
|
136
143
|
await onRescheduled();
|
|
137
144
|
} catch (e) {
|
|
138
|
-
setMsg(e instanceof Error ? e.message :
|
|
145
|
+
setMsg(e instanceof Error ? e.message : copy.slotGone);
|
|
139
146
|
} finally {
|
|
140
147
|
setBusy(false);
|
|
141
148
|
}
|
|
@@ -154,7 +161,7 @@ function Rescheduler(props) {
|
|
|
154
161
|
) });
|
|
155
162
|
}
|
|
156
163
|
return /* @__PURE__ */ jsxs2("div", { className: "msched", children: [
|
|
157
|
-
slots === null ? /* @__PURE__ */ jsx2("p", { className: "meeting-note", children:
|
|
164
|
+
slots === null ? /* @__PURE__ */ jsx2("p", { className: "meeting-note", children: copy.loading }) : slots.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "meeting-note", children: msg ?? copy.noTimes }) : /* @__PURE__ */ jsx2(
|
|
158
165
|
SlotPicker,
|
|
159
166
|
{
|
|
160
167
|
slots,
|
|
@@ -163,7 +170,7 @@ function Rescheduler(props) {
|
|
|
163
170
|
onPick: (s) => void pick(s)
|
|
164
171
|
}
|
|
165
172
|
),
|
|
166
|
-
busy ? /* @__PURE__ */ jsx2("p", { className: "meeting-note", children:
|
|
173
|
+
busy ? /* @__PURE__ */ jsx2("p", { className: "meeting-note", children: copy.rescheduling }) : null,
|
|
167
174
|
msg && slots && slots.length > 0 ? /* @__PURE__ */ jsx2("p", { className: "meeting-note error", children: msg }) : null,
|
|
168
175
|
/* @__PURE__ */ jsx2("p", { className: "meeting-note", children: /* @__PURE__ */ jsx2(
|
|
169
176
|
"a",
|
|
@@ -173,17 +180,17 @@ function Rescheduler(props) {
|
|
|
173
180
|
e.preventDefault();
|
|
174
181
|
setOpen(false);
|
|
175
182
|
},
|
|
176
|
-
children:
|
|
183
|
+
children: copy.keepTime
|
|
177
184
|
}
|
|
178
185
|
) })
|
|
179
186
|
] });
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
// src/ui/members.tsx
|
|
183
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "
|
|
184
|
-
function card(kicker, body) {
|
|
190
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
191
|
+
function card(copy, kicker, body) {
|
|
185
192
|
return /* @__PURE__ */ jsxs3("div", { className: "card", children: [
|
|
186
|
-
/* @__PURE__ */ jsx3("div", { className: "card-label", children:
|
|
193
|
+
/* @__PURE__ */ jsx3("div", { className: "card-label", children: copy.provisional.cardLabel }),
|
|
187
194
|
/* @__PURE__ */ jsxs3("div", { className: "meeting-block", children: [
|
|
188
195
|
/* @__PURE__ */ jsx3("div", { className: "meeting-kicker", children: kicker }),
|
|
189
196
|
body
|
|
@@ -191,47 +198,78 @@ function card(kicker, body) {
|
|
|
191
198
|
] });
|
|
192
199
|
}
|
|
193
200
|
function ProvisionalCard(props) {
|
|
194
|
-
const { api, application, applyHref, onReschedule } = props;
|
|
201
|
+
const { api, application, applyHref, copy, onReschedule } = props;
|
|
195
202
|
if (!application) {
|
|
196
203
|
return card(
|
|
197
|
-
|
|
204
|
+
copy,
|
|
205
|
+
copy.provisional.applicationNeeded,
|
|
198
206
|
/* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
199
|
-
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children:
|
|
200
|
-
/* @__PURE__ */ jsx3("a", { className: "apply-link", href: applyHref, children:
|
|
207
|
+
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.applicationNeededBody }),
|
|
208
|
+
/* @__PURE__ */ jsx3("a", { className: "apply-link", href: applyHref, children: copy.provisional.apply })
|
|
201
209
|
] })
|
|
202
210
|
);
|
|
203
211
|
}
|
|
204
212
|
if (application.status === "refunded") {
|
|
205
|
-
return card(
|
|
213
|
+
return card(
|
|
214
|
+
copy,
|
|
215
|
+
copy.provisional.refunded,
|
|
216
|
+
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.refundedBody })
|
|
217
|
+
);
|
|
206
218
|
}
|
|
207
|
-
const membership = application.paid ? /* @__PURE__ */
|
|
208
|
-
"Your membership is active",
|
|
209
|
-
application.renewalAt ? ` and renews ${fmtDate(application.renewalAt)}` : "",
|
|
210
|
-
"."
|
|
211
|
-
] }) : null;
|
|
219
|
+
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;
|
|
212
220
|
if (application.meetingAt) {
|
|
213
221
|
return card(
|
|
214
|
-
|
|
222
|
+
copy,
|
|
223
|
+
copy.provisional.introductionCall,
|
|
215
224
|
/* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
216
225
|
/* @__PURE__ */ jsx3("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
|
|
217
|
-
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children:
|
|
218
|
-
application.meetUrl ? /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: /* @__PURE__ */ jsx3("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children:
|
|
219
|
-
/* @__PURE__ */ jsx3(
|
|
226
|
+
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.calendarInvite }),
|
|
227
|
+
application.meetUrl ? /* @__PURE__ */ jsx3("div", { className: "meeting-note", children: /* @__PURE__ */ jsx3("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: copy.provisional.joinCall }) }) : null,
|
|
228
|
+
/* @__PURE__ */ jsx3(
|
|
229
|
+
Rescheduler,
|
|
230
|
+
{
|
|
231
|
+
api,
|
|
232
|
+
applicationId: application.id,
|
|
233
|
+
timezone: application.timezone,
|
|
234
|
+
copy: copy.reschedule,
|
|
235
|
+
onRescheduled: onReschedule
|
|
236
|
+
}
|
|
237
|
+
),
|
|
220
238
|
membership
|
|
221
239
|
] })
|
|
222
240
|
);
|
|
223
241
|
}
|
|
224
242
|
return card(
|
|
225
|
-
|
|
243
|
+
copy,
|
|
244
|
+
copy.provisional.bookCall,
|
|
226
245
|
/* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
227
|
-
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children:
|
|
228
|
-
/* @__PURE__ */ jsx3(
|
|
246
|
+
/* @__PURE__ */ jsx3("div", { className: "meeting-note", children: copy.provisional.bookCallBody }),
|
|
247
|
+
/* @__PURE__ */ jsx3(
|
|
248
|
+
Rescheduler,
|
|
249
|
+
{
|
|
250
|
+
api,
|
|
251
|
+
applicationId: application.id,
|
|
252
|
+
timezone: application.timezone,
|
|
253
|
+
copy: copy.reschedule,
|
|
254
|
+
onRescheduled: onReschedule,
|
|
255
|
+
label: copy.provisional.chooseTime
|
|
256
|
+
}
|
|
257
|
+
),
|
|
229
258
|
membership
|
|
230
259
|
] })
|
|
231
260
|
);
|
|
232
261
|
}
|
|
233
262
|
function MembersArea(props) {
|
|
234
|
-
const
|
|
263
|
+
const inheritedCopy = useChapterCopy();
|
|
264
|
+
const {
|
|
265
|
+
api,
|
|
266
|
+
signOut,
|
|
267
|
+
adminHref = "/admin/",
|
|
268
|
+
applyHref = "/join.html",
|
|
269
|
+
memberContent,
|
|
270
|
+
renderProvisional
|
|
271
|
+
} = props;
|
|
272
|
+
const copy = props.copy ?? inheritedCopy.members;
|
|
235
273
|
const [me, setMe] = useState3(null);
|
|
236
274
|
const [error, setError] = useState3(false);
|
|
237
275
|
const reload = async () => {
|
|
@@ -244,8 +282,8 @@ function MembersArea(props) {
|
|
|
244
282
|
useEffect(() => {
|
|
245
283
|
void reload();
|
|
246
284
|
}, []);
|
|
247
|
-
if (error) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children:
|
|
248
|
-
if (!me) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children:
|
|
285
|
+
if (error) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children: copy.loadFailed });
|
|
286
|
+
if (!me) return /* @__PURE__ */ jsx3("p", { className: "meeting-note", children: inheritedCopy.common.loading });
|
|
249
287
|
const role = me.role || "provisional";
|
|
250
288
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
251
289
|
/* @__PURE__ */ jsxs3("div", { className: "card", children: [
|
|
@@ -254,20 +292,38 @@ function MembersArea(props) {
|
|
|
254
292
|
/* @__PURE__ */ jsx3("span", { className: "role-badge " + role, children: role })
|
|
255
293
|
] }),
|
|
256
294
|
/* @__PURE__ */ jsxs3("div", { className: "account-actions", children: [
|
|
257
|
-
/* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children:
|
|
258
|
-
role === "admin" ? /* @__PURE__ */ jsx3("a", { className: "admin-console-link", href: adminHref, children:
|
|
295
|
+
/* @__PURE__ */ jsx3("button", { className: "btn secondary mini", onClick: signOut, children: copy.account.signOut }),
|
|
296
|
+
role === "admin" ? /* @__PURE__ */ jsx3("a", { className: "admin-console-link", href: adminHref, children: copy.account.adminConsole }) : null
|
|
259
297
|
] })
|
|
260
298
|
] }),
|
|
261
|
-
role === "provisional" ?
|
|
299
|
+
role === "provisional" ? (() => {
|
|
300
|
+
const defaultContent = /* @__PURE__ */ jsx3(
|
|
301
|
+
ProvisionalCard,
|
|
302
|
+
{
|
|
303
|
+
api,
|
|
304
|
+
application: me.application,
|
|
305
|
+
applyHref,
|
|
306
|
+
copy,
|
|
307
|
+
onReschedule: reload
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
return renderProvisional?.({
|
|
311
|
+
api,
|
|
312
|
+
application: me.application,
|
|
313
|
+
applyHref,
|
|
314
|
+
reload,
|
|
315
|
+
defaultContent
|
|
316
|
+
}) ?? defaultContent;
|
|
317
|
+
})() : memberContent ?? /* @__PURE__ */ jsx3("div", { className: "card", children: /* @__PURE__ */ jsx3("div", { className: "card-label", children: copy.full.welcome }) })
|
|
262
318
|
] });
|
|
263
319
|
}
|
|
264
320
|
|
|
265
321
|
// src/ui/join.tsx
|
|
266
|
-
import { useEffect as
|
|
322
|
+
import { useEffect as useEffect3, useState as useState6 } from "preact/hooks";
|
|
267
323
|
|
|
268
324
|
// src/ui/payment-step.tsx
|
|
269
|
-
import { useRef, useState as useState4 } from "
|
|
270
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "
|
|
325
|
+
import { useRef, useState as useState4 } from "preact/hooks";
|
|
326
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
271
327
|
var loader = null;
|
|
272
328
|
function loadStripe() {
|
|
273
329
|
const existing = globalThis.Stripe;
|
|
@@ -288,9 +344,20 @@ function loadStripe() {
|
|
|
288
344
|
return loader;
|
|
289
345
|
}
|
|
290
346
|
function PaymentStep(props) {
|
|
291
|
-
const
|
|
347
|
+
const inheritedCopy = useChapterCopy();
|
|
348
|
+
const {
|
|
349
|
+
applicationId,
|
|
350
|
+
refundPolicyText,
|
|
351
|
+
onPaid,
|
|
352
|
+
appearance,
|
|
353
|
+
fonts,
|
|
354
|
+
renderPriceLines,
|
|
355
|
+
children
|
|
356
|
+
} = props;
|
|
357
|
+
const copy = props.copy ?? inheritedCopy.join.payment;
|
|
292
358
|
const [status, setStatus] = useState4("idle");
|
|
293
359
|
const [error, setError] = useState4(null);
|
|
360
|
+
const [lineItems, setLineItems] = useState4(null);
|
|
294
361
|
const mountRef = useRef(null);
|
|
295
362
|
const stripeRef = useRef(null);
|
|
296
363
|
const elementsRef = useRef(null);
|
|
@@ -304,17 +371,22 @@ function PaymentStep(props) {
|
|
|
304
371
|
body: JSON.stringify({ applicationId, refundPolicyAck: true })
|
|
305
372
|
});
|
|
306
373
|
const data = await res.json();
|
|
307
|
-
if (!res.ok || !data.clientSecret || !data.publishableKey) throw new Error(
|
|
374
|
+
if (!res.ok || !data.clientSecret || !data.publishableKey) throw new Error(copy.setupFailed);
|
|
308
375
|
const stripe = (await loadStripe())(data.publishableKey);
|
|
309
|
-
const elements = stripe.elements({
|
|
376
|
+
const elements = stripe.elements({
|
|
377
|
+
clientSecret: data.clientSecret,
|
|
378
|
+
...appearance ? { appearance } : {},
|
|
379
|
+
...fonts ? { fonts } : {}
|
|
380
|
+
});
|
|
310
381
|
const element = elements.create("payment");
|
|
311
382
|
if (mountRef.current) element.mount(mountRef.current);
|
|
312
383
|
stripeRef.current = stripe;
|
|
313
384
|
elementsRef.current = elements;
|
|
385
|
+
setLineItems(data.lineItems ?? null);
|
|
314
386
|
setStatus("ready");
|
|
315
387
|
} catch (e) {
|
|
316
388
|
setStatus("idle");
|
|
317
|
-
setError(e instanceof Error ? e.message :
|
|
389
|
+
setError(e instanceof Error ? e.message : copy.setupFailed);
|
|
318
390
|
}
|
|
319
391
|
};
|
|
320
392
|
const pay = async () => {
|
|
@@ -323,17 +395,23 @@ function PaymentStep(props) {
|
|
|
323
395
|
if (!stripe || !elements) return;
|
|
324
396
|
setStatus("confirming");
|
|
325
397
|
setError(null);
|
|
326
|
-
|
|
398
|
+
let returnUrl;
|
|
399
|
+
if (typeof window !== "undefined") {
|
|
400
|
+
const next = new URL(window.location.href);
|
|
401
|
+
next.searchParams.set("application", applicationId);
|
|
402
|
+
next.searchParams.set("redirect_status", "succeeded");
|
|
403
|
+
returnUrl = next.toString();
|
|
404
|
+
}
|
|
327
405
|
const result = await stripe.confirmPayment({ elements, confirmParams: { return_url: returnUrl }, redirect: "if_required" });
|
|
328
406
|
if (result.error) {
|
|
329
|
-
setError(result.error.message ??
|
|
407
|
+
setError(result.error.message ?? copy.incomplete);
|
|
330
408
|
setStatus("ready");
|
|
331
409
|
return;
|
|
332
410
|
}
|
|
333
411
|
const paid = result.paymentIntent?.status;
|
|
334
412
|
if (paid === "succeeded" || paid === "processing") onPaid();
|
|
335
413
|
else {
|
|
336
|
-
setError(
|
|
414
|
+
setError(copy.incomplete);
|
|
337
415
|
setStatus("ready");
|
|
338
416
|
}
|
|
339
417
|
};
|
|
@@ -351,17 +429,20 @@ function PaymentStep(props) {
|
|
|
351
429
|
),
|
|
352
430
|
/* @__PURE__ */ jsx4("span", { children: refundPolicyText })
|
|
353
431
|
] }),
|
|
354
|
-
status === "loading" ? /* @__PURE__ */ jsx4("p", { className: "pay-status", children:
|
|
432
|
+
status === "loading" ? /* @__PURE__ */ jsx4("p", { className: "pay-status", children: copy.preparing }) : null,
|
|
433
|
+
lineItems && renderPriceLines ? renderPriceLines(lineItems) : null,
|
|
434
|
+
children,
|
|
355
435
|
/* @__PURE__ */ jsx4("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
|
|
356
|
-
status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx4("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ?
|
|
436
|
+
status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx4("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? copy.processing : copy.payAndContinue }) : null,
|
|
357
437
|
error ? /* @__PURE__ */ jsx4("p", { className: "pay-error", children: error }) : null
|
|
358
438
|
] });
|
|
359
439
|
}
|
|
360
440
|
|
|
361
|
-
// src/ui/join.tsx
|
|
362
|
-
import {
|
|
441
|
+
// src/ui/join-booking.tsx
|
|
442
|
+
import { useEffect as useEffect2, useState as useState5 } from "preact/hooks";
|
|
443
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
363
444
|
function JoinBooking(props) {
|
|
364
|
-
const { applicationId, onBooked } = props;
|
|
445
|
+
const { applicationId, copy, onBooked } = props;
|
|
365
446
|
const [state, setState] = useState5(null);
|
|
366
447
|
const [msg, setMsg] = useState5(null);
|
|
367
448
|
const [busy, setBusy] = useState5(false);
|
|
@@ -373,12 +454,12 @@ function JoinBooking(props) {
|
|
|
373
454
|
const data = await res.json();
|
|
374
455
|
if (data.schedulingReady === false || !data.slots?.length) {
|
|
375
456
|
setState(null);
|
|
376
|
-
setMsg(
|
|
457
|
+
setMsg(copy.unavailable);
|
|
377
458
|
return;
|
|
378
459
|
}
|
|
379
460
|
setState({ slots: data.slots, timezone: data.timezone ?? "UTC" });
|
|
380
461
|
} catch {
|
|
381
|
-
setMsg(
|
|
462
|
+
setMsg(copy.loadFailed);
|
|
382
463
|
}
|
|
383
464
|
};
|
|
384
465
|
useEffect2(() => {
|
|
@@ -397,19 +478,19 @@ function JoinBooking(props) {
|
|
|
397
478
|
const data = await res.json();
|
|
398
479
|
if (res.status === 409 && data.code === "calendar_slot_unavailable") {
|
|
399
480
|
setSelected(null);
|
|
400
|
-
setMsg(
|
|
481
|
+
setMsg(copy.slotTaken);
|
|
401
482
|
await load();
|
|
402
483
|
return;
|
|
403
484
|
}
|
|
404
|
-
if (!res.ok) throw new Error(data.error ??
|
|
485
|
+
if (!res.ok) throw new Error(data.error ?? copy.failed);
|
|
405
486
|
onBooked({ startAt: data.startAt ?? selected.startAt, timezone: state?.timezone ?? "UTC" });
|
|
406
487
|
} catch (e) {
|
|
407
|
-
setMsg(e instanceof Error ? e.message :
|
|
488
|
+
setMsg(e instanceof Error ? e.message : copy.failed);
|
|
408
489
|
} finally {
|
|
409
490
|
setBusy(false);
|
|
410
491
|
}
|
|
411
492
|
};
|
|
412
|
-
if (!state) return /* @__PURE__ */ jsx5("p", { className: "slots-status", children: msg ??
|
|
493
|
+
if (!state) return /* @__PURE__ */ jsx5("p", { className: "slots-status", children: msg ?? copy.loading });
|
|
413
494
|
return /* @__PURE__ */ jsxs5("div", { className: "join-book", children: [
|
|
414
495
|
/* @__PURE__ */ jsx5(
|
|
415
496
|
SlotPicker,
|
|
@@ -422,20 +503,82 @@ function JoinBooking(props) {
|
|
|
422
503
|
}
|
|
423
504
|
),
|
|
424
505
|
/* @__PURE__ */ jsxs5("div", { className: "slot-confirm", hidden: !selected, children: [
|
|
425
|
-
/* @__PURE__ */ jsx5("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ?
|
|
506
|
+
/* @__PURE__ */ jsx5("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? copy.booking : copy.book }),
|
|
426
507
|
msg ? /* @__PURE__ */ jsx5("p", { className: "step2-note", children: msg }) : null
|
|
427
508
|
] })
|
|
428
509
|
] });
|
|
429
510
|
}
|
|
511
|
+
|
|
512
|
+
// src/ui/join.tsx
|
|
513
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
514
|
+
async function loadJoinResume(applicationId, fetcher = fetch) {
|
|
515
|
+
const res = await fetcher(`/api/join/resume?application=${encodeURIComponent(applicationId)}`);
|
|
516
|
+
const data = await res.json();
|
|
517
|
+
if (!res.ok) throw new Error(data.error ?? "The application could not be resumed.");
|
|
518
|
+
if (data.state === "done" && data.booked) {
|
|
519
|
+
return { step: "done", applicationId: data.applicationId, booked: data.booked };
|
|
520
|
+
}
|
|
521
|
+
if (data.state === "paymentPending") {
|
|
522
|
+
return { step: "paymentPending", applicationId: data.applicationId };
|
|
523
|
+
}
|
|
524
|
+
if (data.state === "booking") {
|
|
525
|
+
return { step: "booking", applicationId: data.applicationId };
|
|
526
|
+
}
|
|
527
|
+
return { step: "payment", applicationId: data.applicationId };
|
|
528
|
+
}
|
|
430
529
|
function JoinIsland(props) {
|
|
431
|
-
const
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
530
|
+
const inheritedCopy = useChapterCopy();
|
|
531
|
+
const {
|
|
532
|
+
config,
|
|
533
|
+
children,
|
|
534
|
+
membersHref = "/members/",
|
|
535
|
+
renderStepHeader,
|
|
536
|
+
renderDone,
|
|
537
|
+
renderSubmit
|
|
538
|
+
} = props;
|
|
539
|
+
const copy = props.copy ?? config.copy ?? inheritedCopy.join;
|
|
540
|
+
const [state, setState] = useState6(props.initialState ?? { step: "form" });
|
|
541
|
+
const [error, setError] = useState6(null);
|
|
542
|
+
const [submitting, setSubmitting] = useState6(false);
|
|
543
|
+
useEffect3(() => {
|
|
544
|
+
const resolver = props.loadResume ?? (() => {
|
|
545
|
+
if (typeof window === "undefined") return Promise.resolve(null);
|
|
546
|
+
const applicationId = new URL(window.location.href).searchParams.get("application");
|
|
547
|
+
return applicationId ? loadJoinResume(applicationId) : Promise.resolve(null);
|
|
548
|
+
});
|
|
549
|
+
let live = true;
|
|
550
|
+
void resolver().then((resumed) => {
|
|
551
|
+
if (live && resumed) setState(resumed);
|
|
552
|
+
}).catch(() => {
|
|
553
|
+
if (live) setError(copy.form.unexpectedFailure);
|
|
554
|
+
});
|
|
555
|
+
return () => {
|
|
556
|
+
live = false;
|
|
557
|
+
};
|
|
558
|
+
}, [props.loadResume]);
|
|
559
|
+
useEffect3(() => {
|
|
560
|
+
if (state.step !== "paymentPending") return;
|
|
561
|
+
let live = true;
|
|
562
|
+
let timer;
|
|
563
|
+
const poll = async () => {
|
|
564
|
+
try {
|
|
565
|
+
const resumed = props.loadResume ? await props.loadResume() : await loadJoinResume(state.applicationId);
|
|
566
|
+
if (!live || !resumed) return;
|
|
567
|
+
setState(resumed);
|
|
568
|
+
if (resumed.step === "paymentPending") timer = setTimeout(() => void poll(), 1500);
|
|
569
|
+
} catch {
|
|
570
|
+
if (live) timer = setTimeout(() => void poll(), 2500);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
timer = setTimeout(() => void poll(), 1e3);
|
|
574
|
+
return () => {
|
|
575
|
+
live = false;
|
|
576
|
+
if (timer) clearTimeout(timer);
|
|
577
|
+
};
|
|
578
|
+
}, [state, props.loadResume]);
|
|
437
579
|
const submit = async (e) => {
|
|
438
580
|
e.preventDefault();
|
|
581
|
+
if (submitting || props.submitDisabled === true) return;
|
|
439
582
|
setSubmitting(true);
|
|
440
583
|
setError(null);
|
|
441
584
|
try {
|
|
@@ -448,42 +591,80 @@ function JoinIsland(props) {
|
|
|
448
591
|
body: JSON.stringify(fields)
|
|
449
592
|
});
|
|
450
593
|
const data = await res.json();
|
|
451
|
-
if (!res.ok || !data.id) throw new Error(data.error ??
|
|
452
|
-
|
|
453
|
-
|
|
594
|
+
if (!res.ok || !data.id) throw new Error(data.error ?? copy.form.submitFailed);
|
|
595
|
+
setState({
|
|
596
|
+
step: config.paymentsReady ? "payment" : "booking",
|
|
597
|
+
applicationId: data.id
|
|
598
|
+
});
|
|
454
599
|
} catch (err) {
|
|
455
|
-
setError(err instanceof Error ? err.message :
|
|
600
|
+
setError(err instanceof Error ? err.message : copy.form.unexpectedFailure);
|
|
456
601
|
} finally {
|
|
457
602
|
setSubmitting(false);
|
|
458
603
|
}
|
|
459
604
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
605
|
+
const header = renderStepHeader?.({
|
|
606
|
+
state,
|
|
607
|
+
..."applicationId" in state ? { applicationId: state.applicationId } : {}
|
|
608
|
+
});
|
|
609
|
+
if (state.step === "done") {
|
|
610
|
+
const body = renderDone?.({ ...state, membersHref }) ?? /* @__PURE__ */ jsxs6("div", { className: "join-done card", children: [
|
|
611
|
+
/* @__PURE__ */ jsx6("div", { className: "card-label", children: copy.done.label }),
|
|
612
|
+
/* @__PURE__ */ jsx6("p", { className: "meeting-date", children: fullLabel(state.booked.startAt, state.booked.timezone) }),
|
|
613
|
+
/* @__PURE__ */ jsx6("p", { className: "meeting-note", children: copy.done.calendarInvite }),
|
|
614
|
+
/* @__PURE__ */ jsx6("a", { className: "apply-link", href: membersHref, children: copy.done.memberArea })
|
|
615
|
+
] });
|
|
616
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
617
|
+
header,
|
|
618
|
+
body
|
|
466
619
|
] });
|
|
467
620
|
}
|
|
468
|
-
if (step === "
|
|
469
|
-
return /* @__PURE__ */
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
621
|
+
if (state.step === "booking") {
|
|
622
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
623
|
+
header,
|
|
624
|
+
/* @__PURE__ */ jsx6(
|
|
625
|
+
JoinBooking,
|
|
626
|
+
{
|
|
627
|
+
applicationId: state.applicationId,
|
|
628
|
+
copy: copy.booking,
|
|
629
|
+
onBooked: (booked) => {
|
|
630
|
+
setState({ step: "done", applicationId: state.applicationId, booked });
|
|
631
|
+
}
|
|
476
632
|
}
|
|
477
|
-
|
|
478
|
-
);
|
|
633
|
+
)
|
|
634
|
+
] });
|
|
635
|
+
}
|
|
636
|
+
if (state.step === "payment") {
|
|
637
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
638
|
+
header,
|
|
639
|
+
/* @__PURE__ */ jsx6(
|
|
640
|
+
PaymentStep,
|
|
641
|
+
{
|
|
642
|
+
applicationId: state.applicationId,
|
|
643
|
+
refundPolicyText: config.refundPolicyText ?? "",
|
|
644
|
+
copy: copy.payment,
|
|
645
|
+
appearance: props.payment?.appearance,
|
|
646
|
+
fonts: props.payment?.fonts,
|
|
647
|
+
renderPriceLines: props.payment?.renderPriceLines,
|
|
648
|
+
onPaid: () => setState({ step: "booking", applicationId: state.applicationId }),
|
|
649
|
+
children: props.payment?.children
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
] });
|
|
479
653
|
}
|
|
480
|
-
if (step === "
|
|
481
|
-
return /* @__PURE__ */
|
|
654
|
+
if (state.step === "paymentPending") {
|
|
655
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
656
|
+
header,
|
|
657
|
+
/* @__PURE__ */ jsx6("p", { className: "pay-status", role: "status", children: copy.payment.pending })
|
|
658
|
+
] });
|
|
482
659
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
/* @__PURE__ */
|
|
660
|
+
const submitDisabled = submitting || props.submitDisabled === true;
|
|
661
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
662
|
+
header,
|
|
663
|
+
/* @__PURE__ */ jsxs6("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
|
|
664
|
+
children,
|
|
665
|
+
error ? /* @__PURE__ */ jsx6("p", { className: "join-error", children: error }) : null,
|
|
666
|
+
renderSubmit?.({ submitting, disabled: submitDisabled }) ?? /* @__PURE__ */ jsx6("button", { className: "submit-btn", type: "submit", disabled: submitDisabled, children: submitting ? copy.form.submitting : copy.form.submit })
|
|
667
|
+
] })
|
|
487
668
|
] });
|
|
488
669
|
}
|
|
489
670
|
|
|
@@ -500,6 +681,7 @@ export {
|
|
|
500
681
|
Rescheduler,
|
|
501
682
|
MembersArea,
|
|
502
683
|
PaymentStep,
|
|
684
|
+
loadJoinResume,
|
|
503
685
|
JoinIsland
|
|
504
686
|
};
|
|
505
|
-
//# sourceMappingURL=chunk-
|
|
687
|
+
//# sourceMappingURL=chunk-OYXNI6VR.js.map
|