@odla-ai/chapter 0.31.4 → 0.32.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 +92 -2
- package/dist/{chunk-QWMP6TSM.js → chunk-BBCT7U3D.js} +282 -2
- package/dist/chunk-BBCT7U3D.js.map +1 -0
- package/dist/index.cjs +171 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +5 -1
- package/dist/ui/member/index.d.ts +72 -1
- package/dist/ui/member/index.js +5 -1
- package/package.json +1 -1
- package/dist/chunk-QWMP6TSM.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1852,4 +1852,73 @@ declare function loadTiers(db: ChapterDb, group: TierGroupRow): Promise<ChapterT
|
|
|
1852
1852
|
/** The tiers a join page may show: active, and joinable as configured. */
|
|
1853
1853
|
declare function loadOfferableTiers(db: ChapterDb, group: TierGroupRow, hasSecretKey: boolean): Promise<ChapterTier[]>;
|
|
1854
1854
|
|
|
1855
|
-
|
|
1855
|
+
/** The first stable, shared Chapter membership-page renderer contract. */
|
|
1856
|
+
declare const MEMBERSHIP_PAGE_RENDERER: "membership-cards-v1";
|
|
1857
|
+
/** Fixed visual emphasis choices supported by the versioned card renderer. */
|
|
1858
|
+
type MembershipTierVariant = "standard" | "featured" | "supporting";
|
|
1859
|
+
/** A bounded, token-rendered aside within one membership card. */
|
|
1860
|
+
interface MembershipTierCallout {
|
|
1861
|
+
label?: string;
|
|
1862
|
+
bodyMarkdown: string;
|
|
1863
|
+
}
|
|
1864
|
+
/** Scarcity copy displayed without changing tier availability or checkout. */
|
|
1865
|
+
interface MembershipTierCapacity {
|
|
1866
|
+
total: number;
|
|
1867
|
+
label?: string;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Presentation attached to one server-authoritative membership tier.
|
|
1871
|
+
*
|
|
1872
|
+
* Price, free/paid state, identity, and application targeting deliberately do
|
|
1873
|
+
* not live here. They are resolved from the public Chapter join config so
|
|
1874
|
+
* authored marketing copy can never change what checkout charges.
|
|
1875
|
+
*/
|
|
1876
|
+
interface MembershipTierPresentation {
|
|
1877
|
+
tierId: string;
|
|
1878
|
+
variant?: MembershipTierVariant;
|
|
1879
|
+
badge?: string;
|
|
1880
|
+
summaryMarkdown?: string;
|
|
1881
|
+
priceEyebrow?: string;
|
|
1882
|
+
compareAtPriceCents?: number;
|
|
1883
|
+
billingLabel?: string;
|
|
1884
|
+
priceNoteMarkdown?: string;
|
|
1885
|
+
capacity?: MembershipTierCapacity;
|
|
1886
|
+
benefits?: readonly string[];
|
|
1887
|
+
callout?: MembershipTierCallout;
|
|
1888
|
+
ctaLabel?: string;
|
|
1889
|
+
}
|
|
1890
|
+
/** A bounded document that can be reviewed once and rendered by any Chapter host. */
|
|
1891
|
+
interface MembershipPageDocument {
|
|
1892
|
+
schemaVersion: 1;
|
|
1893
|
+
renderer: typeof MEMBERSHIP_PAGE_RENDERER;
|
|
1894
|
+
tiers: readonly MembershipTierPresentation[];
|
|
1895
|
+
footnoteMarkdown?: string;
|
|
1896
|
+
}
|
|
1897
|
+
/** Minimal server-authoritative tier identity and charged amount used during validation. */
|
|
1898
|
+
interface MembershipTierAuthority {
|
|
1899
|
+
id: string;
|
|
1900
|
+
priceCents: number;
|
|
1901
|
+
}
|
|
1902
|
+
/** Live tier authority used to reject missing, unknown, or misleading presentation. */
|
|
1903
|
+
interface MembershipPageValidationOptions {
|
|
1904
|
+
/** When supplied, every live tier must occur exactly once in the document. */
|
|
1905
|
+
tiers?: readonly MembershipTierAuthority[];
|
|
1906
|
+
}
|
|
1907
|
+
/** A normalized document or the complete actionable list of validation failures. */
|
|
1908
|
+
type MembershipPageValidationResult = {
|
|
1909
|
+
ok: true;
|
|
1910
|
+
value: MembershipPageDocument;
|
|
1911
|
+
} | {
|
|
1912
|
+
ok: false;
|
|
1913
|
+
errors: readonly string[];
|
|
1914
|
+
};
|
|
1915
|
+
/**
|
|
1916
|
+
* Validate and normalize a membership-page document without evaluating code,
|
|
1917
|
+
* accepting HTML, or allowing authored links/styles. Markdown slots are later
|
|
1918
|
+
* rendered by @odla-ai/ui's bounded safe Markdown component.
|
|
1919
|
+
*/
|
|
1920
|
+
declare function validateMembershipPageDocument(input: unknown, options?: MembershipPageValidationOptions): MembershipPageValidationResult;
|
|
1921
|
+
/** Validate a document or fail closed with all actionable validation errors. */
|
|
1922
|
+
declare function membershipPageDocument(input: unknown, options?: MembershipPageValidationOptions): MembershipPageDocument;
|
|
1923
|
+
|
|
1924
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, MEMBERSHIP_PAGE_RENDERER, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type MembershipPageDocument, type MembershipPageValidationOptions, type MembershipPageValidationResult, type MembershipTierAuthority, type MembershipTierCallout, type MembershipTierCapacity, type MembershipTierPresentation, type MembershipTierVariant, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, membershipPageDocument, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateMembershipPageDocument, validateScheduling, webhookMutationId };
|
package/dist/index.d.ts
CHANGED
|
@@ -1852,4 +1852,73 @@ declare function loadTiers(db: ChapterDb, group: TierGroupRow): Promise<ChapterT
|
|
|
1852
1852
|
/** The tiers a join page may show: active, and joinable as configured. */
|
|
1853
1853
|
declare function loadOfferableTiers(db: ChapterDb, group: TierGroupRow, hasSecretKey: boolean): Promise<ChapterTier[]>;
|
|
1854
1854
|
|
|
1855
|
-
|
|
1855
|
+
/** The first stable, shared Chapter membership-page renderer contract. */
|
|
1856
|
+
declare const MEMBERSHIP_PAGE_RENDERER: "membership-cards-v1";
|
|
1857
|
+
/** Fixed visual emphasis choices supported by the versioned card renderer. */
|
|
1858
|
+
type MembershipTierVariant = "standard" | "featured" | "supporting";
|
|
1859
|
+
/** A bounded, token-rendered aside within one membership card. */
|
|
1860
|
+
interface MembershipTierCallout {
|
|
1861
|
+
label?: string;
|
|
1862
|
+
bodyMarkdown: string;
|
|
1863
|
+
}
|
|
1864
|
+
/** Scarcity copy displayed without changing tier availability or checkout. */
|
|
1865
|
+
interface MembershipTierCapacity {
|
|
1866
|
+
total: number;
|
|
1867
|
+
label?: string;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Presentation attached to one server-authoritative membership tier.
|
|
1871
|
+
*
|
|
1872
|
+
* Price, free/paid state, identity, and application targeting deliberately do
|
|
1873
|
+
* not live here. They are resolved from the public Chapter join config so
|
|
1874
|
+
* authored marketing copy can never change what checkout charges.
|
|
1875
|
+
*/
|
|
1876
|
+
interface MembershipTierPresentation {
|
|
1877
|
+
tierId: string;
|
|
1878
|
+
variant?: MembershipTierVariant;
|
|
1879
|
+
badge?: string;
|
|
1880
|
+
summaryMarkdown?: string;
|
|
1881
|
+
priceEyebrow?: string;
|
|
1882
|
+
compareAtPriceCents?: number;
|
|
1883
|
+
billingLabel?: string;
|
|
1884
|
+
priceNoteMarkdown?: string;
|
|
1885
|
+
capacity?: MembershipTierCapacity;
|
|
1886
|
+
benefits?: readonly string[];
|
|
1887
|
+
callout?: MembershipTierCallout;
|
|
1888
|
+
ctaLabel?: string;
|
|
1889
|
+
}
|
|
1890
|
+
/** A bounded document that can be reviewed once and rendered by any Chapter host. */
|
|
1891
|
+
interface MembershipPageDocument {
|
|
1892
|
+
schemaVersion: 1;
|
|
1893
|
+
renderer: typeof MEMBERSHIP_PAGE_RENDERER;
|
|
1894
|
+
tiers: readonly MembershipTierPresentation[];
|
|
1895
|
+
footnoteMarkdown?: string;
|
|
1896
|
+
}
|
|
1897
|
+
/** Minimal server-authoritative tier identity and charged amount used during validation. */
|
|
1898
|
+
interface MembershipTierAuthority {
|
|
1899
|
+
id: string;
|
|
1900
|
+
priceCents: number;
|
|
1901
|
+
}
|
|
1902
|
+
/** Live tier authority used to reject missing, unknown, or misleading presentation. */
|
|
1903
|
+
interface MembershipPageValidationOptions {
|
|
1904
|
+
/** When supplied, every live tier must occur exactly once in the document. */
|
|
1905
|
+
tiers?: readonly MembershipTierAuthority[];
|
|
1906
|
+
}
|
|
1907
|
+
/** A normalized document or the complete actionable list of validation failures. */
|
|
1908
|
+
type MembershipPageValidationResult = {
|
|
1909
|
+
ok: true;
|
|
1910
|
+
value: MembershipPageDocument;
|
|
1911
|
+
} | {
|
|
1912
|
+
ok: false;
|
|
1913
|
+
errors: readonly string[];
|
|
1914
|
+
};
|
|
1915
|
+
/**
|
|
1916
|
+
* Validate and normalize a membership-page document without evaluating code,
|
|
1917
|
+
* accepting HTML, or allowing authored links/styles. Markdown slots are later
|
|
1918
|
+
* rendered by @odla-ai/ui's bounded safe Markdown component.
|
|
1919
|
+
*/
|
|
1920
|
+
declare function validateMembershipPageDocument(input: unknown, options?: MembershipPageValidationOptions): MembershipPageValidationResult;
|
|
1921
|
+
/** Validate a document or fail closed with all actionable validation errors. */
|
|
1922
|
+
declare function membershipPageDocument(input: unknown, options?: MembershipPageValidationOptions): MembershipPageDocument;
|
|
1923
|
+
|
|
1924
|
+
export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkReader, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterRunbookHints, type ChapterScheduling, type ChapterSends, type ChapterTier, type ChapterTierConfig, type ClerkInviteInput, type ClerkMetadataInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type JoinConfigTier, type LeaderCrmOptions, type LeaderFormationConfig, type LiveEvent, MEMBERSHIP_PAGE_RENDERER, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type MembershipPageDocument, type MembershipPageValidationOptions, type MembershipPageValidationResult, type MembershipTierAuthority, type MembershipTierCallout, type MembershipTierCapacity, type MembershipTierPresentation, type MembershipTierVariant, type NetworkRecord, type NetworkRecordNote, type NetworkRecordPage, type NetworkRollup, type NetworkRollupTarget, type NetworkSharedNote, type NetworkSnapshot, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedLeaderFormation, type ResolvedNetwork, type ResolvedNetworkReader, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type SharedRecordV1, type SharedRecordV2, type StripeEvent, type StripeResult, type SubmitResult, type TierGroup, type TierGroupRow, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, buildTierSeeds, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, findTier, firstPaymentPatch, formatChapterCopy, formationFields, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, joinConfigTiers, leaderCrmConfig, loadOfferableTiers, loadTiers, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, membershipPageDocument, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, offerableTiers, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolveLeaderFormation, resolvePipeline, resolveScheduling, resolveTiers, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, tierIsFree, tierPayable, updateClerkUserMetadata, updateClerkUserMetadataByEmail, validateMembershipPageDocument, validateScheduling, webhookMutationId };
|
package/dist/index.js
CHANGED
|
@@ -2527,9 +2527,178 @@ async function loadTiers(db, group) {
|
|
|
2527
2527
|
async function loadOfferableTiers(db, group, hasSecretKey) {
|
|
2528
2528
|
return offerableTiers(await loadTiers(db, group), group, hasSecretKey);
|
|
2529
2529
|
}
|
|
2530
|
+
|
|
2531
|
+
// src/membership-page.ts
|
|
2532
|
+
var MEMBERSHIP_PAGE_RENDERER = "membership-cards-v1";
|
|
2533
|
+
var TIER_ID = /^[a-z][a-z0-9-]{0,79}$/;
|
|
2534
|
+
var VARIANTS = /* @__PURE__ */ new Set(["standard", "featured", "supporting"]);
|
|
2535
|
+
var ROOT_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "renderer", "tiers", "footnoteMarkdown"]);
|
|
2536
|
+
var TIER_KEYS = /* @__PURE__ */ new Set([
|
|
2537
|
+
"tierId",
|
|
2538
|
+
"variant",
|
|
2539
|
+
"badge",
|
|
2540
|
+
"summaryMarkdown",
|
|
2541
|
+
"priceEyebrow",
|
|
2542
|
+
"compareAtPriceCents",
|
|
2543
|
+
"billingLabel",
|
|
2544
|
+
"priceNoteMarkdown",
|
|
2545
|
+
"capacity",
|
|
2546
|
+
"benefits",
|
|
2547
|
+
"callout",
|
|
2548
|
+
"ctaLabel"
|
|
2549
|
+
]);
|
|
2550
|
+
var CALLOUT_KEYS = /* @__PURE__ */ new Set(["label", "bodyMarkdown"]);
|
|
2551
|
+
var CAPACITY_KEYS = /* @__PURE__ */ new Set(["total", "label"]);
|
|
2552
|
+
var MAX_TIERS = 20;
|
|
2553
|
+
var MAX_BENEFITS = 24;
|
|
2554
|
+
var MAX_MARKDOWN = 65536;
|
|
2555
|
+
var MAX_LABEL = 160;
|
|
2556
|
+
var objectRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2557
|
+
function rejectUnknownKeys(value, allowed, path, errors) {
|
|
2558
|
+
for (const key of Object.keys(value)) {
|
|
2559
|
+
if (!allowed.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
function boundedString(value, path, errors, max, required = false) {
|
|
2563
|
+
if (value === void 0) {
|
|
2564
|
+
if (required) errors.push(`${path} is required`);
|
|
2565
|
+
return void 0;
|
|
2566
|
+
}
|
|
2567
|
+
if (typeof value !== "string") {
|
|
2568
|
+
errors.push(`${path} must be a string`);
|
|
2569
|
+
return void 0;
|
|
2570
|
+
}
|
|
2571
|
+
const text = value.trim();
|
|
2572
|
+
if (required && !text) errors.push(`${path} is required`);
|
|
2573
|
+
if (text.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
2574
|
+
return text;
|
|
2575
|
+
}
|
|
2576
|
+
function validateMembershipPageDocument(input, options = {}) {
|
|
2577
|
+
const errors = [];
|
|
2578
|
+
const root = objectRecord(input);
|
|
2579
|
+
if (!root) return { ok: false, errors: ["document must be an object"] };
|
|
2580
|
+
rejectUnknownKeys(root, ROOT_KEYS, "document", errors);
|
|
2581
|
+
if (root.schemaVersion !== 1) errors.push("document.schemaVersion must be 1");
|
|
2582
|
+
if (root.renderer !== MEMBERSHIP_PAGE_RENDERER) {
|
|
2583
|
+
errors.push(`document.renderer must be "${MEMBERSHIP_PAGE_RENDERER}"`);
|
|
2584
|
+
}
|
|
2585
|
+
if (!Array.isArray(root.tiers)) errors.push("document.tiers must be an array");
|
|
2586
|
+
const rawTiers = Array.isArray(root.tiers) ? root.tiers : [];
|
|
2587
|
+
if (Array.isArray(root.tiers) && rawTiers.length === 0) errors.push("document.tiers must include at least one tier");
|
|
2588
|
+
if (rawTiers.length > MAX_TIERS) errors.push(`document.tiers supports at most ${MAX_TIERS} entries`);
|
|
2589
|
+
const authority = new Map((options.tiers ?? []).map((tier) => [tier.id, tier]));
|
|
2590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2591
|
+
const tiers2 = [];
|
|
2592
|
+
for (const [index, raw] of rawTiers.slice(0, MAX_TIERS).entries()) {
|
|
2593
|
+
const path = `document.tiers[${index}]`;
|
|
2594
|
+
const item = objectRecord(raw);
|
|
2595
|
+
if (!item) {
|
|
2596
|
+
errors.push(`${path} must be an object`);
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
rejectUnknownKeys(item, TIER_KEYS, path, errors);
|
|
2600
|
+
const tierId = boundedString(item.tierId, `${path}.tierId`, errors, 80, true) ?? "";
|
|
2601
|
+
if (tierId && !TIER_ID.test(tierId)) errors.push(`${path}.tierId must be a lowercase kebab-case id`);
|
|
2602
|
+
if (seen.has(tierId)) errors.push(`${path}.tierId duplicates "${tierId}"`);
|
|
2603
|
+
seen.add(tierId);
|
|
2604
|
+
if (authority.size && !authority.has(tierId)) errors.push(`${path}.tierId "${tierId}" is not an offered tier`);
|
|
2605
|
+
const variant = item.variant;
|
|
2606
|
+
if (variant !== void 0 && (typeof variant !== "string" || !VARIANTS.has(variant))) {
|
|
2607
|
+
errors.push(`${path}.variant must be standard, featured, or supporting`);
|
|
2608
|
+
}
|
|
2609
|
+
const compareAt = item.compareAtPriceCents;
|
|
2610
|
+
if (compareAt !== void 0 && (!Number.isInteger(compareAt) || Number(compareAt) < 0 || Number(compareAt) > 1e8)) {
|
|
2611
|
+
errors.push(`${path}.compareAtPriceCents must be an integer from 0 to 100,000,000`);
|
|
2612
|
+
}
|
|
2613
|
+
const actual = authority.get(tierId)?.priceCents;
|
|
2614
|
+
if (Number.isInteger(compareAt) && actual !== void 0 && Number(compareAt) <= actual) {
|
|
2615
|
+
errors.push(`${path}.compareAtPriceCents must be greater than the authoritative tier price`);
|
|
2616
|
+
}
|
|
2617
|
+
let benefits;
|
|
2618
|
+
if (item.benefits !== void 0) {
|
|
2619
|
+
if (!Array.isArray(item.benefits)) errors.push(`${path}.benefits must be an array`);
|
|
2620
|
+
else {
|
|
2621
|
+
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
2622
|
+
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
2623
|
+
const text = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
2624
|
+
return text ? [text] : [];
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
let callout;
|
|
2629
|
+
if (item.callout !== void 0) {
|
|
2630
|
+
const rawCallout = objectRecord(item.callout);
|
|
2631
|
+
if (!rawCallout) errors.push(`${path}.callout must be an object`);
|
|
2632
|
+
else {
|
|
2633
|
+
rejectUnknownKeys(rawCallout, CALLOUT_KEYS, `${path}.callout`, errors);
|
|
2634
|
+
const bodyMarkdown = boundedString(rawCallout.bodyMarkdown, `${path}.callout.bodyMarkdown`, errors, MAX_MARKDOWN, true);
|
|
2635
|
+
const label = boundedString(rawCallout.label, `${path}.callout.label`, errors, MAX_LABEL);
|
|
2636
|
+
if (bodyMarkdown) callout = { ...label ? { label } : {}, bodyMarkdown };
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
let capacity;
|
|
2640
|
+
if (item.capacity !== void 0) {
|
|
2641
|
+
const rawCapacity = objectRecord(item.capacity);
|
|
2642
|
+
if (!rawCapacity) errors.push(`${path}.capacity must be an object`);
|
|
2643
|
+
else {
|
|
2644
|
+
rejectUnknownKeys(rawCapacity, CAPACITY_KEYS, `${path}.capacity`, errors);
|
|
2645
|
+
const total = rawCapacity.total;
|
|
2646
|
+
if (!Number.isInteger(total) || Number(total) < 1 || Number(total) > 1e6) {
|
|
2647
|
+
errors.push(`${path}.capacity.total must be an integer from 1 to 1,000,000`);
|
|
2648
|
+
}
|
|
2649
|
+
const label = boundedString(rawCapacity.label, `${path}.capacity.label`, errors, MAX_LABEL);
|
|
2650
|
+
if (Number.isInteger(total) && Number(total) >= 1 && Number(total) <= 1e6) {
|
|
2651
|
+
capacity = { total: Number(total), ...label ? { label } : {} };
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
const badge = boundedString(item.badge, `${path}.badge`, errors, MAX_LABEL);
|
|
2656
|
+
const summaryMarkdown = boundedString(item.summaryMarkdown, `${path}.summaryMarkdown`, errors, MAX_MARKDOWN);
|
|
2657
|
+
const priceEyebrow = boundedString(item.priceEyebrow, `${path}.priceEyebrow`, errors, MAX_LABEL);
|
|
2658
|
+
const billingLabel = boundedString(item.billingLabel, `${path}.billingLabel`, errors, MAX_LABEL);
|
|
2659
|
+
const priceNoteMarkdown = boundedString(item.priceNoteMarkdown, `${path}.priceNoteMarkdown`, errors, MAX_MARKDOWN);
|
|
2660
|
+
const ctaLabel = boundedString(item.ctaLabel, `${path}.ctaLabel`, errors, MAX_LABEL);
|
|
2661
|
+
tiers2.push({
|
|
2662
|
+
tierId,
|
|
2663
|
+
...typeof variant === "string" && VARIANTS.has(variant) ? { variant } : {},
|
|
2664
|
+
...badge ? { badge } : {},
|
|
2665
|
+
...summaryMarkdown ? { summaryMarkdown } : {},
|
|
2666
|
+
...priceEyebrow ? { priceEyebrow } : {},
|
|
2667
|
+
...Number.isInteger(compareAt) ? { compareAtPriceCents: Number(compareAt) } : {},
|
|
2668
|
+
...billingLabel ? { billingLabel } : {},
|
|
2669
|
+
...priceNoteMarkdown ? { priceNoteMarkdown } : {},
|
|
2670
|
+
...capacity ? { capacity } : {},
|
|
2671
|
+
...benefits ? { benefits } : {},
|
|
2672
|
+
...callout ? { callout } : {},
|
|
2673
|
+
...ctaLabel ? { ctaLabel } : {}
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
if (authority.size) {
|
|
2677
|
+
for (const tierId of authority.keys()) {
|
|
2678
|
+
if (!seen.has(tierId)) errors.push(`document.tiers is missing offered tier "${tierId}"`);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
const footnoteMarkdown = boundedString(root.footnoteMarkdown, "document.footnoteMarkdown", errors, MAX_MARKDOWN);
|
|
2682
|
+
if (errors.length) return { ok: false, errors };
|
|
2683
|
+
return {
|
|
2684
|
+
ok: true,
|
|
2685
|
+
value: {
|
|
2686
|
+
schemaVersion: 1,
|
|
2687
|
+
renderer: MEMBERSHIP_PAGE_RENDERER,
|
|
2688
|
+
tiers: tiers2,
|
|
2689
|
+
...footnoteMarkdown ? { footnoteMarkdown } : {}
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
function membershipPageDocument(input, options = {}) {
|
|
2694
|
+
const result = validateMembershipPageDocument(input, options);
|
|
2695
|
+
if (!result.ok) throw new TypeError(`Invalid membership page document: ${result.errors.join("; ")}`);
|
|
2696
|
+
return result.value;
|
|
2697
|
+
}
|
|
2530
2698
|
export {
|
|
2531
2699
|
DEFAULT_CHAPTER_COPY,
|
|
2532
2700
|
DEFAULT_SHARE_FIELDS,
|
|
2701
|
+
MEMBERSHIP_PAGE_RENDERER,
|
|
2533
2702
|
SCHEDULING_DEFAULTS,
|
|
2534
2703
|
applicantProfile,
|
|
2535
2704
|
applicationBookingUpdate,
|
|
@@ -2585,6 +2754,7 @@ export {
|
|
|
2585
2754
|
meetingRescheduleUpdate,
|
|
2586
2755
|
memberApplication,
|
|
2587
2756
|
memberSession,
|
|
2757
|
+
membershipPageDocument,
|
|
2588
2758
|
networkSourceTag,
|
|
2589
2759
|
normalizeSharedRecord,
|
|
2590
2760
|
normalizeWebhookEvent,
|
|
@@ -2623,6 +2793,7 @@ export {
|
|
|
2623
2793
|
tierPayable,
|
|
2624
2794
|
updateClerkUserMetadata,
|
|
2625
2795
|
updateClerkUserMetadataByEmail,
|
|
2796
|
+
validateMembershipPageDocument,
|
|
2626
2797
|
validateScheduling,
|
|
2627
2798
|
verifyStripeSignature,
|
|
2628
2799
|
webhookMutationId
|