@odla-ai/chapter 0.41.0 → 0.42.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 +18 -8
- package/dist/{chunk-IAWBE5PX.js → chunk-JXFYOUDL.js} +8 -2
- package/dist/chunk-JXFYOUDL.js.map +1 -0
- package/dist/{chunk-O22RRPBT.js → chunk-MMDWXTJU.js} +2 -2
- package/dist/{chunk-JLLRZO7B.js → chunk-S7CND3R5.js} +348 -281
- package/dist/chunk-S7CND3R5.js.map +1 -0
- package/dist/{copy-context-B14OOiYq.d.ts → copy-context-DuJkH4U9.d.ts} +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/admin/index.js +2 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +5 -3
- package/dist/ui/member/index.d.ts +38 -10
- package/dist/ui/member/index.js +4 -2
- package/dist/worker/index.cjs +69 -12
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +1 -1
- package/dist/worker/index.d.ts +1 -1
- package/dist/worker/index.js +69 -12
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IAWBE5PX.js.map +0 -1
- package/dist/chunk-JLLRZO7B.js.map +0 -1
- /package/dist/{chunk-O22RRPBT.js.map → chunk-MMDWXTJU.js.map} +0 -0
package/dist/ui/admin/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as preact from 'preact';
|
|
2
2
|
import { ComponentChildren, JSX } from 'preact';
|
|
3
3
|
import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
|
|
4
|
-
import { a as Chapter, b as ChapterCopy, M as MemberContentDocument, E as EffectiveMemberContent, c as MemberContentTierAuthority } from '../../copy-context-
|
|
5
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-
|
|
4
|
+
import { a as Chapter, b as ChapterCopy, M as MemberContentDocument, E as EffectiveMemberContent, c as MemberContentTierAuthority } from '../../copy-context-DuJkH4U9.js';
|
|
5
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-DuJkH4U9.js';
|
|
6
6
|
import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
|
|
7
7
|
|
|
8
8
|
/** Normalized public auth configuration consumed by the Clerk gate. */
|
package/dist/ui/admin/index.js
CHANGED
|
@@ -33,11 +33,11 @@ import {
|
|
|
33
33
|
networkRecordsWorkspace,
|
|
34
34
|
peopleSection,
|
|
35
35
|
useAdminResource
|
|
36
|
-
} from "../../chunk-
|
|
36
|
+
} from "../../chunk-MMDWXTJU.js";
|
|
37
37
|
import {
|
|
38
38
|
ChapterCopyProvider,
|
|
39
39
|
useChapterCopy
|
|
40
|
-
} from "../../chunk-
|
|
40
|
+
} from "../../chunk-JXFYOUDL.js";
|
|
41
41
|
export {
|
|
42
42
|
AdminNote,
|
|
43
43
|
AdminPage,
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { AdminAccountMenuProps, AdminBuilderLibraryData, AdminChrome, AdminMemberContentData, AdminNote, AdminPage, AdminResource, AdminRouteState, AdminRouteTarget, AdminRouting, AdminSection, AdminSectionContext, AdminWorkspace, AdminWorkspaceNavProps, AdmissionGrantsWorkspace, AvailabilitySectionOptions, BillingSectionOptions, BuilderLibraryAdmin, ChapterAdmin, ChapterAdminAuthAdapter, ChapterAdminAuthConfig, ChapterAdminAuthLoadContext, ChapterAdminProps, ChapterAdminUser, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, LeaderNetworkOverview, MeetingsSectionOptions, MemberContentAdmin, NetworkRecordsWorkspace, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, admissionGrantsWorkspace, applicationLifecycleAdapter, availabilitySection, billingSection, builderLibrarySection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, loadChapterAdminConfig, loadChapterAdminUser, meetingsSection, memberContentSection, networkRecordsWorkspace, peopleSection, useAdminResource } from './admin/index.js';
|
|
2
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-
|
|
3
|
-
export { ApiFn, BrandStyle, BrandStyleProps, GiftCodeRedeemer, GiftCodeRedeemerCopy, GiftCodeRedeemerProps, GiftOfferCards, GiftOfferCardsMode, GiftOfferCardsProps, JoinBookingSimulation, JoinConfig, JoinFlowState, JoinIsland, JoinIslandProps, JoinSimulation, JoinStepRenderContext, JoinSubmitRenderContext, JoinTier, JoinTierRenderContext, MEMBERSHIP_CARD_TOKEN_DEFAULTS, MemberContentArea, MemberContentAreaProps, MemberGifts, MemberProvisionalRenderContext, MembersArea, MembersAreaProps, MembershipCardTokenName, MembershipCards, MembershipCardsMode, MembershipCardsProps, PaymentPriceLines, PaymentStep, PaymentStepProps, PaymentStepSimulation, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, SubResponse, TierPlaceholder, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, membershipApplicationHref, tierNeedsPayment, timeLabel, tzShort, useTierSelection } from './member/index.js';
|
|
2
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../copy-context-DuJkH4U9.js';
|
|
3
|
+
export { ApiFn, BrandStyle, BrandStyleProps, GiftCodeRedeemResult, GiftCodeRedeemer, GiftCodeRedeemerCopy, GiftCodeRedeemerProps, GiftOfferCards, GiftOfferCardsMode, GiftOfferCardsProps, GiftPurchases, GiftPurchasesProps, JoinBookingSimulation, JoinConfig, JoinFlowState, JoinIsland, JoinIslandProps, JoinSimulation, JoinStepRenderContext, JoinSubmitRenderContext, JoinTier, JoinTierRenderContext, MEMBERSHIP_CARD_TOKEN_DEFAULTS, MemberContentArea, MemberContentAreaProps, MemberGifts, MemberProvisionalRenderContext, MembersArea, MembersAreaProps, MembershipCardTokenName, MembershipCards, MembershipCardsMode, MembershipCardsProps, PaymentPriceLines, PaymentStep, PaymentStepProps, PaymentStepSimulation, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, SubResponse, TierPlaceholder, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, membershipApplicationHref, tierNeedsPayment, timeLabel, tzShort, useTierSelection } from './member/index.js';
|
|
4
4
|
import 'preact';
|
|
5
5
|
import '@odla-ai/crm';
|
|
6
6
|
import '@odla-ai/crm/ui';
|
package/dist/ui/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GiftCodeRedeemer,
|
|
3
3
|
GiftOfferCards,
|
|
4
|
+
GiftPurchases,
|
|
4
5
|
JoinIsland,
|
|
5
6
|
MEMBERSHIP_CARD_TOKEN_DEFAULTS,
|
|
6
7
|
MemberContentArea,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
timeLabel,
|
|
24
25
|
tzShort,
|
|
25
26
|
useTierSelection
|
|
26
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-S7CND3R5.js";
|
|
27
28
|
import {
|
|
28
29
|
AdminNote,
|
|
29
30
|
AdminPage,
|
|
@@ -59,12 +60,12 @@ import {
|
|
|
59
60
|
networkRecordsWorkspace,
|
|
60
61
|
peopleSection,
|
|
61
62
|
useAdminResource
|
|
62
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-MMDWXTJU.js";
|
|
63
64
|
import {
|
|
64
65
|
BrandStyle,
|
|
65
66
|
ChapterCopyProvider,
|
|
66
67
|
useChapterCopy
|
|
67
|
-
} from "../chunk-
|
|
68
|
+
} from "../chunk-JXFYOUDL.js";
|
|
68
69
|
import "../chunk-IVXVECKR.js";
|
|
69
70
|
export {
|
|
70
71
|
AdminNote,
|
|
@@ -76,6 +77,7 @@ export {
|
|
|
76
77
|
ChapterCopyProvider,
|
|
77
78
|
GiftCodeRedeemer,
|
|
78
79
|
GiftOfferCards,
|
|
80
|
+
GiftPurchases,
|
|
79
81
|
JoinIsland,
|
|
80
82
|
LeaderNetworkOverview,
|
|
81
83
|
MEMBERSHIP_CARD_TOKEN_DEFAULTS,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as preact from 'preact';
|
|
2
2
|
import { ComponentChildren, JSX } from 'preact';
|
|
3
|
-
import { d as MemberContentProjection, e as MemberContentPageProjection, b as ChapterCopy, f as ChapterBrand } from '../../copy-context-
|
|
4
|
-
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-
|
|
3
|
+
import { d as MemberContentProjection, e as MemberContentPageProjection, b as ChapterCopy, f as ChapterBrand } from '../../copy-context-DuJkH4U9.js';
|
|
4
|
+
export { C as ChapterCopyProvider, u as useChapterCopy } from '../../copy-context-DuJkH4U9.js';
|
|
5
5
|
import { FieldType, FieldConditions } from '@odla-ai/crm';
|
|
6
6
|
|
|
7
7
|
/** A bookable slot: a start instant in epoch milliseconds. */
|
|
@@ -16,7 +16,7 @@ declare function tzShort(tz: string): string;
|
|
|
16
16
|
declare function dayKey(ms: number, tz: string): string;
|
|
17
17
|
/** Short day label for a day chip, e.g. "Mon, Jun 3". */
|
|
18
18
|
declare function dayLabel(ms: number, tz: string): string;
|
|
19
|
-
/** Time-of-day label for a slot button, e.g. "2:30 PM". */
|
|
19
|
+
/** Time-of-day label for a slot button, e.g. "2:30 PM PST". */
|
|
20
20
|
declare function timeLabel(ms: number, tz: string): string;
|
|
21
21
|
/** Full, human confirmation label, e.g. "Monday, June 3, 2:30 PM PST". */
|
|
22
22
|
declare function fullLabel(ms: number, tz: string): string;
|
|
@@ -359,6 +359,7 @@ type JoinFlowState = {
|
|
|
359
359
|
} | {
|
|
360
360
|
step: "payment";
|
|
361
361
|
applicationId: string;
|
|
362
|
+
giftPurchase?: boolean;
|
|
362
363
|
} | {
|
|
363
364
|
step: "paymentPending";
|
|
364
365
|
applicationId: string;
|
|
@@ -443,6 +444,11 @@ interface JoinIslandProps {
|
|
|
443
444
|
}) => ComponentChildren;
|
|
444
445
|
children?: ComponentChildren;
|
|
445
446
|
};
|
|
447
|
+
/**
|
|
448
|
+
* Authenticated API adapter used to claim a received gift against the current
|
|
449
|
+
* application. When absent, JoinIsland cannot offer recipient claiming.
|
|
450
|
+
*/
|
|
451
|
+
giftClaimApi?: ApiFn;
|
|
446
452
|
/** Run the canonical journey without network, Stripe, or calendar writes. */
|
|
447
453
|
simulation?: JoinSimulation;
|
|
448
454
|
}
|
|
@@ -553,13 +559,28 @@ interface GiftOfferCardsProps {
|
|
|
553
559
|
declare function GiftOfferCards(props: GiftOfferCardsProps): JSX.Element;
|
|
554
560
|
|
|
555
561
|
/** Resolved Chapter copy consumed by {@link GiftCodeRedeemer}. */
|
|
556
|
-
|
|
562
|
+
interface GiftCodeRedeemerCopy {
|
|
563
|
+
code: string;
|
|
564
|
+
redeem: string;
|
|
565
|
+
redeemed: string;
|
|
566
|
+
redeemFailed: string;
|
|
567
|
+
}
|
|
568
|
+
/** Canonical continuation returned after a recipient claim is committed. */
|
|
569
|
+
interface GiftCodeRedeemResult {
|
|
570
|
+
ok: true;
|
|
571
|
+
kind: "application";
|
|
572
|
+
id: string;
|
|
573
|
+
applicationId: string;
|
|
574
|
+
state?: "payment" | "paymentPending" | "booking" | "accepted";
|
|
575
|
+
}
|
|
557
576
|
/** Props for the canonical Chapter member gift-code redemption control. */
|
|
558
577
|
interface GiftCodeRedeemerProps {
|
|
559
578
|
api: ApiFn;
|
|
579
|
+
/** Exact application whose payment requirement the gift must satisfy. */
|
|
580
|
+
applicationId: string;
|
|
560
581
|
copy: GiftCodeRedeemerCopy;
|
|
561
582
|
id?: string;
|
|
562
|
-
onRedeemed?: () => void | Promise<void>;
|
|
583
|
+
onRedeemed?: (result: GiftCodeRedeemResult) => void | Promise<void>;
|
|
563
584
|
}
|
|
564
585
|
/**
|
|
565
586
|
* Redeems one Chapter gift code through the canonical member API.
|
|
@@ -569,14 +590,21 @@ interface GiftCodeRedeemerProps {
|
|
|
569
590
|
* browser capitalization and spellcheck are disabled, while
|
|
570
591
|
* `autocomplete="one-time-code"` allows platform code suggestions.
|
|
571
592
|
*/
|
|
572
|
-
declare function GiftCodeRedeemer({ api, copy, id, onRedeemed }: GiftCodeRedeemerProps): preact.JSX.Element;
|
|
593
|
+
declare function GiftCodeRedeemer({ api, applicationId, copy, id, onRedeemed }: GiftCodeRedeemerProps): preact.JSX.Element;
|
|
573
594
|
|
|
574
|
-
/**
|
|
575
|
-
|
|
595
|
+
/** Props for the authenticated gift purchaser ledger and checkout surface. */
|
|
596
|
+
interface GiftPurchasesProps {
|
|
576
597
|
api: ApiFn;
|
|
577
598
|
copy: ChapterCopy["members"]["gifts"];
|
|
578
599
|
paymentCopy: ChapterCopy["join"]["payment"];
|
|
579
|
-
}
|
|
600
|
+
}
|
|
601
|
+
/** Authenticated buyer offers, purchase history, and code rotation. */
|
|
602
|
+
declare function GiftPurchases(props: GiftPurchasesProps): preact.JSX.Element | null;
|
|
603
|
+
/**
|
|
604
|
+
* Compatibility name for the authenticated gift purchaser surface.
|
|
605
|
+
* @deprecated Use {@link GiftPurchases}; recipient claiming belongs in JoinIsland.
|
|
606
|
+
*/
|
|
607
|
+
declare const MemberGifts: typeof GiftPurchases;
|
|
580
608
|
|
|
581
609
|
/** Props for {@link BrandStyle}. */
|
|
582
610
|
interface BrandStyleProps {
|
|
@@ -588,4 +616,4 @@ interface BrandStyleProps {
|
|
|
588
616
|
* there is no brand to theme). */
|
|
589
617
|
declare function BrandStyle(props: BrandStyleProps): preact.JSX.Element | null;
|
|
590
618
|
|
|
591
|
-
export { type ApiFn, BrandStyle, type BrandStyleProps, GiftCodeRedeemer, type GiftCodeRedeemerCopy, type GiftCodeRedeemerProps, GiftOfferCards, type GiftOfferCardsMode, type GiftOfferCardsProps, type JoinBookingSimulation, type JoinConfig, type JoinFlowState, JoinIsland, type JoinIslandProps, type JoinSimulation, type JoinStepRenderContext, type JoinSubmitRenderContext, type JoinTier, type JoinTierRenderContext, MEMBERSHIP_CARD_TOKEN_DEFAULTS, MemberContentArea, type MemberContentAreaProps, MemberGifts, type MemberProvisionalRenderContext, MembersArea, type MembersAreaProps, type MembershipCardTokenName, MembershipCards, type MembershipCardsMode, type MembershipCardsProps, type PaymentPriceLines, PaymentStep, type PaymentStepProps, type PaymentStepSimulation, type RescheduleProps, Rescheduler, type Slot, SlotPicker, type SlotPickerClasses, type SlotPickerProps, type SubResponse, TierPlaceholder, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, membershipApplicationHref, tierNeedsPayment, timeLabel, tzShort, useTierSelection };
|
|
619
|
+
export { type ApiFn, BrandStyle, type BrandStyleProps, type GiftCodeRedeemResult, GiftCodeRedeemer, type GiftCodeRedeemerCopy, type GiftCodeRedeemerProps, GiftOfferCards, type GiftOfferCardsMode, type GiftOfferCardsProps, GiftPurchases, type GiftPurchasesProps, type JoinBookingSimulation, type JoinConfig, type JoinFlowState, JoinIsland, type JoinIslandProps, type JoinSimulation, type JoinStepRenderContext, type JoinSubmitRenderContext, type JoinTier, type JoinTierRenderContext, MEMBERSHIP_CARD_TOKEN_DEFAULTS, MemberContentArea, type MemberContentAreaProps, MemberGifts, type MemberProvisionalRenderContext, MembersArea, type MembersAreaProps, type MembershipCardTokenName, MembershipCards, type MembershipCardsMode, type MembershipCardsProps, type PaymentPriceLines, PaymentStep, type PaymentStepProps, type PaymentStepSimulation, type RescheduleProps, Rescheduler, type Slot, SlotPicker, type SlotPickerClasses, type SlotPickerProps, type SubResponse, TierPlaceholder, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, loadJoinResume, membershipApplicationHref, tierNeedsPayment, timeLabel, tzShort, useTierSelection };
|
package/dist/ui/member/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GiftCodeRedeemer,
|
|
3
3
|
GiftOfferCards,
|
|
4
|
+
GiftPurchases,
|
|
4
5
|
JoinIsland,
|
|
5
6
|
MEMBERSHIP_CARD_TOKEN_DEFAULTS,
|
|
6
7
|
MemberContentArea,
|
|
@@ -23,18 +24,19 @@ import {
|
|
|
23
24
|
timeLabel,
|
|
24
25
|
tzShort,
|
|
25
26
|
useTierSelection
|
|
26
|
-
} from "../../chunk-
|
|
27
|
+
} from "../../chunk-S7CND3R5.js";
|
|
27
28
|
import {
|
|
28
29
|
BrandStyle,
|
|
29
30
|
ChapterCopyProvider,
|
|
30
31
|
useChapterCopy
|
|
31
|
-
} from "../../chunk-
|
|
32
|
+
} from "../../chunk-JXFYOUDL.js";
|
|
32
33
|
import "../../chunk-IVXVECKR.js";
|
|
33
34
|
export {
|
|
34
35
|
BrandStyle,
|
|
35
36
|
ChapterCopyProvider,
|
|
36
37
|
GiftCodeRedeemer,
|
|
37
38
|
GiftOfferCards,
|
|
39
|
+
GiftPurchases,
|
|
38
40
|
JoinIsland,
|
|
39
41
|
MEMBERSHIP_CARD_TOKEN_DEFAULTS,
|
|
40
42
|
MemberContentArea,
|
package/dist/worker/index.cjs
CHANGED
|
@@ -2009,7 +2009,12 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
2009
2009
|
const group = Array.isArray(groups) ? groups[0] : void 0;
|
|
2010
2010
|
if (!group) return json({ error: "not found" }, 404);
|
|
2011
2011
|
const state = await continueJoin(db, ctx, env, url.origin, app, group);
|
|
2012
|
-
if (state) return json({
|
|
2012
|
+
if (state) return json({
|
|
2013
|
+
state,
|
|
2014
|
+
applicationId,
|
|
2015
|
+
...state === "gift" ? { claimId: app.giftClaimId } : {},
|
|
2016
|
+
...state === "payment" && typeof app.giftOfferId === "string" ? { giftPurchase: true } : {}
|
|
2017
|
+
});
|
|
2013
2018
|
return json({ error: `application cannot resume from status "${String(app.status ?? "")}"` }, 409);
|
|
2014
2019
|
}
|
|
2015
2020
|
if (req.method === "POST" && url.pathname === "/api/applications") {
|
|
@@ -2058,6 +2063,7 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
2058
2063
|
duplicate: result.duplicate,
|
|
2059
2064
|
status: result.status,
|
|
2060
2065
|
...state ? { state } : {},
|
|
2066
|
+
...state === "payment" && requestedGiftId ? { giftPurchase: true } : {},
|
|
2061
2067
|
// Echoed so a site can see (and assert in an integration test) whether its
|
|
2062
2068
|
// join page actually posted the ack. Absent consent is otherwise invisible.
|
|
2063
2069
|
disclaimerAckAt: result.disclaimerAckAt
|
|
@@ -3756,9 +3762,15 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
|
|
|
3756
3762
|
const target = claim.target;
|
|
3757
3763
|
const ops = [];
|
|
3758
3764
|
let result;
|
|
3765
|
+
let applicationGuard;
|
|
3759
3766
|
if (target.type === "tier") {
|
|
3760
|
-
const apps = (await db.query({ applications: { $: { where: { emailNormalized }, limit: 20 } } })).applications;
|
|
3761
|
-
const existing = apps?.filter((row) => row.groupId === chapterId).sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0];
|
|
3767
|
+
const apps = beneficiary.applicationId ? (await db.query({ applications: { $: { where: { id: beneficiary.applicationId }, limit: 1 } } })).applications : (await db.query({ applications: { $: { where: { emailNormalized }, limit: 20 } } })).applications;
|
|
3768
|
+
const existing = beneficiary.applicationId ? apps?.[0] : apps?.filter((row) => row.groupId === chapterId).sort((a, b) => Number(b.createdAt ?? 0) - Number(a.createdAt ?? 0))[0];
|
|
3769
|
+
if (beneficiary.applicationId && (!existing || String(existing.groupId ?? "") !== chapterId || String(existing.emailNormalized ?? "") !== emailNormalized || String(existing.tierId ?? "") !== target.tierId || String(existing.status ?? "") !== "submitted" || typeof existing.stripeSubscriptionId === "string" || typeof existing.giftClaimId === "string" || typeof existing.clerkUserId === "string" && existing.clerkUserId !== beneficiary.userId)) return null;
|
|
3770
|
+
if (beneficiary.applicationId) applicationGuard = {
|
|
3771
|
+
tierId: target.tierId,
|
|
3772
|
+
...typeof existing?.clerkUserId === "string" ? { clerkUserId: existing.clerkUserId } : {}
|
|
3773
|
+
};
|
|
3762
3774
|
const id = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
|
|
3763
3775
|
const status = existing && existing.status !== "submitted" ? String(existing.status) : "paid_pending_vetting";
|
|
3764
3776
|
ops.push({ t: "update", ns: "applications", id, attrs: {
|
|
@@ -3775,6 +3787,7 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
|
|
|
3775
3787
|
} });
|
|
3776
3788
|
result = { kind: "application", id };
|
|
3777
3789
|
} else {
|
|
3790
|
+
if (beneficiary.applicationId) return null;
|
|
3778
3791
|
const id = `gift-entitlement:${String(claim.id)}`;
|
|
3779
3792
|
ops.push({ t: "update", ns: "giftEntitlements", id, attrs: {
|
|
3780
3793
|
id,
|
|
@@ -3798,13 +3811,27 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
|
|
|
3798
3811
|
attemptCount: Number(claim.attemptCount ?? 0) + 1
|
|
3799
3812
|
} });
|
|
3800
3813
|
try {
|
|
3814
|
+
const guards = [{ ns: "giftClaims", id: String(claim.id), exists: true, equals: {
|
|
3815
|
+
status: "pending",
|
|
3816
|
+
codeVersion: Number(claim.codeVersion),
|
|
3817
|
+
codeHash: String(claim.codeHash)
|
|
3818
|
+
} }];
|
|
3819
|
+
if (beneficiary.applicationId) guards.push({
|
|
3820
|
+
ns: "applications",
|
|
3821
|
+
id: beneficiary.applicationId,
|
|
3822
|
+
exists: true,
|
|
3823
|
+
equals: {
|
|
3824
|
+
status: "submitted",
|
|
3825
|
+
groupId: chapterId,
|
|
3826
|
+
emailNormalized,
|
|
3827
|
+
tierId: applicationGuard.tierId,
|
|
3828
|
+
...applicationGuard?.clerkUserId ? { clerkUserId: applicationGuard.clerkUserId } : {}
|
|
3829
|
+
},
|
|
3830
|
+
absent: ["stripeSubscriptionId", "giftClaimId"]
|
|
3831
|
+
});
|
|
3801
3832
|
await db.transact(ops, {
|
|
3802
3833
|
mutationId: `gift-redeem:${String(claim.id)}:${beneficiary.userId}`,
|
|
3803
|
-
guards
|
|
3804
|
-
status: "pending",
|
|
3805
|
-
codeVersion: Number(claim.codeVersion),
|
|
3806
|
-
codeHash: String(claim.codeHash)
|
|
3807
|
-
} }]
|
|
3834
|
+
guards
|
|
3808
3835
|
});
|
|
3809
3836
|
return result;
|
|
3810
3837
|
} catch {
|
|
@@ -4319,15 +4346,35 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4319
4346
|
return json({ error: "too many attempts" }, 429);
|
|
4320
4347
|
}
|
|
4321
4348
|
let code = "";
|
|
4349
|
+
let applicationId = "";
|
|
4322
4350
|
try {
|
|
4323
|
-
|
|
4351
|
+
const body = JSON.parse(await req.text());
|
|
4352
|
+
code = String(body.code ?? "").trim();
|
|
4353
|
+
applicationId = typeof body.applicationId === "string" ? body.applicationId : "";
|
|
4324
4354
|
} catch {
|
|
4325
4355
|
return invalid();
|
|
4326
4356
|
}
|
|
4327
|
-
if (!/^gift1_[A-Za-z0-9_-]{43}$/.test(code)) return invalid();
|
|
4357
|
+
if (!/^gift1_[A-Za-z0-9_-]{43}$/.test(code) || !applicationId) return invalid();
|
|
4358
|
+
const app = first3((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
4359
|
+
const emailNormalized = normalizeChapterEmail(user.email);
|
|
4360
|
+
if (!app || String(app.groupId ?? "") !== ctx.chapter.id || String(app.emailNormalized ?? "") !== emailNormalized || typeof app.clerkUserId === "string" && app.clerkUserId !== user.userId) return invalid();
|
|
4328
4361
|
const codeHash = await hashGiftCode(ctx.chapter.id, code);
|
|
4329
4362
|
const claim = first3((await db.query({ giftClaims: { $: { where: { codeHash }, limit: 1 } } })).giftClaims);
|
|
4330
4363
|
if (!claim || claim.chapterId !== ctx.chapter.id) return invalid();
|
|
4364
|
+
const sameRedemption = (candidate) => candidate.status === "redeemed" && candidate.applicationId === applicationId && candidate.beneficiaryEmailNormalized === emailNormalized && candidate.beneficiaryUserId === user.userId;
|
|
4365
|
+
const success = async () => {
|
|
4366
|
+
const current = first3((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
4367
|
+
const group2 = first3((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4368
|
+
const state = current && group2 ? await continueJoin(db, ctx, env, url.origin, current, group2) : null;
|
|
4369
|
+
return json({
|
|
4370
|
+
ok: true,
|
|
4371
|
+
kind: "application",
|
|
4372
|
+
id: applicationId,
|
|
4373
|
+
applicationId,
|
|
4374
|
+
...state ? { state } : {}
|
|
4375
|
+
});
|
|
4376
|
+
};
|
|
4377
|
+
if (sameRedemption(claim)) return success();
|
|
4331
4378
|
if (claim.status !== "pending" || Number(claim.codeExpiresAt) <= now) {
|
|
4332
4379
|
await db.transact([{ t: "update", ns: "giftClaims", id: String(claim.id), attrs: {
|
|
4333
4380
|
attemptCount: Number(claim.attemptCount ?? 0) + 1,
|
|
@@ -4335,8 +4382,18 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4335
4382
|
} }]).catch(() => void 0);
|
|
4336
4383
|
return invalid();
|
|
4337
4384
|
}
|
|
4338
|
-
const
|
|
4339
|
-
|
|
4385
|
+
const target = claim.target;
|
|
4386
|
+
const group = first3((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4387
|
+
const tier = group ? findTier(await loadTiers(db, group), typeof app.tierId === "string" ? app.tierId : null) : void 0;
|
|
4388
|
+
if (String(app.status ?? "") !== "submitted" || typeof app.stripeSubscriptionId === "string" || typeof app.giftClaimId === "string" || !tier || tierIsFree(tier) || target?.type !== "tier" || target.tierId !== app.tierId) return invalid();
|
|
4389
|
+
const redeemed = await redeemGiftClaim(db, claim, {
|
|
4390
|
+
email: user.email,
|
|
4391
|
+
userId: user.userId,
|
|
4392
|
+
applicationId
|
|
4393
|
+
}, now);
|
|
4394
|
+
if (redeemed) return success();
|
|
4395
|
+
const raced = await loadClaim(db, String(claim.id));
|
|
4396
|
+
return raced && sameRedemption(raced) ? success() : invalid();
|
|
4340
4397
|
}
|
|
4341
4398
|
return null;
|
|
4342
4399
|
};
|