@wix/himalaya-cli 0.1037.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +119 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +51 -1
- package/dist/cli.mjs +2523 -1327
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +3 -2
- package/guides/getting-started.md +3 -2
- package/guides/headless-loop.md +11 -3
- package/guides/himi-skill.md +381 -0
- package/guides/himi-test.md +66 -1
- 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/sdk-reference.json +74 -2
- package/guides/skill.md +122 -0
- package/guides/trim-a-template.md +5 -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/beauty-salon-owner/dev/index.ts +2148 -1262
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/branded-lite/dev/index.ts +61 -25
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/community-club-owner/dev/index.ts +2151 -1265
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/community-club-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/events-meetups-owner/dev/index.ts +2151 -1265
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/events-meetups-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/gym-fitness/dev/index.ts +58 -22
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/gym-fitness-owner/dev/index.ts +2148 -1262
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/nonprofit-cause-owner/dev/index.ts +2148 -1262
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/owner-lite/dev/index.ts +2145 -1259
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/owner-lite/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/podcast-creator-owner/dev/index.ts +2151 -1265
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/portfolio-creative-owner/dev/index.ts +2148 -1262
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/restaurant-owner/dev/index.ts +2148 -1262
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/restaurant-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/retail-store/dev/index.ts +1 -1
- package/templates/retail-store-owner/dev/index.ts +2151 -1265
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/retail-store-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/yoga-studio/dev/index.ts +58 -22
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +144 -29
- package/templates/yoga-studio-owner/dev/index.ts +2148 -1262
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
|
@@ -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,
|