@wix/himalaya-cli 0.1038.0 → 0.1039.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/cli.mjs +1937 -1205
- package/guides/designing-himi-screens.md +3 -2
- package/guides/getting-started.md +3 -2
- package/guides/headless-loop.md +4 -3
- package/guides/himi-skill.md +381 -0
- package/guides/index.json +4 -0
- package/guides/recipe-dynamic-list.md +3 -3
- package/guides/recipe-form-submit.md +3 -3
- package/guides/recipe-onboarding.md +3 -3
- package/guides/recipe-search.md +3 -3
- package/guides/skill.md +122 -0
- package/guides/wix-site-to-himi-app.md +4 -2
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +58 -22
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/branded-lite/dev/index.ts +61 -25
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/gym-fitness/dev/index.ts +58 -22
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/retail-store/dev/index.ts +1 -1
- package/templates/yoga-studio/dev/index.ts +58 -22
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +144 -29
|
@@ -13384,7 +13384,7 @@ const SCREENS = runtimeSetting({
|
|
|
13384
13384
|
"package": "himalaya",
|
|
13385
13385
|
"type": "Text",
|
|
13386
13386
|
"props": {
|
|
13387
|
-
"text": "Browse available appointments
|
|
13387
|
+
"text": "Browse available appointments.",
|
|
13388
13388
|
"style": "body",
|
|
13389
13389
|
"color": "textSecondary"
|
|
13390
13390
|
}
|
|
@@ -48757,7 +48757,7 @@ const SCREENS = runtimeSetting({
|
|
|
48757
48757
|
"package": "himalaya",
|
|
48758
48758
|
"type": "Text",
|
|
48759
48759
|
"props": {
|
|
48760
|
-
"text": "Browse events
|
|
48760
|
+
"text": "Browse events.",
|
|
48761
48761
|
"style": "body",
|
|
48762
48762
|
"color": "textSecondary"
|
|
48763
48763
|
}
|
|
@@ -52094,7 +52094,7 @@ const SCREENS = runtimeSetting({
|
|
|
52094
52094
|
"package": "himalaya",
|
|
52095
52095
|
"type": "Text",
|
|
52096
52096
|
"props": {
|
|
52097
|
-
"text": "Read the latest notes
|
|
52097
|
+
"text": "Read the latest notes.",
|
|
52098
52098
|
"style": "body",
|
|
52099
52099
|
"color": "textSecondary"
|
|
52100
52100
|
}
|
|
@@ -81892,6 +81892,44 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
81892
81892
|
}
|
|
81893
81893
|
}
|
|
81894
81894
|
},
|
|
81895
|
+
{
|
|
81896
|
+
"id": "himi_time.days",
|
|
81897
|
+
"body": {
|
|
81898
|
+
"package": "himalaya",
|
|
81899
|
+
"type": "FilterChips",
|
|
81900
|
+
"props": {
|
|
81901
|
+
"chips": [],
|
|
81902
|
+
"optionsPath": "$service:himiBookingTime.days",
|
|
81903
|
+
"selectedIds": [
|
|
81904
|
+
"$service:himiBookingTime.selectedDay"
|
|
81905
|
+
],
|
|
81906
|
+
"multiSelect": false,
|
|
81907
|
+
"onChange": {
|
|
81908
|
+
"actionId": "tier5.himiBookingTime.selectDay"
|
|
81909
|
+
}
|
|
81910
|
+
}
|
|
81911
|
+
},
|
|
81912
|
+
"modifiers": {
|
|
81913
|
+
"accessibilityLabel": "Choose a day",
|
|
81914
|
+
"hiddenPath": "$service:himiBookingTime.noWindowSlots"
|
|
81915
|
+
}
|
|
81916
|
+
},
|
|
81917
|
+
{
|
|
81918
|
+
"id": "himi_time.day_heading",
|
|
81919
|
+
"body": {
|
|
81920
|
+
"package": "himalaya",
|
|
81921
|
+
"type": "Text",
|
|
81922
|
+
"props": {
|
|
81923
|
+
"text": "$service:himiBookingTime.selectedDayLabel",
|
|
81924
|
+
"style": "title3",
|
|
81925
|
+
"maxLines": 1,
|
|
81926
|
+
"collapseWhenEmpty": true
|
|
81927
|
+
}
|
|
81928
|
+
},
|
|
81929
|
+
"modifiers": {
|
|
81930
|
+
"hiddenPath": "$service:himiBookingTime.noWindowSlots"
|
|
81931
|
+
}
|
|
81932
|
+
},
|
|
81895
81933
|
{
|
|
81896
81934
|
"id": "himi_time.slots",
|
|
81897
81935
|
"body": {
|
|
@@ -81911,27 +81949,6 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
81911
81949
|
"spacing": 0,
|
|
81912
81950
|
"alignment": "leading",
|
|
81913
81951
|
"children": [
|
|
81914
|
-
{
|
|
81915
|
-
"id": "himi_time.day_header",
|
|
81916
|
-
"body": {
|
|
81917
|
-
"package": "himalaya",
|
|
81918
|
-
"type": "Text",
|
|
81919
|
-
"props": {
|
|
81920
|
-
"text": "$item.dayHeader",
|
|
81921
|
-
"style": "title3",
|
|
81922
|
-
"maxLines": 1
|
|
81923
|
-
}
|
|
81924
|
-
},
|
|
81925
|
-
"modifiers": {
|
|
81926
|
-
"padding": {
|
|
81927
|
-
"leading": 2,
|
|
81928
|
-
"trailing": 2,
|
|
81929
|
-
"top": 10,
|
|
81930
|
-
"bottom": 2
|
|
81931
|
-
},
|
|
81932
|
-
"hiddenPath": "$item.noHeader"
|
|
81933
|
-
}
|
|
81934
|
-
},
|
|
81935
81952
|
{
|
|
81936
81953
|
"id": "",
|
|
81937
81954
|
"body": {
|
|
@@ -82055,6 +82072,25 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
82055
82072
|
}
|
|
82056
82073
|
]
|
|
82057
82074
|
}
|
|
82075
|
+
},
|
|
82076
|
+
"modifiers": {
|
|
82077
|
+
"hiddenPath": "$service:himiBookingTime.noDaySlots"
|
|
82078
|
+
}
|
|
82079
|
+
},
|
|
82080
|
+
{
|
|
82081
|
+
"id": "himi_time.day_empty",
|
|
82082
|
+
"body": {
|
|
82083
|
+
"package": "himalaya",
|
|
82084
|
+
"type": "Text",
|
|
82085
|
+
"props": {
|
|
82086
|
+
"text": "$service:himiBookingTime.dayEmptyText",
|
|
82087
|
+
"style": "body",
|
|
82088
|
+
"color": "textSecondary",
|
|
82089
|
+
"collapseWhenEmpty": true
|
|
82090
|
+
}
|
|
82091
|
+
},
|
|
82092
|
+
"modifiers": {
|
|
82093
|
+
"hiddenPath": "$service:himiBookingTime.hasDaySlots"
|
|
82058
82094
|
}
|
|
82059
82095
|
},
|
|
82060
82096
|
{
|
|
@@ -82288,7 +82324,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
82288
82324
|
"top": 14,
|
|
82289
82325
|
"bottom": 14
|
|
82290
82326
|
},
|
|
82291
|
-
"hiddenPath": "$service:himiBookingTime.
|
|
82327
|
+
"hiddenPath": "$service:himiBookingTime.hideSignIn"
|
|
82292
82328
|
}
|
|
82293
82329
|
},
|
|
82294
82330
|
{
|
|
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
|
|
|
3
3
|
// Local-day keying + shared day/time labelling. `addDays` below stays local and
|
|
4
4
|
// UTC-based on purpose: it bounds the availability QUERY window, which is an
|
|
5
5
|
// instant range, not a calendar day.
|
|
6
|
-
import { dayKey, dayLabel, timeLabel, toDate } from "@himalaya/time";
|
|
6
|
+
import { addDays as addLocalDays, dayKey, dayLabel, timeLabel, toDate } from "@himalaya/time";
|
|
7
7
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
8
|
import { bookSlot, getService, queryServiceAvailability, type WixBookingSlotFull } from "@himalaya/wix-bookings";
|
|
9
9
|
import { createCheckout, createOrder } from "@himalaya/wix-ecom";
|
|
@@ -29,25 +29,21 @@ async function cachePut(row: DbEntity): Promise<void> { try { await dbUpsert(ENT
|
|
|
29
29
|
async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTITY, id); } catch { /* capability not granted */ } }
|
|
30
30
|
async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
|
|
31
31
|
|
|
32
|
-
/** One bookable slot
|
|
32
|
+
/** One bookable slot row. Every row is a real slot.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
* (
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* synthetic day-header or a slot, and the template carried two alternative
|
|
40
|
-
* subtrees gated by four mirror booleans (isHeader/notHeader/isSlot/notSlot).
|
|
41
|
-
* Under that shape `slots.length` counted headers too, so every consumer had
|
|
42
|
-
* to remember to filter on `isSlot` first. */
|
|
34
|
+
* #3276 — the screen used to render the WHOLE 14-day window as one flat agenda
|
|
35
|
+
* (the day heading riding on each day's first row), capped at 24 rows, with the
|
|
36
|
+
* sign-in card under all of it. It now shows ONE day at a time: `days` feeds a
|
|
37
|
+
* day strip, `slots` holds only the selected day's rows, and the strip is the
|
|
38
|
+
* heading — so rows no longer carry a per-row one. */
|
|
43
39
|
interface SlotRow {
|
|
44
40
|
id: string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
/** Inverse of `dayHeader` — collapses the heading on non-first rows. */
|
|
48
|
-
noHeader: boolean;
|
|
41
|
+
/** Local calendar-day key (`YYYY-MM-DD`) the slot starts on. */
|
|
42
|
+
dayKey: string;
|
|
49
43
|
/** Day label carried for the selected-line copy, on every row. */
|
|
50
44
|
day: string;
|
|
45
|
+
/** Compact day label for the strip chip ("Sun 27"). */
|
|
46
|
+
dayChip: string;
|
|
51
47
|
title: string;
|
|
52
48
|
subtitle: string;
|
|
53
49
|
selected: boolean;
|
|
@@ -70,7 +66,22 @@ interface S {
|
|
|
70
66
|
* booking through the hosted Wix bookings checkout (GPV) instead of the free
|
|
71
67
|
* bookSlot rung. Set from the service payload during load. */
|
|
72
68
|
serviceIsPaid: boolean;
|
|
69
|
+
/** Day-strip chips: every day of the window (a day with no times shows a zero
|
|
70
|
+
* count rather than being silently skipped), in the FilterChips option shape. */
|
|
71
|
+
days: DayOption[];
|
|
72
|
+
/** The day whose slots are shown — defaults to the first day that has any. */
|
|
73
|
+
selectedDay: string;
|
|
74
|
+
/** Full heading for the selected day ("Today", "Mon, Sep 28"). */
|
|
75
|
+
selectedDayLabel: string;
|
|
76
|
+
/** Only the selected day's slots. */
|
|
73
77
|
slots: SlotRow[];
|
|
78
|
+
/** Copy for a selected day with no times; "" when the day has slots. */
|
|
79
|
+
dayEmptyText: string;
|
|
80
|
+
hasDaySlots: boolean;
|
|
81
|
+
noDaySlots: boolean;
|
|
82
|
+
/** No times anywhere in the window — the day strip and heading step aside
|
|
83
|
+
* for the screen-level empty state. */
|
|
84
|
+
noWindowSlots: boolean;
|
|
74
85
|
selectedSlotId: string;
|
|
75
86
|
selectedLine: string;
|
|
76
87
|
emptyText: string;
|
|
@@ -86,6 +97,9 @@ interface S {
|
|
|
86
97
|
noEmpty: boolean;
|
|
87
98
|
hasSelected: boolean;
|
|
88
99
|
noSelected: boolean;
|
|
100
|
+
/** #3276 — the sign-in/register card belongs to the moment of booking: hidden
|
|
101
|
+
* for a member, and for a visitor until they have picked a time. */
|
|
102
|
+
hideSignIn: boolean;
|
|
89
103
|
canBook: boolean;
|
|
90
104
|
cannotBook: boolean;
|
|
91
105
|
showChoosePrompt: boolean;
|
|
@@ -106,7 +120,25 @@ interface S {
|
|
|
106
120
|
bookingsScreenId: string;
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
/** One day-strip chip — the `FilterChips` option shape `dateStrip` binds. */
|
|
124
|
+
interface DayOption {
|
|
125
|
+
id: string;
|
|
126
|
+
label: string;
|
|
127
|
+
count: number;
|
|
128
|
+
hasSlots: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
109
131
|
let slotsById = new Map<string, WixBookingSlotFull>();
|
|
132
|
+
/** Every slot row in the window, across all days. Kept out of worker state so
|
|
133
|
+
* the state the host receives holds one day's rows, not the whole window. */
|
|
134
|
+
let allRows: SlotRow[] = [];
|
|
135
|
+
|
|
136
|
+
/** Days the strip offers, from today. */
|
|
137
|
+
// No row cap. The old flat list capped the WHOLE window at 24 rows, which
|
|
138
|
+
// silently dropped every day after the first one or two — times a customer
|
|
139
|
+
// could neither see nor book. Worker state now carries one day's rows, so the
|
|
140
|
+
// bound that list needed is gone, and the availability query has no paging.
|
|
141
|
+
const WINDOW_DAYS = 14;
|
|
110
142
|
|
|
111
143
|
function inputId(): string {
|
|
112
144
|
// WS-C — `himalaya.input.id` is the PRIMARY contract: hosts thread the row's
|
|
@@ -227,8 +259,57 @@ function serviceImageUrl(value: unknown): string {
|
|
|
227
259
|
return "";
|
|
228
260
|
}
|
|
229
261
|
|
|
262
|
+
/** The day id out of a chip-selection payload: FilterChips' onChange contract is
|
|
263
|
+
* `{ selectedIds }` on every renderer; the string / `{ dayId }` / `{ id }` arms
|
|
264
|
+
* keep direct dispatches working. A re-tap of the active chip emits `[]`. */
|
|
265
|
+
function chosenDay(arg: unknown): string {
|
|
266
|
+
if (typeof arg === "string") return arg;
|
|
267
|
+
const a = arg as { selectedIds?: unknown; dayId?: unknown; id?: unknown } | undefined;
|
|
268
|
+
const ids = a?.selectedIds;
|
|
269
|
+
if (Array.isArray(ids)) return typeof ids[0] === "string" ? ids[0] : "";
|
|
270
|
+
if (typeof ids === "string") return ids;
|
|
271
|
+
if (typeof a?.dayId === "string") return a.dayId;
|
|
272
|
+
return typeof a?.id === "string" ? a.id : "";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Every day of the window from today (local), plus any day a slot lands on
|
|
276
|
+
* outside it, sorted — each with its slot count. */
|
|
277
|
+
function daysOf(rows: SlotRow[], now: Date = new Date()): DayOption[] {
|
|
278
|
+
const counts = new Map<string, number>();
|
|
279
|
+
const labels = new Map<string, string>();
|
|
280
|
+
for (const row of rows) {
|
|
281
|
+
counts.set(row.dayKey, (counts.get(row.dayKey) ?? 0) + 1);
|
|
282
|
+
if (!labels.has(row.dayKey)) labels.set(row.dayKey, row.dayChip);
|
|
283
|
+
}
|
|
284
|
+
for (let offset = 0; offset < WINDOW_DAYS; offset++) {
|
|
285
|
+
const at = addLocalDays(now, offset);
|
|
286
|
+
const key = at ? dayKey(at) : "";
|
|
287
|
+
// Compact ("Sun 27"), not the full heading: these are pills in a strip.
|
|
288
|
+
if (key && !labels.has(key)) labels.set(key, dayLabel(at, { format: "compact" }));
|
|
289
|
+
}
|
|
290
|
+
return [...labels.keys()].sort().map((key) => {
|
|
291
|
+
const count = counts.get(key) ?? 0;
|
|
292
|
+
return { id: key, label: labels.get(key) ?? key, count, hasSlots: count > 0 };
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Show `day`: its rows (selection flags from the current selection) + heading. */
|
|
297
|
+
function withDay(s: S, day: string): S {
|
|
298
|
+
const rows = allRows.filter((row) => row.dayKey === day);
|
|
299
|
+
const noon = day ? new Date(`${day}T12:00:00`) : null;
|
|
300
|
+
return {
|
|
301
|
+
...s,
|
|
302
|
+
selectedDay: day,
|
|
303
|
+
selectedDayLabel: rows[0]?.day ?? (noon && !Number.isNaN(noon.getTime()) ? dayLabel(noon) : ""),
|
|
304
|
+
slots: rows.map((row) => ({ ...row, selected: row.id === s.selectedSlotId, notSelected: row.id !== s.selectedSlotId })),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
230
308
|
function derive(s: S): S {
|
|
231
|
-
|
|
309
|
+
// `hasSlots` is the WHOLE window (it drives the screen-level empty state); the
|
|
310
|
+
// selected day having none is a per-day line, not an empty screen.
|
|
311
|
+
const hasSlots = s.days.some((d) => d.hasSlots) || s.slots.length > 0;
|
|
312
|
+
const hasDaySlots = s.slots.length > 0;
|
|
232
313
|
const hasSelected = !!s.selectedSlotId;
|
|
233
314
|
const hasSuccess = !!s.successMessage;
|
|
234
315
|
const canBook = s.isMember && hasSelected && !s.bookingInFlight && !hasSuccess;
|
|
@@ -239,6 +320,15 @@ function derive(s: S): S {
|
|
|
239
320
|
hasSlots,
|
|
240
321
|
hasSelected,
|
|
241
322
|
noSelected: !hasSelected,
|
|
323
|
+
hideSignIn: s.isMember || !hasSelected,
|
|
324
|
+
hasDaySlots,
|
|
325
|
+
noDaySlots: !hasDaySlots,
|
|
326
|
+
noWindowSlots: !hasSlots,
|
|
327
|
+
// Only a quiet DAY in a window that has times gets the per-day line; a
|
|
328
|
+
// window with no times at all is the screen-level empty state's job.
|
|
329
|
+
dayEmptyText: hasDaySlots || !s.selectedDay || !hasSlots
|
|
330
|
+
? ""
|
|
331
|
+
: copyParam("time.dayEmptyText", "No times on this day. Try another day."),
|
|
242
332
|
hasSuccess,
|
|
243
333
|
noSuccess: !hasSuccess,
|
|
244
334
|
canBook,
|
|
@@ -336,6 +426,7 @@ const id = inputId();
|
|
|
336
426
|
const svc = defineService<S, {
|
|
337
427
|
load: (s: S, arg?: unknown) => Promise<S>;
|
|
338
428
|
refreshMember: (s: S, arg?: unknown) => Promise<S>;
|
|
429
|
+
selectDay: (s: S, arg?: unknown) => S;
|
|
339
430
|
selectSlot: (s: S, arg?: unknown) => S;
|
|
340
431
|
setEmail: (s: S, arg?: unknown) => S;
|
|
341
432
|
setPassword: (s: S, arg?: unknown) => S;
|
|
@@ -358,7 +449,14 @@ const svc = defineService<S, {
|
|
|
358
449
|
servicePrice: "",
|
|
359
450
|
serviceDuration: "",
|
|
360
451
|
serviceIsPaid: false,
|
|
452
|
+
days: [],
|
|
453
|
+
selectedDay: "",
|
|
454
|
+
selectedDayLabel: "",
|
|
361
455
|
slots: [],
|
|
456
|
+
dayEmptyText: "",
|
|
457
|
+
hasDaySlots: false,
|
|
458
|
+
noDaySlots: true,
|
|
459
|
+
noWindowSlots: true,
|
|
362
460
|
selectedSlotId: "",
|
|
363
461
|
selectedLine: "",
|
|
364
462
|
emptyText: "",
|
|
@@ -374,6 +472,7 @@ const svc = defineService<S, {
|
|
|
374
472
|
noEmpty: true,
|
|
375
473
|
hasSelected: false,
|
|
376
474
|
noSelected: true,
|
|
475
|
+
hideSignIn: true,
|
|
377
476
|
canBook: false,
|
|
378
477
|
cannotBook: true,
|
|
379
478
|
showChoosePrompt: false,
|
|
@@ -409,32 +508,39 @@ const svc = defineService<S, {
|
|
|
409
508
|
// Group by LOCAL calendar day, not by the formatted heading: two
|
|
410
509
|
// distinct days can render the same text, and a UTC-derived key can
|
|
411
510
|
// name a different day than the heading beside it.
|
|
412
|
-
let lastKey = "";
|
|
413
511
|
for (const [index, entry] of (availability.availabilityEntries ?? [])
|
|
414
512
|
.filter((candidate) => candidate.bookable !== false && candidate.slot?.startDate)
|
|
415
|
-
.slice(0, 24)
|
|
416
513
|
.entries()) {
|
|
417
514
|
const slot = entry.slot as WixBookingSlotFull;
|
|
515
|
+
const key = dayKey(slot.startDate);
|
|
516
|
+
if (!key) continue;
|
|
418
517
|
const slotId = String(slot.sessionId ?? slot.startDate ?? `slot-${index}`);
|
|
419
518
|
slotsById.set(slotId, slot);
|
|
420
519
|
const label = fmtRange(slot.startDate, slot.endDate);
|
|
421
|
-
const key = dayKey(slot.startDate);
|
|
422
|
-
const firstOfDay = !!key && key !== lastKey;
|
|
423
|
-
if (firstOfDay) lastKey = key;
|
|
424
520
|
rows.push({
|
|
425
521
|
id: slotId,
|
|
426
|
-
|
|
427
|
-
noHeader: !firstOfDay,
|
|
522
|
+
dayKey: key,
|
|
428
523
|
day: label.day,
|
|
524
|
+
dayChip: dayLabel(slot.startDate, { format: "compact" }),
|
|
429
525
|
title: label.title,
|
|
430
526
|
subtitle: label.subtitle,
|
|
431
527
|
selected: false,
|
|
432
528
|
notSelected: true,
|
|
433
529
|
});
|
|
434
530
|
}
|
|
531
|
+
allRows = rows;
|
|
532
|
+
const days = daysOf(rows);
|
|
533
|
+
// Keep the customer's day across a reload when it still has times;
|
|
534
|
+
// otherwise open on the first day that has any.
|
|
535
|
+
const keep = days.find((d) => d.id === next.selectedDay && d.hasSlots);
|
|
536
|
+
const initialDay = keep?.id ?? days.find((d) => d.hasSlots)?.id ?? days[0]?.id ?? "";
|
|
537
|
+
const selectionAlive = slotsById.has(next.selectedSlotId);
|
|
435
538
|
const header = serviceHeaderFields(serviceRes.service);
|
|
436
|
-
return derive({
|
|
539
|
+
return derive(withDay({
|
|
437
540
|
...next,
|
|
541
|
+
days,
|
|
542
|
+
selectedSlotId: selectionAlive ? next.selectedSlotId : "",
|
|
543
|
+
selectedLine: selectionAlive ? next.selectedLine : "",
|
|
438
544
|
status: "ready",
|
|
439
545
|
errored: false,
|
|
440
546
|
serviceName: serviceRes.service?.name || copyParam("time.serviceNameFallback", "Choose a time"),
|
|
@@ -445,19 +551,28 @@ const svc = defineService<S, {
|
|
|
445
551
|
servicePrice: header.price,
|
|
446
552
|
serviceDuration: header.duration,
|
|
447
553
|
serviceIsPaid: header.paid,
|
|
448
|
-
|
|
449
|
-
});
|
|
554
|
+
}, initialDay));
|
|
450
555
|
} catch {
|
|
451
556
|
slotsById = new Map();
|
|
452
|
-
|
|
557
|
+
allRows = [];
|
|
558
|
+
return derive({ ...next, status: "ready", errored: true, days: [], selectedDay: "", selectedDayLabel: "", slots: [] });
|
|
453
559
|
}
|
|
454
560
|
},
|
|
561
|
+
// #3276 — show one day's times. Re-tapping the active chip emits an empty
|
|
562
|
+
// selection; the strip is single-select with no "none", so the day stays.
|
|
563
|
+
// A time picked on another day stays selected (the selected line and the
|
|
564
|
+
// Book button keep naming it): switching days to compare is not a reset.
|
|
565
|
+
selectDay: (s, arg) => {
|
|
566
|
+
const day = chosenDay(arg);
|
|
567
|
+
if (!day || !s.days.some((d) => d.id === day)) return derive(s);
|
|
568
|
+
return derive(withDay(s, day));
|
|
569
|
+
},
|
|
455
570
|
refreshMember: (s, arg) => { stashCopy(arg); return probeMember(s); },
|
|
456
571
|
selectSlot: (s, arg) => {
|
|
457
572
|
const slotId = typeof arg === "string"
|
|
458
573
|
? arg
|
|
459
574
|
: ((arg as { id?: string; slotId?: string })?.slotId ?? (arg as { id?: string })?.id ?? "");
|
|
460
|
-
const selected =
|
|
575
|
+
const selected = allRows.find((slot) => slot.id === slotId);
|
|
461
576
|
return derive({
|
|
462
577
|
...s,
|
|
463
578
|
selectedSlotId: selected ? slotId : s.selectedSlotId,
|
|
@@ -592,7 +707,7 @@ const svc = defineService<S, {
|
|
|
592
707
|
const email = (await signedInEmail()) || my.email || my.contactEmail || s.email;
|
|
593
708
|
const name = contactName(displayName(my) || email);
|
|
594
709
|
// Optimistic local write — My Bookings repaints from cache immediately (offline or not).
|
|
595
|
-
const selectedSlot =
|
|
710
|
+
const selectedSlot = allRows.find((r) => r.id === s.selectedSlotId);
|
|
596
711
|
const localId = `local-${Date.now()}`;
|
|
597
712
|
const localRow = (id: string): DbEntity => ({
|
|
598
713
|
id,
|
|
@@ -30752,6 +30752,44 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
30752
30752
|
}
|
|
30753
30753
|
}
|
|
30754
30754
|
},
|
|
30755
|
+
{
|
|
30756
|
+
"id": "himi_time.days",
|
|
30757
|
+
"body": {
|
|
30758
|
+
"package": "himalaya",
|
|
30759
|
+
"type": "FilterChips",
|
|
30760
|
+
"props": {
|
|
30761
|
+
"chips": [],
|
|
30762
|
+
"optionsPath": "$service:himiBookingTime.days",
|
|
30763
|
+
"selectedIds": [
|
|
30764
|
+
"$service:himiBookingTime.selectedDay"
|
|
30765
|
+
],
|
|
30766
|
+
"multiSelect": false,
|
|
30767
|
+
"onChange": {
|
|
30768
|
+
"actionId": "tier5.himiBookingTime.selectDay"
|
|
30769
|
+
}
|
|
30770
|
+
}
|
|
30771
|
+
},
|
|
30772
|
+
"modifiers": {
|
|
30773
|
+
"accessibilityLabel": "Choose a day",
|
|
30774
|
+
"hiddenPath": "$service:himiBookingTime.noWindowSlots"
|
|
30775
|
+
}
|
|
30776
|
+
},
|
|
30777
|
+
{
|
|
30778
|
+
"id": "himi_time.day_heading",
|
|
30779
|
+
"body": {
|
|
30780
|
+
"package": "himalaya",
|
|
30781
|
+
"type": "Text",
|
|
30782
|
+
"props": {
|
|
30783
|
+
"text": "$service:himiBookingTime.selectedDayLabel",
|
|
30784
|
+
"style": "title3",
|
|
30785
|
+
"maxLines": 1,
|
|
30786
|
+
"collapseWhenEmpty": true
|
|
30787
|
+
}
|
|
30788
|
+
},
|
|
30789
|
+
"modifiers": {
|
|
30790
|
+
"hiddenPath": "$service:himiBookingTime.noWindowSlots"
|
|
30791
|
+
}
|
|
30792
|
+
},
|
|
30755
30793
|
{
|
|
30756
30794
|
"id": "himi_time.slots",
|
|
30757
30795
|
"body": {
|
|
@@ -30771,27 +30809,6 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
30771
30809
|
"spacing": 0,
|
|
30772
30810
|
"alignment": "leading",
|
|
30773
30811
|
"children": [
|
|
30774
|
-
{
|
|
30775
|
-
"id": "himi_time.day_header",
|
|
30776
|
-
"body": {
|
|
30777
|
-
"package": "himalaya",
|
|
30778
|
-
"type": "Text",
|
|
30779
|
-
"props": {
|
|
30780
|
-
"text": "$item.dayHeader",
|
|
30781
|
-
"style": "title3",
|
|
30782
|
-
"maxLines": 1
|
|
30783
|
-
}
|
|
30784
|
-
},
|
|
30785
|
-
"modifiers": {
|
|
30786
|
-
"padding": {
|
|
30787
|
-
"leading": 2,
|
|
30788
|
-
"trailing": 2,
|
|
30789
|
-
"top": 10,
|
|
30790
|
-
"bottom": 2
|
|
30791
|
-
},
|
|
30792
|
-
"hiddenPath": "$item.noHeader"
|
|
30793
|
-
}
|
|
30794
|
-
},
|
|
30795
30812
|
{
|
|
30796
30813
|
"id": "",
|
|
30797
30814
|
"body": {
|
|
@@ -30915,6 +30932,25 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
30915
30932
|
}
|
|
30916
30933
|
]
|
|
30917
30934
|
}
|
|
30935
|
+
},
|
|
30936
|
+
"modifiers": {
|
|
30937
|
+
"hiddenPath": "$service:himiBookingTime.noDaySlots"
|
|
30938
|
+
}
|
|
30939
|
+
},
|
|
30940
|
+
{
|
|
30941
|
+
"id": "himi_time.day_empty",
|
|
30942
|
+
"body": {
|
|
30943
|
+
"package": "himalaya",
|
|
30944
|
+
"type": "Text",
|
|
30945
|
+
"props": {
|
|
30946
|
+
"text": "$service:himiBookingTime.dayEmptyText",
|
|
30947
|
+
"style": "body",
|
|
30948
|
+
"color": "textSecondary",
|
|
30949
|
+
"collapseWhenEmpty": true
|
|
30950
|
+
}
|
|
30951
|
+
},
|
|
30952
|
+
"modifiers": {
|
|
30953
|
+
"hiddenPath": "$service:himiBookingTime.hasDaySlots"
|
|
30918
30954
|
}
|
|
30919
30955
|
},
|
|
30920
30956
|
{
|
|
@@ -31148,7 +31184,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
31148
31184
|
"top": 14,
|
|
31149
31185
|
"bottom": 14
|
|
31150
31186
|
},
|
|
31151
|
-
"hiddenPath": "$service:himiBookingTime.
|
|
31187
|
+
"hiddenPath": "$service:himiBookingTime.hideSignIn"
|
|
31152
31188
|
}
|
|
31153
31189
|
},
|
|
31154
31190
|
{
|