@wix/himalaya-cli 0.1526.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/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/state.mjs +14 -0
- package/dist/cli.mjs +53 -34
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/himi-test.md +3 -3
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +2027 -759
- 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-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-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-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 +1910 -603
- 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/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-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-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-product/index.ts +10 -4
- 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-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/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-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 +2024 -756
- 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-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-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-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/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-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-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/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-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-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/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-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/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-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/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-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 +2054 -786
- 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-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-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>;
|
|
@@ -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,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>;
|
|
@@ -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" });
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withOnlineWrites, offlineWritesEnabled, offlineWriteMessage } from "../_shared/online-actions.js";
|
|
2
|
+
import { defineService as registerState } from "@himalaya/state";
|
|
1
3
|
// Tier-5 bundle: himi-store — product detail.
|
|
2
4
|
import { withIapPresentation } from "../_shared/iap-presentation.js";
|
|
3
5
|
import { storesAppleSalesEnabled, storesProductOrders, type IapCommerce, type IapPurchaseFields } from "@himalaya/iap-checkout";
|
|
@@ -459,6 +461,7 @@ const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, bu
|
|
|
459
461
|
let productPricing: IapCommerce;
|
|
460
462
|
let checkoutEntered = false;
|
|
461
463
|
const defineService = withIapPresentation<S>({
|
|
464
|
+
register: withOnlineWrites(registerState, ["beginAdd", "addToCart", "buy"], { derive, messageFields: action => action === "buy" ? ["buyNote"] : ["cartHint"] }),
|
|
462
465
|
adapters: SELLS_THROUGH_APPLE ? [storesProductOrders] : [],
|
|
463
466
|
onCreate: commerce => { productPricing = commerce; }, prefix: "product",
|
|
464
467
|
transientKeys: ["price", "optionsIncomplete", "cartHint", "cartHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote", "purchasedNote", "addedSheetPresented", "shareHint"], contentActions: ["load", "paintCached"], concurrentPublishActions: ["buy"],
|
|
@@ -467,12 +470,14 @@ const defineService = withIapPresentation<S>({
|
|
|
467
470
|
const purchase = applePurchase(state);
|
|
468
471
|
const unavailable = iapPriceCopy("product").purchaseUnavailable;
|
|
469
472
|
const blocked = lastProduct?.productType?.toLowerCase() === "digital" || (!state.optionsUnknown && !allowed(inputId()));
|
|
470
|
-
const cartHint =
|
|
473
|
+
const cartHint = state.cartHint === offlineWriteMessage ? state.cartHint
|
|
474
|
+
: !purchase.appleRoute && blocked ? unavailable : state.cartHint === unavailable ? "" : state.cartHint;
|
|
471
475
|
return {
|
|
472
476
|
...state, price: price(inputId()), ...(purchase.appleRoute ? { wasPrice: "", wasPriceEmpty: false, notOnSale: true } : {}), optionsIncomplete: derive(state).optionsIncomplete || !allowed(inputId()) || lastProduct?.productType?.toLowerCase() === "digital",
|
|
473
477
|
cartHint, noCartHint: !cartHint || state.added, addDisabled: state.addDisabled || blocked,
|
|
474
478
|
cartHidden: purchase.appleRoute,
|
|
475
|
-
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled,
|
|
479
|
+
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled,
|
|
480
|
+
buyNote: state.buyNote === offlineWriteMessage ? state.buyNote : purchase.buyNote,
|
|
476
481
|
purchasedNote: purchase.accessReady ? copyParam("product.purchasedText", "Purchased") : "",
|
|
477
482
|
};
|
|
478
483
|
},
|
|
@@ -848,11 +853,11 @@ const svc = defineService<S, {
|
|
|
848
853
|
const sale = lastProduct ? productSale(lastProduct) : { wasPrice: "" };
|
|
849
854
|
const swatches = s.optionGroups.flatMap(g => g.kind === "color" ? g.choices.map(c => c.colorCode).filter(Boolean) : []).slice(0, 5);
|
|
850
855
|
const line: DbEntity = { id: variantKey ? `line-${id}-${variantKey}` : `line-${id}`, productId: id, name: s.name || "product", title: s.name || "product", subtitle: chosen ? `${chosen} · Qty ${quantity}` : `Qty ${quantity}`, quantity: String(quantity), image: s.image, unitPrice, wasPrice: sale.wasPrice, swatches, total: optimisticLineTotal(unitPrice, quantity), hasImage: !!s.image, noImage: !s.image };
|
|
851
|
-
await cartPut(line);
|
|
856
|
+
if (!offlineWritesEnabled) await cartPut(line);
|
|
852
857
|
try {
|
|
853
858
|
await addToCurrentCart(id, quantity, cartOptions);
|
|
854
859
|
} catch (e) {
|
|
855
|
-
if (isBridgeUnavailable(e)) {
|
|
860
|
+
if (!offlineWritesEnabled && isBridgeUnavailable(e)) {
|
|
856
861
|
// Offline / no bridge: the optimistic cache line already shows; queue the
|
|
857
862
|
// add to replay (WriteQueue, idempotent) when connectivity returns.
|
|
858
863
|
await queueWrite("tier5.himiStoreProduct.addToCart", { id, ...(cartOptions ? { options: JSON.stringify(cartOptions) } : {}) });
|
|
@@ -861,6 +866,7 @@ const svc = defineService<S, {
|
|
|
861
866
|
// Roll the optimistic badge back — nothing was added.
|
|
862
867
|
return derive({ ...s, adding: false, added: false, cartCount: before, cartHint: copyParam("product.cartHintFailed", "Could not add to cart. Try again.") });
|
|
863
868
|
}
|
|
869
|
+
if (offlineWritesEnabled) await cartPut(line);
|
|
864
870
|
// The write LANDED. The badge re-read is outside the write's try on purpose: a failed
|
|
865
871
|
// count must never be mistaken for a failed add. Server count wins; else keep optimistic.
|
|
866
872
|
const count = await cartItemCount().catch(() => 0);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from './online-actions.js';
|
|
1
2
|
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
3
|
import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, collectionActionsLabel, type CollectionRow } from './collection-contract.js';
|
|
3
4
|
import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
|
|
@@ -331,6 +332,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
331
332
|
* reducer and reads the private controller snapshot, never an SDUI payload. */
|
|
332
333
|
export function defineBackendCollection<S extends object, Row extends object>(name: string, options: {
|
|
333
334
|
register?: typeof register;
|
|
335
|
+
/** Explicit mutation preflight; unrelated collection services keep their current reducers. */
|
|
336
|
+
onlineWrites?: boolean;
|
|
334
337
|
/** Revalidate already opened pages on navigation return; explicit refresh resets. */
|
|
335
338
|
restorePagesOnLoad?: boolean;
|
|
336
339
|
/** The load action's argument (onAppear params beyond `copy`), before the query runs — for a
|
|
@@ -350,7 +353,8 @@ export function defineBackendCollection<S extends object, Row extends object>(na
|
|
|
350
353
|
controller: controller as unknown as BackendCollection['controller'],
|
|
351
354
|
apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
|
|
352
355
|
});
|
|
353
|
-
const
|
|
356
|
+
const registerService = options.onlineWrites ? withOnlineWrites(options.register ?? register, ['confirmCancel', 'confirmOperation', 'retryOperation', 'commitWrite'], { messageFields: ['actionMessage'] }) : (options.register ?? register);
|
|
357
|
+
const handle = registerService(name, definition);
|
|
354
358
|
// Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
|
|
355
359
|
// when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
|
|
356
360
|
// starts its first load as before, never from rows it chose not to restore. Rows that came back empty are the
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { connectivity, defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
// Template generation enables this behavior only in the selected two starter families.
|
|
4
|
+
export const offlineWritesEnabled = false;
|
|
5
|
+
|
|
6
|
+
export const offlineWriteMessage = 'You’re offline. Connect to save this change, then submit it again.';
|
|
7
|
+
|
|
8
|
+
/** The guard runs before the reducer: entered values survive and no optimistic
|
|
9
|
+
* changes, success state, or queued writes can occur. It never submits on reconnect. */
|
|
10
|
+
export function offlineWriteState<S extends object>(state: S): S {
|
|
11
|
+
const next: Record<string, unknown> = { ...state, error: offlineWriteMessage, message: offlineWriteMessage };
|
|
12
|
+
for (const key of ['saving', 'isSaving', 'submitting', 'isSubmitting', 'adding', 'added', 'bookingInFlight']) {
|
|
13
|
+
if (key in next) next[key] = false;
|
|
14
|
+
}
|
|
15
|
+
for (const key of ['savedMessage', 'successMessage']) {
|
|
16
|
+
if (key in next) next[key] = '';
|
|
17
|
+
}
|
|
18
|
+
// A saved/favourited item is business state, not a submission-success flag.
|
|
19
|
+
return next as S;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface OfflineWriteOptions<S> {
|
|
23
|
+
derive?: (state: S) => S;
|
|
24
|
+
/** Exempt only non-mutating branches of a registered action. */
|
|
25
|
+
requiresConnectivity?: (action: string, state: S, argument?: unknown) => boolean;
|
|
26
|
+
messageFields?: readonly string[] | ((action: string) => readonly string[]);
|
|
27
|
+
draftFields?: readonly string[] | Readonly<Record<string, string>>;
|
|
28
|
+
secretFields?: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function withOnlineWrites<S extends object>(registerService: typeof register, names: readonly string[], options: OfflineWriteOptions<S> = {}): typeof register {
|
|
32
|
+
if (!offlineWritesEnabled) return registerService;
|
|
33
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
34
|
+
let currentState: (() => Record<string, unknown>) | undefined;
|
|
35
|
+
const handle = registerService(name, {
|
|
36
|
+
...definition,
|
|
37
|
+
notHydrated: [...new Set([...(definition.notHydrated ?? []), ...(options.secretFields ?? [])])],
|
|
38
|
+
actions: Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
39
|
+
(state: Record<string, unknown>, ...args: never[]) => {
|
|
40
|
+
if (!names.includes(action)) return reduce(state, ...args);
|
|
41
|
+
if (options.requiresConnectivity?.(action, state as S, args[0]) === false) return reduce(state, ...args);
|
|
42
|
+
const fields = typeof options.messageFields === 'function' ? options.messageFields(action) : options.messageFields ?? ['message'];
|
|
43
|
+
if (connectivity().online) {
|
|
44
|
+
let cleared = false;
|
|
45
|
+
for (const field of ['error', 'message', ...fields]) {
|
|
46
|
+
if (state[field] === offlineWriteMessage) { state[field] = ''; cleared = true; }
|
|
47
|
+
}
|
|
48
|
+
// Keep the runtime's state object: reducers may mutate it and return undefined.
|
|
49
|
+
const result = reduce(state, ...args);
|
|
50
|
+
if (!cleared) return result;
|
|
51
|
+
// Publish cleared feedback even for a void reducer. An async completion must
|
|
52
|
+
// keep any newer state published by another action while it was awaiting.
|
|
53
|
+
const finish = (next: Record<string, unknown> | undefined) => next === undefined ? currentState?.() ?? state : next;
|
|
54
|
+
return result && typeof (result as Promise<unknown>).then === 'function'
|
|
55
|
+
? Promise.resolve(result).then(finish)
|
|
56
|
+
: finish(result as Record<string, unknown> | undefined);
|
|
57
|
+
}
|
|
58
|
+
const draft = { ...state };
|
|
59
|
+
const argument = args[0] as Record<string, unknown> | undefined;
|
|
60
|
+
const draftFields = options.draftFields ?? [];
|
|
61
|
+
const entries = Array.isArray(draftFields) ? draftFields.map(key => [key, key]) : Object.entries(draftFields);
|
|
62
|
+
for (const [key, source] of entries) {
|
|
63
|
+
if (argument && key in state && typeof argument[source] === typeof state[key]) draft[key] = argument[source];
|
|
64
|
+
}
|
|
65
|
+
for (const key of options.secretFields ?? []) draft[key] = '';
|
|
66
|
+
const feedback = Object.fromEntries(fields.map(key => [key, offlineWriteMessage]));
|
|
67
|
+
let next = { ...offlineWriteState(draft), ...feedback };
|
|
68
|
+
if (options.derive) next = options.derive(next as S) as Record<string, unknown>;
|
|
69
|
+
return { ...next, ...feedback };
|
|
70
|
+
},
|
|
71
|
+
])),
|
|
72
|
+
});
|
|
73
|
+
if (typeof handle.getState === 'function') currentState = () => handle.getState();
|
|
74
|
+
return handle;
|
|
75
|
+
}) as typeof register;
|
|
76
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from './online-actions.js';
|
|
1
2
|
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
3
|
import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, collectionActionsLabel, type CollectionRow } from './collection-contract.js';
|
|
3
4
|
import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
|
|
@@ -331,6 +332,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
331
332
|
* reducer and reads the private controller snapshot, never an SDUI payload. */
|
|
332
333
|
export function defineBackendCollection<S extends object, Row extends object>(name: string, options: {
|
|
333
334
|
register?: typeof register;
|
|
335
|
+
/** Explicit mutation preflight; unrelated collection services keep their current reducers. */
|
|
336
|
+
onlineWrites?: boolean;
|
|
334
337
|
/** Revalidate already opened pages on navigation return; explicit refresh resets. */
|
|
335
338
|
restorePagesOnLoad?: boolean;
|
|
336
339
|
/** The load action's argument (onAppear params beyond `copy`), before the query runs — for a
|
|
@@ -350,7 +353,8 @@ export function defineBackendCollection<S extends object, Row extends object>(na
|
|
|
350
353
|
controller: controller as unknown as BackendCollection['controller'],
|
|
351
354
|
apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
|
|
352
355
|
});
|
|
353
|
-
const
|
|
356
|
+
const registerService = options.onlineWrites ? withOnlineWrites(options.register ?? register, ['confirmCancel', 'confirmOperation', 'retryOperation', 'commitWrite'], { messageFields: ['actionMessage'] }) : (options.register ?? register);
|
|
357
|
+
const handle = registerService(name, definition);
|
|
354
358
|
// Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
|
|
355
359
|
// when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
|
|
356
360
|
// starts its first load as before, never from rows it chose not to restore. Rows that came back empty are the
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { connectivity, defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
// Template generation enables this behavior only in the selected two starter families.
|
|
4
|
+
export const offlineWritesEnabled = false;
|
|
5
|
+
|
|
6
|
+
export const offlineWriteMessage = 'You’re offline. Connect to save this change, then submit it again.';
|
|
7
|
+
|
|
8
|
+
/** The guard runs before the reducer: entered values survive and no optimistic
|
|
9
|
+
* changes, success state, or queued writes can occur. It never submits on reconnect. */
|
|
10
|
+
export function offlineWriteState<S extends object>(state: S): S {
|
|
11
|
+
const next: Record<string, unknown> = { ...state, error: offlineWriteMessage, message: offlineWriteMessage };
|
|
12
|
+
for (const key of ['saving', 'isSaving', 'submitting', 'isSubmitting', 'adding', 'added', 'bookingInFlight']) {
|
|
13
|
+
if (key in next) next[key] = false;
|
|
14
|
+
}
|
|
15
|
+
for (const key of ['savedMessage', 'successMessage']) {
|
|
16
|
+
if (key in next) next[key] = '';
|
|
17
|
+
}
|
|
18
|
+
// A saved/favourited item is business state, not a submission-success flag.
|
|
19
|
+
return next as S;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface OfflineWriteOptions<S> {
|
|
23
|
+
derive?: (state: S) => S;
|
|
24
|
+
/** Exempt only non-mutating branches of a registered action. */
|
|
25
|
+
requiresConnectivity?: (action: string, state: S, argument?: unknown) => boolean;
|
|
26
|
+
messageFields?: readonly string[] | ((action: string) => readonly string[]);
|
|
27
|
+
draftFields?: readonly string[] | Readonly<Record<string, string>>;
|
|
28
|
+
secretFields?: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function withOnlineWrites<S extends object>(registerService: typeof register, names: readonly string[], options: OfflineWriteOptions<S> = {}): typeof register {
|
|
32
|
+
if (!offlineWritesEnabled) return registerService;
|
|
33
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
34
|
+
let currentState: (() => Record<string, unknown>) | undefined;
|
|
35
|
+
const handle = registerService(name, {
|
|
36
|
+
...definition,
|
|
37
|
+
notHydrated: [...new Set([...(definition.notHydrated ?? []), ...(options.secretFields ?? [])])],
|
|
38
|
+
actions: Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
39
|
+
(state: Record<string, unknown>, ...args: never[]) => {
|
|
40
|
+
if (!names.includes(action)) return reduce(state, ...args);
|
|
41
|
+
if (options.requiresConnectivity?.(action, state as S, args[0]) === false) return reduce(state, ...args);
|
|
42
|
+
const fields = typeof options.messageFields === 'function' ? options.messageFields(action) : options.messageFields ?? ['message'];
|
|
43
|
+
if (connectivity().online) {
|
|
44
|
+
let cleared = false;
|
|
45
|
+
for (const field of ['error', 'message', ...fields]) {
|
|
46
|
+
if (state[field] === offlineWriteMessage) { state[field] = ''; cleared = true; }
|
|
47
|
+
}
|
|
48
|
+
// Keep the runtime's state object: reducers may mutate it and return undefined.
|
|
49
|
+
const result = reduce(state, ...args);
|
|
50
|
+
if (!cleared) return result;
|
|
51
|
+
// Publish cleared feedback even for a void reducer. An async completion must
|
|
52
|
+
// keep any newer state published by another action while it was awaiting.
|
|
53
|
+
const finish = (next: Record<string, unknown> | undefined) => next === undefined ? currentState?.() ?? state : next;
|
|
54
|
+
return result && typeof (result as Promise<unknown>).then === 'function'
|
|
55
|
+
? Promise.resolve(result).then(finish)
|
|
56
|
+
: finish(result as Record<string, unknown> | undefined);
|
|
57
|
+
}
|
|
58
|
+
const draft = { ...state };
|
|
59
|
+
const argument = args[0] as Record<string, unknown> | undefined;
|
|
60
|
+
const draftFields = options.draftFields ?? [];
|
|
61
|
+
const entries = Array.isArray(draftFields) ? draftFields.map(key => [key, key]) : Object.entries(draftFields);
|
|
62
|
+
for (const [key, source] of entries) {
|
|
63
|
+
if (argument && key in state && typeof argument[source] === typeof state[key]) draft[key] = argument[source];
|
|
64
|
+
}
|
|
65
|
+
for (const key of options.secretFields ?? []) draft[key] = '';
|
|
66
|
+
const feedback = Object.fromEntries(fields.map(key => [key, offlineWriteMessage]));
|
|
67
|
+
let next = { ...offlineWriteState(draft), ...feedback };
|
|
68
|
+
if (options.derive) next = options.derive(next as S) as Record<string, unknown>;
|
|
69
|
+
return { ...next, ...feedback };
|
|
70
|
+
},
|
|
71
|
+
])),
|
|
72
|
+
});
|
|
73
|
+
if (typeof handle.getState === 'function') currentState = () => handle.getState();
|
|
74
|
+
return handle;
|
|
75
|
+
}) as typeof register;
|
|
76
|
+
}
|
|
@@ -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>;
|
|
@@ -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" });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withOnlineWrites } from './online-actions.js';
|
|
1
2
|
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
3
|
import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, collectionActionsLabel, type CollectionRow } from './collection-contract.js';
|
|
3
4
|
import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
|
|
@@ -331,6 +332,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
331
332
|
* reducer and reads the private controller snapshot, never an SDUI payload. */
|
|
332
333
|
export function defineBackendCollection<S extends object, Row extends object>(name: string, options: {
|
|
333
334
|
register?: typeof register;
|
|
335
|
+
/** Explicit mutation preflight; unrelated collection services keep their current reducers. */
|
|
336
|
+
onlineWrites?: boolean;
|
|
334
337
|
/** Revalidate already opened pages on navigation return; explicit refresh resets. */
|
|
335
338
|
restorePagesOnLoad?: boolean;
|
|
336
339
|
/** The load action's argument (onAppear params beyond `copy`), before the query runs — for a
|
|
@@ -350,7 +353,8 @@ export function defineBackendCollection<S extends object, Row extends object>(na
|
|
|
350
353
|
controller: controller as unknown as BackendCollection['controller'],
|
|
351
354
|
apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
|
|
352
355
|
});
|
|
353
|
-
const
|
|
356
|
+
const registerService = options.onlineWrites ? withOnlineWrites(options.register ?? register, ['confirmCancel', 'confirmOperation', 'retryOperation', 'commitWrite'], { messageFields: ['actionMessage'] }) : (options.register ?? register);
|
|
357
|
+
const handle = registerService(name, definition);
|
|
354
358
|
// Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
|
|
355
359
|
// when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
|
|
356
360
|
// starts its first load as before, never from rows it chose not to restore. Rows that came back empty are the
|