@wix/himalaya-cli 0.1587.0 → 0.1589.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/sdk-types/src/capabilities.generated.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/torch/src/index.d.ts +39 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/webview-page/src/capabilities.generated.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +13 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/extensions.d.ts +28 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +15 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/torch.mjs +78 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +14 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +4 -0
- package/dist/cli.mjs +92 -46
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +16 -0
- package/guides/app-config.md +1 -1
- package/guides/designing-himi-screens.md +18 -0
- package/guides/importable-sdks.md +1 -0
- package/guides/qa-builds.md +12 -0
- package/guides/sdk-reference.json +99 -0
- package/guides/web-page-sdk-reference.md +1 -0
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/branded-lite/config.ts +7 -0
- package/templates/branded-lite/dev/index.ts +469 -0
- package/templates/branded-lite/himalaya.content.json +3 -0
- package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-my-groups/index.ts +236 -0
- package/templates/community-club/config.ts +7 -0
- package/templates/community-club/dev/index.ts +469 -0
- package/templates/community-club/himalaya.content.json +3 -0
- package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/community-club/tier5-src/himi-branded-lite-my-groups/index.ts +236 -0
- package/templates/events-meetups/config.ts +7 -0
- package/templates/events-meetups/dev/index.ts +469 -0
- package/templates/events-meetups/himalaya.content.json +3 -0
- package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-my-groups/index.ts +236 -0
- package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/nonprofit-cause/config.ts +7 -0
- package/templates/nonprofit-cause/dev/index.ts +469 -0
- package/templates/nonprofit-cause/himalaya.content.json +3 -0
- package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-my-groups/index.ts +236 -0
- package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +1 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { defineService } from "@himalaya/state";
|
|
2
|
+
import { getGroup, listMemberships, type WixGroup, type WixGroupMembership } from "@himalaya/wix-groups";
|
|
3
|
+
import { getMyMember } from "@himalaya/wix-members";
|
|
4
|
+
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
5
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
6
|
+
import { memberAccess, memberAccessDenied, type MemberAccess } from "../_shared/member-access.js";
|
|
7
|
+
|
|
8
|
+
// The groups the SIGNED-IN member belongs to (the One App "My groups" list). Two reads, both as the
|
|
9
|
+
// member: the memberships (GET /social-groups-proxy/members/v2/members/{id}/memberships, which Wix
|
|
10
|
+
// answers for the caller's own id only) and then one getGroup per membership, because a membership
|
|
11
|
+
// carries a group id and a role, never the group's name or cover.
|
|
12
|
+
//
|
|
13
|
+
// A visitor makes ZERO calls: the screen shows its sign-in card. The member id is the current
|
|
14
|
+
// member's (`getMyMember`), never taken from input. Read-only and online-only.
|
|
15
|
+
|
|
16
|
+
type Failure = "forbidden" | "failed";
|
|
17
|
+
|
|
18
|
+
/** Memberships read in one page. A member with more is told so, not shown a silently short list. */
|
|
19
|
+
const PAGE_LIMIT = 50;
|
|
20
|
+
const DETAIL_SCREEN = "himi_group";
|
|
21
|
+
|
|
22
|
+
interface MyGroupRow {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
/** "Admin", "Member" or "Pending request" — the member's standing in the group. */
|
|
26
|
+
statusLabel: string;
|
|
27
|
+
meta: string;
|
|
28
|
+
image: string;
|
|
29
|
+
initial: string;
|
|
30
|
+
hasImage: boolean;
|
|
31
|
+
noImage: boolean;
|
|
32
|
+
detailScreenId: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface State {
|
|
36
|
+
status: "loading" | "ready" | "error" | "signedOut";
|
|
37
|
+
rows: MyGroupRow[];
|
|
38
|
+
emptyText: string;
|
|
39
|
+
errorText: string;
|
|
40
|
+
/** Some groups could not be read, or the member has more than one page: said above the list. */
|
|
41
|
+
partialText: string;
|
|
42
|
+
notLoading: boolean;
|
|
43
|
+
notErrored: boolean;
|
|
44
|
+
noRows: boolean;
|
|
45
|
+
noEmpty: boolean;
|
|
46
|
+
noPartial: boolean;
|
|
47
|
+
needsSignIn: boolean;
|
|
48
|
+
noSignIn: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const initial: State = {
|
|
52
|
+
status: "loading",
|
|
53
|
+
rows: [],
|
|
54
|
+
emptyText: "",
|
|
55
|
+
errorText: "",
|
|
56
|
+
partialText: "",
|
|
57
|
+
notLoading: false,
|
|
58
|
+
notErrored: true,
|
|
59
|
+
noRows: true,
|
|
60
|
+
noEmpty: true,
|
|
61
|
+
noPartial: true,
|
|
62
|
+
needsSignIn: false,
|
|
63
|
+
noSignIn: true,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Bumped by every load, so a response that arrives after a newer load began is dropped. */
|
|
67
|
+
let loadSeq = 0;
|
|
68
|
+
|
|
69
|
+
const signedOutState: State = { ...initial, status: "signedOut", notLoading: true, needsSignIn: true, noSignIn: false };
|
|
70
|
+
|
|
71
|
+
function errorState(failure: Failure): State {
|
|
72
|
+
return {
|
|
73
|
+
...initial,
|
|
74
|
+
status: "error",
|
|
75
|
+
notLoading: true,
|
|
76
|
+
notErrored: false,
|
|
77
|
+
errorText: failure === "forbidden"
|
|
78
|
+
? copyParam("myGroups.forbiddenText", "Your groups are unavailable for this account. Sign in again.")
|
|
79
|
+
: copyParam("myGroups.errorText", "Your groups could not be loaded."),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function failureOf(error: unknown): Failure {
|
|
84
|
+
return memberAccessDenied(error) ? "forbidden" : "failed";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isNotFound(error: unknown): boolean {
|
|
88
|
+
return /HTTP 404\b/.test(String((error as Error)?.message ?? error));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function statusLabelOf(membership: WixGroupMembership): string {
|
|
92
|
+
if ((membership.status ?? "").toUpperCase() === "PENDING") return copyParam("myGroups.status.pending", "Pending request");
|
|
93
|
+
return (membership.role?.value ?? "").toUpperCase() === "ADMIN"
|
|
94
|
+
? copyParam("myGroups.status.admin", "Admin")
|
|
95
|
+
: copyParam("myGroups.status.member", "Member");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function rowOf(group: WixGroup, membership: WixGroupMembership): MyGroupRow {
|
|
99
|
+
const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
|
|
100
|
+
const count = group.membersCount ?? group.memberCount;
|
|
101
|
+
const image = groupImage(group);
|
|
102
|
+
return {
|
|
103
|
+
id: String(membership.groupId),
|
|
104
|
+
title,
|
|
105
|
+
statusLabel: statusLabelOf(membership),
|
|
106
|
+
meta: typeof count === "number" ? `${count} ${count === 1 ? "member" : "members"}` : "",
|
|
107
|
+
image,
|
|
108
|
+
initial: (title[0] || "G").toUpperCase(),
|
|
109
|
+
hasImage: Boolean(image),
|
|
110
|
+
noImage: !image,
|
|
111
|
+
detailScreenId: DETAIL_SCREEN,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** JOINED and PENDING memberships with a group id; an UNKNOWN status is not a membership to show. */
|
|
116
|
+
function shownMemberships(response: unknown): WixGroupMembership[] | undefined {
|
|
117
|
+
const memberships = (response as { memberships?: unknown } | null | undefined)?.memberships;
|
|
118
|
+
if (!Array.isArray(memberships)) return undefined;
|
|
119
|
+
const seen = new Set<string>();
|
|
120
|
+
return (memberships as WixGroupMembership[]).filter((m) => {
|
|
121
|
+
const status = (m?.status ?? "").toUpperCase();
|
|
122
|
+
if (typeof m?.groupId !== "string" || !m.groupId || (status !== "JOINED" && status !== "PENDING") || seen.has(m.groupId)) return false;
|
|
123
|
+
seen.add(m.groupId);
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readyState(rows: MyGroupRow[], partialText: string): State {
|
|
129
|
+
return {
|
|
130
|
+
...initial,
|
|
131
|
+
status: "ready",
|
|
132
|
+
rows,
|
|
133
|
+
emptyText: copyParam("myGroups.emptyText", "You haven’t joined any groups yet."),
|
|
134
|
+
partialText,
|
|
135
|
+
notLoading: true,
|
|
136
|
+
noRows: rows.length === 0,
|
|
137
|
+
noEmpty: rows.length > 0,
|
|
138
|
+
noPartial: !partialText,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const sameMember = (a: MemberAccess, b: MemberAccess) => a.email === b.email && a.scope === b.scope;
|
|
143
|
+
|
|
144
|
+
/** Whose groups the rows on screen are. A load for anybody else clears them first. */
|
|
145
|
+
let rowsOwner: MemberAccess | undefined;
|
|
146
|
+
|
|
147
|
+
/** The sign-in card is what the screen shows. A load that finds a member now (they signed in and came back)
|
|
148
|
+
* shows the loading state at once instead of leaving the card up over the network calls. A module flag, not
|
|
149
|
+
* a read of the state: the reload this starts must not see the card again and restart forever. */
|
|
150
|
+
let showingSignIn = false;
|
|
151
|
+
|
|
152
|
+
/** The member changed while this load ran or the rows were on screen: show the loading state at once,
|
|
153
|
+
* so the previous member's group names and standing are never on screen, and load for the current one. */
|
|
154
|
+
function restart(): State {
|
|
155
|
+
rowsOwner = undefined;
|
|
156
|
+
showingSignIn = false;
|
|
157
|
+
loadSeq++;
|
|
158
|
+
void svc.load();
|
|
159
|
+
return { ...initial };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** An error for THIS member, unless a newer load began or the member left meanwhile: then it is stale. */
|
|
163
|
+
async function failureFor(seq: number, access: MemberAccess, failure: Failure): Promise<State | undefined> {
|
|
164
|
+
if (seq !== loadSeq) return undefined;
|
|
165
|
+
const now = await memberAccess();
|
|
166
|
+
// A newer load began while the identity was read: its answer is the fresher one.
|
|
167
|
+
if (seq !== loadSeq) return undefined;
|
|
168
|
+
if (!sameMember(access, now)) return restart();
|
|
169
|
+
return errorState(failure);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function loadOnce(access: MemberAccess): Promise<State | undefined> {
|
|
173
|
+
const seq = ++loadSeq;
|
|
174
|
+
if (!access.email) { showingSignIn = true; return signedOutState; }
|
|
175
|
+
try {
|
|
176
|
+
const me = await getMyMember();
|
|
177
|
+
const memberId = String(me?.member?.id ?? "");
|
|
178
|
+
if (!memberId) return failureFor(seq, access, "failed");
|
|
179
|
+
const response = await listMemberships(memberId, { limit: PAGE_LIMIT });
|
|
180
|
+
const memberships = shownMemberships(response);
|
|
181
|
+
if (!memberships) return failureFor(seq, access, "failed");
|
|
182
|
+
const reads = await Promise.allSettled(memberships.map((m) => getGroup(String(m.groupId))));
|
|
183
|
+
// Another load began, or the member signed out / switched account while these reads were in
|
|
184
|
+
// flight: these rows belong to somebody else now.
|
|
185
|
+
if (seq !== loadSeq) return undefined;
|
|
186
|
+
const now = await memberAccess();
|
|
187
|
+
// Re-checked after the await: a newer load may have committed while the identity was read.
|
|
188
|
+
if (seq !== loadSeq) return undefined;
|
|
189
|
+
if (!sameMember(access, now)) return restart();
|
|
190
|
+
const rows: MyGroupRow[] = [];
|
|
191
|
+
let failed = 0;
|
|
192
|
+
let firstFailure: unknown;
|
|
193
|
+
reads.forEach((read, index) => {
|
|
194
|
+
if (read.status === "fulfilled") {
|
|
195
|
+
// Social Groups wraps the entity as `{ group }`; the typed signature claims a bare group.
|
|
196
|
+
const group = ((read.value as unknown as { group?: WixGroup }).group ?? read.value) as WixGroup;
|
|
197
|
+
if (group && typeof group === "object") { rows.push(rowOf(group, memberships[index])); return; }
|
|
198
|
+
}
|
|
199
|
+
// A group deleted since the membership was listed is gone, not a failed read.
|
|
200
|
+
if (read.status === "rejected" && isNotFound(read.reason)) return;
|
|
201
|
+
failed++;
|
|
202
|
+
firstFailure ??= read.status === "rejected" ? read.reason : undefined;
|
|
203
|
+
});
|
|
204
|
+
// Memberships exist but not one group could be shown: that is unreadable (a pending request on a
|
|
205
|
+
// private group answers 404 until approved) or gone, never "you have no groups".
|
|
206
|
+
if (memberships.length > 0 && rows.length === 0) return errorState(failed > 0 ? failureOf(firstFailure) : "failed");
|
|
207
|
+
// Wix omits `total` when it is too many to count, and says so in `tooManyToCount`.
|
|
208
|
+
const truncated = response.metadata?.tooManyToCount === true || Number(response.metadata?.total ?? 0) > PAGE_LIMIT;
|
|
209
|
+
const partialText = [
|
|
210
|
+
failed > 0 ? copyParam("myGroups.partialText", "Some of your groups could not be loaded.") : "",
|
|
211
|
+
truncated ? copyParam("myGroups.truncatedText", "Showing your first groups only.") : "",
|
|
212
|
+
].filter(Boolean).join(" ");
|
|
213
|
+
rowsOwner = access;
|
|
214
|
+
return readyState(rows, partialText);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// A request that rejects because the member left is the old member's error, not the current one's.
|
|
217
|
+
return failureFor(seq, access, failureOf(error));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const svc = defineService<State, {
|
|
222
|
+
load: (state: State, arg?: unknown) => Promise<State | undefined>;
|
|
223
|
+
}>("himiBrandedMyGroups", {
|
|
224
|
+
state: initial,
|
|
225
|
+
actions: {
|
|
226
|
+
load: async (_state, arg) => {
|
|
227
|
+
stashCopy(arg);
|
|
228
|
+
const access = await memberAccess();
|
|
229
|
+
if ((rowsOwner && !sameMember(rowsOwner, access)) || (showingSignIn && access.email)) return restart();
|
|
230
|
+
return loadOnce(access);
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
svc.load();
|
|
236
|
+
export { svc };
|
|
@@ -231,6 +231,13 @@ export default defineApp({
|
|
|
231
231
|
"parentScreenId": "himi_group",
|
|
232
232
|
"tabId": "groups"
|
|
233
233
|
},
|
|
234
|
+
{
|
|
235
|
+
"screenId": "himi_my_groups",
|
|
236
|
+
"isPublic": true,
|
|
237
|
+
"paramKeys": [],
|
|
238
|
+
"parentScreenId": "himi_groups",
|
|
239
|
+
"tabId": "groups"
|
|
240
|
+
},
|
|
234
241
|
{
|
|
235
242
|
"screenId": "himi_account",
|
|
236
243
|
"isPublic": true,
|