@odla-ai/chapter 0.3.0 → 0.5.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/index.cjs +254 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +269 -1
- package/dist/index.d.ts +269 -1
- package/dist/index.js +254 -3
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +169 -2
- package/dist/ui/index.js +597 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/worker/index.cjs +759 -146
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +7 -4
- package/dist/worker/index.d.ts +7 -4
- package/dist/worker/index.js +758 -147
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -427,7 +427,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
427
427
|
);
|
|
428
428
|
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial };
|
|
429
429
|
}
|
|
430
|
-
function joinConfig(group,
|
|
430
|
+
function joinConfig(group, paymentsReady2) {
|
|
431
431
|
return {
|
|
432
432
|
id: group.id,
|
|
433
433
|
name: group.name,
|
|
@@ -438,7 +438,7 @@ function joinConfig(group, paymentsReady) {
|
|
|
438
438
|
trustCopy: group.trustCopy ?? "",
|
|
439
439
|
commitmentText: group.commitmentText ?? "",
|
|
440
440
|
normsText: group.normsText ?? "",
|
|
441
|
-
paymentsReady
|
|
441
|
+
paymentsReady: paymentsReady2
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
444
|
|
|
@@ -587,6 +587,70 @@ async function verifyStripeSignature(payload, header, secret, opts = {}) {
|
|
|
587
587
|
const mac = await crypto.subtle.sign("HMAC", key, enc.encode(`${t}.${payload}`));
|
|
588
588
|
return timingSafeEqual(toHex(mac), v1);
|
|
589
589
|
}
|
|
590
|
+
function paymentsReady(group, hasSecretKey) {
|
|
591
|
+
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
592
|
+
}
|
|
593
|
+
function stripeForm(params) {
|
|
594
|
+
const out = new URLSearchParams();
|
|
595
|
+
for (const [k, v] of Object.entries(params)) {
|
|
596
|
+
if (v === void 0 || v === null) continue;
|
|
597
|
+
if (typeof v === "object") {
|
|
598
|
+
for (const [k2, v2] of Object.entries(v)) {
|
|
599
|
+
if (v2 !== void 0 && v2 !== null) out.append(`${k}[${k2}]`, String(v2));
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
out.append(k, String(v));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return out.toString();
|
|
606
|
+
}
|
|
607
|
+
function subscriptionIdempotencyKey(applicationId) {
|
|
608
|
+
return `sub:${applicationId}`;
|
|
609
|
+
}
|
|
610
|
+
function webhookMutationId(eventId) {
|
|
611
|
+
return `stripe:${eventId}`;
|
|
612
|
+
}
|
|
613
|
+
function findApplicationRef(obj) {
|
|
614
|
+
const metaOf = (v) => v && typeof v === "object" ? v.metadata ?? {} : {};
|
|
615
|
+
const pick = (m) => typeof m.applicationId === "string" ? m.applicationId : void 0;
|
|
616
|
+
const applicationId = pick(metaOf(obj)) ?? pick(metaOf(obj.subscription_details)) ?? pick(metaOf(obj.parent?.subscription_details));
|
|
617
|
+
const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
|
|
618
|
+
return { ...applicationId ? { applicationId } : {}, ...customerId ? { customerId } : {} };
|
|
619
|
+
}
|
|
620
|
+
function normalizeWebhookEvent(event) {
|
|
621
|
+
const obj = event.data?.object ?? {};
|
|
622
|
+
const ref = findApplicationRef(obj);
|
|
623
|
+
switch (event.type) {
|
|
624
|
+
case "invoice.paid": {
|
|
625
|
+
const lines = obj.lines?.data ?? [];
|
|
626
|
+
const periodEnd = lines[0]?.period?.end;
|
|
627
|
+
const renewalAt = typeof periodEnd === "number" ? periodEnd * 1e3 : void 0;
|
|
628
|
+
const kind = obj.billing_reason === "subscription_create" ? "first_payment" : "renewal";
|
|
629
|
+
return { kind, ...ref, ...renewalAt !== void 0 ? { renewalAt } : {} };
|
|
630
|
+
}
|
|
631
|
+
case "charge.refunded":
|
|
632
|
+
return { kind: "refunded", ...ref };
|
|
633
|
+
case "customer.subscription.deleted":
|
|
634
|
+
return { kind: "canceled", ...ref };
|
|
635
|
+
default:
|
|
636
|
+
return { kind: "ignored", type: event.type };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function firstPaymentPatch(currentStatus, renewalAt) {
|
|
640
|
+
return {
|
|
641
|
+
...currentStatus === "submitted" ? { status: "paid_pending_vetting" } : {},
|
|
642
|
+
...renewalAt !== void 0 ? { renewalAt } : {}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function renewalPatch(renewalAt) {
|
|
646
|
+
return { renewalAt };
|
|
647
|
+
}
|
|
648
|
+
function refundedPatch() {
|
|
649
|
+
return { status: "refunded" };
|
|
650
|
+
}
|
|
651
|
+
function canceledPatch() {
|
|
652
|
+
return { canceled: true };
|
|
653
|
+
}
|
|
590
654
|
|
|
591
655
|
// src/network.ts
|
|
592
656
|
import { createRecord, updateRecord } from "@odla-ai/crm";
|
|
@@ -616,31 +680,218 @@ async function projectSharedRecord(deps, person) {
|
|
|
616
680
|
const created = await createRecord(crmDeps, { type: "person", input, mutationId: `share:${person.hubRecordId}` });
|
|
617
681
|
return { recordId: created.id };
|
|
618
682
|
}
|
|
683
|
+
|
|
684
|
+
// src/session.ts
|
|
685
|
+
function applicationSummary(app) {
|
|
686
|
+
return {
|
|
687
|
+
id: app.id,
|
|
688
|
+
firstName: app.firstName ?? null,
|
|
689
|
+
lastName: app.lastName ?? null,
|
|
690
|
+
email: app.email ?? null,
|
|
691
|
+
status: app.status,
|
|
692
|
+
createdAt: app.createdAt ?? null,
|
|
693
|
+
meetingLink: app.meetingLink ?? null,
|
|
694
|
+
paid: Boolean(app.stripeSubscriptionId) && app.status !== "refunded",
|
|
695
|
+
renewalAt: app.renewalAt ?? null,
|
|
696
|
+
canceled: app.canceled === true
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function memberApplication(app, meeting, defaultTimezone) {
|
|
700
|
+
const summary = applicationSummary(app);
|
|
701
|
+
let meetingAt = app.meetingAt ?? null;
|
|
702
|
+
let meetUrl = null;
|
|
703
|
+
let timezone = defaultTimezone;
|
|
704
|
+
if (meeting) {
|
|
705
|
+
timezone = meeting.timezone ?? timezone;
|
|
706
|
+
if (meeting.status === "scheduled") {
|
|
707
|
+
meetingAt = meeting.startAt ?? null;
|
|
708
|
+
meetUrl = meeting.meetUrl ?? null;
|
|
709
|
+
} else {
|
|
710
|
+
meetingAt = null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return { ...summary, meetingAt, meetUrl, timezone };
|
|
714
|
+
}
|
|
715
|
+
function memberSession(user, opts) {
|
|
716
|
+
return {
|
|
717
|
+
userId: user.userId,
|
|
718
|
+
email: user.email ?? null,
|
|
719
|
+
role: user.role,
|
|
720
|
+
superAdmin: opts.superAdmin,
|
|
721
|
+
application: opts.application
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/brand.ts
|
|
726
|
+
function paletteVar(key) {
|
|
727
|
+
return key.startsWith("--") ? key : `--${key}`;
|
|
728
|
+
}
|
|
729
|
+
function cleanValue(value) {
|
|
730
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
731
|
+
}
|
|
732
|
+
function brandTokens(brand) {
|
|
733
|
+
if (!brand) return "";
|
|
734
|
+
const decls = [];
|
|
735
|
+
for (const [key, value] of Object.entries(brand.palette ?? {})) {
|
|
736
|
+
if (typeof value === "string" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);
|
|
737
|
+
}
|
|
738
|
+
const fonts = brand.fonts;
|
|
739
|
+
if (fonts?.display) decls.push(`--ui-font-display: ${cleanValue(fonts.display)};`);
|
|
740
|
+
if (fonts?.body) decls.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
|
|
741
|
+
if (fonts?.numeral) decls.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
|
|
742
|
+
return decls.length ? `:root {
|
|
743
|
+
${decls.join("\n ")}
|
|
744
|
+
}
|
|
745
|
+
` : "";
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/scheduling.ts
|
|
749
|
+
var SCHEDULING_DEFAULTS = {
|
|
750
|
+
slotMinutes: 45,
|
|
751
|
+
days: [1, 2, 3, 4, 5],
|
|
752
|
+
startHour: 9,
|
|
753
|
+
endHour: 17,
|
|
754
|
+
timezone: "America/Los_Angeles",
|
|
755
|
+
minNoticeHours: 24,
|
|
756
|
+
windowDays: 14,
|
|
757
|
+
summaryTemplate: "Introduction call with {{firstName}} {{lastName}}"
|
|
758
|
+
};
|
|
759
|
+
function isValidTimeZone(tz) {
|
|
760
|
+
try {
|
|
761
|
+
new Intl.DateTimeFormat(void 0, { timeZone: tz });
|
|
762
|
+
return true;
|
|
763
|
+
} catch {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function resolveScheduling(config) {
|
|
768
|
+
const d = config ?? {};
|
|
769
|
+
const c = {
|
|
770
|
+
slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,
|
|
771
|
+
days: d.days ?? SCHEDULING_DEFAULTS.days,
|
|
772
|
+
startHour: d.startHour ?? SCHEDULING_DEFAULTS.startHour,
|
|
773
|
+
endHour: d.endHour ?? SCHEDULING_DEFAULTS.endHour,
|
|
774
|
+
timezone: d.timezone ?? SCHEDULING_DEFAULTS.timezone,
|
|
775
|
+
minNoticeHours: d.minNoticeHours ?? SCHEDULING_DEFAULTS.minNoticeHours,
|
|
776
|
+
windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,
|
|
777
|
+
summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate
|
|
778
|
+
};
|
|
779
|
+
const fail = (msg) => {
|
|
780
|
+
throw new Error(`scheduling: ${msg}`);
|
|
781
|
+
};
|
|
782
|
+
if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) fail("slotMinutes must be 15\u2013240");
|
|
783
|
+
if (!(c.windowDays >= 1 && c.windowDays <= 62)) fail("windowDays must be 1\u201362 (FreeBusy caps at 62)");
|
|
784
|
+
if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) fail("minNoticeHours must be 0\u2013336");
|
|
785
|
+
if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) fail("require 0 \u2264 startHour < endHour \u2264 24");
|
|
786
|
+
const days = [...c.days];
|
|
787
|
+
if (!days.length || !days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {
|
|
788
|
+
fail("days must be a non-empty list of weekday integers 0\u20136");
|
|
789
|
+
}
|
|
790
|
+
if (typeof c.timezone !== "string" || !isValidTimeZone(c.timezone)) fail(`invalid IANA timezone "${c.timezone}"`);
|
|
791
|
+
if (typeof c.summaryTemplate !== "string") fail("summaryTemplate must be a string");
|
|
792
|
+
return { ...c, days };
|
|
793
|
+
}
|
|
794
|
+
var BOOKABLE_STATUSES = ["submitted", "paid_pending_vetting", "call_scheduled"];
|
|
795
|
+
function canBookFrom(status) {
|
|
796
|
+
return BOOKABLE_STATUSES.includes(status);
|
|
797
|
+
}
|
|
798
|
+
function slotWindow(now, windowDays) {
|
|
799
|
+
return { from: now, to: now + windowDays * 864e5 };
|
|
800
|
+
}
|
|
801
|
+
function endForSlot(startAt, slotMinutes) {
|
|
802
|
+
return startAt + slotMinutes * 6e4;
|
|
803
|
+
}
|
|
804
|
+
function isSlotAvailable(slots, startAt) {
|
|
805
|
+
return slots.some((s) => s.startAt === startAt);
|
|
806
|
+
}
|
|
807
|
+
function renderSummary(template, app) {
|
|
808
|
+
return template.replace("{{firstName}}", app.firstName ?? "").replace("{{lastName}}", app.lastName ?? "");
|
|
809
|
+
}
|
|
810
|
+
function bookingDecision(existing) {
|
|
811
|
+
const eventId = existing?.googleEventId ?? null;
|
|
812
|
+
return { reschedule: Boolean(eventId), eventId };
|
|
813
|
+
}
|
|
814
|
+
function introIdempotencyKey(applicationId) {
|
|
815
|
+
return `application:${applicationId}:intro`;
|
|
816
|
+
}
|
|
817
|
+
function meetingCreateRow(i) {
|
|
818
|
+
return {
|
|
819
|
+
id: i.meetingId,
|
|
820
|
+
applicationId: i.applicationId,
|
|
821
|
+
groupId: i.groupId,
|
|
822
|
+
startAt: i.startAt,
|
|
823
|
+
endAt: i.endAt,
|
|
824
|
+
timezone: i.timezone,
|
|
825
|
+
status: "scheduled",
|
|
826
|
+
googleEventId: i.googleEventId,
|
|
827
|
+
...i.meetUrl ? { meetUrl: i.meetUrl } : {},
|
|
828
|
+
...i.htmlLink ? { htmlLink: i.htmlLink } : {},
|
|
829
|
+
drift: "none",
|
|
830
|
+
createdAt: i.createdAt
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function meetingRescheduleUpdate(startAt, endAt) {
|
|
834
|
+
return { startAt, endAt, drift: "none" };
|
|
835
|
+
}
|
|
836
|
+
function applicationBookingUpdate(currentStatus, startAt, htmlLink) {
|
|
837
|
+
return {
|
|
838
|
+
meetingAt: startAt,
|
|
839
|
+
...htmlLink ? { meetingLink: htmlLink } : {},
|
|
840
|
+
...currentStatus !== "call_scheduled" ? { status: "call_scheduled" } : {}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
619
843
|
export {
|
|
844
|
+
BOOKABLE_STATUSES,
|
|
845
|
+
SCHEDULING_DEFAULTS,
|
|
846
|
+
applicationBookingUpdate,
|
|
847
|
+
applicationSummary,
|
|
848
|
+
bookingDecision,
|
|
849
|
+
brandTokens,
|
|
620
850
|
buildGroupSeed,
|
|
621
851
|
canApprove,
|
|
622
852
|
canBook,
|
|
853
|
+
canBookFrom,
|
|
623
854
|
canChangeRole,
|
|
624
855
|
canTransition,
|
|
856
|
+
canceledPatch,
|
|
625
857
|
chapterDb,
|
|
626
858
|
createChapterIntegration,
|
|
627
859
|
defaultCrm,
|
|
628
860
|
defineChapter,
|
|
861
|
+
endForSlot,
|
|
862
|
+
findApplicationRef,
|
|
863
|
+
firstPaymentPatch,
|
|
629
864
|
getVaultSecret,
|
|
865
|
+
introIdempotencyKey,
|
|
630
866
|
isAdminRole,
|
|
631
867
|
isAlreadySent,
|
|
868
|
+
isSlotAvailable,
|
|
632
869
|
joinConfig,
|
|
870
|
+
meetingCreateRow,
|
|
871
|
+
meetingRescheduleUpdate,
|
|
872
|
+
memberApplication,
|
|
873
|
+
memberSession,
|
|
874
|
+
normalizeWebhookEvent,
|
|
875
|
+
paymentsReady,
|
|
633
876
|
planDelivery,
|
|
634
877
|
projectSharedRecord,
|
|
878
|
+
refundedPatch,
|
|
635
879
|
render,
|
|
880
|
+
renderSummary,
|
|
636
881
|
renderTemplateBody,
|
|
882
|
+
renewalPatch,
|
|
637
883
|
resolveApplication,
|
|
638
884
|
resolveAuth,
|
|
639
885
|
resolvePipeline,
|
|
886
|
+
resolveScheduling,
|
|
640
887
|
roleFromClaim,
|
|
641
888
|
sharedPersonInput,
|
|
889
|
+
slotWindow,
|
|
642
890
|
stageIndex,
|
|
891
|
+
stripeForm,
|
|
643
892
|
submitApplication,
|
|
644
|
-
|
|
893
|
+
subscriptionIdempotencyKey,
|
|
894
|
+
verifyStripeSignature,
|
|
895
|
+
webhookMutationId
|
|
645
896
|
};
|
|
646
897
|
//# sourceMappingURL=index.js.map
|