@wix/himalaya-cli 0.1488.0 → 0.1490.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.
Files changed (57) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +12 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +13 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +48 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +22 -0
  5. package/dist/cli.mjs +157 -48
  6. package/dist/preview-assets.generated.bin +0 -0
  7. package/guides/widgets.md +20 -7
  8. package/package.json +2 -2
  9. package/templates/beauty-salon/config.ts +13 -0
  10. package/templates/beauty-salon/dev/index.ts +784 -0
  11. package/templates/beauty-salon/himalaya.content.json +3 -0
  12. package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  13. package/templates/beauty-salon/tier5-src/_shared/branded-load-state.ts +106 -0
  14. package/templates/beauty-salon/tier5-src/_shared/himi-bookings.ts +14 -0
  15. package/templates/beauty-salon/tier5-src/_shared/member-account-routes.ts +2 -1
  16. package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +25 -11
  17. package/templates/beauty-salon/tier5-src/himi-branded-lite-agenda/index.ts +769 -0
  18. package/templates/branded-lite/config.ts +13 -0
  19. package/templates/branded-lite/dev/index.ts +784 -0
  20. package/templates/branded-lite/himalaya.content.json +3 -0
  21. package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  22. package/templates/branded-lite/tier5-src/_shared/himi-bookings.ts +14 -0
  23. package/templates/branded-lite/tier5-src/_shared/member-account-routes.ts +2 -1
  24. package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +25 -11
  25. package/templates/branded-lite/tier5-src/himi-branded-lite-agenda/index.ts +769 -0
  26. package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  27. package/templates/community-club/tier5-src/_shared/member-account-routes.ts +2 -1
  28. package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  29. package/templates/events-meetups/tier5-src/_shared/member-account-routes.ts +2 -1
  30. package/templates/gym-fitness/config.ts +13 -0
  31. package/templates/gym-fitness/dev/index.ts +784 -0
  32. package/templates/gym-fitness/himalaya.content.json +3 -0
  33. package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  34. package/templates/gym-fitness/tier5-src/_shared/branded-load-state.ts +106 -0
  35. package/templates/gym-fitness/tier5-src/_shared/himi-bookings.ts +14 -0
  36. package/templates/gym-fitness/tier5-src/_shared/member-account-routes.ts +2 -1
  37. package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +25 -11
  38. package/templates/gym-fitness/tier5-src/himi-branded-lite-agenda/index.ts +769 -0
  39. package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  40. package/templates/nonprofit-cause/tier5-src/_shared/member-account-routes.ts +2 -1
  41. package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  42. package/templates/podcast-creator/tier5-src/_shared/member-account-routes.ts +2 -1
  43. package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  44. package/templates/portfolio-creative/tier5-src/_shared/member-account-routes.ts +2 -1
  45. package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  46. package/templates/restaurant/tier5-src/_shared/member-account-routes.ts +2 -1
  47. package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  48. package/templates/retail-store/tier5-src/_shared/member-account-routes.ts +2 -1
  49. package/templates/yoga-studio/config.ts +13 -0
  50. package/templates/yoga-studio/dev/index.ts +784 -0
  51. package/templates/yoga-studio/himalaya.content.json +3 -0
  52. package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +2 -1
  53. package/templates/yoga-studio/tier5-src/_shared/branded-load-state.ts +106 -0
  54. package/templates/yoga-studio/tier5-src/_shared/himi-bookings.ts +14 -0
  55. package/templates/yoga-studio/tier5-src/_shared/member-account-routes.ts +2 -1
  56. package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +25 -11
  57. package/templates/yoga-studio/tier5-src/himi-branded-lite-agenda/index.ts +769 -0
@@ -13,6 +13,9 @@
13
13
  "himi_addresses": {
14
14
  "displayName": "Addresses"
15
15
  },
16
+ "himi_agenda": {
17
+ "displayName": "Agenda"
18
+ },
16
19
  "himi_app_info": {
17
20
  "displayName": "App Info"
18
21
  },
@@ -48,7 +48,7 @@ export const HOSTED_ALWAYS: readonly BrandedLiteVertical[] = ["members"];
48
48
 
49
49
  /** The screen each vertical's nav rows open, so a route to an uninstalled vertical can be answered. */
