@wix/himalaya-cli 0.1025.0 → 0.1027.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +9 -0
- package/dist/cli.mjs +1052 -655
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/app-config.md +6 -0
- package/guides/headless-loop.md +3 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +1476 -553
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/beauty-salon/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-orders/index.ts +4 -1
- package/templates/beauty-salon/tier5-src/wix-spaces-collection-detail/index.ts +9 -9
- package/templates/beauty-salon/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/beauty-salon/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/beauty-salon/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/beauty-salon/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/beauty-salon-owner/dev/index.ts +857 -458
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/branded-lite/config.ts +3 -1
- package/templates/branded-lite/dev/index.ts +6123 -2565
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/branded-lite/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-orders/index.ts +4 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +7 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-simple-detail/index.ts +4 -3
- package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +17 -4
- package/templates/branded-lite/tier5-src/wix-spaces-collection-detail/index.ts +9 -9
- package/templates/branded-lite/tier5-src/wix-spaces-coupon-detail/index.ts +7 -9
- package/templates/branded-lite/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/branded-lite/tier5-src/wix-spaces-food-order-detail/index.ts +6 -3
- package/templates/branded-lite/tier5-src/wix-spaces-form-detail/index.ts +7 -9
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +7 -9
- package/templates/branded-lite/tier5-src/wix-spaces-invoice-detail/index.ts +7 -9
- package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +8 -8
- package/templates/branded-lite/tier5-src/wix-spaces-photo-albums/index.ts +8 -2
- package/templates/branded-lite/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/branded-lite/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/branded-lite/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/community-club/dev/index.ts +582 -239
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/community-club/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/community-club/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/community-club/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/community-club-owner/dev/index.ts +857 -458
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/events-meetups/dev/index.ts +547 -226
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/events-meetups/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/events-meetups/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/events-meetups-owner/dev/index.ts +815 -458
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/gym-fitness/dev/index.ts +1609 -569
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/gym-fitness/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-orders/index.ts +4 -1
- package/templates/gym-fitness/tier5-src/wix-spaces-collection-detail/index.ts +9 -9
- package/templates/gym-fitness/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/gym-fitness/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/gym-fitness/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/gym-fitness/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/gym-fitness-owner/dev/index.ts +758 -338
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/himi-chat/dev/index.ts +26 -1
- package/templates/nonprofit-cause/dev/index.ts +978 -341
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/nonprofit-cause/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/nonprofit-cause/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/nonprofit-cause/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/nonprofit-cause/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/nonprofit-cause/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/nonprofit-cause-owner/dev/index.ts +716 -338
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/owner-lite/dev/index.ts +758 -338
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/podcast-creator/dev/index.ts +1111 -399
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/podcast-creator/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-simple-detail/index.ts +4 -3
- package/templates/podcast-creator/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/podcast-creator/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/podcast-creator/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/podcast-creator/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/podcast-creator-owner/dev/index.ts +857 -458
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/portfolio-creative/dev/index.ts +964 -345
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/portfolio-creative/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-orders/index.ts +4 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +7 -2
- package/templates/portfolio-creative/tier5-src/wix-spaces-collection-detail/index.ts +9 -9
- package/templates/portfolio-creative/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/portfolio-creative/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/portfolio-creative-owner/dev/index.ts +857 -458
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/restaurant/dev/index.ts +738 -284
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/restaurant/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/restaurant/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/restaurant/tier5-src/wix-spaces-food-order-detail/index.ts +6 -3
- package/templates/restaurant/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/restaurant-owner/dev/index.ts +758 -338
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/retail-store/dev/index.ts +768 -298
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/retail-store/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-orders/index.ts +4 -1
- package/templates/retail-store/tier5-src/wix-spaces-collection-detail/index.ts +9 -9
- package/templates/retail-store/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/retail-store/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/retail-store-owner/dev/index.ts +716 -338
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/surface-showcase/dev/index.ts +27 -0
- package/templates/yoga-studio/dev/index.ts +1147 -390
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/yoga-studio/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/yoga-studio/tier5-src/wix-spaces-create-post/index.ts +14 -4
- package/templates/yoga-studio/tier5-src/wix-spaces-plan-detail/index.ts +7 -9
- package/templates/yoga-studio/tier5-src/wix-spaces-subscription-detail/index.ts +12 -11
- package/templates/yoga-studio/tier5-src/wix-spaces-view-profile/index.ts +22 -12
- package/templates/yoga-studio-owner/dev/index.ts +716 -338
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +28 -3
|
@@ -8,6 +8,9 @@ export interface CollectionConfig {
|
|
|
8
8
|
image?: string;
|
|
9
9
|
filter?: string;
|
|
10
10
|
filterValues?: string[];
|
|
11
|
+
/** A state key holding `{ label }` rows (e.g. a worker's full category list) whose
|
|
12
|
+
* labels are filter choices even when no loaded row carries them yet. */
|
|
13
|
+
filterValuesFrom?: string;
|
|
11
14
|
searchPlaceholder?: string;
|
|
12
15
|
sortLabels?: { default: string; az: string; za: string };
|
|
13
16
|
metadata?: string[];
|
|
@@ -20,15 +23,22 @@ export interface CollectionConfig {
|
|
|
20
23
|
reviewSelectedAction?: string;
|
|
21
24
|
/** Optional comfortable grid media height for immersive visual catalogs. */
|
|
22
25
|
gridImageHeight?: number;
|
|
26
|
+
/** The card's primary verb, shown as a visible button beside the overflow.
|
|
27
|
+
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
28
|
+
* row's own tap action (the detail screen that owns the verb). */
|
|
29
|
+
primaryVerb?: { label: string; screenId?: string };
|
|
23
30
|
}
|
|
31
|
+
/** At or below this many records, with no query/filter narrowing them and no further
|
|
32
|
+
* page, the list/grid toggle, Refine, chips, search and count are noise. */
|
|
33
|
+
export const COLLECTION_CHROME_MIN_ITEMS = 4;
|
|
24
34
|
export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
25
35
|
himiStoreShop: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', view: 'grid', backendQuery: true },
|
|
26
|
-
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
36
|
+
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Service name starts with…', primaryVerb: { label: 'Book', screenId: 'himi_time' } },
|
|
27
37
|
himiBookingBookings: { rows: 'rows', title: 'title', subtitle: 'subtitle', metadata: ['status'], view: 'list', backendQuery: true,
|
|
28
38
|
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Exact booking ID', filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
29
39
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
30
|
-
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, searchPlaceholder: 'Exact event title', filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' } },
|
|
31
|
-
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', view: 'grid' },
|
|
40
|
+
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, searchPlaceholder: 'Exact event title', filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' }, primaryVerb: { label: 'Register' } },
|
|
41
|
+
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid' },
|
|
32
42
|
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true },
|
|
33
43
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
34
44
|
himiOwnerLiteServices: { rows: 'services', title: 'name', subtitle: 'subtitle', view: 'list', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
@@ -45,14 +55,14 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
45
55
|
sortLabels: { default: 'Newest', az: 'Oldest', za: 'Order # ↓' } },
|
|
46
56
|
himiBrandedPlans: { rows: 'rows', title: 'name', subtitle: 'description', metadata: ['priceLine', 'periodLine', 'trialBadge', 'perk1', 'perk2', 'perk3'], view: 'grid' },
|
|
47
57
|
himiBrandedPrograms: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
48
|
-
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list' },
|
|
58
|
+
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list', primaryVerb: { label: 'Get code' } },
|
|
49
59
|
himiBrandedNotifications: { rows: 'items', title: 'name', subtitle: 'subtitle', image: 'photo', metadata: ['unreadDot'], view: 'list' },
|
|
50
|
-
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid' },
|
|
60
|
+
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Redeem' } },
|
|
51
61
|
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author', 'stars'], view: 'list' },
|
|
52
62
|
himiBrandedPortfolio: { rows: 'projects', title: 'title', subtitle: 'subtitle', image: 'image', view: 'grid', gridImageHeight: 180 },
|
|
53
63
|
himiBrandedInvoices: { rows: 'invoices', title: 'title', subtitle: 'subtitle', view: 'list' },
|
|
54
64
|
himiBrandedBadges: { rows: 'badges', title: 'name', subtitle: 'description', view: 'grid' },
|
|
55
|
-
himiBrandedDonations: { rows: 'campaigns', title: 'name', subtitle: 'description', view: 'grid' },
|
|
65
|
+
himiBrandedDonations: { rows: 'campaigns', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Donate' } },
|
|
56
66
|
himiBrandedGiftCards: { rows: 'cards', title: 'name', subtitle: 'description', view: 'grid' },
|
|
57
67
|
himiBrandedVideos: { rows: 'channels', title: 'title', subtitle: 'description', view: 'grid' },
|
|
58
68
|
himiBrandedPodcasts: { rows: 'episodes', title: 'title', subtitle: 'subtitle', image: 'image', metadata: ['progressLabel'], view: 'list' },
|
|
@@ -114,7 +124,7 @@ export function collectionTitle(row: CollectionRow, config: CollectionConfig): s
|
|
|
114
124
|
return scalar(row[config.title]) || scalar(row.title) || scalar(row.name) || scalar(row.display) || scalar(row.label);
|
|
115
125
|
}
|
|
116
126
|
/** Does not mutate domain rows, paging, or domain-derived state. */
|
|
117
|
-
export function projectCollection(source: CollectionRow[], config: CollectionConfig, prefs: CollectionPreferences, backendQuery = false) {
|
|
127
|
+
export function projectCollection(source: CollectionRow[], config: CollectionConfig, prefs: CollectionPreferences, backendQuery = false, knownFilters: string[] = []) {
|
|
118
128
|
const query = prefs.query.trim().toLocaleLowerCase();
|
|
119
129
|
const unique = new Set<string>();
|
|
120
130
|
// Menu sections describe their following items; they are not actionable
|
|
@@ -130,7 +140,7 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
|
|
|
130
140
|
if (unique.has(id)) return false;
|
|
131
141
|
unique.add(id); return true;
|
|
132
142
|
});
|
|
133
|
-
const filters = config.filterValues ?? [...new Set(rows.map(row => config.filter ? scalar(row[config.filter]) : '').filter(Boolean))].sort();
|
|
143
|
+
const filters = config.filterValues ?? [...new Set([...knownFilters, ...rows.map(row => config.filter ? scalar(row[config.filter]) : '')].filter(Boolean))].sort();
|
|
134
144
|
const visible = rows.filter(row => {
|
|
135
145
|
if (backendQuery) return true;
|
|
136
146
|
if (prefs.filter === 'saved' && !prefs.savedIds.includes(collectionId(row))) return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
-
import { COLLECTIONS, normalizeCollection, projectCollection, collectionId, type CollectionRow } from './collection-contract.js';
|
|
2
|
+
import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, type CollectionRow } from './collection-contract.js';
|
|
3
3
|
import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
|
|
4
4
|
import { copyParam, stashCopy } from './himi-copy.js';
|
|
5
5
|
import { createCollectionQuery, type CollectionQuerySource, type CollectionQueryState } from './collection-query.js';
|
|
@@ -54,6 +54,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
54
54
|
/** Navigation can open a collection in a useful, reviewable context. Keep
|
|
55
55
|
* this input additive and constrained to the same criteria the collection UI
|
|
56
56
|
* can select; arbitrary route values must never become backend filters. */
|
|
57
|
+
/** Labels of the worker-listed filter choices (`filterValuesFrom`), minus the "All" row. */
|
|
58
|
+
const listedFilters = (state: State): string[] => config.filterValuesFrom && Array.isArray(state[config.filterValuesFrom])
|
|
59
|
+
? (state[config.filterValuesFrom] as unknown[]).flatMap(row => {
|
|
60
|
+
const r = row && typeof row === 'object' ? row as Record<string, unknown> : {};
|
|
61
|
+
return typeof r.label === 'string' && r.id !== '' ? [r.label] : [];
|
|
62
|
+
})
|
|
63
|
+
: [];
|
|
64
|
+
let pendingFilter = '';
|
|
57
65
|
const applyNavigationCriteria = (arg: unknown, state: State): boolean => {
|
|
58
66
|
const input = object(arg);
|
|
59
67
|
let changed = false;
|
|
@@ -70,9 +78,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
70
78
|
const configured = config.filterValues ?? [];
|
|
71
79
|
const rows = Array.isArray(state[config.rows]) ? state[config.rows] as CollectionRow[] : [];
|
|
72
80
|
const derived = config.filter ? rows.map(row => String(row[config.filter!] ?? '')).filter(Boolean) : [];
|
|
81
|
+
const known = listedFilters(state);
|
|
73
82
|
const allowed = filter === 'all' || filter === 'saved' ||
|
|
74
|
-
(filter.startsWith('value:') && [...configured, ...derived].includes(filter.slice(6)));
|
|
83
|
+
(filter.startsWith('value:') && [...configured, ...derived, ...known].includes(filter.slice(6)));
|
|
75
84
|
if (allowed) { prefs.filter = filter; changed = true; }
|
|
85
|
+
// A worker-listed choice (e.g. a blog category) may not have loaded yet at boot:
|
|
86
|
+
// hold it and settle it against the list once the list arrives, instead of
|
|
87
|
+
// discarding a valid deep link before load().
|
|
88
|
+
else if (filter.startsWith('value:') && config.filterValuesFrom && known.length === 0) pendingFilter = filter;
|
|
76
89
|
}
|
|
77
90
|
if (changed) {
|
|
78
91
|
prefs.selectedIds = [];
|
|
@@ -90,7 +103,12 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
90
103
|
const derive = (s: State): State => {
|
|
91
104
|
syncSaved();
|
|
92
105
|
const rows = Array.isArray(s[config.rows]) ? (s[config.rows] as CollectionRow[]).filter(x => x && typeof x === 'object') : [];
|
|
93
|
-
const
|
|
106
|
+
const known = listedFilters(s);
|
|
107
|
+
if (pendingFilter && known.length) {
|
|
108
|
+
if (known.includes(pendingFilter.slice(6))) { prefs.filter = pendingFilter; persisted.write(prefs); }
|
|
109
|
+
pendingFilter = '';
|
|
110
|
+
}
|
|
111
|
+
const projected = projectCollection(rows, config, prefs, !!backend && config.backendQuery === true, known);
|
|
94
112
|
projected.items.forEach(row => { row.collectionSelectLabel = row.collectionSelected ? copyParam('collection.selected', 'Selected') : copyParam('collection.select', 'Select'); });
|
|
95
113
|
if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
|
|
96
114
|
const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, { id: 'saved', label: copyParam('collection.saved', 'Saved') },
|
|
@@ -106,7 +124,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
106
124
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
107
125
|
const errorMessage = queryError || domainError || copyParam('collection.loadError', 'This collection could not load. Try again.');
|
|
108
126
|
const canRetry = !!backend || typeof def.actions?.refresh === 'function' || typeof def.actions?.load === 'function' || typeof def.actions?.collectionRetry === 'function';
|
|
127
|
+
// A default view is what the screen opens on; anything else is the member's own
|
|
128
|
+
// narrowing, which keeps the chrome (and the Clear that undoes it) on screen.
|
|
129
|
+
// "All" and the collection's own default (e.g. Upcoming) are both un-narrowed: Clear
|
|
130
|
+
// resets to "All", so showing it on either would offer a reset that changes nothing.
|
|
131
|
+
const narrowed = !!prefs.query.trim() || (prefs.filter !== 'all' && prefs.filter !== initialCollection(config).filter);
|
|
132
|
+
const hideChrome = !narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS;
|
|
109
133
|
return { ...s, collection: { ...prefs, ...projected,
|
|
134
|
+
hideChrome, noActiveCriteria: !narrowed,
|
|
110
135
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
111
136
|
cursor: s.pageError === true || s.loadingMore === true ? '' : s.nextCursor,
|
|
112
137
|
noMore: !s.nextCursor || s.pageError === true || s.loadingMore === true,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Whether a detail screen's entity is there — the worker half of the shared not-found
|
|
2
|
+
// state (the `detailNotFound` descriptor builder).
|
|
3
|
+
//
|
|
4
|
+
// A detail screen's descriptor asks two questions, both as `hiddenWhen` flags:
|
|
5
|
+
// • `noEntity` → hide the entity's content AND its actions (Like, Follow, Send…)
|
|
6
|
+
// • `hideNotFound` → hide the not-found state
|
|
7
|
+
// Four answers exist, and only one of them shows the not-found state:
|
|
8
|
+
// loading — a loading line (`hideLoading` false); no content, no actions, no verdict
|
|
9
|
+
// found — content + actions shown
|
|
10
|
+
// missing — not-found shown, content + actions hidden
|
|
11
|
+
// failed — a load-error line (`hideLoadError` false); no content, no actions
|
|
12
|
+
// A load that FAILS for any reason other than "not found" is `failed`, never `missing`:
|
|
13
|
+
// an offline device must not tell the user their invoice was deleted.
|
|
14
|
+
|
|
15
|
+
export type Presence = "loading" | "found" | "missing" | "failed";
|
|
16
|
+
|
|
17
|
+
export interface PresenceFlags {
|
|
18
|
+
found: boolean;
|
|
19
|
+
notFound: boolean;
|
|
20
|
+
noEntity: boolean;
|
|
21
|
+
hideNotFound: boolean;
|
|
22
|
+
hideLoading: boolean;
|
|
23
|
+
hideLoadError: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function presence(p: Presence): PresenceFlags {
|
|
27
|
+
return {
|
|
28
|
+
found: p === "found", notFound: p === "missing", noEntity: p !== "found", hideNotFound: p !== "missing",
|
|
29
|
+
hideLoading: p !== "loading", hideLoadError: p !== "failed",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The entity id the host baked into this detail screen (`himi.open` params). */
|
|
34
|
+
export function detailInputId(): string {
|
|
35
|
+
const v = (globalThis as { himalaya?: { input?: { id?: unknown } } }).himalaya?.input?.id;
|
|
36
|
+
return typeof v === "string" ? v.trim() : "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** True when a Wix read failed because the record does not exist: the SDK rejects
|
|
40
|
+
* with "HTTP <code>: <body>", so an HTTP 404, or Wix's `NOT_FOUND` error code. A bare
|
|
41
|
+
* "not found" in a message is NOT enough — "capability wix_api not found" or "Method
|
|
42
|
+
* not found" is a broken integration, and must not tell the user the record is gone. */
|
|
43
|
+
export function isNotFoundError(error: unknown): boolean {
|
|
44
|
+
const msg = String((error as { message?: unknown } | null)?.message ?? error ?? "");
|
|
45
|
+
return /\bHTTP\s*404\b/.test(msg) || /\bNOT_FOUND\b/.test(msg);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `missing` for a not-found failure, `failed` (claim nothing) otherwise. */
|
|
49
|
+
export function presenceAfterError(error: unknown): PresenceFlags {
|
|
50
|
+
return presence(isNotFoundError(error) ? "missing" : "failed");
|
|
51
|
+
}
|
|
@@ -54,6 +54,7 @@ interface State {
|
|
|
54
54
|
noEmpty: boolean;
|
|
55
55
|
hideList: boolean;
|
|
56
56
|
needsSignIn?: boolean;
|
|
57
|
+
noSignIn?: boolean;
|
|
57
58
|
nextCursor?: string;
|
|
58
59
|
complete?: boolean;
|
|
59
60
|
pageError?: boolean;
|
|
@@ -127,7 +128,9 @@ function derive(state: State): State {
|
|
|
127
128
|
notLoading: state.status !== "loading",
|
|
128
129
|
isErrored: state.status === "error",
|
|
129
130
|
notErrored: state.status !== "error",
|
|
130
|
-
|
|
131
|
+
// A guest gets the shared member gate, not the "Browse products" empty card.
|
|
132
|
+
noEmpty: state.status !== "ready" || hasRows || state.needsSignIn === true,
|
|
133
|
+
noSignIn: state.status !== "ready" || state.needsSignIn !== true,
|
|
131
134
|
hideList: state.status !== "ready" || state.needsSignIn === true,
|
|
132
135
|
};
|
|
133
136
|
}
|
|
@@ -7,19 +7,17 @@ import {
|
|
|
7
7
|
getStoreCategory,
|
|
8
8
|
type WixCollection,
|
|
9
9
|
} from "@himalaya/wix-stores-collections";
|
|
10
|
+
import { presence, presenceAfterError, detailInputId, type PresenceFlags } from "../_shared/detail-presence.js";
|
|
10
11
|
|
|
11
|
-
interface S { status: "loading" | "ready"; name: string; subtitle: string; description: string }
|
|
12
|
+
interface S extends PresenceFlags { status: "loading" | "ready"; name: string; subtitle: string; description: string }
|
|
12
13
|
|
|
13
|
-
function inputId(): string {
|
|
14
|
-
return (globalThis as { himalaya?: { input?: { id?: string } } }).himalaya?.input?.id ?? "";
|
|
15
|
-
}
|
|
16
14
|
|
|
17
15
|
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollectionDetail", {
|
|
18
|
-
state: { status: "loading", name: "", subtitle: "", description: "" },
|
|
16
|
+
state: { status: "loading", name: "", subtitle: "", description: "", ...presence("loading") },
|
|
19
17
|
actions: {
|
|
20
18
|
load: async (s) => {
|
|
21
|
-
const id =
|
|
22
|
-
if (!id) return { ...s, status: "ready" };
|
|
19
|
+
const id = detailInputId();
|
|
20
|
+
if (!id) return { ...s, status: "ready", ...presence("missing") };
|
|
23
21
|
try {
|
|
24
22
|
const res = await getStoreCategory(id);
|
|
25
23
|
const c = res.category;
|
|
@@ -29,6 +27,7 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollectionDe
|
|
|
29
27
|
name: c.name || "Category",
|
|
30
28
|
subtitle: categoryCount(c),
|
|
31
29
|
description: c.description || "",
|
|
30
|
+
...presence("found"),
|
|
32
31
|
};
|
|
33
32
|
}
|
|
34
33
|
} catch { /* fall through to V1 */ }
|
|
@@ -43,9 +42,10 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollectionDe
|
|
|
43
42
|
name: c.name || "Collection",
|
|
44
43
|
subtitle: n != null ? `${n} ${n === 1 ? "product" : "products"}` : "",
|
|
45
44
|
description: c.description || "",
|
|
45
|
+
...presence("found"),
|
|
46
46
|
};
|
|
47
|
-
} catch {
|
|
48
|
-
return { ...s, status: "ready" };
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return { ...s, status: "ready", ...presenceAfterError(e) };
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
},
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// create endpoint in the SDK; blog is the available authored-content write.)
|
|
5
5
|
import { defineService } from "@himalaya/state";
|
|
6
6
|
import { createDraftPost, publishDraftPost } from "@himalaya/wix-blog";
|
|
7
|
+
import { memberAccess } from "../_shared/member-access.js";
|
|
7
8
|
|
|
8
9
|
// `bodyFocus` is a focus TOKEN, not a flag: the body field focuses whenever this
|
|
9
10
|
// becomes a new non-empty value (`TextFieldProps.focus_path`). A monotonic counter
|
|
@@ -14,7 +15,11 @@ import { createDraftPost, publishDraftPost } from "@himalaya/wix-blog";
|
|
|
14
15
|
// the new state), so there is no way to observe an intermediate value while `submit`
|
|
15
16
|
// is awaiting the network. A type that promises a state nobody can ever see reads as
|
|
16
17
|
// an in-flight guard that exists — see `posting` below for the one that works.
|
|
17
|
-
|
|
18
|
+
// `needsSignIn` / `noSignIn` / `hideForm` drive the member gate: a guest sees the
|
|
19
|
+
// gate and NO form, a member sees the form, and until `load` has asked the
|
|
20
|
+
// host who is signed in, neither shows — so a guest can never start typing a post
|
|
21
|
+
// that would only fail on submit.
|
|
22
|
+
interface S { status: "ready"; title: string; body: string; hint: string; bodyFocus: string; needsSignIn: boolean; noSignIn: boolean; hideForm: boolean }
|
|
18
23
|
|
|
19
24
|
// The real double-press guard, module-local because it must live OUTSIDE published
|
|
20
25
|
// state for the reason above. One worker instance owns one composer, so a module flag
|
|
@@ -33,15 +38,20 @@ function val(arg?: unknown): string {
|
|
|
33
38
|
return typeof arg === "string" ? arg : ((arg as { value?: string; text?: string })?.value ?? (arg as { text?: string })?.text ?? "");
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
const svc = defineService<S, { setTitle: (s: S, a?: unknown) => S; setBody: (s: S, a?: unknown) => S; focusBody: (s: S) => S; submit: (s: S) => Promise<S> }>("spacesCreatePost", {
|
|
37
|
-
state: { status: "ready", title: "", body: "", hint: "Share something with the community.", bodyFocus: "" },
|
|
41
|
+
const svc = defineService<S, { load: (s: S) => Promise<S>; setTitle: (s: S, a?: unknown) => S; setBody: (s: S, a?: unknown) => S; focusBody: (s: S) => S; submit: (s: S) => Promise<S> }>("spacesCreatePost", {
|
|
42
|
+
state: { status: "ready", title: "", body: "", hint: "Share something with the community.", bodyFocus: "", needsSignIn: false, noSignIn: true, hideForm: true },
|
|
38
43
|
actions: {
|
|
44
|
+
load: async (s) => {
|
|
45
|
+
const { email } = await memberAccess();
|
|
46
|
+
return { ...s, needsSignIn: !email, noSignIn: !!email, hideForm: !email };
|
|
47
|
+
},
|
|
39
48
|
setTitle: (s, a) => ({ ...s, title: val(a) }),
|
|
40
49
|
setBody: (s, a) => ({ ...s, body: val(a) }),
|
|
41
50
|
// Title's Next key lands here. The keyboard stays up (that is the "next"
|
|
42
51
|
// contract) and the body field takes focus.
|
|
43
52
|
focusBody: (s) => ({ ...s, bodyFocus: bumpToken(s.bodyFocus) }),
|
|
44
53
|
submit: async (s) => {
|
|
54
|
+
if (s.needsSignIn) return { ...s, hint: "Sign in to post." };
|
|
45
55
|
const title = s.title.trim();
|
|
46
56
|
if (!title) return { ...s, hint: "Please enter a title." };
|
|
47
57
|
// A second press while the first is still in flight is a NO-OP, not a second
|
|
@@ -63,7 +73,7 @@ const svc = defineService<S, { setTitle: (s: S, a?: unknown) => S; setBody: (s:
|
|
|
63
73
|
// steals focus, so those transitions must clear it (see himi-store-account's
|
|
64
74
|
// NO_FOCUS on its mode switches). Same token, opposite fix; the deciding
|
|
65
75
|
// question is always whether the field stays mounted.
|
|
66
|
-
return { status: "ready", title: "", body: "", bodyFocus: s.bodyFocus, hint: `Posted “${title}” ✓ — it now appears in Blog.` };
|
|
76
|
+
return { ...s, status: "ready", title: "", body: "", bodyFocus: s.bodyFocus, hint: `Posted “${title}” ✓ — it now appears in Blog.` };
|
|
67
77
|
} catch {
|
|
68
78
|
return { ...s, status: "ready", hint: "Couldn't post — check your connection and try again." };
|
|
69
79
|
} finally {
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
// himalaya.input.id carries the plan id; fetched via getPlan.
|
|
3
3
|
import { defineService } from "@himalaya/state";
|
|
4
4
|
import { getPlan, type WixPlan } from "@himalaya/wix-pricing-plans";
|
|
5
|
+
import { presence, presenceAfterError, detailInputId, type PresenceFlags } from "../_shared/detail-presence.js";
|
|
5
6
|
|
|
6
|
-
interface S { status: "loading" | "ready"; name: string; price: string; description: string; perks: string }
|
|
7
|
+
interface S extends PresenceFlags { status: "loading" | "ready"; name: string; price: string; description: string; perks: string }
|
|
7
8
|
|
|
8
|
-
function inputId(): string {
|
|
9
|
-
return (globalThis as { himalaya?: { input?: { id?: string } } }).himalaya?.input?.id ?? "";
|
|
10
|
-
}
|
|
11
9
|
function priceOf(p: WixPlan): string {
|
|
12
10
|
const m = p.pricing?.price as unknown as { formattedAmount?: string; amount?: string; currency?: string } | undefined;
|
|
13
11
|
if (!m) return p.pricing?.freeTrialDays ? "Free trial" : "";
|
|
@@ -15,17 +13,17 @@ function priceOf(p: WixPlan): string {
|
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesPlanDetail", {
|
|
18
|
-
state: { status: "loading", name: "", price: "", description: "", perks: "" },
|
|
16
|
+
state: { status: "loading", name: "", price: "", description: "", perks: "", ...presence("loading") },
|
|
19
17
|
actions: {
|
|
20
18
|
load: async (s) => {
|
|
21
|
-
const id =
|
|
22
|
-
if (!id) return { ...s, status: "ready" };
|
|
19
|
+
const id = detailInputId();
|
|
20
|
+
if (!id) return { ...s, status: "ready", ...presence("missing") };
|
|
23
21
|
try {
|
|
24
22
|
const res = await getPlan(id);
|
|
25
23
|
const p = ((res as unknown as { plan?: WixPlan }).plan) ?? (res as unknown as WixPlan);
|
|
26
24
|
const perks = (p.perks?.values ?? []).map((v) => `• ${v}`).join("\n");
|
|
27
|
-
return { status: "ready", name: p.name || "Plan", price: priceOf(p), description: p.description || "", perks };
|
|
28
|
-
} catch { return { ...s, status: "ready" }; }
|
|
25
|
+
return { status: "ready", name: p.name || "Plan", price: priceOf(p), description: p.description || "", perks, ...presence("found") };
|
|
26
|
+
} catch (e) { return { ...s, status: "ready", ...presenceAfterError(e) }; }
|
|
29
27
|
},
|
|
30
28
|
},
|
|
31
29
|
});
|
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
import { defineService } from "@himalaya/state";
|
|
3
3
|
import { getPlanOrder, type WixPlanOrder } from "@himalaya/wix-pricing-plans";
|
|
4
4
|
import { formatDateMedium } from "../_shared/engine-intl.js";
|
|
5
|
+
import { presence, presenceAfterError, detailInputId, type PresenceFlags } from "../_shared/detail-presence.js";
|
|
5
6
|
|
|
6
|
-
interface S { status: "loading" | "ready"; name: string; subtitle: string; price: string; validity: string }
|
|
7
|
+
interface S extends PresenceFlags { status: "loading" | "ready"; name: string; subtitle: string; price: string; validity: string }
|
|
7
8
|
|
|
8
|
-
function inputId(): string {
|
|
9
|
-
return (globalThis as { himalaya?: { input?: { id?: string } } }).himalaya?.input?.id ?? "";
|
|
10
|
-
}
|
|
11
9
|
|
|
10
|
+
// Shown by hosts without the shared not-found state (the Spaces clone renders `subtitle`
|
|
11
|
+
// directly); branded-lite hides the whole content block behind `notFound`.
|
|
12
12
|
const EMPTY_DETAIL = "Open a subscription from My Subscriptions — detail needs a plan-order id.";
|
|
13
13
|
const LOAD_FAILED = "This subscription could not be loaded. Try again from My Subscriptions.";
|
|
14
14
|
|
|
15
15
|
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesSubscriptionDetail", {
|
|
16
|
-
state: { status: "loading", name: "", subtitle: "", price: "", validity: "" },
|
|
16
|
+
state: { status: "loading", name: "", subtitle: "", price: "", validity: "", ...presence("loading") },
|
|
17
17
|
actions: {
|
|
18
18
|
load: async (s) => {
|
|
19
|
-
const id =
|
|
20
|
-
//
|
|
21
|
-
if (!id) return { status: "ready", name: "", subtitle: EMPTY_DETAIL, price: "", validity: "" };
|
|
19
|
+
const id = detailInputId();
|
|
20
|
+
// No order id (a stale link, a route opened directly) → the shared not-found state.
|
|
21
|
+
if (!id) return { status: "ready", name: "", subtitle: EMPTY_DETAIL, price: "", validity: "", ...presence("missing") };
|
|
22
22
|
try {
|
|
23
23
|
const res = await getPlanOrder(id);
|
|
24
24
|
const o = (((res as unknown as { order?: WixPlanOrder }).order) ?? (res as unknown as WixPlanOrder)) as unknown as {
|
|
@@ -29,9 +29,10 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesSubscription
|
|
|
29
29
|
const price = o.pricing?.prices?.[0]?.price?.formattedTotal || "";
|
|
30
30
|
const end = o.currentCycle?.endedDate ? `Renews ${formatDateMedium(new Date(o.currentCycle.endedDate))}` : "";
|
|
31
31
|
return { status: "ready", name: o.planName || "Subscription",
|
|
32
|
-
subtitle: o.status ? o.status.charAt(0) + o.status.slice(1).toLowerCase() : "", price, validity: end };
|
|
33
|
-
} catch {
|
|
34
|
-
|
|
32
|
+
subtitle: o.status ? o.status.charAt(0) + o.status.slice(1).toLowerCase() : "", price, validity: end, ...presence("found") };
|
|
33
|
+
} catch (e) {
|
|
34
|
+
const flags = presenceAfterError(e);
|
|
35
|
+
return { status: "ready", name: "", subtitle: flags.notFound ? "" : LOAD_FAILED, price: "", validity: "", ...flags };
|
|
35
36
|
}
|
|
36
37
|
},
|
|
37
38
|
},
|
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
// paint the seeded "apptestercoowner" demo identity when the session is a real
|
|
4
4
|
// member (or a guest — show honesty instead).
|
|
5
5
|
import { defineService } from "@himalaya/state";
|
|
6
|
-
import {
|
|
6
|
+
import { memberAccess } from "../_shared/member-access.js";
|
|
7
7
|
import { getMyData, displayName } from "@himalaya/wix-users";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// `needsSignIn` / `noSignIn` drive the shared member gate: a guest gets the
|
|
10
|
+
// gate with a Sign in button and no placeholder profile. `profileHidden` stays true until
|
|
11
|
+
// `load` has resolved a member, so a guest never glimpses the placeholder avatar while
|
|
12
|
+
// the membership check is still running; `hideLoading` is false for exactly that moment,
|
|
13
|
+
// so the screen shows a loading line instead of going blank.
|
|
14
|
+
interface S { status: "loading" | "ready"; name: string; handle: string; about: string; initial: string; photo: string; needsSignIn: boolean; noSignIn: boolean; profileHidden: boolean; hideLoading: boolean }
|
|
10
15
|
|
|
11
16
|
const EMPTY: S = {
|
|
12
17
|
status: "loading",
|
|
@@ -15,18 +20,17 @@ const EMPTY: S = {
|
|
|
15
20
|
about: "No info to display.",
|
|
16
21
|
initial: "?",
|
|
17
22
|
photo: "",
|
|
23
|
+
needsSignIn: false,
|
|
24
|
+
noSignIn: true,
|
|
25
|
+
profileHidden: true,
|
|
26
|
+
hideLoading: false,
|
|
18
27
|
};
|
|
19
28
|
|
|
29
|
+
// The SAME member check create-post and orders use (`memberAccess`: the host's
|
|
30
|
+
// memberIdentity, falling back to wix-users only on hosts that predate that action), so
|
|
31
|
+
// one shared gate never gives two answers in one app.
|
|
20
32
|
async function sessionEmail(): Promise<string> {
|
|
21
|
-
|
|
22
|
-
const identity = await dispatch("himi.wix.memberIdentity") as unknown;
|
|
23
|
-
const direct = identity as { email?: unknown } | null;
|
|
24
|
-
const wrapped = identity as { payload?: { email?: unknown } } | null;
|
|
25
|
-
const email = direct?.email ?? wrapped?.payload?.email;
|
|
26
|
-
return typeof email === "string" ? email.trim() : "";
|
|
27
|
-
} catch {
|
|
28
|
-
return "";
|
|
29
|
-
}
|
|
33
|
+
return (await memberAccess()).email;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
function wixApi() {
|
|
@@ -37,7 +41,7 @@ function fromParts(name: string, handleRaw: string, about: string, photo = ""):
|
|
|
37
41
|
const clean = name.trim();
|
|
38
42
|
const handle = "@" + (handleRaw || clean || "member").replace(/\s+/g, "").replace(/^@+/, "").toLowerCase();
|
|
39
43
|
const initial = (clean.split(/\s+/).filter(Boolean).map((w) => w[0]).join("").slice(0, 2) || "?").toUpperCase();
|
|
40
|
-
return { status: "ready", name: clean || "Member", handle, about: about || "No info to display.", initial, photo };
|
|
44
|
+
return { status: "ready", name: clean || "Member", handle, about: about || "No info to display.", initial, photo, needsSignIn: false, noSignIn: true, profileHidden: false, hideLoading: true };
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesViewProfile", {
|
|
@@ -48,11 +52,17 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesViewProfile"
|
|
|
48
52
|
if (!email) {
|
|
49
53
|
return {
|
|
50
54
|
status: "ready",
|
|
55
|
+
// Kept for hosts without the shared member gate (the Spaces clone renders these
|
|
56
|
+
// fields directly); branded-lite hides the profile block behind `profileHidden`.
|
|
51
57
|
name: "Sign in to view your profile",
|
|
52
58
|
handle: "",
|
|
53
59
|
about: "Your public profile is available after you sign in from Account.",
|
|
54
60
|
initial: "?",
|
|
55
61
|
photo: "",
|
|
62
|
+
needsSignIn: true,
|
|
63
|
+
noSignIn: false,
|
|
64
|
+
profileHidden: true,
|
|
65
|
+
hideLoading: true,
|
|
56
66
|
};
|
|
57
67
|
}
|
|
58
68
|
try {
|