@wix/himalaya-cli 0.1569.0 → 0.1571.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/l10n-strings.d.ts +3 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/business-contact-service.d.ts +17 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/business-contact.d.ts +73 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/invite-target.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/invite.d.ts +148 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +6 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/auth.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core/validation.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +575 -91
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth.mjs +7 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +1 -0
- package/dist/cli.mjs +1897 -1226
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/base44-app-to-himi-app.md +1 -1
- package/guides/capabilities.md +7 -0
- package/guides/getting-started.md +5 -0
- package/guides/himi-json-output.md +11 -0
- package/guides/himi-skill.md +5 -0
- package/guides/one-app-to-himi-app.md +267 -8
- package/guides/sdk-reference.json +2 -2
- package/guides/skill.md +5 -0
- package/guides/splash-screens.md +10 -1
- package/guides/tier-5-add-a-screen.md +20 -0
- package/guides/trim-a-template.md +5 -0
- package/guides/wix-business-context.md +11 -2
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +8 -1
- package/templates/beauty-salon/dev/index.ts +244 -1
- package/templates/beauty-salon/himalaya.content.json +3 -0
- package/templates/beauty-salon/tier5-src/_shared/member-read.ts +52 -0
- package/templates/beauty-salon/tier5-src/_shared/program-description.ts +11 -0
- package/templates/beauty-salon/tier5-src/business-contact/index.ts +6 -0
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/beauty-salon-owner/config.ts +1 -1
- package/templates/branded-lite/config.ts +8 -1
- package/templates/branded-lite/dev/index.ts +409 -2
- package/templates/branded-lite/himalaya.content.json +3 -0
- package/templates/branded-lite/tier5-src/_shared/member-read.ts +52 -0
- package/templates/branded-lite/tier5-src/_shared/program-description.ts +11 -0
- package/templates/branded-lite/tier5-src/business-contact/index.ts +6 -0
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +39 -11
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +3 -7
- package/templates/branded-lite/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +101 -34
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +23 -6
- package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +26 -11
- package/templates/community-club/config.ts +8 -1
- package/templates/community-club/dev/index.ts +278 -2
- package/templates/community-club/himalaya.content.json +3 -0
- package/templates/community-club/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/community-club/tier5-src/_shared/member-read.ts +52 -0
- package/templates/community-club/tier5-src/_shared/program-description.ts +11 -0
- package/templates/community-club/tier5-src/business-contact/index.ts +6 -0
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/community-club/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/community-club-owner/config.ts +1 -1
- package/templates/events-meetups/config.ts +8 -1
- package/templates/events-meetups/dev/index.ts +278 -2
- package/templates/events-meetups/himalaya.content.json +3 -0
- package/templates/events-meetups/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/events-meetups/tier5-src/_shared/member-read.ts +52 -0
- package/templates/events-meetups/tier5-src/_shared/program-description.ts +11 -0
- package/templates/events-meetups/tier5-src/business-contact/index.ts +6 -0
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/events-meetups/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/events-meetups-owner/config.ts +1 -1
- package/templates/gym-fitness/config.ts +8 -1
- package/templates/gym-fitness/dev/index.ts +278 -2
- package/templates/gym-fitness/himalaya.content.json +3 -0
- package/templates/gym-fitness/tier5-src/_shared/member-read.ts +52 -0
- package/templates/gym-fitness/tier5-src/_shared/program-description.ts +11 -0
- package/templates/gym-fitness/tier5-src/business-contact/index.ts +6 -0
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/gym-fitness/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/gym-fitness-owner/config.ts +1 -1
- package/templates/nonprofit-cause/config.ts +8 -1
- package/templates/nonprofit-cause/dev/index.ts +278 -2
- package/templates/nonprofit-cause/himalaya.content.json +3 -0
- package/templates/nonprofit-cause/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/nonprofit-cause/tier5-src/_shared/member-read.ts +52 -0
- package/templates/nonprofit-cause/tier5-src/_shared/program-description.ts +11 -0
- package/templates/nonprofit-cause/tier5-src/business-contact/index.ts +6 -0
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/nonprofit-cause/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/nonprofit-cause-owner/config.ts +1 -1
- package/templates/owner-lite/config.ts +1 -1
- package/templates/podcast-creator/config.ts +8 -1
- package/templates/podcast-creator/dev/index.ts +278 -2
- package/templates/podcast-creator/himalaya.content.json +3 -0
- package/templates/podcast-creator/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/podcast-creator/tier5-src/_shared/member-read.ts +52 -0
- package/templates/podcast-creator/tier5-src/_shared/program-description.ts +11 -0
- package/templates/podcast-creator/tier5-src/business-contact/index.ts +6 -0
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/podcast-creator/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/podcast-creator-owner/config.ts +1 -1
- package/templates/portfolio-creative/config.ts +8 -1
- package/templates/portfolio-creative/dev/index.ts +278 -2
- package/templates/portfolio-creative/himalaya.content.json +3 -0
- package/templates/portfolio-creative/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-read.ts +52 -0
- package/templates/portfolio-creative/tier5-src/_shared/program-description.ts +11 -0
- package/templates/portfolio-creative/tier5-src/business-contact/index.ts +6 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/portfolio-creative/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/portfolio-creative-owner/config.ts +1 -1
- package/templates/restaurant/config.ts +8 -1
- package/templates/restaurant/dev/index.ts +278 -2
- package/templates/restaurant/himalaya.content.json +3 -0
- package/templates/restaurant/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/restaurant/tier5-src/_shared/member-read.ts +52 -0
- package/templates/restaurant/tier5-src/_shared/program-description.ts +11 -0
- package/templates/restaurant/tier5-src/business-contact/index.ts +6 -0
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/restaurant/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/restaurant-owner/config.ts +1 -1
- package/templates/retail-store/config.ts +8 -1
- package/templates/retail-store/dev/index.ts +244 -1
- package/templates/retail-store/himalaya.content.json +3 -0
- package/templates/retail-store/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/retail-store/tier5-src/_shared/member-read.ts +52 -0
- package/templates/retail-store/tier5-src/_shared/program-description.ts +11 -0
- package/templates/retail-store/tier5-src/business-contact/index.ts +6 -0
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/retail-store-owner/config.ts +1 -1
- package/templates/yoga-studio/config.ts +8 -1
- package/templates/yoga-studio/dev/index.ts +306 -2
- package/templates/yoga-studio/himalaya.content.json +3 -0
- package/templates/yoga-studio/tier5-src/_shared/member-read.ts +52 -0
- package/templates/yoga-studio/tier5-src/_shared/program-description.ts +11 -0
- package/templates/yoga-studio/tier5-src/business-contact/index.ts +6 -0
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +3 -7
- package/templates/yoga-studio/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/yoga-studio-owner/config.ts +1 -1
|
@@ -5,6 +5,8 @@ import { queryEvents, type WixEvent } from "@himalaya/wix-events";
|
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
6
|
import { confirmedRsvpIds, eventStartMillis, normalizeEvent, type EventRow } from "../_shared/himi-events";
|
|
7
7
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
8
|
+
import { memberAccess } from "../_shared/member-access.js";
|
|
9
|
+
import { readAccess, assertReadAccess, readAccessDenied, readSignInFlags, NO_READ_SIGN_IN } from "../_shared/member-read.js";
|
|
8
10
|
|
|
9
11
|
// Offline-first (data-offline): My Events is a read-through over the same `events`
|
|
10
12
|
// catalog the list caches — paint the last-synced rows (filtered to this device's
|
|
@@ -27,6 +29,7 @@ async function writeCached(rows: CachedEventRow[]): Promise<void> {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
interface S {
|
|
32
|
+
signInRequired: boolean; noSignIn: boolean; signInScreenId: string;
|
|
30
33
|
status: "loading" | "ready";
|
|
31
34
|
events: EventRow[];
|
|
32
35
|
emptyText: string;
|
|
@@ -98,11 +101,10 @@ function unverifiedEvent(event: CachedEventRow): CachedEventRow {
|
|
|
98
101
|
};
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: CachedEventRow[]; verificationUnavailable: boolean }> {
|
|
104
|
+
/** Verify device-local candidates against the current member. Member-only mode
|
|
105
|
+
* requires successful verification; standalone mode retains failed probes as
|
|
106
|
+
* explicitly unverified device-local history. */
|
|
107
|
+
async function confirmRsvps(candidates: CachedEventRow[], memberOnly = false): Promise<{ events: CachedEventRow[]; verificationUnavailable: boolean }> {
|
|
106
108
|
// Raw wixApi chokepoint rather than a generated client: the member RSVP read lives in
|
|
107
109
|
// events **v2**, and `@himalaya/wix-events` is the v1 domain module. Adding a
|
|
108
110
|
// `wix-events-v2` specifier is not an option — the umbrella package deliberately
|
|
@@ -122,7 +124,9 @@ async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: Cac
|
|
|
122
124
|
event: held ? { ...event, status: [event.status, copyParam("myEvents.rsvpConfirmed", "RSVP confirmed")].filter(Boolean).join(" · ") } : null,
|
|
123
125
|
verificationUnavailable: false,
|
|
124
126
|
};
|
|
125
|
-
} catch {
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Device candidates carry no member ownership proof until this read succeeds.
|
|
129
|
+
if (memberOnly) throw error;
|
|
126
130
|
// Fail-open, but never claim backend confirmation while its member-readable
|
|
127
131
|
// probe is unavailable. The device id remains a candidate, explicitly labeled.
|
|
128
132
|
return { event: unverifiedEvent(event), verificationUnavailable: true };
|
|
@@ -134,8 +138,8 @@ async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: Cac
|
|
|
134
138
|
};
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
const
|
|
138
|
-
|
|
141
|
+
const INITIAL: S = derive({
|
|
142
|
+
...NO_READ_SIGN_IN, signInScreenId: "himi_account",
|
|
139
143
|
status: "loading",
|
|
140
144
|
events: [],
|
|
141
145
|
emptyText: "",
|
|
@@ -147,45 +151,75 @@ const svc = defineService<S, { load: (s: S, arg?: unknown) => Promise<S> }>("him
|
|
|
147
151
|
isLoading: true,
|
|
148
152
|
notLoading: false,
|
|
149
153
|
noEmpty: true,
|
|
150
|
-
})
|
|
154
|
+
});
|
|
155
|
+
let memberOnlyRequested = false;
|
|
156
|
+
let loadVersion = 0;
|
|
157
|
+
const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown) => Promise<S | undefined> }>("himiMyEvents", {
|
|
158
|
+
state: INITIAL,
|
|
151
159
|
actions: {
|
|
152
|
-
|
|
160
|
+
prepare: (s) => ({ ...s, ...INITIAL }),
|
|
161
|
+
load: async (s, arg): Promise<S | undefined> => {
|
|
153
162
|
stashCopy(arg);
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
? derive({ ...s, status: "ready", errored: false, events: cachedEvents, identityLine: identity, verificationUnavailable: cachedEvents.length > 0 })
|
|
161
|
-
: s;
|
|
162
|
-
// 2) Revalidate from Wix and refresh the cache.
|
|
163
|
+
const version = ++loadVersion;
|
|
164
|
+
const options = arg as { memberOnly?: unknown; signInScreenId?: unknown } | undefined;
|
|
165
|
+
if (options?.memberOnly !== undefined) memberOnlyRequested = options.memberOnly === "1";
|
|
166
|
+
const memberOnly = memberOnlyRequested;
|
|
167
|
+
const clean = { ...INITIAL, signInScreenId: options?.signInScreenId === "himi_sign_in" ? "himi_sign_in" : s.signInScreenId };
|
|
168
|
+
await svc.prepare();
|
|
163
169
|
try {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
const member = memberOnly ? await readAccess() : null;
|
|
171
|
+
if (memberOnly && !member?.member.email) throw new Error("Unauthenticated: sign in to see your events");
|
|
172
|
+
const confirmed = new Set(confirmedRsvpIds());
|
|
173
|
+
const identity = await identityLine();
|
|
174
|
+
// 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
|
|
175
|
+
// Device-local candidates have no member ownership proof. Branded must
|
|
176
|
+
// verify them online rather than show a previous member's cached RSVP list.
|
|
177
|
+
const cached = memberOnly ? [] : await readCached();
|
|
178
|
+
const cachedEvents = cached.filter((event) => confirmed.has(event.id)).map(displayEventRow);
|
|
179
|
+
const base = cached.length
|
|
180
|
+
? derive({ ...clean, status: "ready", errored: false, events: cachedEvents, identityLine: identity, verificationUnavailable: cachedEvents.length > 0 })
|
|
181
|
+
: clean;
|
|
182
|
+
// 2) Revalidate from Wix and refresh the cache.
|
|
183
|
+
try {
|
|
184
|
+
const res = await queryEvents({ limit: 50 });
|
|
185
|
+
const all = ((res as unknown as { events?: WixEvent[] }).events ?? []).map(cachedEventRow);
|
|
186
|
+
if (member) await assertReadAccess(member);
|
|
187
|
+
if (version !== loadVersion) return undefined;
|
|
188
|
+
await writeCached(all);
|
|
189
|
+
// Phase 3c — make each row AUTHORITATIVE. The device list is only a candidate
|
|
190
|
+
// set: it never forgets, so an RSVP cancelled elsewhere (web, or by the
|
|
191
|
+
// business) would otherwise show here forever. Confirm each candidate against
|
|
192
|
+
// Wix and drop the ones the member no longer holds.
|
|
193
|
+
//
|
|
194
|
+
// Per-event by necessity, not by choice: a member CANNOT enumerate their own
|
|
195
|
+
// RSVPs. Verified live against a real member token — `queryRsvps` (cross-event)
|
|
196
|
+
// 403s "WIX_EVENTS.READ_RSVP permission required", `listOrders` 403s
|
|
197
|
+
// READ_ORDERS, and `/rsvps/current` 400s without an eventId. Only the
|
|
198
|
+
// single-event read is member-readable, so the fan-out below IS the ceiling
|
|
199
|
+
// until those member scopes exist server-side.
|
|
200
|
+
const candidates = all.filter((event) => confirmed.has(event.id)).map(displayEventRow);
|
|
201
|
+
const confirmation = await confirmRsvps(candidates, memberOnly);
|
|
202
|
+
if (member) await assertReadAccess(member);
|
|
203
|
+
if (version !== loadVersion) return undefined;
|
|
204
|
+
return derive({ ...base, status: "ready", errored: false, events: confirmation.events, identityLine: identity, verificationUnavailable: confirmation.verificationUnavailable });
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (memberOnly && readAccessDenied(error)) throw error;
|
|
207
|
+
if (member) await assertReadAccess(member);
|
|
208
|
+
if (version !== loadVersion) return undefined;
|
|
209
|
+
// Offline / fetch failure: keep the cached rows instead of a blank error state.
|
|
210
|
+
return cached.length
|
|
211
|
+
? base
|
|
212
|
+
: derive({ ...clean, status: "ready", errored: true, events: [], identityLine: identity });
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
const failure = memberOnly && !(await memberAccess()).email ? new Error("Unauthenticated: sign in to see your events") : error;
|
|
216
|
+
const flags = await readSignInFlags(failure);
|
|
217
|
+
return version === loadVersion ? derive({ ...clean, ...flags, status: "ready", errored: true, events: [] }) : undefined;
|
|
186
218
|
}
|
|
187
219
|
},
|
|
188
220
|
},
|
|
189
221
|
});
|
|
190
|
-
|
|
222
|
+
// Wait for the descriptor's load action to select member-only or device-local scope.
|
|
223
|
+
// An eager unconfigured read could briefly expose guest KV before Branded opts in.
|
|
224
|
+
svc.prepare();
|
|
191
225
|
export { svc };
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","stream","wix_api"],
|
|
41
41
|
designTokensPath: "./tokens.json",
|
|
42
42
|
// Native fallback; the T5 splash descriptor owns the editable launch appearance.
|
|
43
43
|
splash: {
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme","wix_cashier"],
|
|
41
41
|
cache: {
|
|
42
42
|
"rules": [
|
|
43
43
|
{
|
|
@@ -81,6 +81,13 @@ export default defineApp({
|
|
|
81
81
|
"parentScreenId": "himi_account",
|
|
82
82
|
"tabId": "profile"
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
"screenId": "himi_business_contact",
|
|
86
|
+
"isPublic": true,
|
|
87
|
+
"paramKeys": [],
|
|
88
|
+
"parentScreenId": "himi_account",
|
|
89
|
+
"tabId": "profile"
|
|
90
|
+
},
|
|
84
91
|
{
|
|
85
92
|
"screenId": "himi_appearance",
|
|
86
93
|
"isPublic": true,
|
|
@@ -5755,6 +5755,219 @@ const SCREENS = runtimeSetting({
|
|
|
5755
5755
|
"bundleVersion": "0.0.1"
|
|
5756
5756
|
}
|
|
5757
5757
|
},
|
|
5758
|
+
"himi_business_contact": {
|
|
5759
|
+
"screenId": "himi_business_contact",
|
|
5760
|
+
"title": "Business contact",
|
|
5761
|
+
"renderer": "sdui_intent",
|
|
5762
|
+
"schemaVersion": 1,
|
|
5763
|
+
"cacheControl": {
|
|
5764
|
+
"maxAge": 0,
|
|
5765
|
+
"staleWhileRevalidate": 0
|
|
5766
|
+
},
|
|
5767
|
+
"payload": {
|
|
5768
|
+
"id": "himi_business_contact.root",
|
|
5769
|
+
"body": {
|
|
5770
|
+
"package": "himalaya",
|
|
5771
|
+
"type": "VStack",
|
|
5772
|
+
"props": {
|
|
5773
|
+
"spacing": 16,
|
|
5774
|
+
"alignment": "leading",
|
|
5775
|
+
"children": [
|
|
5776
|
+
{
|
|
5777
|
+
"id": "himi_business_contact.section",
|
|
5778
|
+
"body": {
|
|
5779
|
+
"package": "himalaya",
|
|
5780
|
+
"type": "VStack",
|
|
5781
|
+
"props": {
|
|
5782
|
+
"spacing": 12,
|
|
5783
|
+
"alignment": "leading",
|
|
5784
|
+
"children": [
|
|
5785
|
+
{
|
|
5786
|
+
"id": "himi_business_contact.section.directions_copy",
|
|
5787
|
+
"body": {
|
|
5788
|
+
"package": "himalaya",
|
|
5789
|
+
"type": "Text",
|
|
5790
|
+
"props": {
|
|
5791
|
+
"text": "Directions",
|
|
5792
|
+
"style": "body",
|
|
5793
|
+
"collapseWhenEmpty": true
|
|
5794
|
+
}
|
|
5795
|
+
},
|
|
5796
|
+
"modifiers": {
|
|
5797
|
+
"hidden": true
|
|
5798
|
+
}
|
|
5799
|
+
},
|
|
5800
|
+
{
|
|
5801
|
+
"id": "himi_business_contact.section.unavailable",
|
|
5802
|
+
"body": {
|
|
5803
|
+
"package": "himalaya",
|
|
5804
|
+
"type": "Text",
|
|
5805
|
+
"props": {
|
|
5806
|
+
"text": "Contact details unavailable",
|
|
5807
|
+
"style": "body",
|
|
5808
|
+
"collapseWhenEmpty": true
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
},
|
|
5812
|
+
{
|
|
5813
|
+
"id": "himi_business_contact.section.member_count",
|
|
5814
|
+
"body": {
|
|
5815
|
+
"package": "himalaya",
|
|
5816
|
+
"type": "FragmentSlot",
|
|
5817
|
+
"props": {
|
|
5818
|
+
"casePath": "$service:businessContact.memberCountCase",
|
|
5819
|
+
"defaultCase": "unavailable",
|
|
5820
|
+
"cases": [
|
|
5821
|
+
{
|
|
5822
|
+
"id": "one",
|
|
5823
|
+
"content": {
|
|
5824
|
+
"id": "himi_business_contact.section.members_one",
|
|
5825
|
+
"body": {
|
|
5826
|
+
"package": "himalaya",
|
|
5827
|
+
"type": "HStack",
|
|
5828
|
+
"props": {
|
|
5829
|
+
"spacing": 4,
|
|
5830
|
+
"alignment": "center",
|
|
5831
|
+
"children": [
|
|
5832
|
+
{
|
|
5833
|
+
"id": "himi_business_contact.section.count_one",
|
|
5834
|
+
"body": {
|
|
5835
|
+
"package": "himalaya",
|
|
5836
|
+
"type": "Text",
|
|
5837
|
+
"props": {
|
|
5838
|
+
"text": "$service:businessContact.memberCount",
|
|
5839
|
+
"style": "footnote",
|
|
5840
|
+
"collapseWhenEmpty": true
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
},
|
|
5844
|
+
{
|
|
5845
|
+
"id": "himi_business_contact.section.unit_one",
|
|
5846
|
+
"body": {
|
|
5847
|
+
"package": "himalaya",
|
|
5848
|
+
"type": "Text",
|
|
5849
|
+
"props": {
|
|
5850
|
+
"text": "member",
|
|
5851
|
+
"style": "footnote",
|
|
5852
|
+
"collapseWhenEmpty": true
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
]
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
},
|
|
5861
|
+
{
|
|
5862
|
+
"id": "other",
|
|
5863
|
+
"content": {
|
|
5864
|
+
"id": "himi_business_contact.section.members_other",
|
|
5865
|
+
"body": {
|
|
5866
|
+
"package": "himalaya",
|
|
5867
|
+
"type": "HStack",
|
|
5868
|
+
"props": {
|
|
5869
|
+
"spacing": 4,
|
|
5870
|
+
"alignment": "center",
|
|
5871
|
+
"children": [
|
|
5872
|
+
{
|
|
5873
|
+
"id": "himi_business_contact.section.count_other",
|
|
5874
|
+
"body": {
|
|
5875
|
+
"package": "himalaya",
|
|
5876
|
+
"type": "Text",
|
|
5877
|
+
"props": {
|
|
5878
|
+
"text": "$service:businessContact.memberCount",
|
|
5879
|
+
"style": "footnote",
|
|
5880
|
+
"collapseWhenEmpty": true
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
},
|
|
5884
|
+
{
|
|
5885
|
+
"id": "himi_business_contact.section.unit_other",
|
|
5886
|
+
"body": {
|
|
5887
|
+
"package": "himalaya",
|
|
5888
|
+
"type": "Text",
|
|
5889
|
+
"props": {
|
|
5890
|
+
"text": "members",
|
|
5891
|
+
"style": "footnote",
|
|
5892
|
+
"collapseWhenEmpty": true
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
]
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
},
|
|
5901
|
+
{
|
|
5902
|
+
"id": "unavailable",
|
|
5903
|
+
"content": {
|
|
5904
|
+
"id": "himi_business_contact.section.members_unavailable",
|
|
5905
|
+
"body": {
|
|
5906
|
+
"package": "himalaya",
|
|
5907
|
+
"type": "Text",
|
|
5908
|
+
"props": {
|
|
5909
|
+
"text": "Member count unavailable",
|
|
5910
|
+
"style": "footnote",
|
|
5911
|
+
"collapseWhenEmpty": true
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
]
|
|
5917
|
+
}
|
|
5918
|
+
},
|
|
5919
|
+
"modifiers": {
|
|
5920
|
+
"hidden": true
|
|
5921
|
+
}
|
|
5922
|
+
},
|
|
5923
|
+
{
|
|
5924
|
+
"id": "himi_business_contact.section.feedback",
|
|
5925
|
+
"body": {
|
|
5926
|
+
"package": "himalaya",
|
|
5927
|
+
"type": "Text",
|
|
5928
|
+
"props": {
|
|
5929
|
+
"text": "$service:businessContact.message",
|
|
5930
|
+
"style": "footnote",
|
|
5931
|
+
"collapseWhenEmpty": true
|
|
5932
|
+
}
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
]
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
]
|
|
5940
|
+
}
|
|
5941
|
+
},
|
|
5942
|
+
"modifiers": {
|
|
5943
|
+
"background": {
|
|
5944
|
+
"token": "surfacePrimary"
|
|
5945
|
+
},
|
|
5946
|
+
"padding": {
|
|
5947
|
+
"leading": 20,
|
|
5948
|
+
"trailing": 20,
|
|
5949
|
+
"top": 18,
|
|
5950
|
+
"bottom": 28
|
|
5951
|
+
},
|
|
5952
|
+
"onAppear": {
|
|
5953
|
+
"actionId": "tier5.businessContact.load",
|
|
5954
|
+
"args": {
|
|
5955
|
+
"showMemberCount": false
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
},
|
|
5960
|
+
"logicBundle": {
|
|
5961
|
+
"url": "http://localhost:8190/v1/bundles/business-contact?integrity=",
|
|
5962
|
+
"integrity": "",
|
|
5963
|
+
"contentType": "application/javascript+ts-bundle",
|
|
5964
|
+
"declaredCapabilities": [
|
|
5965
|
+
"open_url"
|
|
5966
|
+
],
|
|
5967
|
+
"minBinaryVersion": "0.1.0",
|
|
5968
|
+
"bundleVersion": "0.0.1"
|
|
5969
|
+
}
|
|
5970
|
+
},
|
|
5758
5971
|
"himi_appearance": {
|
|
5759
5972
|
"screenId": "himi_appearance",
|
|
5760
5973
|
"renderer": "sdui_intent",
|
|
@@ -24635,6 +24848,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
24635
24848
|
"style": "body",
|
|
24636
24849
|
"color": "textSecondary"
|
|
24637
24850
|
}
|
|
24851
|
+
},
|
|
24852
|
+
"modifiers": {
|
|
24853
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
24638
24854
|
}
|
|
24639
24855
|
},
|
|
24640
24856
|
{
|
|
@@ -24649,6 +24865,32 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
24649
24865
|
"actionId": "tier5.himiBlogPost.retry"
|
|
24650
24866
|
}
|
|
24651
24867
|
}
|
|
24868
|
+
},
|
|
24869
|
+
"modifiers": {
|
|
24870
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
24871
|
+
}
|
|
24872
|
+
},
|
|
24873
|
+
{
|
|
24874
|
+
"id": "himi_blog_post.retry.sign_in",
|
|
24875
|
+
"body": {
|
|
24876
|
+
"package": "himalaya",
|
|
24877
|
+
"type": "Button",
|
|
24878
|
+
"props": {
|
|
24879
|
+
"title": "Sign in",
|
|
24880
|
+
"style": "primary",
|
|
24881
|
+
"action": {
|
|
24882
|
+
"actionId": "himi.open",
|
|
24883
|
+
"params": {
|
|
24884
|
+
"screenId": "himi_sign_in",
|
|
24885
|
+
"mode": "signin",
|
|
24886
|
+
"modal": "1"
|
|
24887
|
+
},
|
|
24888
|
+
"presentation": "sheet"
|
|
24889
|
+
}
|
|
24890
|
+
}
|
|
24891
|
+
},
|
|
24892
|
+
"modifiers": {
|
|
24893
|
+
"hiddenPath": "$service:himiBlogPost.noSignIn"
|
|
24652
24894
|
}
|
|
24653
24895
|
}
|
|
24654
24896
|
]
|
|
@@ -25353,7 +25595,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
25353
25595
|
"onAppear": {
|
|
25354
25596
|
"actionId": "tier5.himiBlogPost.load",
|
|
25355
25597
|
"params": {
|
|
25356
|
-
"id": "__HIMI_ID__"
|
|
25598
|
+
"id": "__HIMI_ID__",
|
|
25599
|
+
"signInScreenId": "himi_sign_in"
|
|
25357
25600
|
}
|
|
25358
25601
|
}
|
|
25359
25602
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Load-state honesty for the branded-lite vertical workers.
|
|
2
|
+
//
|
|
3
|
+
// These workers used to answer every failed or empty query with representative SAMPLE
|
|
4
|
+
// rows — discount codes, gift-card balances, invoices, reservations, inbox messages —
|
|
5
|
+
// under a "Sample content" banner. On a customer build that is invented data presented
|
|
6
|
+
// as the customer's own: a code that does not exist, a balance in the wrong currency, an
|
|
7
|
+
// "Overdue" invoice, a booking labelled "tap to cancel". The banner did not make it
|
|
8
|
+
// honest; the rows were still the first thing a visitor read.
|
|
9
|
+
//
|
|
10
|
+
// The rule now:
|
|
11
|
+
// • query FAILED → status "error" with a customer sentence, a Retry, and — when
|
|
12
|
+
// Wix refused an anonymous visitor (401) — a Sign in path instead;
|
|
13
|
+
// • query was EMPTY → status "ready", no rows, the vertical's own empty sentence;
|
|
14
|
+
// • sample rows → ONLY when the host explicitly asked for sample data (the web
|
|
15
|
+
// preview's sample data mode, `__himiOwnerPreview: "sample"`).
|
|
16
|
+
// Never on a device, never as a fallback.
|
|
17
|
+
//
|
|
18
|
+
// Every field here is flat and boolean-gated because descriptors can only
|
|
19
|
+
// `hiddenWhen(<path>)` on a TRUE value: `notErrored` hides the error card, `noSignIn`
|
|
20
|
+
// hides the sign-in CTA, `signInRequired` hides Retry when retrying cannot help.
|
|
21
|
+
import { copyParam } from "./himi-copy.js";
|
|
22
|
+
|
|
23
|
+
export interface LoadFlags {
|
|
24
|
+
status: "loading" | "ready" | "error";
|
|
25
|
+
/** The customer-facing failure sentence; "" unless status is "error". */
|
|
26
|
+
error: string;
|
|
27
|
+
signInRequired: boolean;
|
|
28
|
+
notErrored: boolean;
|
|
29
|
+
/** hiddenWhen gate for a loading skeleton. */
|
|
30
|
+
notLoading: boolean;
|
|
31
|
+
/** Loading or failed — gates content that has nothing to say in either. */
|
|
32
|
+
busy: boolean;
|
|
33
|
+
noSignIn: boolean;
|
|
34
|
+
isMock: boolean;
|
|
35
|
+
notMock: boolean;
|
|
36
|
+
notMockEmpty: boolean;
|
|
37
|
+
notMockUnavailable: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const LOADING: LoadFlags = {
|
|
41
|
+
status: "loading", error: "", signInRequired: false, notErrored: true, notLoading: false, busy: true, noSignIn: true,
|
|
42
|
+
isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const READY: LoadFlags = { ...LOADING, status: "ready", notLoading: true, busy: false };
|
|
46
|
+
|
|
47
|
+
/** Did the HOST ask for sample data? Only the web preview's sample mode does; a native
|
|
48
|
+
* build, a visitor preview and a live owner preview never carry it. */
|
|
49
|
+
export function samplesRequested(): boolean {
|
|
50
|
+
const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
51
|
+
return input?.__himiOwnerPreview === "sample";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Wix refused an ANONYMOUS visitor: signing in is the fix, not retrying. A 403 is not
|
|
55
|
+
* this — it also answers "feature not installed" and a signed-in member without the
|
|
56
|
+
* permission, where a Sign in button leads nowhere — so it stays a Retry-able failure. */
|
|
57
|
+
export function needsSignIn(err: unknown): boolean {
|
|
58
|
+
const msg = String((err as { message?: unknown })?.message ?? err ?? "");
|
|
59
|
+
return /\b401\b|unauthori[sz]ed|unauthenticated/i.test(msg);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LoadCopy<T> {
|
|
63
|
+
/** The copy namespace (`giftCards`, `coupons`, …) — keys resolve through copyParam. */
|
|
64
|
+
prefix: string;
|
|
65
|
+
/** Shown when the site genuinely has none. */
|
|
66
|
+
empty: string;
|
|
67
|
+
/** Shown when the query failed. */
|
|
68
|
+
error: string;
|
|
69
|
+
/** Shown when Wix refused the visitor (member-only data). */
|
|
70
|
+
signIn: string;
|
|
71
|
+
/** Sample rows for the explicit sample preview ONLY. */
|
|
72
|
+
sample: () => T[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type Rows<K extends string, T> = { [P in K]: T[] } & { emptyText: string } & LoadFlags;
|
|
76
|
+
|
|
77
|
+
/** A query answered. Rows win; an empty answer is a real empty state. */
|
|
78
|
+
export function loaded<K extends string, T>(key: K, rows: T[], copy: LoadCopy<T>): Rows<K, T> {
|
|
79
|
+
if (rows.length) return { ...READY, [key]: rows, emptyText: "" } as Rows<K, T>;
|
|
80
|
+
if (samplesRequested()) return sampleRows(key, copy, "empty");
|
|
81
|
+
return { ...READY, [key]: [], emptyText: copyParam(`${copy.prefix}.emptyText`, copy.empty) } as Rows<K, T>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A query threw. Never rows the customer did not create. */
|
|
85
|
+
export function loadFailed<K extends string, T>(key: K, err: unknown, copy: LoadCopy<T>): Rows<K, T> {
|
|
86
|
+
if (samplesRequested()) return sampleRows(key, copy, "unavailable");
|
|
87
|
+
const signIn = needsSignIn(err);
|
|
88
|
+
return {
|
|
89
|
+
...READY,
|
|
90
|
+
status: "error",
|
|
91
|
+
[key]: [],
|
|
92
|
+
emptyText: "",
|
|
93
|
+
error: signIn ? copyParam(`${copy.prefix}.signInText`, copy.signIn) : copyParam(`${copy.prefix}.errorText`, copy.error),
|
|
94
|
+
signInRequired: signIn,
|
|
95
|
+
notErrored: false,
|
|
96
|
+
busy: true,
|
|
97
|
+
noSignIn: !signIn,
|
|
98
|
+
} as Rows<K, T>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function sampleRows<K extends string, T>(key: K, copy: LoadCopy<T>, reason: "empty" | "unavailable"): Rows<K, T> {
|
|
102
|
+
return {
|
|
103
|
+
...READY, [key]: copy.sample(), emptyText: "",
|
|
104
|
+
isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable",
|
|
105
|
+
} as Rows<K, T>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { memberAccess, memberAccessChanged, memberAccessDenied, type MemberAccess } from "./member-access.js";
|
|
2
|
+
import { needsSignIn } from "./branded-load-state.js";
|
|
3
|
+
|
|
4
|
+
/** Public reads stay public. Capture their reader only to reject a result that
|
|
5
|
+
* belongs to the identity/site which was active before the request. */
|
|
6
|
+
export interface ReadAccess { member: MemberAccess; tenant: string; }
|
|
7
|
+
export async function readAccess(): Promise<ReadAccess> {
|
|
8
|
+
const member = await memberAccess();
|
|
9
|
+
const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
10
|
+
return { member, tenant: JSON.stringify([input?.__himiPreferenceAccount ?? "", input?.__himiPreferenceBusiness ?? ""]) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function assertReadAccess(access: ReadAccess): Promise<void> {
|
|
14
|
+
const current = await readAccess();
|
|
15
|
+
if (current.member.email !== access.member.email || current.member.scope !== access.member.scope || current.tenant !== access.tenant) throw memberAccessChanged();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bridge errors put the actual status first; codes quoted inside the body are data. */
|
|
19
|
+
function httpStatus(error: unknown): string | undefined {
|
|
20
|
+
return /^HTTP\s*(\d{3})\b/i.exec(String((error as { message?: unknown })?.message ?? error))?.[1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const NO_READ_SIGN_IN = { signInRequired: false, noSignIn: true };
|
|
24
|
+
|
|
25
|
+
/** An authorization refusal must not fall back to a previously cached article. */
|
|
26
|
+
export function readAccessDenied(error: unknown): boolean {
|
|
27
|
+
let cause = error;
|
|
28
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
29
|
+
const status = httpStatus(cause);
|
|
30
|
+
if (status ? status === "401" || status === "403" : memberAccessDenied(cause) || needsSignIn(cause)) return true;
|
|
31
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A permission failure alone says nothing about whether signing in can help.
|
|
37
|
+
* In particular, a private member profile can be unavailable to members too.
|
|
38
|
+
* Only an authentication refusal for a current visitor offers Sign in. Follow
|
|
39
|
+
* SDK wrapper causes (blog getPost preserves its original read error there). */
|
|
40
|
+
export async function readSignInFlags(error: unknown): Promise<typeof NO_READ_SIGN_IN> {
|
|
41
|
+
let authenticationRequired = false;
|
|
42
|
+
let permissionRefused = false;
|
|
43
|
+
let cause = error;
|
|
44
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
45
|
+
const status = httpStatus(cause);
|
|
46
|
+
permissionRefused ||= status === "403";
|
|
47
|
+
authenticationRequired ||= status ? status === "401" : needsSignIn(cause);
|
|
48
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
49
|
+
}
|
|
50
|
+
const signInRequired = !permissionRefused && authenticationRequired && !(await memberAccess()).email;
|
|
51
|
+
return { signInRequired, noSignIn: !signInRequired };
|
|
52
|
+
}
|