@odla-ai/chapter 0.22.1 → 0.24.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 +99 -26
- package/dist/{chunk-4COPR4SY.js → chunk-GMGTJIY3.js} +292 -97
- package/dist/chunk-GMGTJIY3.js.map +1 -0
- package/dist/chunk-WGQO4WDJ.js +412 -0
- package/dist/chunk-WGQO4WDJ.js.map +1 -0
- package/dist/{chunk-C4XQSRW7.js → chunk-ZPAJL7KG.js} +225 -190
- package/dist/chunk-ZPAJL7KG.js.map +1 -0
- package/dist/{types-CgWUQ2MH.d.ts → copy-context-Dp5hvj5W.d.ts} +85 -20
- package/dist/index.cjs +340 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -24
- package/dist/index.d.ts +111 -24
- package/dist/index.js +340 -9
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +5 -2
- package/dist/ui/admin/index.js +8 -3
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +11 -5
- package/dist/ui/member/index.d.ts +118 -2
- package/dist/ui/member/index.js +10 -4
- package/dist/worker/index.cjs +274 -213
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +74 -19
- package/dist/worker/index.d.ts +74 -19
- package/dist/worker/index.js +274 -213
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/adopt-existing.md +23 -5
- package/runbooks/greenfield.md +85 -4
- package/dist/chunk-3QKGKUTX.js +0 -91
- package/dist/chunk-3QKGKUTX.js.map +0 -1
- package/dist/chunk-4COPR4SY.js.map +0 -1
- package/dist/chunk-C4XQSRW7.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
DEFAULT_CHAPTER_COPY: () => DEFAULT_CHAPTER_COPY,
|
|
23
24
|
DEFAULT_SHARE_FIELDS: () => DEFAULT_SHARE_FIELDS,
|
|
24
25
|
SCHEDULING_DEFAULTS: () => SCHEDULING_DEFAULTS,
|
|
25
26
|
applicantProfile: () => applicantProfile,
|
|
@@ -35,6 +36,7 @@ __export(index_exports, {
|
|
|
35
36
|
canChangeRole: () => canChangeRole,
|
|
36
37
|
canTransition: () => canTransition,
|
|
37
38
|
canceledPatch: () => canceledPatch,
|
|
39
|
+
chapterBrandFromTokens: () => chapterBrandFromTokens,
|
|
38
40
|
chapterDb: () => chapterDb,
|
|
39
41
|
clampArray: () => clampArray,
|
|
40
42
|
clerkGetUser: () => clerkGetUser,
|
|
@@ -54,6 +56,7 @@ __export(index_exports, {
|
|
|
54
56
|
endForSlot: () => endForSlot,
|
|
55
57
|
findApplicationRef: () => findApplicationRef,
|
|
56
58
|
firstPaymentPatch: () => firstPaymentPatch,
|
|
59
|
+
formatChapterCopy: () => formatChapterCopy,
|
|
57
60
|
getVaultSecret: () => getVaultSecret,
|
|
58
61
|
hasDisclaimerAck: () => hasDisclaimerAck,
|
|
59
62
|
introIdempotencyKey: () => introIdempotencyKey,
|
|
@@ -83,6 +86,7 @@ __export(index_exports, {
|
|
|
83
86
|
renewalPatch: () => renewalPatch,
|
|
84
87
|
resolveApplication: () => resolveApplication,
|
|
85
88
|
resolveAuth: () => resolveAuth,
|
|
89
|
+
resolveChapterCopy: () => resolveChapterCopy,
|
|
86
90
|
resolvePipeline: () => resolvePipeline,
|
|
87
91
|
resolveScheduling: () => resolveScheduling,
|
|
88
92
|
roleFromClaim: () => roleFromClaim,
|
|
@@ -506,8 +510,8 @@ function resolveApplication(a) {
|
|
|
506
510
|
maxLen: a?.maxLen ?? {},
|
|
507
511
|
defaultMaxLen: a?.defaultMaxLen ?? 2e3,
|
|
508
512
|
bodyCap: a?.bodyCap ?? 32768,
|
|
509
|
-
requireDisclaimerAck: a?.requireDisclaimerAck ??
|
|
510
|
-
profileFields: a?.profileFields ??
|
|
513
|
+
requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
|
|
514
|
+
profileFields: a?.profileFields ?? [],
|
|
511
515
|
crmFields: a?.crmFields ?? [],
|
|
512
516
|
maxArrayLen: a?.maxArrayLen ?? 100,
|
|
513
517
|
validateEmail: a?.validateEmail ?? true
|
|
@@ -527,7 +531,7 @@ function hasDisclaimerAck(fields) {
|
|
|
527
531
|
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
528
532
|
function applicantProfile(chapter, fields) {
|
|
529
533
|
const app = chapter.application;
|
|
530
|
-
const allowed = (f) => app.profileFields
|
|
534
|
+
const allowed = (f) => app.profileFields.includes(f);
|
|
531
535
|
const profile = {};
|
|
532
536
|
for (const f of [...app.required, ...app.optional]) {
|
|
533
537
|
if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
|
|
@@ -584,6 +588,300 @@ function joinConfig(group, paymentsReady2) {
|
|
|
584
588
|
};
|
|
585
589
|
}
|
|
586
590
|
|
|
591
|
+
// src/copy-defaults-admin.ts
|
|
592
|
+
var DEFAULT_ADMIN_COPY = {
|
|
593
|
+
auth: {
|
|
594
|
+
checking: "Checking access\u2026",
|
|
595
|
+
notAuthorized: "Not authorized",
|
|
596
|
+
accountNotAuthorized: "{account} isn't on the admin list.",
|
|
597
|
+
thisAccount: "This account",
|
|
598
|
+
signOut: "Sign out",
|
|
599
|
+
loading: "Loading\u2026",
|
|
600
|
+
signInNotConfigured: "Sign-in not configured",
|
|
601
|
+
missingPublishableKey: "No Clerk publishable key is set for this environment yet.",
|
|
602
|
+
noWorkspaces: "No admin workspaces are configured.",
|
|
603
|
+
signInTagline: "Admin sign-in, invite only"
|
|
604
|
+
},
|
|
605
|
+
shell: {
|
|
606
|
+
adminRole: "admin",
|
|
607
|
+
adminConsole: "Admin Console",
|
|
608
|
+
adminName: "Admin",
|
|
609
|
+
navigationLabel: "Admin navigation"
|
|
610
|
+
},
|
|
611
|
+
workspaces: {
|
|
612
|
+
dashboard: "Dashboard",
|
|
613
|
+
overview: "Overview",
|
|
614
|
+
billing: "Billing",
|
|
615
|
+
people: "People",
|
|
616
|
+
settings: "Settings",
|
|
617
|
+
calendar: "Calendar",
|
|
618
|
+
email: "Email",
|
|
619
|
+
dashboardViewsLabel: "Dashboard views",
|
|
620
|
+
settingsViewsLabel: "Settings views",
|
|
621
|
+
collectionsLabel: "CRM collections"
|
|
622
|
+
},
|
|
623
|
+
dashboard: {
|
|
624
|
+
loading: "Loading dashboard\u2026",
|
|
625
|
+
loadFailed: "Couldn't load the dashboard",
|
|
626
|
+
upcomingCalls: "Upcoming calls",
|
|
627
|
+
callsNeedAttention: "{count} need attention",
|
|
628
|
+
noUpcomingCalls: "No upcoming calls.",
|
|
629
|
+
drift: "drift",
|
|
630
|
+
open: "Open",
|
|
631
|
+
meet: "Meet",
|
|
632
|
+
applications: "Applications",
|
|
633
|
+
newMembers: "New members",
|
|
634
|
+
newMembersUnavailable: "Appears once billing is connected.",
|
|
635
|
+
revenueAdded: "Revenue \xB7 annual run rate added",
|
|
636
|
+
revenue: "Revenue",
|
|
637
|
+
revenueUnavailable: "Appears once billing is connected.",
|
|
638
|
+
pipeline: "Pipeline",
|
|
639
|
+
pipelineLabel: "Membership pipeline",
|
|
640
|
+
thisWeek: "+{count} this week",
|
|
641
|
+
noChange: "no change",
|
|
642
|
+
activeMemberships: "Active memberships",
|
|
643
|
+
annualRunRate: "Annual run rate",
|
|
644
|
+
testMode: "test mode"
|
|
645
|
+
},
|
|
646
|
+
billing: {
|
|
647
|
+
loading: "Loading\u2026",
|
|
648
|
+
loadFailed: "Couldn't load billing",
|
|
649
|
+
notConfigured: "Billing isn't configured. No Stripe key is vaulted for this group.",
|
|
650
|
+
active: "Active",
|
|
651
|
+
annualized: "Annualized",
|
|
652
|
+
renewingSoon: "Renewing within 60 days",
|
|
653
|
+
pastDue: "Past due",
|
|
654
|
+
subscriptions: "Subscriptions",
|
|
655
|
+
testMode: "Stripe test mode",
|
|
656
|
+
truncated: "Showing the first 100 subscriptions. The list is truncated.",
|
|
657
|
+
name: "Name",
|
|
658
|
+
email: "Email",
|
|
659
|
+
application: "Application",
|
|
660
|
+
subscription: "Subscription",
|
|
661
|
+
cancelling: "cancelling",
|
|
662
|
+
amount: "Amount",
|
|
663
|
+
renews: "Renews"
|
|
664
|
+
},
|
|
665
|
+
availability: {
|
|
666
|
+
loading: "Loading\u2026",
|
|
667
|
+
loadFailed: "Couldn't load availability",
|
|
668
|
+
saved: "Saved.",
|
|
669
|
+
title: "Booking availability",
|
|
670
|
+
days: "Days",
|
|
671
|
+
startHour: "Start hour (0\u201324)",
|
|
672
|
+
endHour: "End hour (0\u201324)",
|
|
673
|
+
slotMinutes: "Slot length (minutes)",
|
|
674
|
+
minNoticeHours: "Minimum notice (hours)",
|
|
675
|
+
windowDays: "Booking window (days)",
|
|
676
|
+
timezone: "Timezone",
|
|
677
|
+
summaryTemplate: "Event summary template",
|
|
678
|
+
save: "Save availability",
|
|
679
|
+
dayLabels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
680
|
+
},
|
|
681
|
+
email: {
|
|
682
|
+
loading: "Loading\u2026",
|
|
683
|
+
loadFailed: "Couldn't load email config",
|
|
684
|
+
saved: "Saved.",
|
|
685
|
+
sent: "Sent {template} to {address}{redirected}.",
|
|
686
|
+
testFailed: "Test send did not deliver.",
|
|
687
|
+
delivery: "Delivery",
|
|
688
|
+
notificationAddress: "Notification address",
|
|
689
|
+
replyTo: "Reply-to",
|
|
690
|
+
debugInbox: "Debug inbox",
|
|
691
|
+
debugInboxHint: "Non-production sends redirect here.",
|
|
692
|
+
sendTest: "Send test",
|
|
693
|
+
enabled: "Enabled",
|
|
694
|
+
subject: "Subject",
|
|
695
|
+
body: "Body",
|
|
696
|
+
save: "Save changes",
|
|
697
|
+
sendLog: "Send log",
|
|
698
|
+
sentColumn: "Sent",
|
|
699
|
+
templateColumn: "Template",
|
|
700
|
+
statusColumn: "Status",
|
|
701
|
+
toColumn: "To",
|
|
702
|
+
failed: "failed",
|
|
703
|
+
redirected: "redirected",
|
|
704
|
+
delivered: "delivered"
|
|
705
|
+
},
|
|
706
|
+
meetings: {
|
|
707
|
+
loading: "Loading\u2026",
|
|
708
|
+
loadFailed: "Couldn't load meetings",
|
|
709
|
+
cancelConfirm: "Cancel this call? The calendar provider notifies the attendee.",
|
|
710
|
+
newStartPrompt: "New start time (for example, 2026-08-01 14:00):",
|
|
711
|
+
parseFailed: "Couldn't parse that time.",
|
|
712
|
+
agenda: "Agenda",
|
|
713
|
+
empty: "No meetings.",
|
|
714
|
+
unknown: "(unknown)",
|
|
715
|
+
drift: "drift",
|
|
716
|
+
meet: "Meet",
|
|
717
|
+
reschedule: "Reschedule",
|
|
718
|
+
cancel: "Cancel"
|
|
719
|
+
},
|
|
720
|
+
network: {
|
|
721
|
+
title: "Share to a follower",
|
|
722
|
+
allowlistDescription: "Sends the target's allowlisted fields. Its pipeline and account state remain local.",
|
|
723
|
+
sharing: "Sharing\u2026",
|
|
724
|
+
shareWith: "Share with {target}",
|
|
725
|
+
shared: "Shared with {target}.",
|
|
726
|
+
shareFailed: "Could not share with {target}.",
|
|
727
|
+
deliveryFailed: "Delivery failed."
|
|
728
|
+
},
|
|
729
|
+
records: {
|
|
730
|
+
workflowMissing: "This record is not linked to an application workflow.",
|
|
731
|
+
comms: "Comms",
|
|
732
|
+
commsHistory: "Comms history",
|
|
733
|
+
scheduling: "Scheduling",
|
|
734
|
+
billing: "Billing",
|
|
735
|
+
notes: "Notes",
|
|
736
|
+
access: "Access",
|
|
737
|
+
sharing: "Sharing",
|
|
738
|
+
lifecycle: "Access & lifecycle",
|
|
739
|
+
role: "Role",
|
|
740
|
+
superAdminHint: "Super-admin, managed in odla Studio.",
|
|
741
|
+
noAccount: "No linked account yet. The role is set once they sign in.",
|
|
742
|
+
approve: "Approve",
|
|
743
|
+
refund: "Refund",
|
|
744
|
+
approved: "Approved.",
|
|
745
|
+
refunded: "Refund issued.",
|
|
746
|
+
roleSet: "Role set to {role}.",
|
|
747
|
+
messageSent: "Message sent.",
|
|
748
|
+
messageNotSent: "Message was not sent.",
|
|
749
|
+
loadingTemplates: "Loading templates\u2026",
|
|
750
|
+
template: "Template",
|
|
751
|
+
chooseTemplate: "Choose a template\u2026",
|
|
752
|
+
sending: "Sending\u2026",
|
|
753
|
+
send: "Send",
|
|
754
|
+
noMessages: "No messages recorded.",
|
|
755
|
+
message: "Message",
|
|
756
|
+
newStartPrompt: "New start time:",
|
|
757
|
+
parseFailed: "Couldn't parse that time.",
|
|
758
|
+
cancelConfirm: "Cancel this call?",
|
|
759
|
+
callCancelled: "Call cancelled.",
|
|
760
|
+
callRescheduled: "Call rescheduled.",
|
|
761
|
+
noApplication: "No application is linked to this record.",
|
|
762
|
+
loadingMeetings: "Loading meetings\u2026",
|
|
763
|
+
noMeetings: "No meetings recorded.",
|
|
764
|
+
joinMeeting: "Join meeting",
|
|
765
|
+
openCalendar: "Open calendar",
|
|
766
|
+
reschedule: "Reschedule",
|
|
767
|
+
cancel: "Cancel"
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/copy-defaults.ts
|
|
772
|
+
var DEFAULT_COMMON_COPY = {
|
|
773
|
+
loading: "Loading\u2026",
|
|
774
|
+
saved: "Saved.",
|
|
775
|
+
unknown: "(unknown)"
|
|
776
|
+
};
|
|
777
|
+
var DEFAULT_JOIN_COPY = {
|
|
778
|
+
form: {
|
|
779
|
+
submit: "Submit application",
|
|
780
|
+
submitting: "Submitting\u2026",
|
|
781
|
+
submitFailed: "Your application could not be submitted.",
|
|
782
|
+
unexpectedFailure: "Something went wrong. Please try again."
|
|
783
|
+
},
|
|
784
|
+
booking: {
|
|
785
|
+
unavailable: "Scheduling is briefly unavailable. We'll reach out by email to arrange your call.",
|
|
786
|
+
loadFailed: "Times are briefly unavailable. Please try again.",
|
|
787
|
+
slotTaken: "That time was just taken. Here are the current openings.",
|
|
788
|
+
failed: "The booking could not be completed.",
|
|
789
|
+
loading: "Loading available times\u2026",
|
|
790
|
+
book: "Book this time",
|
|
791
|
+
booking: "Booking\u2026"
|
|
792
|
+
},
|
|
793
|
+
payment: {
|
|
794
|
+
setupFailed: "Payment could not be set up. Please try again.",
|
|
795
|
+
preparing: "Preparing secure payment\u2026",
|
|
796
|
+
pending: "Confirming your payment\u2026",
|
|
797
|
+
processing: "Processing\u2026",
|
|
798
|
+
payAndContinue: "Pay and continue",
|
|
799
|
+
incomplete: "The payment did not complete. Please try again.",
|
|
800
|
+
consent: "I have read and agree to the pricing and refund policy above."
|
|
801
|
+
},
|
|
802
|
+
done: {
|
|
803
|
+
label: "You're booked",
|
|
804
|
+
calendarInvite: "A calendar invitation with the video call link is on its way to your email.",
|
|
805
|
+
memberArea: "Go to your member area"
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
var DEFAULT_MEMBERS_COPY = {
|
|
809
|
+
loadFailed: "Sign in is briefly unavailable. Please refresh.",
|
|
810
|
+
account: {
|
|
811
|
+
signOut: "Sign out",
|
|
812
|
+
adminConsole: "Admin console"
|
|
813
|
+
},
|
|
814
|
+
provisional: {
|
|
815
|
+
cardLabel: "Your Application",
|
|
816
|
+
applicationNeeded: "One step remains",
|
|
817
|
+
applicationNeededBody: "Your account is ready, and the application that completes it takes a few minutes.",
|
|
818
|
+
apply: "Apply for membership",
|
|
819
|
+
refunded: "Membership refunded",
|
|
820
|
+
refundedBody: "Your fee has been refunded in full and your membership is canceled.",
|
|
821
|
+
active: "Your membership is active.",
|
|
822
|
+
renews: "Your membership is active and renews {date}.",
|
|
823
|
+
introductionCall: "Your introduction call",
|
|
824
|
+
calendarInvite: "A calendar invitation with the video call link is in your email.",
|
|
825
|
+
joinCall: "Join the video call",
|
|
826
|
+
bookCall: "Book your introduction call",
|
|
827
|
+
bookCallBody: "Your application is in. Choose a time below, and a calendar invitation will reach your email.",
|
|
828
|
+
chooseTime: "Choose a time"
|
|
829
|
+
},
|
|
830
|
+
full: {
|
|
831
|
+
welcome: "Welcome back."
|
|
832
|
+
},
|
|
833
|
+
reschedule: {
|
|
834
|
+
changeTime: "Change your time",
|
|
835
|
+
unavailable: "Scheduling is briefly unavailable. We'll reach out by email to arrange your call.",
|
|
836
|
+
loadFailed: "Times are briefly unavailable. Please try again.",
|
|
837
|
+
slotGone: "That time is no longer available. Please pick another.",
|
|
838
|
+
loading: "Loading available times\u2026",
|
|
839
|
+
noTimes: "No open times right now. Please check back soon.",
|
|
840
|
+
rescheduling: "Rescheduling\u2026",
|
|
841
|
+
keepTime: "Keep my current time"
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// src/copy-resolve.ts
|
|
846
|
+
var DEFAULT_CHAPTER_COPY = {
|
|
847
|
+
common: DEFAULT_COMMON_COPY,
|
|
848
|
+
join: DEFAULT_JOIN_COPY,
|
|
849
|
+
members: DEFAULT_MEMBERS_COPY,
|
|
850
|
+
admin: DEFAULT_ADMIN_COPY
|
|
851
|
+
};
|
|
852
|
+
var isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
853
|
+
function mergeCopy(base, input, path) {
|
|
854
|
+
if (typeof base === "string") {
|
|
855
|
+
if (input === void 0) return base;
|
|
856
|
+
if (typeof input !== "string") throw new Error(`${path}: must be a string`);
|
|
857
|
+
return input;
|
|
858
|
+
}
|
|
859
|
+
if (Array.isArray(base)) {
|
|
860
|
+
if (input === void 0) return [...base];
|
|
861
|
+
if (!Array.isArray(input) || !input.every((item) => typeof item === "string")) {
|
|
862
|
+
throw new Error(`${path}: must be an array of strings`);
|
|
863
|
+
}
|
|
864
|
+
return [...input];
|
|
865
|
+
}
|
|
866
|
+
if (!isObject(base)) return base;
|
|
867
|
+
if (input !== void 0 && !isObject(input)) throw new Error(`${path}: must be an object`);
|
|
868
|
+
const overrides = input;
|
|
869
|
+
const result = {};
|
|
870
|
+
for (const [key, value] of Object.entries(base)) {
|
|
871
|
+
result[key] = mergeCopy(value, overrides?.[key], `${path}.${key}`);
|
|
872
|
+
}
|
|
873
|
+
return result;
|
|
874
|
+
}
|
|
875
|
+
function resolveChapterCopy(input) {
|
|
876
|
+
return mergeCopy(DEFAULT_CHAPTER_COPY, input, "defineChapter.copy");
|
|
877
|
+
}
|
|
878
|
+
function formatChapterCopy(template, values) {
|
|
879
|
+
return template.replace(
|
|
880
|
+
/\{([a-zA-Z][a-zA-Z0-9]*)\}/g,
|
|
881
|
+
(match, key) => Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
587
885
|
// src/config.ts
|
|
588
886
|
var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
589
887
|
var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
|
|
@@ -662,11 +960,15 @@ function defineChapter(config) {
|
|
|
662
960
|
if (!config.prices || typeof config.prices.standardCents !== "number") {
|
|
663
961
|
throw new Error("defineChapter.prices.standardCents: required in chapter mode");
|
|
664
962
|
}
|
|
963
|
+
if (config.account === void 0) {
|
|
964
|
+
throw new Error('defineChapter.account: required in chapter mode; choose "none", "create", or "invite" explicitly');
|
|
965
|
+
}
|
|
665
966
|
}
|
|
666
967
|
const auth = resolveAuth(mode, config.auth);
|
|
667
968
|
const pipeline = resolvePipeline(config.pipeline);
|
|
668
969
|
const application = resolveApplication(config.application);
|
|
669
970
|
const brand = { ...config.brand, wordmark: config.brand?.wordmark ?? name };
|
|
971
|
+
const copy = resolveChapterCopy(config.copy);
|
|
670
972
|
const network = resolveNetwork(config, crm);
|
|
671
973
|
const { schema, rules } = chapterDb(mode, auth);
|
|
672
974
|
const services = config.services ?? ["db", "calendar", "o11y"];
|
|
@@ -721,6 +1023,7 @@ function defineChapter(config) {
|
|
|
721
1023
|
name,
|
|
722
1024
|
mode,
|
|
723
1025
|
brand,
|
|
1026
|
+
copy,
|
|
724
1027
|
network,
|
|
725
1028
|
crm,
|
|
726
1029
|
auth,
|
|
@@ -756,6 +1059,16 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
756
1059
|
if (group) {
|
|
757
1060
|
seeds.push({ id: "group", ns: "groups", key: { attr: "id", value: chapter.id }, attrs: { ...group, createdAt: now } });
|
|
758
1061
|
}
|
|
1062
|
+
const isLeader = (chapter.config.network?.targets?.length ?? 0) > 0;
|
|
1063
|
+
const runbooks = isLeader ? {
|
|
1064
|
+
role: "leader",
|
|
1065
|
+
slugs: ["chapter-leader", "chapter-network", "crm"],
|
|
1066
|
+
guidance: `${chapter.name} is a LEADER hub: it holds the CRM and shares records with its chapters. Read it with \`odla-ai runbook get chapter-leader\`.`
|
|
1067
|
+
} : {
|
|
1068
|
+
role: "follower",
|
|
1069
|
+
slugs: ["chapter-follower", "chapter-network"],
|
|
1070
|
+
guidance: `${chapter.name} is a FOLLOWER site: it receives people and records from a leader hub. Read it with \`odla-ai runbook get chapter-follower\`.`
|
|
1071
|
+
};
|
|
759
1072
|
return {
|
|
760
1073
|
id: "chapter",
|
|
761
1074
|
title: `Chapter: ${chapter.name}`,
|
|
@@ -766,7 +1079,8 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
766
1079
|
},
|
|
767
1080
|
rules: { ...crmDesc.rules, ...chapter.rules },
|
|
768
1081
|
seeds,
|
|
769
|
-
probes: [...crmDesc.probes ?? []]
|
|
1082
|
+
probes: [...crmDesc.probes ?? []],
|
|
1083
|
+
runbooks
|
|
770
1084
|
};
|
|
771
1085
|
}
|
|
772
1086
|
|
|
@@ -1153,7 +1467,7 @@ function billingColumns(app) {
|
|
|
1153
1467
|
async function syncApplicationToCrm(deps, opts) {
|
|
1154
1468
|
const emailKey = str(opts.app.email).toLowerCase();
|
|
1155
1469
|
if (!emailKey) return null;
|
|
1156
|
-
const
|
|
1470
|
+
const recordDeps = { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId };
|
|
1157
1471
|
const input = personInputFromApp(deps.chapter, opts.app);
|
|
1158
1472
|
const { crm_record } = await deps.db.query({
|
|
1159
1473
|
crm_record: { $: { where: { type: "person", primaryEmail: emailKey }, limit: 1 } }
|
|
@@ -1163,16 +1477,16 @@ async function syncApplicationToCrm(deps, opts) {
|
|
|
1163
1477
|
let recordId;
|
|
1164
1478
|
if (existing && typeof existing.id === "string") {
|
|
1165
1479
|
recordId = existing.id;
|
|
1166
|
-
await (0, import_crm4.updateRecord)(
|
|
1480
|
+
await (0, import_crm4.updateRecord)(recordDeps, { id: recordId, input });
|
|
1167
1481
|
} else {
|
|
1168
|
-
const created = await (0, import_crm4.createRecord)(
|
|
1482
|
+
const created = await (0, import_crm4.createRecord)(recordDeps, { type: "person", input, ...stage ? { stage } : {} });
|
|
1169
1483
|
recordId = created.id;
|
|
1170
1484
|
}
|
|
1171
1485
|
if (existing && stage && existing.stage !== stage) {
|
|
1172
|
-
await (0, import_crm4.setStage)(
|
|
1486
|
+
await (0, import_crm4.setStage)(recordDeps, { id: recordId, to: stage, authorId: "system", mutationId: `crm:stage:${recordId}:${stage}` }).catch(() => void 0);
|
|
1173
1487
|
}
|
|
1174
1488
|
await deps.db.transact([{ t: "update", ns: "crm_record", id: recordId, attrs: billingColumns(opts.app) }]);
|
|
1175
|
-
await (0, import_crm4.linkIdentity)(
|
|
1489
|
+
await (0, import_crm4.linkIdentity)(recordDeps, { recordId, email: emailKey, mutationId: `crm:link:${recordId}:${emailKey}` }).catch(() => void 0);
|
|
1176
1490
|
return recordId;
|
|
1177
1491
|
}
|
|
1178
1492
|
async function backfillCrm(deps) {
|
|
@@ -1542,6 +1856,7 @@ function brandTokens(brand, options = {}) {
|
|
|
1542
1856
|
if (fonts?.body) light.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
|
|
1543
1857
|
if (fonts?.numeral) light.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
|
|
1544
1858
|
const dark = [...paletteDecls(brand.paletteDark), ...semanticDecls(brand.tokensDark)];
|
|
1859
|
+
const invert = paletteDecls(brand.paletteInvert);
|
|
1545
1860
|
const selector = options.selector ?? ":root";
|
|
1546
1861
|
const darkSelector = selector === ":root" ? ':root[data-theme="dark"]' : `${selector}[data-theme="dark"]`;
|
|
1547
1862
|
const systemSelector = selector === ":root" ? ':root:not([data-theme="light"])' : `${selector}:not([data-theme="light"])`;
|
|
@@ -1557,10 +1872,26 @@ function brandTokens(brand, options = {}) {
|
|
|
1557
1872
|
@media (prefers-color-scheme: dark) {
|
|
1558
1873
|
${systemSelector} ${block}
|
|
1559
1874
|
}
|
|
1875
|
+
`;
|
|
1876
|
+
}
|
|
1877
|
+
if (invert.length) {
|
|
1878
|
+
const invertSelector = selector === ":root" ? ".ui-invert" : `${selector}.ui-invert, ${selector} .ui-invert`;
|
|
1879
|
+
css += `${invertSelector} {
|
|
1880
|
+
color-scheme: dark;
|
|
1881
|
+
${invert.join("\n ")}
|
|
1882
|
+
}
|
|
1560
1883
|
`;
|
|
1561
1884
|
}
|
|
1562
1885
|
return css;
|
|
1563
1886
|
}
|
|
1887
|
+
function chapterBrandFromTokens(compiled, base = {}) {
|
|
1888
|
+
return {
|
|
1889
|
+
...base,
|
|
1890
|
+
palette: { ...compiled.light, ...base.palette },
|
|
1891
|
+
paletteDark: { ...compiled.dark, ...base.paletteDark },
|
|
1892
|
+
paletteInvert: { ...compiled.dark, ...base.paletteInvert }
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1564
1895
|
|
|
1565
1896
|
// src/reconcile.ts
|
|
1566
1897
|
function isReconcilable(meeting, now) {
|