@wix/himalaya-cli 0.1525.0 → 0.1527.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/dev-server/src/builder.d.ts +14 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/connectivity.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +22 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/worker-subtree-safe-types.generated.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +13 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +14 -0
- package/dist/cli.mjs +157 -40
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +43 -18
- package/guides/himi-test.md +3 -3
- package/guides/recipe-dynamic-list.md +7 -7
- package/guides/recipe-four-state.md +47 -41
- package/guides/recipe-search.md +1 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +1943 -646
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/beauty-salon/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/beauty-salon/tier5-src/himi-booking-bookings/index.ts +1 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +803 -284
- package/templates/beauty-salon/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +3 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-order/index.ts +3 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +10 -4
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/branded-lite/dev/index.ts +2404 -1020
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/branded-lite/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/branded-lite/tier5-src/business-chat/index.ts +4 -0
- package/templates/branded-lite/tier5-src/himi-booking-bookings/index.ts +1 -0
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +803 -284
- package/templates/branded-lite/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +3 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-feed/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-group/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifications/index.ts +6 -4
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +6 -4
- package/templates/branded-lite/tier5-src/himi-branded-lite-order/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-reservations/index.ts +6 -4
- package/templates/branded-lite/tier5-src/himi-branded-lite-reserve/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/branded-lite/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +10 -4
- package/templates/community-club/dev/index.ts +40 -21
- package/templates/community-club/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/community-club/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/community-club/tier5-src/himi-branded-lite-feed/index.ts +3 -1
- package/templates/community-club/tier5-src/himi-branded-lite-group/index.ts +3 -1
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/community-club/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/community-club/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/community-club-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/events-meetups/dev/index.ts +36 -19
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/events-meetups/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-feed/index.ts +3 -1
- package/templates/events-meetups/tier5-src/himi-branded-lite-group/index.ts +3 -1
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/events-meetups/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/events-meetups-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/gym-fitness/dev/index.ts +1944 -645
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/gym-fitness/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/gym-fitness/tier5-src/himi-booking-bookings/index.ts +1 -0
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +803 -284
- package/templates/gym-fitness/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +3 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-order/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/gym-fitness/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +10 -4
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/nonprofit-cause/dev/index.ts +47 -29
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/nonprofit-cause/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-feed/index.ts +3 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group/index.ts +3 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/owner-lite/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/podcast-creator/dev/index.ts +47 -29
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/podcast-creator/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-feed/index.ts +3 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/podcast-creator/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/portfolio-creative/dev/index.ts +52 -27
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/portfolio-creative/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +3 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-order/index.ts +3 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/portfolio-creative/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +10 -4
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/restaurant/dev/index.ts +48 -25
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/restaurant/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-reserve/index.ts +3 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/restaurant/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/restaurant-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/retail-store/dev/index.ts +44 -23
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/retail-store/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-address/index.ts +3 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-addresses/index.ts +3 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +3 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-order/index.ts +3 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +10 -4
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/retail-store-owner/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/yoga-studio/dev/index.ts +2102 -811
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/yoga-studio/tier5-src/_shared/online-actions.ts +76 -0
- package/templates/yoga-studio/tier5-src/himi-booking-bookings/index.ts +1 -0
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +803 -284
- package/templates/yoga-studio/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +13 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +3 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-wallet/index.ts +3 -1
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +8 -1
- package/templates/yoga-studio/tier5-src/himi-events-rsvp/index.ts +7 -4
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/online-actions.ts +76 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: Branded Lite — add / edit one saved address (himi_address).
|
|
2
3
|
//
|
|
3
4
|
// No `id` nav param → a new address; `?id=` → that address, prefilled. Save writes it
|
|
4
5
|
// to the member's address book (@himalaya/wix-ecom) and goes back to My Addresses,
|
|
5
6
|
// which reloads on appear. Edit also offers Remove, behind an in-screen confirm.
|
|
6
|
-
import { defineService, navigate } from "@himalaya/state";
|
|
7
|
+
import { defineService as registerService, navigate } from "@himalaya/state";
|
|
7
8
|
import {
|
|
8
9
|
createMemberAddress,
|
|
9
10
|
deleteMemberAddress,
|
|
@@ -176,6 +177,7 @@ async function remove(state: State): Promise<State> {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
const defineService = withOnlineWrites(registerService, ["save", "remove"], { derive });
|
|
179
181
|
const svc = defineService<State, {
|
|
180
182
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
181
183
|
retry: (state: State) => Promise<State>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: Branded Lite — My Addresses (the member's saved address book).
|
|
2
3
|
//
|
|
3
4
|
// The same book the site's Members Area "My Addresses" page and checkout use
|
|
4
5
|
// (addresses-web, see @himalaya/wix-ecom listMemberAddresses). A member sees every
|
|
5
6
|
// saved address, picks the default, and removes one after an in-screen confirm;
|
|
6
7
|
// add/edit open himi_address. A guest gets the shared member gate.
|
|
7
|
-
import { defineService, navigate } from "@himalaya/state";
|
|
8
|
+
import { defineService as registerService, navigate } from "@himalaya/state";
|
|
8
9
|
import {
|
|
9
10
|
deleteMemberAddress,
|
|
10
11
|
listMemberAddresses,
|
|
@@ -152,6 +153,7 @@ async function write(state: State, run: () => Promise<unknown>, done: string, fa
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
const defineService = withOnlineWrites(registerService, ["setDefault", "remove"], { derive });
|
|
155
157
|
const svc = defineService<State, {
|
|
156
158
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
157
159
|
retry: (state: State) => Promise<State>;
|
|
@@ -598,6 +598,9 @@ function initial(): S {
|
|
|
598
598
|
};
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
/** How the app opens its sign-in screen, from the appear params ("" = push). */
|
|
602
|
+
let signInPresentation: "" | "sheet" | "full_screen" = "";
|
|
603
|
+
|
|
601
604
|
const svc = defineService<S, {
|
|
602
605
|
load: (s: S, p?: unknown) => Promise<S | undefined>;
|
|
603
606
|
retry: (s: S) => Promise<S | undefined>;
|
|
@@ -614,6 +617,8 @@ const svc = defineService<S, {
|
|
|
614
617
|
actions: {
|
|
615
618
|
load: async (s, p) => {
|
|
616
619
|
stashCopy(p);
|
|
620
|
+
const presentation = argOf(p, "signInPresentation");
|
|
621
|
+
if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
|
|
617
622
|
const fw = Number(argOf(p, "firstWeekday") || s.firstWeekday);
|
|
618
623
|
const today = dayKey(new Date());
|
|
619
624
|
// First appearance: open on this week. A re-appearance keeps the member's place and
|
|
@@ -739,7 +744,10 @@ const svc = defineService<S, {
|
|
|
739
744
|
const before = await memberAccess().catch(() => undefined);
|
|
740
745
|
if (!before?.email) {
|
|
741
746
|
busy = false;
|
|
742
|
-
|
|
747
|
+
// As a modal it only closes, and this screen loads again (its re-appearance reload).
|
|
748
|
+
return navigate(s, signInPresentation
|
|
749
|
+
? { screenId: "himi_sign_in", presentation: signInPresentation, args: { mode: "signin", modal: "1" } }
|
|
750
|
+
: { screenId: "himi_sign_in", args: { mode: "signin", returnScreenId: "himi_agenda" } });
|
|
743
751
|
}
|
|
744
752
|
const name = await memberName();
|
|
745
753
|
const current = await memberAccess().catch(() => undefined);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
2
|
+
import { defineService as registerState } from "@himalaya/state";
|
|
1
3
|
import { getStoreProduct } from "../_shared/himi-store.js";
|
|
2
4
|
import { iapPriceCopy } from "../_shared/iap-worker.js";
|
|
3
5
|
import { withIapPresentation } from "../_shared/iap-presentation.js";
|
|
@@ -378,6 +380,7 @@ function couponDetails(result: unknown): { id: string; code: string } {
|
|
|
378
380
|
|
|
379
381
|
let cartPricing: IapCommerce;
|
|
380
382
|
const defineService = withIapPresentation<State>({
|
|
383
|
+
register: withOnlineWrites(registerState, ["remove", "increment", "decrement", "checkout", "beginPromo", "applyPromo", "removePromo"], { derive, messageFields: action => action.includes("Promo") ? ["promoError"] : ["checkoutHint"] }),
|
|
381
384
|
onCreate: commerce => { cartPricing = commerce; },
|
|
382
385
|
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
383
386
|
items: state => state.items.flatMap(row => row.catalogReference?.catalogItemId ? [{ reference: { kind: "storesProduct", id: row.catalogReference.catalogItemId }, commerceDisplayPrice: row.commerceTotal ?? row.total }] : []),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: himi-branded-lite — community activity feed (design §4.5).
|
|
2
3
|
//
|
|
3
4
|
// The feed rides the Wix Groups social feed: a feed item lives in a group
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
// worker's signed-in flags (the himi_group join-button pattern) and the
|
|
10
11
|
// sign-in card routes to himi_account — never a dead tap.
|
|
11
12
|
// Four-state (loading / error / rows / empty) like the sibling branded workers.
|
|
12
|
-
import { defineService } from "@himalaya/state";
|
|
13
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
13
14
|
import {
|
|
14
15
|
createFeedItem,
|
|
15
16
|
listFeedItems,
|
|
@@ -301,6 +302,7 @@ async function loadFeed(state: State): Promise<State> {
|
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
|
|
305
|
+
const defineService = withOnlineWrites(registerService, ["post", "react"], { derive, draftFields: { draft: "message" } });
|
|
304
306
|
const svc = defineService<State, {
|
|
305
307
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
306
308
|
filter: (state: State, arg?: unknown) => State;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
2
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
2
3
|
import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, type WixGroup } from "@himalaya/wix-groups";
|
|
3
4
|
import { getMember } from "@himalaya/wix-members";
|
|
4
5
|
import { signedInEmail } from "../_shared/himi-account";
|
|
@@ -159,6 +160,7 @@ const initial: State = {
|
|
|
159
160
|
hideContent: true,
|
|
160
161
|
};
|
|
161
162
|
|
|
163
|
+
const defineService = withOnlineWrites(registerService, ["join","leave"]);
|
|
162
164
|
const svc = defineService<State, {
|
|
163
165
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
164
166
|
join: (state: State) => Promise<State>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// himi-branded-lite-notifications — reads the member's notifications via Members Feed Viewer (queryNotifications).
|
|
2
3
|
// a failed query is an error with Retry (or Sign in when Wix refuses the
|
|
3
4
|
// visitor) and an empty one is a real empty state — never sample rows presented as
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
// (_shared/branded-load-state.ts).
|
|
6
7
|
// mark-one-read / mark-all-read stay wired: they update state optimistically and
|
|
7
8
|
// best-effort the real write (a no-op on sample rows).
|
|
8
|
-
import { defineService } from "../_shared/collection-service.js";
|
|
9
|
+
import { defineService as registerService } from "../_shared/collection-service.js";
|
|
9
10
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
10
11
|
import { queryNotifications, markRead, markAllRead, type MfvNotification } from "@himalaya/wix-members-feed-viewer";
|
|
11
12
|
|
|
@@ -22,7 +23,7 @@ interface Row {
|
|
|
22
23
|
dest: string;
|
|
23
24
|
}
|
|
24
25
|
interface S extends LoadFlags {
|
|
25
|
-
items: Row[]; emptyText: string;
|
|
26
|
+
items: Row[]; emptyText: string; actionMessage: string;
|
|
26
27
|
isMock: boolean; notMock: boolean;
|
|
27
28
|
/** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
|
|
28
29
|
* shows only when the site really has none, and the unavailable one only when the
|
|
@@ -100,17 +101,18 @@ const COPY: LoadCopy<Row> = {
|
|
|
100
101
|
sample: () => MOCK.map((r) => ({ ...r })),
|
|
101
102
|
};
|
|
102
103
|
|
|
104
|
+
const defineService = withOnlineWrites(registerService, ["markOneRead","markAll"], { messageFields: ["actionMessage"] });
|
|
103
105
|
const svc = defineService<S, {
|
|
104
106
|
load: (s: S) => Promise<S>;
|
|
105
107
|
markOneRead: (s: S, id: string) => Promise<S>;
|
|
106
108
|
markAll: (s: S) => Promise<S>;
|
|
107
109
|
}>("himiBrandedNotifications", {
|
|
108
|
-
state: { ...LOADING, items: [], emptyText: "" },
|
|
110
|
+
state: { ...LOADING, items: [], emptyText: "", actionMessage: "" },
|
|
109
111
|
actions: {
|
|
110
112
|
load: async (s) => {
|
|
111
113
|
try {
|
|
112
114
|
const items = (await queryNotifications(25)).map(norm);
|
|
113
|
-
return { ...s, ...loaded("items", items, COPY) };
|
|
115
|
+
return { ...s, ...loaded("items", items, COPY), actionMessage: "" };
|
|
114
116
|
} catch (err) {
|
|
115
117
|
return { ...s, ...loadFailed("items", err, COPY) };
|
|
116
118
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// himi-branded-lite-notifsettings — reads the site's notification topics + the member's preferences.
|
|
2
3
|
// a failed query is an error with Retry (or Sign in when Wix refuses the
|
|
3
4
|
// visitor) and an empty one is a real empty state — never sample rows presented as
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
// (_shared/branded-load-state.ts).
|
|
6
7
|
// any exist. INTERACTIVE: tapping a row toggles that topic On/Off — optimistic +
|
|
7
8
|
// best-effort upsertMyNotificationPreferences (a no-op on sample rows).
|
|
8
|
-
import { defineService } from "@himalaya/state";
|
|
9
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
9
10
|
import { withEmptyFlags } from "../_shared/empty-flags.js";
|
|
10
11
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
11
12
|
import {
|
|
@@ -31,7 +32,7 @@ interface Row { id: string; name: string; subtitle: string; note: string; on: bo
|
|
|
31
32
|
/** Inverse of `locked` (hiddenWhen hides on TRUE) and the plain On/Off a locked row shows. */
|
|
32
33
|
notLocked: boolean; stateLabel: string }
|
|
33
34
|
interface S extends LoadFlags {
|
|
34
|
-
topics: Row[]; emptyText: string;
|
|
35
|
+
topics: Row[]; emptyText: string; actionMessage: string;
|
|
35
36
|
isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
|
|
36
37
|
* shows only when the site really has none, and the unavailable one only when the
|
|
37
38
|
* query failed. A failed query must never render the sentence that claims the
|
|
@@ -91,11 +92,12 @@ const COPY: LoadCopy<Row> = {
|
|
|
91
92
|
sample: () => MOCK.map((r) => ({ ...r })),
|
|
92
93
|
};
|
|
93
94
|
|
|
95
|
+
const defineService = withOnlineWrites(registerService, ["toggle"], { messageFields: ["actionMessage"] });
|
|
94
96
|
const svc = defineService<S, {
|
|
95
97
|
load: (s: S) => Promise<S>;
|
|
96
98
|
toggle: (s: S, p?: { id?: string; value?: unknown }) => Promise<S>;
|
|
97
99
|
}>("himiBrandedNotifSettings", withEmptyFlags({
|
|
98
|
-
state: { ...LOADING, topics: [], emptyText: "" },
|
|
100
|
+
state: { ...LOADING, topics: [], emptyText: "", actionMessage: "" },
|
|
99
101
|
actions: {
|
|
100
102
|
load: async (s) => {
|
|
101
103
|
try {
|
|
@@ -105,7 +107,7 @@ const svc = defineService<S, {
|
|
|
105
107
|
]);
|
|
106
108
|
const prefs = readPreferences(prefsRes);
|
|
107
109
|
const topics = readTopics(topicsRes).map((t) => norm(t, prefs));
|
|
108
|
-
return { ...s, ...loaded("topics", topics, COPY) };
|
|
110
|
+
return { ...s, ...loaded("topics", topics, COPY), actionMessage: "" };
|
|
109
111
|
} catch (err) {
|
|
110
112
|
return { ...s, ...loadFailed("topics", err, COPY) };
|
|
111
113
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
import { createIapCommerce, classifyCart } from "@himalaya/iap-checkout";
|
|
2
3
|
import { getStoreProduct } from "../_shared/himi-store.js";
|
|
3
4
|
import { iapPriceCopy } from "../_shared/iap-worker.js";
|
|
4
|
-
import { defineService } from "@himalaya/state";
|
|
5
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
5
6
|
import { orderMember, assertOrderMember, ownedOrderCache, orderAccessDenied, orderAccessChanged, type OrderMember } from "../_shared/member-orders.js";
|
|
6
7
|
import { addToCurrentCart, getOrder, searchOrders, type WixOrder } from "@himalaya/wix-ecom";
|
|
7
8
|
import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
|
|
@@ -313,6 +314,7 @@ async function load(state: State, arg?: unknown): Promise<State | undefined> {
|
|
|
313
314
|
const FOLLOW_TTL_SECONDS = 24 * 60 * 60;
|
|
314
315
|
|
|
315
316
|
const reorderPricing = createIapCommerce();
|
|
317
|
+
const defineService = withOnlineWrites(registerService, ["reorder", "follow", "unfollow"], { derive, messageFields: action => action === "reorder" ? ["reorderMessage"] : ["liveNote"] });
|
|
316
318
|
const svc = defineService<State, {
|
|
317
319
|
load: (state: State, arg?: unknown) => Promise<State | undefined>;
|
|
318
320
|
retry: (state: State) => Promise<State | undefined>;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// `load` is both the onAppear action and what an embed controller's `refresh()` runs (arg-less:
|
|
10
10
|
// the stashed appear params keep the verticals and the copy pack), so a host that hears
|
|
11
11
|
// `booking.created` / `booking.cancelled` re-reads the summary without a remount.
|
|
12
|
-
import { defineService } from "@himalaya/state";
|
|
12
|
+
import { defineService, navigate } from "@himalaya/state";
|
|
13
13
|
import { listMyPlanOrders, type WixPlanOrder } from "@himalaya/wix-pricing-plans";
|
|
14
14
|
import { fetchMemberBookings, type MemberBookingRow } from "../_shared/member-bookings.js";
|
|
15
15
|
import { memberAccess, assertMemberAccess, memberAccessDenied, type MemberAccess } from "../_shared/member-access.js";
|
|
@@ -52,6 +52,7 @@ const PLANS_SCREEN = "himi_plans";
|
|
|
52
52
|
const SIGN_IN_SCREEN = "himi_sign_in";
|
|
53
53
|
|
|
54
54
|
let stashedVerticals: string[] | null = null;
|
|
55
|
+
let signInPresentation: "sheet" | "full_screen" | undefined;
|
|
55
56
|
let generation = 0;
|
|
56
57
|
|
|
57
58
|
function appearVerticals(arg: unknown): string[] {
|
|
@@ -190,6 +191,8 @@ async function read(arg: unknown, again = true): Promise<PlaceSummaryState> {
|
|
|
190
191
|
* write back over the newer result. */
|
|
191
192
|
async function load(_s: PlaceSummaryState, arg?: unknown): Promise<PlaceSummaryState | undefined> {
|
|
192
193
|
stashCopy(arg);
|
|
194
|
+
const presentation = arg && typeof arg === "object" ? (arg as Record<string, unknown>).signInPresentation : undefined;
|
|
195
|
+
if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
|
|
193
196
|
const mine = ++generation;
|
|
194
197
|
const next = await read(arg);
|
|
195
198
|
return mine === generation ? next : undefined;
|
|
@@ -198,11 +201,19 @@ async function load(_s: PlaceSummaryState, arg?: unknown): Promise<PlaceSummaryS
|
|
|
198
201
|
const svc = defineService<PlaceSummaryState, {
|
|
199
202
|
load: (s: PlaceSummaryState, arg?: unknown) => Promise<PlaceSummaryState | undefined>;
|
|
200
203
|
retry: (s: PlaceSummaryState) => Promise<PlaceSummaryState | undefined>;
|
|
204
|
+
openCta: (s: PlaceSummaryState) => PlaceSummaryState;
|
|
201
205
|
}>("himiPlaceSummary", {
|
|
202
206
|
state: BLANK,
|
|
203
207
|
actions: {
|
|
204
208
|
load,
|
|
205
209
|
retry: (s) => load(s),
|
|
210
|
+
openCta: (s) => {
|
|
211
|
+
if (s.hideCta || !s.ctaScreenId) return s;
|
|
212
|
+
// No return path: the tile is embedded, so the form closes over its host.
|
|
213
|
+
return navigate(s, s.ctaScreenId === SIGN_IN_SCREEN && signInPresentation
|
|
214
|
+
? { screenId: SIGN_IN_SCREEN, presentation: signInPresentation, args: { mode: "signin", modal: "1" } }
|
|
215
|
+
: { screenId: s.ctaScreenId, args: { mode: s.ctaMode } });
|
|
216
|
+
},
|
|
206
217
|
},
|
|
207
218
|
});
|
|
208
219
|
export { svc };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { offlineWriteMessage, offlineWritesEnabled, withOnlineWrites } from "../_shared/online-actions.js";
|
|
2
|
+
import { connectivity } from "@himalaya/state";
|
|
3
|
+
import { defineService as registerService } from "../_shared/collection-service.js";
|
|
2
4
|
import { createOnlineOrder, listPublicPlans, type WixPlan } from "@himalaya/wix-pricing-plans";
|
|
3
5
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
4
6
|
import { open as openUrl } from "@himalaya/open-url";
|
|
@@ -252,6 +254,8 @@ async function regularJoinMessage(row: PlanRow, reference: { kind: string; id: s
|
|
|
252
254
|
}
|
|
253
255
|
return copyTemplate("plans.joinOnSiteMessage", "Memberships are purchased on the website — visit the site to join {name}.", { name: row.name });
|
|
254
256
|
}
|
|
257
|
+
// A link may fail to open. Its fallback becomes a write and needs its own preflight.
|
|
258
|
+
if (offlineWritesEnabled && !connectivity().online) return offlineWriteMessage;
|
|
255
259
|
try {
|
|
256
260
|
const created = await createOnlineOrder(row.id);
|
|
257
261
|
const snapshotId = created.purchaseFlowId;
|
|
@@ -291,6 +295,14 @@ async function regularJoinMessage(row: PlanRow, reference: { kind: string; id: s
|
|
|
291
295
|
return copyTemplate("plans.joinOnSiteMessage", "Memberships are purchased on the website — visit the site to join {name}.", { name: row.name });
|
|
292
296
|
}
|
|
293
297
|
|
|
298
|
+
const defineService = withOnlineWrites(registerService, ["join"], {
|
|
299
|
+
messageFields: ["actionMessage"],
|
|
300
|
+
requiresConnectivity: (_action, state: State, arg) => {
|
|
301
|
+
const row = state.rows.find(candidate => candidate.id === idOf(arg));
|
|
302
|
+
if (!row || commerce.purchaseFields({ kind: "pricingPlan", id: row.id }, iapPurchaseCopy("plans")).appleRoute) return true;
|
|
303
|
+
return !row.joinUrl && !(isHostedWorker() && !row.free);
|
|
304
|
+
},
|
|
305
|
+
});
|
|
294
306
|
const svc = defineService<State, {
|
|
295
307
|
load: (state: State, arg?: unknown) => Promise<State | undefined>;
|
|
296
308
|
pricingPublish: (state: State) => State;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: himi-branded-lite — single online-program detail (design §3.5
|
|
2
3
|
// `programs` vertical, screen full-potential pass).
|
|
3
4
|
//
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
// reads every program as `disabled` and keeps Enrol on its own commerce. An Apple-routed program
|
|
20
21
|
// offers Get access in place of Enrol and sells only through the shared checkout, never through the
|
|
21
22
|
// Wix payment rail.
|
|
22
|
-
import { defineService } from "@himalaya/state";
|
|
23
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
23
24
|
import {
|
|
24
25
|
getProgram,
|
|
25
26
|
joinProgram,
|
|
@@ -386,6 +387,7 @@ type Actions = {
|
|
|
386
387
|
iapPoll: (state: State) => Promise<undefined>;
|
|
387
388
|
};
|
|
388
389
|
|
|
390
|
+
const defineService = withOnlineWrites(registerService, ["enroll","buy"], { derive, messageFields: ["actionMessage"] });
|
|
389
391
|
const svc = defineService<State, Actions>("himiBrandedProgram", {
|
|
390
392
|
state: INITIAL_STATE,
|
|
391
393
|
notHydrated: [...PRICING_NOT_HYDRATED],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// himi-branded-lite-reservations — branded-lite "My Reservations" worker. Reads
|
|
2
3
|
// the member's real Wix Table Reservations (queryReservations). A failed query is an
|
|
3
4
|
// error with Retry (or Sign in when Wix refuses the visitor), an empty one is a real
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
// flat grid it replaced repeated "Fri, Aug 8 · 7:30 PM" on every card because
|
|
12
13
|
// no row knew what day the row above it was on. `Agenda` owns the day headers
|
|
13
14
|
// and the time column, so a row carries only what is actually per-reservation.
|
|
14
|
-
import { defineService } from "@himalaya/state";
|
|
15
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
15
16
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
16
17
|
import { queryReservations, cancelReservation, type WixReservation } from "@himalaya/wix-table-reservations";
|
|
17
18
|
import { type CalendarEvent } from "@himalaya/time";
|
|
@@ -21,7 +22,7 @@ interface S extends LoadFlags {
|
|
|
21
22
|
reservations: Row[];
|
|
22
23
|
/** The Agenda wire shape, derived from `reservations` on every transition. */
|
|
23
24
|
events: CalendarEvent[];
|
|
24
|
-
emptyText: string;
|
|
25
|
+
emptyText: string; actionMessage: string;
|
|
25
26
|
isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
|
|
26
27
|
* shows only when the site really has none, and the unavailable one only when the
|
|
27
28
|
* query failed. A failed query must never render the sentence that claims the
|
|
@@ -144,11 +145,12 @@ function tappedId(p: unknown): string {
|
|
|
144
145
|
return "";
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
const defineService = withOnlineWrites(registerService, ["cancel"], { messageFields: ["actionMessage"] });
|
|
147
149
|
const svc = defineService<S, {
|
|
148
150
|
load: (s: S) => Promise<S>;
|
|
149
151
|
cancel: (s: S, p?: unknown) => Promise<S>;
|
|
150
152
|
}>("himiBrandedReservations", {
|
|
151
|
-
state: { ...LOADING, reservations: [], events: [], emptyText: "" },
|
|
153
|
+
state: { ...LOADING, reservations: [], events: [], emptyText: "", actionMessage: "" },
|
|
152
154
|
actions: {
|
|
153
155
|
load: async (s) => {
|
|
154
156
|
try {
|
|
@@ -156,7 +158,7 @@ const svc = defineService<S, {
|
|
|
156
158
|
// to hand it chronological order rather than newest-first.
|
|
157
159
|
const r = await queryReservations({ cursorPaging: { limit: 20 }, sort: [{ fieldName: "details.startDate", order: "ASC" }] });
|
|
158
160
|
const reservations = (r.reservations ?? []).map(norm);
|
|
159
|
-
return settle(s, loaded("reservations", reservations, COPY));
|
|
161
|
+
return settle({ ...s, actionMessage: "" }, loaded("reservations", reservations, COPY));
|
|
160
162
|
} catch (err) {
|
|
161
163
|
return settle(s, loadFailed("reservations", err, COPY));
|
|
162
164
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: himi-branded-lite — reservation request form (design §4.4).
|
|
2
3
|
//
|
|
3
4
|
// The visitor rung of Wix Table Reservations, per the REST sample flow:
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
// on a Tuesday the restaurant is shut. A day strip over a bookable-times run is
|
|
20
21
|
// how every real reservation flow asks the question, and both presets ride
|
|
21
22
|
// FilterChips, so they render on all seven surfaces with no proto change.
|
|
22
|
-
import { defineService } from "@himalaya/state";
|
|
23
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
23
24
|
import {
|
|
24
25
|
createHeldReservation,
|
|
25
26
|
listReservationLocations,
|
|
@@ -255,6 +256,7 @@ async function load(state: State, arg?: unknown): Promise<State> {
|
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
const defineService = withOnlineWrites(registerService, ["submit"], { derive });
|
|
258
260
|
const svc = defineService<State, {
|
|
259
261
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
260
262
|
setPartySize: (state: State, arg?: unknown) => State;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: Branded Lite — My Wallet (the member's saved payment methods).
|
|
2
3
|
//
|
|
3
4
|
// The cards a member chose to save at checkout, read from Wix Payments on the member's
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
// page, a member can make one the default for checkout or remove one — there is no
|
|
6
7
|
// "add a card" here: a card is saved while paying. Only the last four digits, the
|
|
7
8
|
// expiry and the cardholder name ever reach the app.
|
|
8
|
-
import { defineService } from "@himalaya/state";
|
|
9
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
9
10
|
import {
|
|
10
11
|
deleteSavedPaymentMethod,
|
|
11
12
|
listMySavedPaymentMethods,
|
|
@@ -173,6 +174,7 @@ async function write(state: State, run: () => Promise<unknown>, done: string, fa
|
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
const defineService = withOnlineWrites(registerService, ["setPrimary", "remove"], { derive });
|
|
176
178
|
const svc = defineService<State, {
|
|
177
179
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
178
180
|
retry: (state: State) => Promise<State>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Tier-5 bundle: himi-events — event detail.
|
|
2
2
|
import { formatCurrency } from "../_shared/engine-intl.js";
|
|
3
|
-
import { defineService } from "@himalaya/state";
|
|
3
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
4
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
4
5
|
import { createEvent, getPermissionStatus, requestPermission } from "@himalaya/calendar";
|
|
5
6
|
import { open } from "@himalaya/open-url";
|
|
6
7
|
import { share as shareSheet } from "@himalaya/share";
|
|
@@ -431,6 +432,12 @@ async function enrichRegistration(id: string, e: WixEvent): Promise<WixEvent> {
|
|
|
431
432
|
}
|
|
432
433
|
}
|
|
433
434
|
|
|
435
|
+
const defineService = withOnlineWrites(registerService, ["buyApple", "openRegistration"], {
|
|
436
|
+
derive,
|
|
437
|
+
messageFields: ["actionMessage"],
|
|
438
|
+
requiresConnectivity: (action, state) => action !== "openRegistration"
|
|
439
|
+
|| (state.registrationKind === "tickets" && !!state.id && !isHostedWorker()),
|
|
440
|
+
});
|
|
434
441
|
const svc = defineService<S, {
|
|
435
442
|
load: (s: S, arg?: unknown) => Promise<S | undefined>;
|
|
436
443
|
openMap: (s: S) => Promise<S>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { withOnlineWrites, offlineWritesEnabled } from "../_shared/online-actions.js";
|
|
1
2
|
// Tier-5 bundle: himi-events — member RSVP flow.
|
|
2
|
-
import { defineService } from "@himalaya/state";
|
|
3
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
3
4
|
import { dispatch } from "@himalaya/dispatch";
|
|
4
5
|
import { createRsvp, getEvent, type WixEvent } from "@himalaya/wix-events";
|
|
5
6
|
import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
|
|
@@ -125,6 +126,7 @@ function contactName(dataLine: string): { first: string; last: string } {
|
|
|
125
126
|
return { first: parts[0], last: parts.slice(1).join(" ") };
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
const defineService = withOnlineWrites(registerService, ["rsvp", "signIn"], { derive, secretFields: ["password"] });
|
|
128
130
|
const svc = defineService<S, {
|
|
129
131
|
load: (s: S, arg?: unknown) => Promise<S>;
|
|
130
132
|
setEmail: (s: S, arg?: unknown) => S;
|
|
@@ -202,16 +204,17 @@ const svc = defineService<S, {
|
|
|
202
204
|
// Optimistic local write — the RSVP row lands in the cache before the Wix POST.
|
|
203
205
|
const rsvpRow = (id: string): DbEntity => ({ id, eventId: s.id, firstName: name.first, lastName: name.last, email: guestEmail, status: "YES", title: s.title });
|
|
204
206
|
const localId = s.id;
|
|
205
|
-
await cachePut(rsvpRow(localId));
|
|
207
|
+
if (!offlineWritesEnabled) await cachePut(rsvpRow(localId));
|
|
206
208
|
try {
|
|
207
209
|
const res = await createRsvp(s.id, name.first, name.last, guestEmail, "YES");
|
|
208
210
|
// Reconcile the optimistic local id with the server-assigned rsvp id.
|
|
209
211
|
const serverId = res.rsvp?.id;
|
|
210
|
-
if (
|
|
212
|
+
if (offlineWritesEnabled) await cachePut(rsvpRow(serverId || localId));
|
|
213
|
+
else if (serverId && serverId !== localId) { await cacheDrop(localId); await cachePut(rsvpRow(serverId)); }
|
|
211
214
|
rememberRsvp(s.id);
|
|
212
215
|
return derive({ ...s, successMessage: copyParam("rsvp.successMessage", "You are going. Added to My Events."), message: "" });
|
|
213
216
|
} catch (e) {
|
|
214
|
-
if (isBridgeUnavailable(e)) {
|
|
217
|
+
if (!offlineWritesEnabled && isBridgeUnavailable(e)) {
|
|
215
218
|
// Offline / no bridge: the optimistic cache row already shows; queue the
|
|
216
219
|
// write to replay (WriteQueue, idempotent) when connectivity returns.
|
|
217
220
|
await queueWrite("tier5.himiEventsRsvp.rsvp", { id: s.id, firstName: name.first, lastName: name.last, email: guestEmail, status: "YES" });
|
|
@@ -26,6 +26,10 @@ interface AuthRouteInput {
|
|
|
26
26
|
returnScreenId?: string;
|
|
27
27
|
returnId?: string;
|
|
28
28
|
returnChannelId?: string;
|
|
29
|
+
/** Standalone sign-in only: "1" when it was opened as a modal (sheet / cover). */
|
|
30
|
+
modal?: string;
|
|
31
|
+
/** Modal only: the tab whose stack the return screen opens on once the modal closes. */
|
|
32
|
+
returnTab?: string;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
// The screen's boot input survives refresh; consuming it must survive too.
|
|
@@ -61,6 +65,12 @@ function authIntentFromArg(arg: unknown, prev: Pick<S, "authIntent" | "authConte
|
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/** How the standalone form leaves. A modal is closed: on web `back` pops the tab's stack,
|
|
69
|
+
* not the modal. A pushed form goes back. */
|
|
70
|
+
function leaveNav(): "dismiss" | "back" {
|
|
71
|
+
return routeInput().modal === "1" ? "dismiss" : "back";
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
/** Where a successful sign-in returns to, or null. Never Account itself — that would
|
|
65
75
|
* only push a second copy of the screen the member is already on. */
|
|
66
76
|
function resumeTarget(s: Pick<S, "returnScreenId" | "returnId" | "returnChannelId">): { screenId: string; args?: Record<string, string> } | null {
|
|
@@ -78,11 +88,26 @@ function resumeTarget(s: Pick<S, "returnScreenId" | "returnId" | "returnChannelI
|
|
|
78
88
|
* On the standalone sign-in screen the target is pushed, not a `replace`: on iOS and
|
|
79
89
|
* Android `replace` resets the stack to its root, which would drop the screens the
|
|
80
90
|
* visitor came through. Back from the target shows the form again, and the form sends a
|
|
81
|
-
* member back on appear (settle). With no target the form goes back.
|
|
91
|
+
* member back on appear (settle). With no target the form goes back.
|
|
92
|
+
*
|
|
93
|
+
* A modal form closes. A target pushed from here would open inside the modal (iOS, Android)
|
|
94
|
+
* or under it (web), so a modal that names a `returnTab` hands the target to that tab: the
|
|
95
|
+
* host closes the modal, then pushes it on that tab's stack (Account's member rows). A host
|
|
96
|
+
* showing no such tab (a drawer, a narrow Mac window) drops that request whole, modal and
|
|
97
|
+
* all, so the modal is also dismissed directly: it always closes, and the row opens where
|
|
98
|
+
* the tab exists. With no `returnTab` it only closes, and the screen under it loads again —
|
|
99
|
+
* the screen that opened the modal is the return target. */
|
|
82
100
|
function resumeAfterAuth(next: S): S {
|
|
83
101
|
const target = resumeTarget(next);
|
|
84
102
|
if (!next.isMember) return next;
|
|
85
|
-
|
|
103
|
+
const tab = routeInput().modal === "1" ? routeInput().returnTab ?? "" : "";
|
|
104
|
+
if (target && tab && next.standalone) {
|
|
105
|
+
routeInputConsumed = true;
|
|
106
|
+
void dispatch("himi.open", { presentation: "dismiss" });
|
|
107
|
+
const cleared = derive({ ...next, authIntent: "", authContextName: "", returnScreenId: "", returnId: "", returnChannelId: "" });
|
|
108
|
+
return navigate(cleared, target.args ? { tab, screenId: target.screenId, args: target.args } : { tab, screenId: target.screenId });
|
|
109
|
+
}
|
|
110
|
+
if (!target || routeInput().modal === "1") return next.standalone ? navigate(next, { presentation: leaveNav() }) : next;
|
|
86
111
|
routeInputConsumed = true;
|
|
87
112
|
const cleared = derive({ ...next, authIntent: "", authContextName: "", returnScreenId: "", returnId: "", returnChannelId: "" });
|
|
88
113
|
return navigate(cleared, target);
|
|
@@ -96,11 +121,31 @@ function forwardToSignIn(next: S): S {
|
|
|
96
121
|
if (!next.authIntent && !next.returnScreenId) return next;
|
|
97
122
|
routeInputConsumed = true;
|
|
98
123
|
const args: Record<string, string> = { mode: "signin" };
|
|
99
|
-
for (const key of ["authIntent", "authContextName"
|
|
100
|
-
if (next[key]) args[key === "authIntent" ? "intent" :
|
|
124
|
+
for (const key of ["authIntent", "authContextName"] as const) {
|
|
125
|
+
if (next[key]) args[key === "authIntent" ? "intent" : "contextName"] = next[key];
|
|
126
|
+
}
|
|
127
|
+
return openSignIn(derive({ ...next, authIntent: "", authContextName: "", returnScreenId: "", returnId: "", returnChannelId: "" }),
|
|
128
|
+
args, resumeTarget(next));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Account sends a guest to the standalone sign-in screen, with `after`, the member screen to
|
|
132
|
+
* open once signed in. With a `signInPresentation` appear param it opens as that modal, and
|
|
133
|
+
* `after` goes on Account's own tab (`signInReturnTab`) when the modal closes; pushed, the form
|
|
134
|
+
* pushes `after` itself (resumeAfterAuth). */
|
|
135
|
+
function openSignIn(next: S, args: Record<string, string>, after: { screenId: string; args?: Record<string, string> } | null): S {
|
|
136
|
+
const screenId = next.signInScreen || "himi_sign_in";
|
|
137
|
+
const presentation = signInPresentationRequested;
|
|
138
|
+
const back: Record<string, string> = {};
|
|
139
|
+
if (after) {
|
|
140
|
+
back.returnScreenId = after.screenId;
|
|
141
|
+
if (after.args?.id) back.returnId = after.args.id;
|
|
142
|
+
if (after.args?.channelId) back.returnChannelId = after.args.channelId;
|
|
101
143
|
}
|
|
102
|
-
return navigate(
|
|
103
|
-
|
|
144
|
+
if (!presentation) return navigate(next, { screenId, args: { ...args, ...back } });
|
|
145
|
+
const modalArgs: Record<string, string> = { ...args, modal: "1" };
|
|
146
|
+
// With no tab to open it on, a modal has no way to the member screen: it only closes.
|
|
147
|
+
if (after && signInReturnTabRequested) Object.assign(modalArgs, back, { returnTab: signInReturnTabRequested });
|
|
148
|
+
return navigate(next, { screenId, presentation, args: modalArgs });
|
|
104
149
|
}
|
|
105
150
|
|
|
106
151
|
/** After a load: the standalone sign-in screen sends a member back (a stale link, or a
|
|
@@ -128,8 +173,11 @@ function settle(next: S, arg: unknown, latest = true): S {
|
|
|
128
173
|
// The appear refresh returns `_nav`, which every host applies — binaries from before
|
|
129
174
|
// worker nav dispatch included. Only an arg-less load, whose result iOS
|
|
130
175
|
// discards, dispatches.
|
|
131
|
-
if (strField(arg, "standalone") === "1") return navigate(cleared, { presentation:
|
|
132
|
-
|
|
176
|
+
if (strField(arg, "standalone") === "1") return navigate(cleared, { presentation: leaveNav() });
|
|
177
|
+
// Not from a modal: nothing pushed over it comes back to it, and on iOS the closing modal's
|
|
178
|
+
// own screen reloads here once, after it has asked to close — a late dismiss would close
|
|
179
|
+
// whatever modal is open by then.
|
|
180
|
+
if (routeInput().modal !== "1") void dispatch("himi.open", { presentation: leaveNav() });
|
|
133
181
|
return cleared;
|
|
134
182
|
}
|
|
135
183
|
// Account forwards only on an appear refresh: the boot `load()` has no params and its
|
|
@@ -180,6 +228,10 @@ let appliedSeq = 0;
|
|
|
180
228
|
// The appear params belong to the worker session too, like the two flags above: a probe
|
|
181
229
|
// dropped because the visitor typed must not take them with it.
|
|
182
230
|
let signInScreenRequested = "";
|
|
231
|
+
/** Account only: how it opens the standalone sign-in screen ("" = push). */
|
|
232
|
+
let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
|
|
233
|
+
/** Account only: its tab, where a member screen opens after a modal sign-in. */
|
|
234
|
+
let signInReturnTabRequested = "";
|
|
183
235
|
let standaloneRequested = false;
|
|
184
236
|
let pollIdentityRequested = false;
|
|
185
237
|
// Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
|
|
@@ -210,6 +262,9 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
|
|
|
210
262
|
passwordDraft = "";
|
|
211
263
|
}
|
|
212
264
|
signInScreenRequested ||= strField(arg, "signInScreen") || s.signInScreen;
|
|
265
|
+
const presentation = strField(arg, "signInPresentation");
|
|
266
|
+
if (presentation === "sheet" || presentation === "full_screen") signInPresentationRequested = presentation;
|
|
267
|
+
signInReturnTabRequested ||= strField(arg, "signInReturnTab");
|
|
213
268
|
// Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
|
|
214
269
|
standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
|
|
215
270
|
pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
|
|
@@ -676,6 +731,7 @@ const svc = defineService<S, {
|
|
|
676
731
|
signOut: (s: S) => Promise<S>;
|
|
677
732
|
sync: (s: S) => Promise<S | undefined>;
|
|
678
733
|
openMember: (s: S, arg?: unknown) => Promise<S>;
|
|
734
|
+
leave: (s: S) => S;
|
|
679
735
|
restorePublish: (s: S) => S;
|
|
680
736
|
iapRestore: (s: S) => Promise<undefined>;
|
|
681
737
|
}>("himiStoreAccount", {
|
|
@@ -756,7 +812,8 @@ const svc = defineService<S, {
|
|
|
756
812
|
return "next" in r ? r.next : undefined;
|
|
757
813
|
},
|
|
758
814
|
// A row to a member-only screen shows to a guest too. A member opens it; a guest signs
|
|
759
|
-
// in first and the sign-in screen
|
|
815
|
+
// in first, and the sign-in screen then opens the row's screen — as a modal, by closing
|
|
816
|
+
// and pushing it on this tab (openSignIn) — so Back returns here either way.
|
|
760
817
|
openMember: async (s, arg): Promise<S> => {
|
|
761
818
|
const screenId = strField(arg, "screenId");
|
|
762
819
|
if (!screenId) return s;
|
|
@@ -764,8 +821,10 @@ const svc = defineService<S, {
|
|
|
764
821
|
// From the state as it is now: a probe may have landed during the identity read.
|
|
765
822
|
const now = svc.getState();
|
|
766
823
|
if (member) return navigate(now, { screenId });
|
|
767
|
-
return
|
|
824
|
+
return openSignIn(now, { mode: "signin" }, { screenId });
|
|
768
825
|
},
|
|
826
|
+
// The standalone form's Continue: leaves the form the way it was opened.
|
|
827
|
+
leave: (s) => navigate(s, { presentation: leaveNav() }),
|
|
769
828
|
// Typing means any focus request was delivered: clear the tokens (safe while mounted, since
|
|
770
829
|
// bumpToken never repeats), so a visit rarely ends — and a pooled form rarely reopens —
|
|
771
830
|
// holding one.
|