50
50
  export const VERTICAL_ROOT_SCREENS: Readonly<Record<string, readonly string[]>> = {
51
- store: ["himi_shop"], bookings: ["himi_services"], restaurants: ["himi_menu", "himi_reserve"],
51
+ store: ["himi_shop"], bookings: ["himi_services", "himi_agenda"], restaurants: ["himi_menu", "himi_reserve"],
52
52
  programs: ["himi_programs"], plans: ["himi_plans"], feed: ["himi_feed"], members: ["himi_members"],
53
53
  forum: ["himi_forum"], coupons: ["himi_coupons"], notifications: ["himi_notifications"],
54
54
  loyalty: ["himi_loyalty"], reviews: ["himi_reviews"], portfolio: ["himi_portfolio"],
@@ -67,6 +67,7 @@ export const VERTICAL_ROOT_SCREENS: Readonly<Record<string, readonly string[]>>
67
67
  export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLiteVertical[]>> = {
68
68
  himi_address: ["restaurants", "store"],
69
69
  himi_addresses: ["restaurants", "store"],
70
+ himi_agenda: ["bookings"],
70
71
  himi_badge: ["badges"],
71
72
  himi_badges: ["badges"],
72
73
  himi_blog: ["blog"],
@@ -39,6 +39,20 @@ export function slotStartInstant(slot: { startDate?: string; timezone?: unknown
39
39
  return wall - (Number.isFinite(again) ? again : offset);
40
40
  }
41
41
 
42
+ /** `himi_time` reads the next two weeks. A host's `start` past them (an Agenda session weeks
43
+ * ahead) is read on its own — that session's local day only, never everything up to it — so it
44
+ * can be preselected while each read stays bounded. `undefined` when the start is inside the two
45
+ * weeks. */
46
+ export function availabilityBeyond(now: Date, start = ""): { from: Date; until: Date } | undefined {
47
+ const end = now.getTime() + 14 * 86_400_000;
48
+ const at = start ? Date.parse(start) : NaN;
49
+ if (!Number.isFinite(at) || at < end) return undefined;
50
+ const d = new Date(at);
51
+ const dayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate());
52
+ // Never back into the two weeks already read: a slot in both would be listed twice.
53
+ return { from: new Date(Math.max(dayStart.getTime(), end)), until: new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1) };
54
+ }
55
+
42
56
  /** The slot a host asked to open at (`himi_time` input `start`, an ISO 8601 instant): the id of the
43
57
  * slot that starts at that same instant, or "" when none does (taken since, or out of the window).
44
58
  * Compared as instants, so `…T16:30:00Z` finds a slot listed as `…T18:30:00+02:00`, or as a local
@@ -18,7 +18,8 @@ export const MEMBER_ACCOUNT_ROUTES_MIN_BINARY = "1.4.0";
18
18
  export const MEMBER_MEDIA_FIELD_MIN_BINARY = "1.5.0";
19
19
  export const MEMBER_PASSWORD_ACTIONS_MIN_BINARY = "1.5.0";
20
20
 
21
- function atLeast(version: string, floor: string): boolean {
21
+ /** `version` (a HimalayaCore binary version, pre-release suffix ignored) is `floor` or later. */
22
+ export function atLeast(version: string, floor: string): boolean {
22
23
  const parts = (v: string) => v.split("-")[0].split(".").map((p) => Number.parseInt(p, 10) || 0);
23
24
  const a = parts(version), b = parts(floor);
24
25
  for (let i = 0; i < Math.max(a.length, b.length); i++) {
@@ -17,7 +17,7 @@ import { stashCopy, copyParam } from "../_shared/himi-copy.js";
17
17
  import { upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "@himalaya/local-db";
18
18
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
19
19
  import { emitHostEvent, hostEventInstant } from "../_shared/host-events.js";
20
- import { slotIdAt } from "../_shared/himi-bookings.js";
20
+ import { availabilityBeyond, slotIdAt } from "../_shared/himi-bookings.js";
21
21
  import { wixMediaUrl, WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
22
22
 
23
23
  // Offline-first (data-offline): booking a slot is WRITE-BEHIND against the
@@ -167,6 +167,15 @@ let pendingStart = (() => {
167
167
  const start = (globalThis as { himalaya?: { input?: { start?: unknown } } }).himalaya?.input?.start;
168
168
  return typeof start === "string" && Number.isFinite(Date.parse(start)) ? start : "";
169
169
  })();
170
+ /** The exact session a host opened this screen at (`himalaya.input.sessionId`), when it names one:
171
+ * two sessions of a class can start at the same instant, and `start` alone picks the first. */
172
+ /** A host's `start` past the two-week window, kept for the life of this worker: a pick on that day
173
+ * clears `pendingStart`, yet the reload after the guest sign-in step must still read it. */
174
+ const farStart = pendingStart;
175
+ let pendingSession = (() => {
176
+ const id = (globalThis as { himalaya?: { input?: { sessionId?: unknown } } }).himalaya?.input?.sessionId;
177
+ return typeof id === "string" ? id.trim() : "";
178
+ })();
170
179
 
171
180
  function strOf(arg: unknown): string {
172
181
  if (typeof arg === "string") return arg;
@@ -197,12 +206,6 @@ function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
197
206
  });
198
207
  }
199
208
 
200
- function addDays(date: Date, days: number): Date {
201
- const next = new Date(date);
202
- next.setUTCDate(next.getUTCDate() + days);
203
- return next;
204
- }
205
-
206
209
  function isoDate(date: Date): string {
207
210
  return date.toISOString();
208
211
  }
@@ -559,16 +562,21 @@ const svc = defineService<S, {
559
562
  let next = derive({ ...s, status: "loading", errored: false, message: "", successMessage: "" });
560
563
  next = await probeMember(next);
561
564
  try {
562
- const [serviceRes, availability] = await Promise.all([
565
+ const now = new Date();
566
+ const beyond = availabilityBeyond(now, farStart);
567
+ const [serviceRes, availability, target] = await Promise.all([
563
568
  getService(s.id).catch(() => ({ service: undefined })),
564
- queryServiceAvailability(s.id, isoDate(new Date()), isoDate(addDays(new Date(), 14))),
569
+ queryServiceAvailability(s.id, isoDate(now), isoDate(new Date(now.getTime() + WINDOW_DAYS * 86_400_000))),
570
+ // A host's start past the window (an Agenda session weeks out): its day alone, so it can
571
+ // be preselected. Its failure costs only that preselection.
572
+ beyond ? queryServiceAvailability(s.id, isoDate(beyond.from), isoDate(beyond.until)).catch(() => ({ availabilityEntries: [] })) : undefined,
565
573
  ]);
566
574
  slotsById = new Map();
567
575
  const rows: SlotRow[] = [];
568
576
  // Group by LOCAL calendar day, not by the formatted heading: two
569
577
  // distinct days can render the same text, and a UTC-derived key can
570
578
  // name a different day than the heading beside it.
571
- for (const [index, entry] of (availability.availabilityEntries ?? [])
579
+ for (const [index, entry] of [...(availability.availabilityEntries ?? []), ...(target?.availabilityEntries ?? [])]
572
580
  .filter((candidate) => candidate.bookable !== false && candidate.slot?.startDate)
573
581
  .entries()) {
574
582
  const slot = entry.slot as WixBookingSlotFull;
@@ -595,7 +603,11 @@ const svc = defineService<S, {
595
603
  const chosen: S = selectionRevision !== revisionAtStart ? (svc.getState?.() ?? next) : next;
596
604
  // A host's `start` (deep entry): that slot preselected, or — taken since — its day.
597
605
  const wantedStart = pendingStart;
598
- const startRow = wantedStart ? rows.find((r) => r.id === slotIdAt(slotsById, wantedStart)) : undefined;
606
+ // The named session (a slot row's id IS its session id), else the slot at that instant. A
607
+ // named session that is gone is "taken since" — never another session at the same time.
608
+ const startRow = !wantedStart ? undefined
609
+ : pendingSession ? rows.find((r) => r.id === pendingSession)
610
+ : rows.find((r) => r.id === slotIdAt(slotsById, wantedStart));
599
611
  // The requested day even with no time left on it: "taken since" opens there, nothing selected.
600
612
  const startDay = wantedStart ? days.find((d) => d.id === dayKey(wantedStart)) : undefined;
601
613
  const selectionAlive = !!startRow || slotsById.has(chosen.selectedSlotId);
@@ -638,6 +650,7 @@ const svc = defineService<S, {
638
650
  const day = chosenDay(arg);
639
651
  if (!day || !s.days.some((d) => d.id === day)) return derive(s);
640
652
  pendingStart = "";
653
+ pendingSession = "";
641
654
  return derive(withDay(s, day));
642
655
  },
643
656
  refreshMember: (s, arg) => { stashCopy(arg); return probeMember(s); },
@@ -649,6 +662,7 @@ const svc = defineService<S, {
649
662
  if (selected) {
650
663
  selectionRevision++;
651
664
  pendingStart = "";
665
+ pendingSession = "";
652
666
  if (s.isMember) clearPick();
653
667
  else rememberPick(s.id, slotId);
654
668
  }