@odla-ai/chapter 0.4.0 → 0.7.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/dist/ui/index.js CHANGED
@@ -8,6 +8,7 @@ import { useCallback, useEffect, useState } from "react";
8
8
  import { TopBar, TopBarLink } from "@odla-ai/ui/components";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  var badgeText = (brand) => brand.badge ?? brand.name.slice(0, 3).toUpperCase();
11
+ var brandDisplay = (brand) => brand.wordmark ?? brand.name;
11
12
  var S = {
12
13
  gate: {
13
14
  minHeight: "100vh",
@@ -66,7 +67,7 @@ function Gate(props) {
66
67
  return /* @__PURE__ */ jsx("div", { style: S.gate, children: /* @__PURE__ */ jsxs("div", { style: S.gateInner, children: [
67
68
  /* @__PURE__ */ jsxs("div", { style: S.brandBox, children: [
68
69
  /* @__PURE__ */ jsx("div", { style: S.badge, children: badgeText(brand) }),
69
- /* @__PURE__ */ jsx("h1", { style: S.h1, children: brand.name }),
70
+ /* @__PURE__ */ jsx("h1", { style: S.h1, children: brandDisplay(brand) }),
70
71
  tagline ? /* @__PURE__ */ jsx("p", { style: { ...S.muted, margin: "8px 0 0", fontSize: 14 }, children: tagline }) : null
71
72
  ] }),
72
73
  children
@@ -99,7 +100,7 @@ function AdminShell(props) {
99
100
  /* @__PURE__ */ jsxs(TopBar, { children: [
100
101
  /* @__PURE__ */ jsxs("a", { className: "topbar-brand", href: "/", style: { display: "inline-flex", alignItems: "center" }, children: [
101
102
  /* @__PURE__ */ jsx("span", { style: S.badgeSm, children: badgeText(brand) }),
102
- brand.name
103
+ brandDisplay(brand)
103
104
  ] }),
104
105
  /* @__PURE__ */ jsx("nav", { className: "topbar-nav", "aria-label": "Admin navigation", children: sections.map((s) => /* @__PURE__ */ jsx(TopBarLink, { active: section === s.id, onClick: () => go(s.id), children: s.label }, s.id)) }),
105
106
  /* @__PURE__ */ jsx("div", { className: "topbar-spacer" }),
@@ -212,8 +213,69 @@ function ChapterAdmin(props) {
212
213
  ] });
213
214
  }
214
215
 
216
+ // src/ui/admin-people.tsx
217
+ import { useState as useState3 } from "react";
218
+ import { CrmList, RecordPanel, useCrmQuery, useCrmRecord } from "@odla-ai/crm/ui";
219
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
220
+ function PeopleBody(props) {
221
+ const { crm, client, type } = props;
222
+ const hookClient = client;
223
+ const query = useCrmQuery(hookClient, type);
224
+ const [openId, setOpenId] = useState3(null);
225
+ const record = useCrmRecord(hookClient, openId);
226
+ const [saving, setSaving] = useState3(false);
227
+ const saveFields = async (input) => {
228
+ if (!openId) return;
229
+ setSaving(true);
230
+ try {
231
+ await client.updateRecord(openId, { input });
232
+ record.refresh();
233
+ query.refresh();
234
+ } finally {
235
+ setSaving(false);
236
+ }
237
+ };
238
+ const moveStage = async (to) => {
239
+ if (!openId) return;
240
+ await client.setStage(openId, to);
241
+ record.refresh();
242
+ query.refresh();
243
+ };
244
+ const addTag = async (tag) => {
245
+ if (openId) {
246
+ await client.addTag(openId, tag);
247
+ record.refresh();
248
+ }
249
+ };
250
+ const removeTag = async (tag) => {
251
+ if (openId) {
252
+ await client.removeTag(openId, tag);
253
+ record.refresh();
254
+ }
255
+ };
256
+ return /* @__PURE__ */ jsxs3("div", { className: "wrap", children: [
257
+ /* @__PURE__ */ jsx3(CrmList, { crm, type, query, onOpenRecord: (r) => setOpenId(r.id) }),
258
+ record.detail ? /* @__PURE__ */ jsx3(
259
+ RecordPanel,
260
+ {
261
+ crm,
262
+ detail: record.detail,
263
+ onSaveFields: saveFields,
264
+ onMoveStage: moveStage,
265
+ onAddTag: addTag,
266
+ onRemoveTag: removeTag,
267
+ saving
268
+ }
269
+ ) : null
270
+ ] });
271
+ }
272
+ function peopleSection(options) {
273
+ const { crm, id = "people", label = "People", type = "person" } = options;
274
+ return { id, label, render: (ctx) => /* @__PURE__ */ jsx3(PeopleBody, { crm, client: ctx.client, type }) };
275
+ }
276
+
215
277
  // src/ui/slot-picker.tsx
216
- import { useMemo as useMemo2, useState as useState3 } from "react";
278
+ import { useMemo as useMemo2, useState as useState4 } from "react";
217
279
 
218
280
  // src/ui/datetime.ts
219
281
  function tzShort(tz) {
@@ -264,7 +326,7 @@ function groupSlotsByDay(slots, tz) {
264
326
  }
265
327
 
266
328
  // src/ui/slot-picker.tsx
267
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
329
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
268
330
  var DEFAULT_CLASSES = {
269
331
  days: "slot-days",
270
332
  day: "slot-day",
@@ -275,13 +337,13 @@ function SlotPicker(props) {
275
337
  const { slots, timezone, selectedStartAt, onPick, onDayChange, classes = DEFAULT_CLASSES } = props;
276
338
  const byDay = useMemo2(() => groupSlotsByDay(slots, timezone), [slots, timezone]);
277
339
  const dayKeys = [...byDay.keys()];
278
- const [activeDay, setActiveDay] = useState3(dayKeys[0]);
340
+ const [activeDay, setActiveDay] = useState4(dayKeys[0]);
279
341
  const day = activeDay !== void 0 && byDay.has(activeDay) ? activeDay : dayKeys[0];
280
342
  const times = (day !== void 0 ? byDay.get(day) : void 0) ?? [];
281
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
282
- /* @__PURE__ */ jsx3("div", { className: classes.days, children: dayKeys.map((key) => {
343
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
344
+ /* @__PURE__ */ jsx4("div", { className: classes.days, children: dayKeys.map((key) => {
283
345
  const first = byDay.get(key)?.[0];
284
- return /* @__PURE__ */ jsx3(
346
+ return /* @__PURE__ */ jsx4(
285
347
  "button",
286
348
  {
287
349
  type: "button",
@@ -296,7 +358,7 @@ function SlotPicker(props) {
296
358
  key
297
359
  );
298
360
  }) }),
299
- /* @__PURE__ */ jsx3("div", { className: classes.times, children: times.map((s) => /* @__PURE__ */ jsx3(
361
+ /* @__PURE__ */ jsx4("div", { className: classes.times, children: times.map((s) => /* @__PURE__ */ jsx4(
300
362
  "button",
301
363
  {
302
364
  type: "button",
@@ -311,18 +373,18 @@ function SlotPicker(props) {
311
373
  }
312
374
 
313
375
  // src/ui/members.tsx
314
- import { useEffect as useEffect3, useState as useState5 } from "react";
376
+ import { useEffect as useEffect3, useState as useState6 } from "react";
315
377
 
316
378
  // src/ui/reschedule.tsx
317
- import { useState as useState4 } from "react";
318
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
379
+ import { useState as useState5 } from "react";
380
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
319
381
  function Rescheduler(props) {
320
382
  const { api, applicationId, timezone, onRescheduled, label = "Change your time" } = props;
321
- const [open, setOpen] = useState4(false);
322
- const [slots, setSlots] = useState4(null);
323
- const [tz, setTz] = useState4(timezone);
324
- const [busy, setBusy] = useState4(false);
325
- const [msg, setMsg] = useState4(null);
383
+ const [open, setOpen] = useState5(false);
384
+ const [slots, setSlots] = useState5(null);
385
+ const [tz, setTz] = useState5(timezone);
386
+ const [busy, setBusy] = useState5(false);
387
+ const [msg, setMsg] = useState5(null);
326
388
  const start = async () => {
327
389
  setOpen(true);
328
390
  setSlots(null);
@@ -355,7 +417,7 @@ function Rescheduler(props) {
355
417
  }
356
418
  };
357
419
  if (!open) {
358
- return /* @__PURE__ */ jsx4("p", { className: "meeting-note", children: /* @__PURE__ */ jsx4(
420
+ return /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: /* @__PURE__ */ jsx5(
359
421
  "a",
360
422
  {
361
423
  href: "#",
@@ -367,8 +429,8 @@ function Rescheduler(props) {
367
429
  }
368
430
  ) });
369
431
  }
370
- return /* @__PURE__ */ jsxs4("div", { className: "msched", children: [
371
- slots === null ? /* @__PURE__ */ jsx4("p", { className: "meeting-note", children: "Loading available times\u2026" }) : slots.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "meeting-note", children: msg ?? "No open times right now. Please check back soon." }) : /* @__PURE__ */ jsx4(
432
+ return /* @__PURE__ */ jsxs5("div", { className: "msched", children: [
433
+ slots === null ? /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: "Loading available times\u2026" }) : slots.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: msg ?? "No open times right now. Please check back soon." }) : /* @__PURE__ */ jsx5(
372
434
  SlotPicker,
373
435
  {
374
436
  slots,
@@ -377,9 +439,9 @@ function Rescheduler(props) {
377
439
  onPick: (s) => void pick(s)
378
440
  }
379
441
  ),
380
- busy ? /* @__PURE__ */ jsx4("p", { className: "meeting-note", children: "Rescheduling\u2026" }) : null,
381
- msg && slots && slots.length > 0 ? /* @__PURE__ */ jsx4("p", { className: "meeting-note error", children: msg }) : null,
382
- /* @__PURE__ */ jsx4("p", { className: "meeting-note", children: /* @__PURE__ */ jsx4(
442
+ busy ? /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: "Rescheduling\u2026" }) : null,
443
+ msg && slots && slots.length > 0 ? /* @__PURE__ */ jsx5("p", { className: "meeting-note error", children: msg }) : null,
444
+ /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: /* @__PURE__ */ jsx5(
383
445
  "a",
384
446
  {
385
447
  href: "#",
@@ -394,12 +456,12 @@ function Rescheduler(props) {
394
456
  }
395
457
 
396
458
  // src/ui/members.tsx
397
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
459
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
398
460
  function card(kicker, body) {
399
- return /* @__PURE__ */ jsxs5("div", { className: "card", children: [
400
- /* @__PURE__ */ jsx5("div", { className: "card-label", children: "Your Application" }),
401
- /* @__PURE__ */ jsxs5("div", { className: "meeting-block", children: [
402
- /* @__PURE__ */ jsx5("div", { className: "meeting-kicker", children: kicker }),
461
+ return /* @__PURE__ */ jsxs6("div", { className: "card", children: [
462
+ /* @__PURE__ */ jsx6("div", { className: "card-label", children: "Your Application" }),
463
+ /* @__PURE__ */ jsxs6("div", { className: "meeting-block", children: [
464
+ /* @__PURE__ */ jsx6("div", { className: "meeting-kicker", children: kicker }),
403
465
  body
404
466
  ] })
405
467
  ] });
@@ -409,16 +471,16 @@ function ProvisionalCard(props) {
409
471
  if (!application) {
410
472
  return card(
411
473
  "One step remains",
412
- /* @__PURE__ */ jsxs5(Fragment3, { children: [
413
- /* @__PURE__ */ jsx5("div", { className: "meeting-note", children: "Your account is ready, and the application that completes it takes a few minutes." }),
414
- /* @__PURE__ */ jsx5("a", { className: "apply-link", href: applyHref, children: "Apply for membership" })
474
+ /* @__PURE__ */ jsxs6(Fragment3, { children: [
475
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: "Your account is ready, and the application that completes it takes a few minutes." }),
476
+ /* @__PURE__ */ jsx6("a", { className: "apply-link", href: applyHref, children: "Apply for membership" })
415
477
  ] })
416
478
  );
417
479
  }
418
480
  if (application.status === "refunded") {
419
- return card("Membership refunded", /* @__PURE__ */ jsx5("div", { className: "meeting-note", children: "Your fee has been refunded in full and your membership is canceled." }));
481
+ return card("Membership refunded", /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: "Your fee has been refunded in full and your membership is canceled." }));
420
482
  }
421
- const membership = application.paid ? /* @__PURE__ */ jsxs5("div", { className: "meeting-note", children: [
483
+ const membership = application.paid ? /* @__PURE__ */ jsxs6("div", { className: "meeting-note", children: [
422
484
  "Your membership is active",
423
485
  application.renewalAt ? ` and renews ${fmtDate(application.renewalAt)}` : "",
424
486
  "."
@@ -426,28 +488,28 @@ function ProvisionalCard(props) {
426
488
  if (application.meetingAt) {
427
489
  return card(
428
490
  "Your introduction call",
429
- /* @__PURE__ */ jsxs5(Fragment3, { children: [
430
- /* @__PURE__ */ jsx5("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
431
- /* @__PURE__ */ jsx5("div", { className: "meeting-note", children: "A calendar invitation with the video call link is in your email." }),
432
- application.meetUrl ? /* @__PURE__ */ jsx5("div", { className: "meeting-note", children: /* @__PURE__ */ jsx5("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: "Join the video call" }) }) : null,
433
- /* @__PURE__ */ jsx5(Rescheduler, { api, applicationId: application.id, timezone: application.timezone, onRescheduled: onReschedule }),
491
+ /* @__PURE__ */ jsxs6(Fragment3, { children: [
492
+ /* @__PURE__ */ jsx6("div", { className: "meeting-date", children: fullLabel(application.meetingAt, application.timezone) }),
493
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: "A calendar invitation with the video call link is in your email." }),
494
+ application.meetUrl ? /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: /* @__PURE__ */ jsx6("a", { href: application.meetUrl, target: "_blank", rel: "noopener", children: "Join the video call" }) }) : null,
495
+ /* @__PURE__ */ jsx6(Rescheduler, { api, applicationId: application.id, timezone: application.timezone, onRescheduled: onReschedule }),
434
496
  membership
435
497
  ] })
436
498
  );
437
499
  }
438
500
  return card(
439
501
  "Book your introduction call",
440
- /* @__PURE__ */ jsxs5(Fragment3, { children: [
441
- /* @__PURE__ */ jsx5("div", { className: "meeting-note", children: "Your application is in. Choose a time below, and a calendar invitation will reach your email." }),
442
- /* @__PURE__ */ jsx5(Rescheduler, { api, applicationId: application.id, timezone: application.timezone, onRescheduled: onReschedule, label: "Choose a time" }),
502
+ /* @__PURE__ */ jsxs6(Fragment3, { children: [
503
+ /* @__PURE__ */ jsx6("div", { className: "meeting-note", children: "Your application is in. Choose a time below, and a calendar invitation will reach your email." }),
504
+ /* @__PURE__ */ jsx6(Rescheduler, { api, applicationId: application.id, timezone: application.timezone, onRescheduled: onReschedule, label: "Choose a time" }),
443
505
  membership
444
506
  ] })
445
507
  );
446
508
  }
447
509
  function MembersArea(props) {
448
510
  const { api, signOut, adminHref = "/admin/", applyHref = "/join.html", memberContent } = props;
449
- const [me, setMe] = useState5(null);
450
- const [error, setError] = useState5(false);
511
+ const [me, setMe] = useState6(null);
512
+ const [error, setError] = useState6(false);
451
513
  const reload = async () => {
452
514
  try {
453
515
  setMe(await api("/api/me"));
@@ -458,30 +520,30 @@ function MembersArea(props) {
458
520
  useEffect3(() => {
459
521
  void reload();
460
522
  }, []);
461
- if (error) return /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: "Sign in is briefly unavailable. Please refresh." });
462
- if (!me) return /* @__PURE__ */ jsx5("p", { className: "meeting-note", children: "Loading\u2026" });
523
+ if (error) return /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "Sign in is briefly unavailable. Please refresh." });
524
+ if (!me) return /* @__PURE__ */ jsx6("p", { className: "meeting-note", children: "Loading\u2026" });
463
525
  const role = me.role || "provisional";
464
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
465
- /* @__PURE__ */ jsxs5("div", { className: "card", children: [
466
- /* @__PURE__ */ jsxs5("div", { className: "member-row", children: [
467
- /* @__PURE__ */ jsx5("span", { className: "member-email", children: me.email }),
468
- /* @__PURE__ */ jsx5("span", { className: "role-badge " + role, children: role })
526
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
527
+ /* @__PURE__ */ jsxs6("div", { className: "card", children: [
528
+ /* @__PURE__ */ jsxs6("div", { className: "member-row", children: [
529
+ /* @__PURE__ */ jsx6("span", { className: "member-email", children: me.email }),
530
+ /* @__PURE__ */ jsx6("span", { className: "role-badge " + role, children: role })
469
531
  ] }),
470
- /* @__PURE__ */ jsxs5("div", { className: "account-actions", children: [
471
- /* @__PURE__ */ jsx5("button", { className: "btn secondary mini", onClick: signOut, children: "Sign out" }),
472
- role === "admin" ? /* @__PURE__ */ jsx5("a", { className: "admin-console-link", href: adminHref, children: "Admin console" }) : null
532
+ /* @__PURE__ */ jsxs6("div", { className: "account-actions", children: [
533
+ /* @__PURE__ */ jsx6("button", { className: "btn secondary mini", onClick: signOut, children: "Sign out" }),
534
+ role === "admin" ? /* @__PURE__ */ jsx6("a", { className: "admin-console-link", href: adminHref, children: "Admin console" }) : null
473
535
  ] })
474
536
  ] }),
475
- role === "provisional" ? /* @__PURE__ */ jsx5(ProvisionalCard, { api, application: me.application, applyHref, onReschedule: reload }) : memberContent ?? /* @__PURE__ */ jsx5("div", { className: "card", children: /* @__PURE__ */ jsx5("div", { className: "card-label", children: "Welcome back." }) })
537
+ role === "provisional" ? /* @__PURE__ */ jsx6(ProvisionalCard, { api, application: me.application, applyHref, onReschedule: reload }) : memberContent ?? /* @__PURE__ */ jsx6("div", { className: "card", children: /* @__PURE__ */ jsx6("div", { className: "card-label", children: "Welcome back." }) })
476
538
  ] });
477
539
  }
478
540
 
479
541
  // src/ui/join.tsx
480
- import { useEffect as useEffect4, useState as useState7 } from "react";
542
+ import { useEffect as useEffect4, useState as useState8 } from "react";
481
543
 
482
544
  // src/ui/payment-step.tsx
483
- import { useRef, useState as useState6 } from "react";
484
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
545
+ import { useRef, useState as useState7 } from "react";
546
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
485
547
  var loader = null;
486
548
  function loadStripe() {
487
549
  const existing = globalThis.Stripe;
@@ -503,8 +565,8 @@ function loadStripe() {
503
565
  }
504
566
  function PaymentStep(props) {
505
567
  const { applicationId, refundPolicyText, onPaid } = props;
506
- const [status, setStatus] = useState6("idle");
507
- const [error, setError] = useState6(null);
568
+ const [status, setStatus] = useState7("idle");
569
+ const [error, setError] = useState7(null);
508
570
  const mountRef = useRef(null);
509
571
  const stripeRef = useRef(null);
510
572
  const elementsRef = useRef(null);
@@ -551,9 +613,9 @@ function PaymentStep(props) {
551
613
  setStatus("ready");
552
614
  }
553
615
  };
554
- return /* @__PURE__ */ jsxs6("div", { className: "join-pay", children: [
555
- /* @__PURE__ */ jsxs6("label", { className: "compliance-box", children: [
556
- /* @__PURE__ */ jsx6(
616
+ return /* @__PURE__ */ jsxs7("div", { className: "join-pay", children: [
617
+ /* @__PURE__ */ jsxs7("label", { className: "compliance-box", children: [
618
+ /* @__PURE__ */ jsx7(
557
619
  "input",
558
620
  {
559
621
  type: "checkbox",
@@ -563,23 +625,23 @@ function PaymentStep(props) {
563
625
  }
564
626
  }
565
627
  ),
566
- /* @__PURE__ */ jsx6("span", { children: refundPolicyText })
628
+ /* @__PURE__ */ jsx7("span", { children: refundPolicyText })
567
629
  ] }),
568
- status === "loading" ? /* @__PURE__ */ jsx6("p", { className: "pay-status", children: "Preparing secure payment\u2026" }) : null,
569
- /* @__PURE__ */ jsx6("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
570
- status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx6("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? "Processing\u2026" : "Pay and continue" }) : null,
571
- error ? /* @__PURE__ */ jsx6("p", { className: "pay-error", children: error }) : null
630
+ status === "loading" ? /* @__PURE__ */ jsx7("p", { className: "pay-status", children: "Preparing secure payment\u2026" }) : null,
631
+ /* @__PURE__ */ jsx7("div", { ref: mountRef, hidden: status === "idle" || status === "loading" }),
632
+ status === "ready" || status === "confirming" ? /* @__PURE__ */ jsx7("button", { className: "submit-btn", disabled: status === "confirming", onClick: () => void pay(), children: status === "confirming" ? "Processing\u2026" : "Pay and continue" }) : null,
633
+ error ? /* @__PURE__ */ jsx7("p", { className: "pay-error", children: error }) : null
572
634
  ] });
573
635
  }
574
636
 
575
637
  // src/ui/join.tsx
576
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
638
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
577
639
  function JoinBooking(props) {
578
640
  const { applicationId, onBooked } = props;
579
- const [state, setState] = useState7(null);
580
- const [msg, setMsg] = useState7(null);
581
- const [busy, setBusy] = useState7(false);
582
- const [selected, setSelected] = useState7(null);
641
+ const [state, setState] = useState8(null);
642
+ const [msg, setMsg] = useState8(null);
643
+ const [busy, setBusy] = useState8(false);
644
+ const [selected, setSelected] = useState8(null);
583
645
  const load = async () => {
584
646
  setMsg(null);
585
647
  try {
@@ -623,9 +685,9 @@ function JoinBooking(props) {
623
685
  setBusy(false);
624
686
  }
625
687
  };
626
- if (!state) return /* @__PURE__ */ jsx7("p", { className: "slots-status", children: msg ?? "Loading available times\u2026" });
627
- return /* @__PURE__ */ jsxs7("div", { className: "join-book", children: [
628
- /* @__PURE__ */ jsx7(
688
+ if (!state) return /* @__PURE__ */ jsx8("p", { className: "slots-status", children: msg ?? "Loading available times\u2026" });
689
+ return /* @__PURE__ */ jsxs8("div", { className: "join-book", children: [
690
+ /* @__PURE__ */ jsx8(
629
691
  SlotPicker,
630
692
  {
631
693
  slots: state.slots,
@@ -635,19 +697,19 @@ function JoinBooking(props) {
635
697
  onDayChange: () => setSelected(null)
636
698
  }
637
699
  ),
638
- /* @__PURE__ */ jsxs7("div", { className: "slot-confirm", hidden: !selected, children: [
639
- /* @__PURE__ */ jsx7("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? "Booking\u2026" : "Book this time" }),
640
- msg ? /* @__PURE__ */ jsx7("p", { className: "step2-note", children: msg }) : null
700
+ /* @__PURE__ */ jsxs8("div", { className: "slot-confirm", hidden: !selected, children: [
701
+ /* @__PURE__ */ jsx8("button", { className: "submit-btn", disabled: busy, onClick: () => void book(), children: busy ? "Booking\u2026" : "Book this time" }),
702
+ msg ? /* @__PURE__ */ jsx8("p", { className: "step2-note", children: msg }) : null
641
703
  ] })
642
704
  ] });
643
705
  }
644
706
  function JoinIsland(props) {
645
707
  const { config, children, membersHref = "/members/" } = props;
646
- const [step, setStep] = useState7("form");
647
- const [applicationId, setApplicationId] = useState7(null);
648
- const [error, setError] = useState7(null);
649
- const [submitting, setSubmitting] = useState7(false);
650
- const [booked, setBooked] = useState7(null);
708
+ const [step, setStep] = useState8("form");
709
+ const [applicationId, setApplicationId] = useState8(null);
710
+ const [error, setError] = useState8(null);
711
+ const [submitting, setSubmitting] = useState8(false);
712
+ const [booked, setBooked] = useState8(null);
651
713
  const submit = async (e) => {
652
714
  e.preventDefault();
653
715
  setSubmitting(true);
@@ -672,15 +734,15 @@ function JoinIsland(props) {
672
734
  }
673
735
  };
674
736
  if (step === "done" && booked) {
675
- return /* @__PURE__ */ jsxs7("div", { className: "join-done card", children: [
676
- /* @__PURE__ */ jsx7("div", { className: "card-label", children: "You're booked" }),
677
- /* @__PURE__ */ jsx7("p", { className: "meeting-date", children: fullLabel(booked.startAt, booked.timezone) }),
678
- /* @__PURE__ */ jsx7("p", { className: "meeting-note", children: "A calendar invitation with the video call link is on its way to your email." }),
679
- /* @__PURE__ */ jsx7("a", { className: "apply-link", href: membersHref, children: "Go to your member area" })
737
+ return /* @__PURE__ */ jsxs8("div", { className: "join-done card", children: [
738
+ /* @__PURE__ */ jsx8("div", { className: "card-label", children: "You're booked" }),
739
+ /* @__PURE__ */ jsx8("p", { className: "meeting-date", children: fullLabel(booked.startAt, booked.timezone) }),
740
+ /* @__PURE__ */ jsx8("p", { className: "meeting-note", children: "A calendar invitation with the video call link is on its way to your email." }),
741
+ /* @__PURE__ */ jsx8("a", { className: "apply-link", href: membersHref, children: "Go to your member area" })
680
742
  ] });
681
743
  }
682
744
  if (step === "book" && applicationId) {
683
- return /* @__PURE__ */ jsx7(
745
+ return /* @__PURE__ */ jsx8(
684
746
  JoinBooking,
685
747
  {
686
748
  applicationId,
@@ -692,15 +754,47 @@ function JoinIsland(props) {
692
754
  );
693
755
  }
694
756
  if (step === "pay" && applicationId) {
695
- return /* @__PURE__ */ jsx7(PaymentStep, { applicationId, refundPolicyText: config.refundPolicyText ?? "", onPaid: () => setStep("book") });
757
+ return /* @__PURE__ */ jsx8(PaymentStep, { applicationId, refundPolicyText: config.refundPolicyText ?? "", onPaid: () => setStep("book") });
696
758
  }
697
- return /* @__PURE__ */ jsxs7("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
759
+ return /* @__PURE__ */ jsxs8("form", { className: "join-form", onSubmit: (e) => void submit(e), children: [
698
760
  children,
699
- error ? /* @__PURE__ */ jsx7("p", { className: "join-error", children: error }) : null,
700
- /* @__PURE__ */ jsx7("button", { className: "submit-btn", type: "submit", disabled: submitting, children: submitting ? "Submitting\u2026" : "Submit application" })
761
+ error ? /* @__PURE__ */ jsx8("p", { className: "join-error", children: error }) : null,
762
+ /* @__PURE__ */ jsx8("button", { className: "submit-btn", type: "submit", disabled: submitting, children: submitting ? "Submitting\u2026" : "Submit application" })
701
763
  ] });
702
764
  }
765
+
766
+ // src/brand.ts
767
+ function paletteVar(key) {
768
+ return key.startsWith("--") ? key : `--${key}`;
769
+ }
770
+ function cleanValue(value) {
771
+ return value.replace(/[<>{};]/g, "").trim();
772
+ }
773
+ function brandTokens(brand) {
774
+ if (!brand) return "";
775
+ const decls = [];
776
+ for (const [key, value] of Object.entries(brand.palette ?? {})) {
777
+ if (typeof value === "string" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);
778
+ }
779
+ const fonts = brand.fonts;
780
+ if (fonts?.display) decls.push(`--ui-font-display: ${cleanValue(fonts.display)};`);
781
+ if (fonts?.body) decls.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
782
+ if (fonts?.numeral) decls.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
783
+ return decls.length ? `:root {
784
+ ${decls.join("\n ")}
785
+ }
786
+ ` : "";
787
+ }
788
+
789
+ // src/ui/brand-style.tsx
790
+ import { jsx as jsx9 } from "react/jsx-runtime";
791
+ function BrandStyle(props) {
792
+ const css = brandTokens(props.brand);
793
+ if (!css) return null;
794
+ return /* @__PURE__ */ jsx9("style", { children: css });
795
+ }
703
796
  export {
797
+ BrandStyle,
704
798
  ChapterAdmin,
705
799
  JoinIsland,
706
800
  MembersArea,
@@ -713,6 +807,7 @@ export {
713
807
  fmtMoney,
714
808
  fullLabel,
715
809
  groupSlotsByDay,
810
+ peopleSection,
716
811
  timeLabel,
717
812
  tzShort
718
813
  };