@wix/himalaya-cli 0.1495.0 → 0.1497.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/attendance-classifier.d.ts +37 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing-contract.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +11 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +27 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-verticals/attendance.d.ts +10 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-verticals/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +33 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +346 -26
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +132 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +366 -48
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +5 -0
- package/dist/cli.mjs +816 -667
- package/guides/iap-products.md +7 -1
- package/guides/sdk-reference.json +376 -27
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +394 -0
- package/templates/beauty-salon/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/beauty-salon/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/beauty-salon/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +52 -7
- package/templates/branded-lite/dev/index.ts +1073 -284
- package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +35 -4
- package/templates/branded-lite/tier5-src/_shared/business-chat-transport.ts +12 -5
- package/templates/branded-lite/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/branded-lite/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/branded-lite/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/branded-lite/tier5-src/business-chat/index.ts +50 -17
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +52 -7
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/community-club/dev/index.ts +397 -2
- package/templates/community-club/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/community-club/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/community-club/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/events-meetups/dev/index.ts +397 -2
- package/templates/events-meetups/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/events-meetups/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/events-meetups/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/gym-fitness/dev/index.ts +828 -39
- package/templates/gym-fitness/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/gym-fitness/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/gym-fitness/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +52 -7
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/nonprofit-cause/dev/index.ts +397 -2
- package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/podcast-creator/dev/index.ts +397 -2
- package/templates/podcast-creator/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/podcast-creator/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/podcast-creator/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/portfolio-creative/dev/index.ts +397 -2
- package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/portfolio-creative/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/restaurant/dev/index.ts +397 -2
- package/templates/restaurant/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/restaurant/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/restaurant/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +112 -21
- package/templates/yoga-studio/dev/index.ts +828 -39
- package/templates/yoga-studio/tier5-src/_shared/iap-attendance-form.ts +106 -0
- package/templates/yoga-studio/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
- package/templates/yoga-studio/tier5-src/_shared/iap-attendance.ts +38 -0
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +52 -7
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +112 -21
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { attendanceCheckoutRoute, availableTicketAttendanceOf, bookingAttendanceOf, classifyForApple, hostSessionFromGlobal, iapPriceFields, type BookingAttendance, type CatalogReference, type IapPollDirective, type TicketAttendance } from "@himalaya/iap";
|
|
2
|
+
import { bookingOrders, createIapCommerce, eventTicketOrders, type IapOrderItem } from "@himalaya/iap-checkout";
|
|
3
|
+
import { copyParam, copyTemplate } from "./himi-copy.js";
|
|
4
|
+
import { createAttendanceForm, type AttendanceField } from "./iap-attendance-form.js";
|
|
5
|
+
import { attendancePlatform } from "./iap-attendance.js";
|
|
6
|
+
import { iapPriceCopy, iapPurchaseCopy } from "./iap-worker.js";
|
|
7
|
+
|
|
8
|
+
export interface AttendancePurchaseFields {
|
|
9
|
+
iapBuyHidden: boolean;
|
|
10
|
+
iapBuyDisabled: boolean;
|
|
11
|
+
iapBuyLabel: string;
|
|
12
|
+
iapBusy: boolean;
|
|
13
|
+
iapPrice: string;
|
|
14
|
+
iapNote: string;
|
|
15
|
+
iapRetryHidden: boolean;
|
|
16
|
+
iapFormHidden: boolean;
|
|
17
|
+
iapFormError: string;
|
|
18
|
+
iapSignInHidden: boolean;
|
|
19
|
+
iapFields: AttendanceField[];
|
|
20
|
+
_poll?: IapPollDirective;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ATTENDANCE_INITIAL: AttendancePurchaseFields = {
|
|
24
|
+
iapBuyHidden: true, iapBuyDisabled: true, iapBuyLabel: "", iapBusy: false,
|
|
25
|
+
iapPrice: "", iapNote: "", iapRetryHidden: true, iapFormHidden: true, iapFormError: "", iapSignInHidden: true, iapFields: [],
|
|
26
|
+
};
|
|
27
|
+
export const ATTENDANCE_NOT_HYDRATED = [...Object.keys(ATTENDANCE_INITIAL), "_poll"] as (keyof AttendancePurchaseFields & string)[];
|
|
28
|
+
|
|
29
|
+
export function createAttendancePurchase(options: { kind: "bookingsService" | "eventsTicket"; prefix: string; pollAction: string; onChange(): void }) {
|
|
30
|
+
const { prefix } = options;
|
|
31
|
+
let form = createAttendanceForm(prefix);
|
|
32
|
+
const submittedForms = new Map<string, ReturnType<typeof createAttendanceForm>>();
|
|
33
|
+
const session = hostSessionFromGlobal();
|
|
34
|
+
const commerce = createIapCommerce({ adapters: [options.kind === "bookingsService" ? bookingOrders : eventTicketOrders], onChange: options.onChange });
|
|
35
|
+
let generation = session.current().generation;
|
|
36
|
+
let reference: CatalogReference | undefined;
|
|
37
|
+
let metadata: BookingAttendance | TicketAttendance | undefined;
|
|
38
|
+
let platform = "unknown";
|
|
39
|
+
let started = false;
|
|
40
|
+
let inFlight = false;
|
|
41
|
+
let formError = "";
|
|
42
|
+
let sequence = 0;
|
|
43
|
+
let formRevision = 0;
|
|
44
|
+
const attempts = new Map<string, string>();
|
|
45
|
+
session.subscribe(() => { syncSession(); if (reference) options.onChange(); });
|
|
46
|
+
|
|
47
|
+
function syncSession(): void {
|
|
48
|
+
const next = session.current().generation;
|
|
49
|
+
if (next === generation) return;
|
|
50
|
+
generation = next;
|
|
51
|
+
form.clear();
|
|
52
|
+
submittedForms.clear();
|
|
53
|
+
formError = "";
|
|
54
|
+
attempts.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function refresh(): Promise<void> {
|
|
58
|
+
const next = await attendancePlatform(publishPlatform);
|
|
59
|
+
if (next !== "unknown" || platform === "unknown") platform = next;
|
|
60
|
+
await resume();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function resume(): Promise<void> {
|
|
64
|
+
if (reference) await commerce.loadPrices([{ reference }]);
|
|
65
|
+
const first = !started;
|
|
66
|
+
started = true;
|
|
67
|
+
await (first ? commerce.start() : commerce.reconcile());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function publishPlatform(next: string): void {
|
|
71
|
+
if (next === "unknown" && platform !== "unknown") return;
|
|
72
|
+
platform = next;
|
|
73
|
+
void resume().then(options.onChange, options.onChange);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function show(): void {
|
|
77
|
+
void attendancePlatform(publishPlatform).then(publishPlatform, options.onChange);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function booking(id: string, service: unknown): Promise<void> {
|
|
81
|
+
syncSession();
|
|
82
|
+
reference = { kind: options.kind, id };
|
|
83
|
+
metadata = bookingAttendanceOf(service);
|
|
84
|
+
selectForm(id, (next) => next.booking(service));
|
|
85
|
+
formRevision++;
|
|
86
|
+
formError = "";
|
|
87
|
+
await show();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function ticket(id: string, event: unknown, available: unknown): Promise<void> {
|
|
91
|
+
syncSession();
|
|
92
|
+
reference = id ? { kind: options.kind, id } : undefined;
|
|
93
|
+
metadata = availableTicketAttendanceOf(event, available);
|
|
94
|
+
selectForm(id, (next) => next.event((event as { form?: unknown } | undefined)?.form));
|
|
95
|
+
formRevision++;
|
|
96
|
+
formError = "";
|
|
97
|
+
await show();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function selectForm(id: string, initialize: (next: ReturnType<typeof createAttendanceForm>) => void): void {
|
|
101
|
+
const key = `${options.kind}:${id}`;
|
|
102
|
+
const retained = submittedForms.get(key);
|
|
103
|
+
form = retained ?? createAttendanceForm(prefix);
|
|
104
|
+
if (!retained) initialize(form);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fields(selected = true, publishPoll = true): AttendancePurchaseFields {
|
|
108
|
+
syncSession();
|
|
109
|
+
if (!reference || !metadata) return ATTENDANCE_INITIAL;
|
|
110
|
+
const currency = metadata.currency ?? "";
|
|
111
|
+
const classified = classifyForApple(metadata, currency);
|
|
112
|
+
const candidate = platform === "ios" && classified === "appleIap";
|
|
113
|
+
const price = iapPriceFields(commerce.pricing.view(reference), iapPriceCopy(prefix));
|
|
114
|
+
const copy = { ...iapPurchaseCopy(prefix), buy: copyParam(`${prefix}.appleBuyCta`, options.kind === "bookingsService" ? "Book with Apple" : "Buy ticket with Apple"), buyAnother: copyParam(`${prefix}.buyAnotherCta`, options.kind === "bookingsService" ? "Book again" : "Buy another ticket") };
|
|
115
|
+
const purchase = commerce.purchaseFields(reference, copy);
|
|
116
|
+
const apple = candidate || (platform === "ios" && classified !== "wixPayments" && purchase.appleRoute);
|
|
117
|
+
const enabled = attendanceCheckoutRoute(metadata, currency, platform) === "appleIap";
|
|
118
|
+
const poll = publishPoll ? commerce.pollDirective(options.pollAction) : undefined;
|
|
119
|
+
const state = commerce.state();
|
|
120
|
+
for (const [target, attempt] of attempts) {
|
|
121
|
+
const key = `${reference.kind}:${reference.id}#${encodeURIComponent(attempt)}`;
|
|
122
|
+
if ((state.phase === "accessReady" && state.item === key) || state.payments.some((p) => p.item === key && p.access === "granted")) attempts.delete(target);
|
|
123
|
+
}
|
|
124
|
+
const formKey = `${reference.kind}:${reference.id}`;
|
|
125
|
+
if (![...attempts.keys()].some((target) => target.startsWith(`${formKey}:`))) submittedForms.delete(formKey);
|
|
126
|
+
const own = state.item?.startsWith(`${reference.kind}:${reference.id}#`);
|
|
127
|
+
const completed = state.phase === "accessReady" && own;
|
|
128
|
+
const member = session.current().kind === "member";
|
|
129
|
+
let note = "";
|
|
130
|
+
if (apple && !enabled) note = copy.unavailable;
|
|
131
|
+
else if (apple && !member) note = copy.signInRequired;
|
|
132
|
+
else if (apple && purchase.buyNote) note = purchase.buyNote;
|
|
133
|
+
else if (apple && !form.supported()) note = copyParam(`${prefix}.formUnavailableText`, "This registration form is not supported yet.");
|
|
134
|
+
else if (apple && completed) note = copyParam(`${prefix}.purchaseCompleteText`, options.kind === "bookingsService" ? "Your booking is confirmed." : "Your ticket is confirmed.");
|
|
135
|
+
else if (apple && own && state.phase === "cancelled") note = copyParam(`${prefix}.purchaseCancelledText`, "Purchase cancelled. Your place is not confirmed.");
|
|
136
|
+
return {
|
|
137
|
+
iapBuyHidden: !apple, iapBuyDisabled: !enabled || !selected || !member || !form.supported() || purchase.buyDisabled || inFlight,
|
|
138
|
+
iapBuyLabel: purchase.buyLabel || copy.buy, iapBusy: purchase.buyBusy || inFlight,
|
|
139
|
+
iapPrice: apple ? price.priceText : "", iapRetryHidden: !apple || (price.hidePriceRetry && commerce.scopeComplete()),
|
|
140
|
+
iapNote: note,
|
|
141
|
+
iapFormHidden: !apple || !selected || !form.supported(), iapFormError: formError,
|
|
142
|
+
iapSignInHidden: !apple || member || options.kind === "bookingsService",
|
|
143
|
+
iapFields: form.rows().map((f, index, rows) => ({ ...f,
|
|
144
|
+
id: `${generation}:${formRevision}:${f.id}`, fieldId: f.id,
|
|
145
|
+
errorHidden: !f.error,
|
|
146
|
+
options: f.options.map((o) => ({ ...o, fieldId: f.id, displayLabel: (f.kind === "multiple" ? f.selectedIds.includes(o.id) : f.value === o.id) ? `${o.label} ✓` : o.label })),
|
|
147
|
+
label: f.required ? `${f.label} *` : f.label,
|
|
148
|
+
inputLabel: copyTemplate(`${prefix}.fieldAccessibilityLabel`, "{section}: {field}", { section: f.id.startsWith("buyer:") ? copyParam(`${prefix}.buyerDetailsLabel`, "Buyer details") : copyParam(`${prefix}.guestDetailsLabel`, "Ticket holder details"), field: f.label }),
|
|
149
|
+
sectionLabel: index === 0 ? copyParam(`${prefix}.buyerDetailsLabel`, "Buyer details") : f.id.startsWith("guest:") && rows[index - 1].id.startsWith("buyer:") ? copyParam(`${prefix}.guestDetailsLabel`, "Ticket holder details") : "",
|
|
150
|
+
})), ...(poll ? { _poll: poll } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function buy(selection: { key: string; slot?: Record<string, unknown>; eventId?: string }): Promise<void> {
|
|
155
|
+
syncSession();
|
|
156
|
+
if (!reference || !metadata || fields(true, false).iapBuyDisabled || inFlight) return;
|
|
157
|
+
if (options.kind === "eventsTicket" && reference.id !== selection.key) return;
|
|
158
|
+
if (!form.validate()) { formError = copyParam(`${prefix}.formRequiredText`, "Complete the required details before purchasing."); return; }
|
|
159
|
+
if (session.current().kind !== "member") { formError = iapPurchaseCopy(prefix).signInRequired; return; }
|
|
160
|
+
const currentGeneration = generation;
|
|
161
|
+
const ref = reference;
|
|
162
|
+
submittedForms.set(`${ref.kind}:${ref.id}`, form);
|
|
163
|
+
const target = `${ref.kind}:${ref.id}:${selection.key}`;
|
|
164
|
+
let attemptId = attempts.get(target);
|
|
165
|
+
const state = commerce.state();
|
|
166
|
+
const attemptKey = attemptId ? `${ref.kind}:${ref.id}#${encodeURIComponent(attemptId)}` : "";
|
|
167
|
+
const completed = attemptId && ((state.phase === "accessReady" && state.item === attemptKey) || state.payments.some((p) => p.item === attemptKey && p.access === "granted"));
|
|
168
|
+
if (!attemptId || completed) { attemptId = `${Date.now().toString(36)}-${++sequence}-${Math.random().toString(36).slice(2)}`; attempts.set(target, attemptId); }
|
|
169
|
+
const item: IapOrderItem = { reference: ref, attemptId, quantity: 1, attendance: {
|
|
170
|
+
amount: metadata.amount!, currency: metadata.currency!,
|
|
171
|
+
...(options.kind === "bookingsService" ? { booking: { slot: selection.slot ?? {}, contactDetails: form.buyer() } } : { ticket: { eventId: selection.eventId ?? "", ...form.ticket(ref.id) } }),
|
|
172
|
+
} };
|
|
173
|
+
inFlight = true;
|
|
174
|
+
formError = "";
|
|
175
|
+
options.onChange();
|
|
176
|
+
try {
|
|
177
|
+
await commerce.buy(item);
|
|
178
|
+
const after = commerce.state();
|
|
179
|
+
if (after.phase === "accessReady" && after.item === `${ref.kind}:${ref.id}#${encodeURIComponent(attemptId)}`) attempts.delete(target);
|
|
180
|
+
}
|
|
181
|
+
finally { inFlight = false; syncSession(); if (generation !== currentGeneration) form.clear(); options.onChange(); }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function change(arg: unknown): void {
|
|
185
|
+
syncSession();
|
|
186
|
+
const state = commerce.state();
|
|
187
|
+
if (reference) {
|
|
188
|
+
for (const [target, attempt] of attempts) {
|
|
189
|
+
const item = `${reference.kind}:${reference.id}#${encodeURIComponent(attempt)}`;
|
|
190
|
+
const canCorrect = state.phase === "cancelled" || (state.phase === "refused" && (state.failure === "orderRefused" || state.failure === "productChanged"));
|
|
191
|
+
if (canCorrect && state.item === item) { attempts.delete(target); submittedForms.delete(`${reference.kind}:${reference.id}`); }
|
|
192
|
+
}
|
|
193
|
+
const referencePrefix = `${reference.kind}:${reference.id}:`;
|
|
194
|
+
if (inFlight || [...attempts.keys()].some((target) => target.startsWith(referencePrefix))) {
|
|
195
|
+
formError = copyParam(`${prefix}.detailsLockedText`, "Finish the current purchase before changing registration details.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
form.change(arg);
|
|
200
|
+
formError = "";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
booking, ticket, fields, buy,
|
|
205
|
+
usesRegularPlatform: () => platform === "android" || platform === "web",
|
|
206
|
+
invalidate: () => { reference = undefined; metadata = undefined; options.onChange(); },
|
|
207
|
+
change,
|
|
208
|
+
refresh,
|
|
209
|
+
poll: async () => { syncSession(); await commerce.poll(); },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { get as appInfo } from "@himalaya/app-info";
|
|
2
|
+
import { attendanceCheckoutRoute, bookingAttendanceOf, ticketAttendanceOf } from "@himalaya/iap";
|
|
3
|
+
|
|
4
|
+
let knownPlatform: string | undefined;
|
|
5
|
+
|
|
6
|
+
export function attendancePlatform(onResolved?: (platform: string) => void): Promise<string> {
|
|
7
|
+
if (knownPlatform) return Promise.resolve(knownPlatform);
|
|
8
|
+
const info = appInfo().then((value) => value.platform, () => "unknown").then((platform) => {
|
|
9
|
+
if (platform !== "unknown") knownPlatform = platform;
|
|
10
|
+
return platform;
|
|
11
|
+
});
|
|
12
|
+
const host = globalThis as { setTimeout?: (fn: () => void, ms: number) => unknown; clearTimeout?: (timer: unknown) => void };
|
|
13
|
+
if (!host.setTimeout) {
|
|
14
|
+
void info.then((platform) => onResolved?.(platform));
|
|
15
|
+
return Promise.resolve("unknown");
|
|
16
|
+
}
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
let timedOut = false;
|
|
19
|
+
const timer = host.setTimeout!(() => { timedOut = true; resolve("unknown"); }, 1500);
|
|
20
|
+
void info.then((platform) => { host.clearTimeout?.(timer); resolve(platform); if (timedOut) onResolved?.(platform); });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function mayUseRegularBookingCheckout(service: unknown): Promise<boolean> {
|
|
25
|
+
const metadata = bookingAttendanceOf(service);
|
|
26
|
+
if (metadata.priceType === "NO_FEE" || isZero(metadata.amount)) return true;
|
|
27
|
+
return attendanceCheckoutRoute(metadata, metadata.currency ?? "", await attendancePlatform()) === "regular";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function mayUseRegularTicketCheckout(event: unknown, ticket: { free?: boolean; price?: { amount?: string; currency?: string }; saleStatus?: string } | undefined): Promise<boolean> {
|
|
31
|
+
if (ticket?.free === true || isZero(ticket?.price?.amount)) return true;
|
|
32
|
+
const metadata = ticketAttendanceOf(event, { hidden: false, saleStatus: ticket?.saleStatus });
|
|
33
|
+
return attendanceCheckoutRoute(metadata, ticket?.price?.currency ?? "", await attendancePlatform()) === "regular";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isZero(value: unknown): boolean {
|
|
37
|
+
return typeof value === "string" && /^0+(?:\.0+)?$/.test(value);
|
|
38
|
+
}
|
|
@@ -36,6 +36,23 @@ import {
|
|
|
36
36
|
} from "../_shared/himi-events";
|
|
37
37
|
import { initials } from "../_shared/himi-text.js";
|
|
38
38
|
import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
39
|
+
import { mayUseRegularTicketCheckout } from "../_shared/iap-attendance.js";
|
|
40
|
+
import { ATTENDANCE_INITIAL, ATTENDANCE_NOT_HYDRATED, createAttendancePurchase, type AttendancePurchaseFields } from "../_shared/iap-attendance-purchase.js";
|
|
41
|
+
import { resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
|
|
42
|
+
|
|
43
|
+
let ticketCheckoutAllowed = false;
|
|
44
|
+
let paymentEvent: unknown;
|
|
45
|
+
let paymentTickets = new Map<string, WixAvailableTicket>();
|
|
46
|
+
let ticketSelectionVersion = 0;
|
|
47
|
+
let lastReady: S | undefined;
|
|
48
|
+
const applePurchase = createAttendancePurchase({ kind: "eventsTicket", prefix: "event", pollAction: "himiEventDetail.iapPoll", onChange: () => { void svc.iapPublish(); } });
|
|
49
|
+
|
|
50
|
+
async function priceTicket(event: unknown, id: string, version: number): Promise<boolean> {
|
|
51
|
+
const available = paymentTickets.get(id);
|
|
52
|
+
if (version !== ticketSelectionVersion) return false;
|
|
53
|
+
await applePurchase.ticket(id, event, available);
|
|
54
|
+
return version === ticketSelectionVersion;
|
|
55
|
+
}
|
|
39
56
|
|
|
40
57
|
/** A hosted signed-in event-page open is in flight (see openRegistration). */
|
|
41
58
|
let hostedOpenInFlight = false;
|
|
@@ -88,7 +105,7 @@ async function writeCachedRow(row: EventRow): Promise<void> {
|
|
|
88
105
|
try { await upsert(ENTITY, row as unknown as DbEntity); } catch { /* capability not granted → online-only */ }
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
interface S {
|
|
108
|
+
interface S extends AttendancePurchaseFields {
|
|
92
109
|
status: "loading" | "ready";
|
|
93
110
|
id: string;
|
|
94
111
|
title: string;
|
|
@@ -139,6 +156,8 @@ interface S {
|
|
|
139
156
|
/** The tier the CTA will reserve. Defaults to the cheapest on-sale tier so the
|
|
140
157
|
* button is never dead, and follows the buyer's tap after that. */
|
|
141
158
|
selectedTierId: string;
|
|
159
|
+
ticketPurchaseBlocked: boolean;
|
|
160
|
+
regularTicketsHidden: boolean;
|
|
142
161
|
/** "General · $25" — echoed next to the CTA so the choice is visible. */
|
|
143
162
|
selectedTierLabel: string;
|
|
144
163
|
/** External/ticketed registration URL when the payload carries one. */
|
|
@@ -179,7 +198,7 @@ function tierPrice(t: WixAvailableTicket): string {
|
|
|
179
198
|
}
|
|
180
199
|
|
|
181
200
|
function tierAvailable(t: WixAvailableTicket): boolean {
|
|
182
|
-
return !!t.id && !/SALE_ENDED|CANCELED|SOLD_OUT/i.test(t.saleStatus ?? "");
|
|
201
|
+
return !!t.id && !/SALE_SCHEDULED|SALE_ENDED|CANCELED|SOLD_OUT/i.test(t.saleStatus ?? "");
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
/** Map the buyer-visible tiers onto rows, cheapest first (so the default pick is
|
|
@@ -205,26 +224,36 @@ function tierRows(defs: WixAvailableTicket[], selectedId: string): TicketTierRow
|
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
/** Best-effort tier read — never sinks the screen if the tier endpoint fails. */
|
|
208
|
-
async function loadTiers(eventId: string): Promise<TicketTierRow[]> {
|
|
227
|
+
async function loadTiers(eventId: string): Promise<{ rows: TicketTierRow[]; tickets: Map<string, WixAvailableTicket> }> {
|
|
209
228
|
try {
|
|
210
229
|
const response = await queryAvailableTickets(eventId);
|
|
211
230
|
const defs = response.definitions ?? [];
|
|
231
|
+
const tickets = new Map(defs.filter((t) => t.id).map((t) => [t.id!, t]));
|
|
212
232
|
const cheapestAvailable = defs
|
|
213
233
|
.filter(tierAvailable)
|
|
214
234
|
.slice()
|
|
215
235
|
.sort((a, b) => Number(a.price?.amount ?? 0) - Number(b.price?.amount ?? 0))[0];
|
|
216
|
-
return tierRows(defs, cheapestAvailable?.id ?? "");
|
|
236
|
+
return { rows: tierRows(defs, cheapestAvailable?.id ?? ""), tickets };
|
|
217
237
|
} catch {
|
|
218
|
-
return [];
|
|
238
|
+
return { rows: [], tickets: new Map() };
|
|
219
239
|
}
|
|
220
240
|
}
|
|
221
241
|
|
|
222
242
|
function derive(s: S): S {
|
|
243
|
+
const regular = ticketCheckoutAllowed || applePurchase.usesRegularPlatform();
|
|
223
244
|
const hasContent = !!s.title;
|
|
224
245
|
const hasMapMarkers = s.mapMarkers.length > 0;
|
|
225
246
|
const hasAttendees = s.attendeeAvatars.length > 0;
|
|
226
|
-
|
|
247
|
+
const purchase = applePurchase.fields(!!s.selectedTierId);
|
|
248
|
+
const tiers = s.ticketTiers.map((t) => ({ ...t, priceLine: regular ? tierPrice(paymentTickets.get(t.id) ?? {}) : t.id === s.selectedTierId ? purchase.iapPrice : "" }));
|
|
249
|
+
const next = {
|
|
227
250
|
...s,
|
|
251
|
+
_poll: undefined,
|
|
252
|
+
...purchase,
|
|
253
|
+
actionMessage: regular && !ticketCheckoutAllowed && s.actionMessage === copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") ? "" : s.actionMessage,
|
|
254
|
+
ticketTiers: tiers,
|
|
255
|
+
regularTicketsHidden: s.registrationKind !== "tickets" || !purchase.iapBuyHidden,
|
|
256
|
+
ticketPurchaseBlocked: s.registrationKind === "tickets" && !regular,
|
|
228
257
|
hasDescriptionJson: s.descriptionJson !== "",
|
|
229
258
|
noMap: !s.mapUrl,
|
|
230
259
|
hasMapMarkers,
|
|
@@ -239,8 +268,8 @@ function derive(s: S): S {
|
|
|
239
268
|
showTierPicker: s.ticketTiers.length > 1,
|
|
240
269
|
hideTierPicker: s.ticketTiers.length <= 1,
|
|
241
270
|
selectedTierLabel: (() => {
|
|
242
|
-
const picked =
|
|
243
|
-
return picked ?
|
|
271
|
+
const picked = tiers.find((t) => t.id === s.selectedTierId);
|
|
272
|
+
return picked ? [picked.name, picked.priceLine].filter(Boolean).join(" · ") : "";
|
|
244
273
|
})(),
|
|
245
274
|
showTickets: s.registrationKind === "tickets",
|
|
246
275
|
hideTickets: s.registrationKind !== "tickets",
|
|
@@ -256,6 +285,8 @@ function derive(s: S): S {
|
|
|
256
285
|
? copyParam("event.errorText", "Event could not be loaded.")
|
|
257
286
|
: copyParam("event.notFoundText", "Event not found.")),
|
|
258
287
|
};
|
|
288
|
+
if (next.status === "ready") lastReady = next;
|
|
289
|
+
return next;
|
|
259
290
|
}
|
|
260
291
|
|
|
261
292
|
function calendarStamp(ms: number): string {
|
|
@@ -288,7 +319,7 @@ function calendarFallbackUrl(s: S, end: number): string {
|
|
|
288
319
|
|
|
289
320
|
/** The Events v3 fieldsets the detail reads (see `WixEventFieldset`). DETAILS carries mainImage and
|
|
290
321
|
* shortDescription: REGISTRATION fills eventType, which skips the query enrichment that used to supply them. */
|
|
291
|
-
export const EVENT_DETAIL_FIELDSETS: WixEventFieldset[] = ["DETAILS", "TEXTS", "REGISTRATION", "URLS"];
|
|
322
|
+
export const EVENT_DETAIL_FIELDSETS: WixEventFieldset[] = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "FORM"];
|
|
292
323
|
|
|
293
324
|
/** Map the decoded registration/eventType onto the CTA variant. The decoded
|
|
294
325
|
* `eventType` already folds registration.type / registration.initialType /
|
|
@@ -401,16 +432,23 @@ async function enrichRegistration(id: string, e: WixEvent): Promise<WixEvent> {
|
|
|
401
432
|
}
|
|
402
433
|
|
|
403
434
|
const svc = defineService<S, {
|
|
404
|
-
load: (s: S, arg?: unknown) => Promise<S>;
|
|
435
|
+
load: (s: S, arg?: unknown) => Promise<S | undefined>;
|
|
405
436
|
openMap: (s: S) => Promise<S>;
|
|
406
|
-
selectTier: (s: S, arg?: unknown) => Promise<S>;
|
|
437
|
+
selectTier: (s: S, arg?: unknown) => Promise<S | undefined>;
|
|
407
438
|
openRegistration: (s: S) => Promise<S>;
|
|
408
439
|
share: (s: S) => Promise<S>;
|
|
409
440
|
addToCalendar: (s: S) => Promise<S>;
|
|
410
441
|
/** [rico] #3372 — RichContent's on_link: `{ url }` of the tapped link. */
|
|
411
442
|
openLink: (s: S, arg?: unknown) => Promise<S>;
|
|
443
|
+
buyApple: (s: S) => Promise<S>;
|
|
444
|
+
setIapField: (s: S, arg?: unknown) => S;
|
|
445
|
+
iapPublish: (s: S) => S;
|
|
446
|
+
iapRefresh: (s: S) => Promise<S>;
|
|
447
|
+
iapPoll: (s: S) => Promise<S>;
|
|
412
448
|
}>("himiEventDetail", {
|
|
449
|
+
notHydrated: [...ATTENDANCE_NOT_HYDRATED, "ticketPurchaseBlocked"],
|
|
413
450
|
state: derive({
|
|
451
|
+
...ATTENDANCE_INITIAL,
|
|
414
452
|
status: "loading",
|
|
415
453
|
id: inputId(),
|
|
416
454
|
title: "",
|
|
@@ -433,6 +471,8 @@ const svc = defineService<S, {
|
|
|
433
471
|
hasAttendees: false,
|
|
434
472
|
noAttendees: true,
|
|
435
473
|
registrationKind: "rsvp",
|
|
474
|
+
ticketPurchaseBlocked: true,
|
|
475
|
+
regularTicketsHidden: true,
|
|
436
476
|
showRsvp: true,
|
|
437
477
|
hideRsvp: false,
|
|
438
478
|
showTickets: false,
|
|
@@ -462,7 +502,19 @@ const svc = defineService<S, {
|
|
|
462
502
|
noEmpty: true,
|
|
463
503
|
}),
|
|
464
504
|
actions: {
|
|
465
|
-
|
|
505
|
+
iapPublish: (s) => derive(lastReady ?? s),
|
|
506
|
+
iapRefresh: async (): Promise<S> => { await applePurchase.refresh(); return derive(svc.getState()); },
|
|
507
|
+
iapPoll: async (): Promise<S> => { await applePurchase.poll(); return derive(svc.getState()); },
|
|
508
|
+
setIapField: (s, arg) => { applePurchase.change(arg); return derive(s); },
|
|
509
|
+
buyApple: async (s: S): Promise<S> => {
|
|
510
|
+
if (!s.selectedTierId || !s.ticketTiers.some((t) => t.id === s.selectedTierId && t.available)) return derive(s);
|
|
511
|
+
await applePurchase.buy({ key: s.selectedTierId, eventId: s.id });
|
|
512
|
+
return derive(svc.getState());
|
|
513
|
+
},
|
|
514
|
+
load: async (s: S, arg?: unknown): Promise<S | undefined> => {
|
|
515
|
+
const selectionVersion = ++ticketSelectionVersion;
|
|
516
|
+
ticketCheckoutAllowed = false;
|
|
517
|
+
applePurchase.invalidate();
|
|
466
518
|
stashCopy(arg);
|
|
467
519
|
const id = s.id || inputId();
|
|
468
520
|
if (!id) return derive({ ...s, status: "ready", errored: true });
|
|
@@ -479,7 +531,15 @@ const svc = defineService<S, {
|
|
|
479
531
|
// screen BEFORE the CTA is tapped. Best-effort: a tier read that fails
|
|
480
532
|
// leaves the picker collapsed and the CTA falls back to its own read.
|
|
481
533
|
const kind = registrationKindOf(e);
|
|
482
|
-
const
|
|
534
|
+
const loaded = kind === "tickets" ? await loadTiers(id) : { rows: [], tickets: new Map<string, WixAvailableTicket>() };
|
|
535
|
+
const tiers = loaded.rows;
|
|
536
|
+
const selectedTierId = tiers.find((t) => t.isSelected)?.id ?? tiers.find((t) => t.available)?.id ?? "";
|
|
537
|
+
const regular = kind !== "tickets" || await mayUseRegularTicketCheckout(e, loaded.tickets.get(selectedTierId));
|
|
538
|
+
if (selectionVersion !== ticketSelectionVersion) return undefined;
|
|
539
|
+
paymentEvent = e;
|
|
540
|
+
paymentTickets = loaded.tickets;
|
|
541
|
+
ticketCheckoutAllowed = regular;
|
|
542
|
+
if (kind === "tickets" && !await priceTicket(e, selectedTierId, selectionVersion)) return undefined;
|
|
483
543
|
// Write-through: cache the fetched row (same EventRow shape as the list) so a
|
|
484
544
|
// later offline open of this detail can fall back to it.
|
|
485
545
|
await writeCachedRow(normalizeEvent(e));
|
|
@@ -493,7 +553,7 @@ const svc = defineService<S, {
|
|
|
493
553
|
ticketTiers: tiers,
|
|
494
554
|
// Default the pick to the cheapest AVAILABLE tier so the CTA is never
|
|
495
555
|
// dead; the buyer's tap overrides it from there.
|
|
496
|
-
selectedTierId
|
|
556
|
+
selectedTierId,
|
|
497
557
|
title,
|
|
498
558
|
initial: (title.trim()[0] || "E").toUpperCase(),
|
|
499
559
|
when: eventWhen(e),
|
|
@@ -514,11 +574,12 @@ const svc = defineService<S, {
|
|
|
514
574
|
shareUrl: eventPageUrlOf(e),
|
|
515
575
|
calendarStart: eventStartMillis(e) ?? 0,
|
|
516
576
|
calendarEnd: eventEndMillis(e) ?? 0,
|
|
517
|
-
actionMessage: "",
|
|
577
|
+
actionMessage: ticketCheckoutAllowed || !applePurchase.fields(true, false).iapBuyHidden ? "" : copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable."),
|
|
518
578
|
statusLabel: titleCase(e.eventStatus) || titleCase((e as unknown as { status?: string }).status),
|
|
519
579
|
});
|
|
520
580
|
} catch {
|
|
521
581
|
// Offline / fetch failure: fall back to the last-synced cached row before erroring.
|
|
582
|
+
if (selectionVersion !== ticketSelectionVersion) return undefined;
|
|
522
583
|
const cached = await readCachedRow(id);
|
|
523
584
|
if (cached) {
|
|
524
585
|
const title = cached.title || "Event";
|
|
@@ -552,22 +613,46 @@ const svc = defineService<S, {
|
|
|
552
613
|
return derive({ ...s, actionMessage: copyParam("event.mapOpenFailed", "Could not open maps on this device.") });
|
|
553
614
|
}
|
|
554
615
|
},
|
|
555
|
-
selectTier: async (s, arg) => {
|
|
616
|
+
selectTier: async (s: S, arg?: unknown): Promise<S | undefined> => {
|
|
556
617
|
const id = typeof arg === "string" ? arg : (arg as { id?: unknown } | undefined)?.id;
|
|
557
618
|
const wanted = typeof id === "string" ? id : "";
|
|
558
619
|
const tier = s.ticketTiers.find((t) => t.id === wanted);
|
|
559
620
|
// Ignore a tap on an off-sale row rather than letting the CTA promise a
|
|
560
621
|
// ticket it cannot reserve.
|
|
561
622
|
if (!tier || !tier.available) return s;
|
|
623
|
+
const selectionVersion = ++ticketSelectionVersion;
|
|
624
|
+
ticketCheckoutAllowed = false;
|
|
625
|
+
applePurchase.invalidate();
|
|
626
|
+
const regular = await mayUseRegularTicketCheckout(paymentEvent, paymentTickets.get(wanted));
|
|
627
|
+
if (selectionVersion !== ticketSelectionVersion) return undefined;
|
|
628
|
+
ticketCheckoutAllowed = regular;
|
|
629
|
+
if (!await priceTicket(paymentEvent, wanted, selectionVersion)) return undefined;
|
|
562
630
|
return derive({
|
|
563
631
|
...s,
|
|
564
632
|
selectedTierId: wanted,
|
|
565
633
|
ticketTiers: s.ticketTiers.map((t) => ({ ...t, isSelected: t.id === wanted, notSelected: t.id !== wanted })),
|
|
566
634
|
// A new pick invalidates whatever the last attempt said.
|
|
567
|
-
actionMessage: "",
|
|
635
|
+
actionMessage: ticketCheckoutAllowed || !applePurchase.fields(true, false).iapBuyHidden ? "" : copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable."),
|
|
568
636
|
});
|
|
569
637
|
},
|
|
570
|
-
openRegistration: async (s) => {
|
|
638
|
+
openRegistration: async (s): Promise<S> => {
|
|
639
|
+
let eventPageAllowed = true;
|
|
640
|
+
let checkoutEvent: WixEvent | undefined;
|
|
641
|
+
if (s.registrationKind === "tickets") {
|
|
642
|
+
try {
|
|
643
|
+
const current = await getEvent(s.id, { fields: EVENT_DETAIL_FIELDSETS });
|
|
644
|
+
const event = ((current as unknown as { event?: WixEvent }).event ?? current) as WixEvent;
|
|
645
|
+
checkoutEvent = event;
|
|
646
|
+
eventPageAllowed = await mayUseRegularTicketCheckout(event, undefined);
|
|
647
|
+
ticketCheckoutAllowed = eventPageAllowed;
|
|
648
|
+
if (!ticketCheckoutAllowed) {
|
|
649
|
+
const tickets = await queryAvailableTickets(s.id);
|
|
650
|
+
const selected = (tickets.definitions ?? []).find((ticket) => ticket.id === s.selectedTierId);
|
|
651
|
+
ticketCheckoutAllowed = await mayUseRegularTicketCheckout(event, selected);
|
|
652
|
+
}
|
|
653
|
+
if (!ticketCheckoutAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
|
|
654
|
+
} catch { return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") }); }
|
|
655
|
+
}
|
|
571
656
|
// Phase 2c — NATIVE ticket purchase. Reserve the cheapest available tier,
|
|
572
657
|
// turn the hold into an order, then settle it on the shared in-app pay rail
|
|
573
658
|
// (@wix/himalaya/checkout): wallet where the app has one, in-app Wix cashier
|
|
@@ -598,9 +683,12 @@ const svc = defineService<S, {
|
|
|
598
683
|
// picker is collapsed.
|
|
599
684
|
const tickets = await queryAvailableTickets(s.id);
|
|
600
685
|
const live = (tickets.definitions ?? []).filter(tierAvailable);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
686
|
+
// A recovered first read must show the offer before it can be sold.
|
|
687
|
+
if (live.length > 0 && !s.selectedTierId && s.ticketTiers.length === 0) return svc.load();
|
|
688
|
+
const tier = live.find((t) => t.id === s.selectedTierId);
|
|
689
|
+
if (!tier) return derive({ ...s, actionMessage: copyParam("event.ticketChanged", "That ticket is no longer available. Choose a ticket again.") });
|
|
690
|
+
ticketCheckoutAllowed = await mayUseRegularTicketCheckout(checkoutEvent, tier);
|
|
691
|
+
if (!ticketCheckoutAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
|
|
604
692
|
if (tier?.id) {
|
|
605
693
|
const held = await createTicketReservation(s.id, [{ ticketDefinitionId: tier.id, quantity: 1 }]);
|
|
606
694
|
reservationId = held.id ?? "";
|
|
@@ -643,6 +731,7 @@ const svc = defineService<S, {
|
|
|
643
731
|
// fall through to the hosted rail below
|
|
644
732
|
}
|
|
645
733
|
// Hosted eventsCheckout — pinned to the live reservation when we have one.
|
|
734
|
+
if (!eventPageAllowed && !reservationId) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
|
|
646
735
|
try {
|
|
647
736
|
const params = reservationId ? { reservationId } : s.eventSlug ? { eventSlug: s.eventSlug } : undefined;
|
|
648
737
|
if (params) {
|
|
@@ -667,6 +756,7 @@ const svc = defineService<S, {
|
|
|
667
756
|
// page → nothing) on its own. Nothing here can change between its prepare and its open (the event
|
|
668
757
|
// is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
|
|
669
758
|
// A host that refused or failed leaves the visitor page below.
|
|
759
|
+
if (!eventPageAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
|
|
670
760
|
if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
|
|
671
761
|
// One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
|
|
672
762
|
// replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
|
|
@@ -747,5 +837,6 @@ const svc = defineService<S, {
|
|
|
747
837
|
}),
|
|
748
838
|
},
|
|
749
839
|
});
|
|
840
|
+
resetUnhydratedKeysOnOlderRuntime(svc, { ...svc.getState(), ...ATTENDANCE_INITIAL, ticketPurchaseBlocked: true }, [...ATTENDANCE_NOT_HYDRATED, "ticketPurchaseBlocked"]);
|
|
750
841
|
svc.load();
|
|
751
842
|
export { svc };
|