@wix/agent-skills 1.20.0 → 1.20.1

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 (152) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-headless-fast/SKILL.md +200 -34
  3. package/skills/wix-headless-fast/entry/skill.md +10 -2
  4. package/skills/wix-headless-fast/install/attach.mjs +253 -0
  5. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  6. package/skills/wix-headless-fast/install/fast-path.mjs +29 -34
  7. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
  8. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  9. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  10. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  11. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  12. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  13. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  14. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  15. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  16. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  17. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  18. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  19. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  20. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  21. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  22. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
  23. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  24. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  25. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  26. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  27. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  28. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  29. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  30. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  31. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  32. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  33. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  34. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  35. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  36. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
  37. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  38. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  39. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  40. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  41. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  42. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  43. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  44. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  45. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
  46. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  47. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  48. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  49. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  50. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  51. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  52. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  53. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  54. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  55. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  56. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  57. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  58. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  59. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  60. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
  61. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  62. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  63. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  64. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  65. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  66. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  67. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  68. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  69. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  70. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  71. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
  72. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  73. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  74. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  75. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  76. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  77. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  78. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  79. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  80. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
  81. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  82. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  83. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  84. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  85. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  86. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  87. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  88. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  89. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  90. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  91. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  92. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  93. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  94. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  95. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
  96. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  97. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  98. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  99. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  100. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  101. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  102. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  103. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  104. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  105. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  106. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  107. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  108. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  109. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  110. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
  111. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  112. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  113. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  114. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  115. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  116. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  117. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  118. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  119. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  120. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  121. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  122. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  123. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  124. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  125. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  126. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  127. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  128. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
  129. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  130. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  131. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  132. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  133. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +182 -63
  134. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  135. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
  136. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
  137. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
  138. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  139. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
  140. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
  141. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
  142. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  143. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  144. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  145. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  146. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -9
  147. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +6 -35
  148. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  149. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
  150. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  151. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  152. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -1,177 +1,34 @@
1
- // The whole table-reservation state machine: location, date + party size, AVAILABLE slots,
2
- // the 10-minute hold, the details form, and confirm. All correctness (AVAILABLE-only slots,
3
- // hold → reserve with the revision, firstName + E.164 phone, RESERVED vs REQUESTED) lives in
4
- // the data layer — this hook orchestrates; you own how it looks. Client-only: availability
5
- // is timezone-specific, so render the surface with client:only="react" in Astro.
6
- import { useEffect, useState } from "react";
7
- import {
8
- completeReservation,
9
- fetchReservationLocations,
10
- fetchReservationSlots,
11
- holdReservation,
12
- } from "../../wix/restaurants/reservations";
13
- import type {
14
- ReservationConfirmation,
15
- ReservationHold,
16
- ReservationLocationInfo,
17
- ReservationReservee,
18
- ReservationSlot,
19
- } from "../../wix/restaurants/types";
20
-
21
- const todayKey = (): string => {
22
- const d = new Date();
23
- const pad = (n: number) => String(n).padStart(2, "0");
24
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
25
- };
26
-
27
- export interface UseReservation {
28
- /** null while loading; [] when Table Reservations isn't set up (honest empty state). */
29
- locations: ReservationLocationInfo[] | null;
30
- /** The active location (default first). Render a picker only when locations.length > 1. */
31
- location: ReservationLocationInfo | null;
32
- setLocationId: (id: string) => void;
33
- /** "YYYY-MM-DD" for a date input. */
34
- date: string;
35
- setDate: (date: string) => void;
36
- /** "HH:mm" anchor for the slot fan-out. */
37
- time: string;
38
- setTime: (time: string) => void;
39
- /** Clamped to the location's partySizeMin/Max. */
40
- partySize: number;
41
- setPartySize: (size: number) => void;
42
- /** AVAILABLE slots for the current query; null until findSlots ran. */
43
- slots: ReservationSlot[] | null;
44
- findSlots: () => Promise<void>;
45
- /** Set after holdSlot — the visitor has 10 minutes to confirm. */
46
- held: ReservationHold | null;
47
- holdSlot: (slot: ReservationSlot) => Promise<void>;
48
- reservee: ReservationReservee;
49
- setReserveeField: (field: keyof ReservationReservee, value: string) => void;
50
- /** True when a slot is held and firstName + phone are filled — gate the confirm CTA. */
51
- canConfirm: boolean;
52
- confirm: () => Promise<void>;
53
- /** Set on success. REQUESTED → tell the visitor approval is pending, not confirmed. */
54
- confirmed: ReservationConfirmation | null;
55
- /** Back to slot picking (keeps date/party). */
56
- reset: () => void;
57
- loading: boolean;
58
- error: string | null;
59
- }
1
+ // React binding of the reservation store (wix/restaurants/reservation-store.ts) — the whole
2
+ // table-reservation state machine lives there, framework-free: location, date + party size,
3
+ // AVAILABLE slots, the 10-minute hold, the details form, and confirm. All correctness (AVAILABLE-only
4
+ // slots, hold → reserve with the revision, firstName + E.164 phone, RESERVED vs REQUESTED) lives in
5
+ // the data layer; you own how it looks. Client-only: availability is timezone-specific, so render
6
+ // the surface with client:only="react" in Astro.
7
+ import { useEffect, useRef, useSyncExternalStore } from "react";
8
+ import { createReservationStore, type ReservationState, type ReservationStore } from "../../wix/restaurants/reservation-store";
9
+
10
+ export type UseReservation = ReservationState &
11
+ Pick<ReservationStore, "setLocationId" | "setDate" | "setTime" | "setPartySize" | "findSlots" | "holdSlot" | "setReserveeField" | "confirm" | "reset">;
60
12
 
61
13
  export function useReservation(): UseReservation {
62
- const [locations, setLocations] = useState<ReservationLocationInfo[] | null>(null);
63
- const [locationId, setLocationId] = useState<string | null>(null);
64
- const [date, setDate] = useState<string>(todayKey());
65
- const [time, setTime] = useState<string>("19:00");
66
- const [partySize, setPartySizeRaw] = useState<number>(2);
67
- const [slots, setSlots] = useState<ReservationSlot[] | null>(null);
68
- const [held, setHeld] = useState<ReservationHold | null>(null);
69
- const [reservee, setReservee] = useState<ReservationReservee>({ firstName: "", phone: "" });
70
- const [confirmed, setConfirmed] = useState<ReservationConfirmation | null>(null);
71
- const [loading, setLoading] = useState(false);
72
- const [error, setError] = useState<string | null>(null);
73
-
14
+ const ref = useRef<ReservationStore | null>(null);
15
+ if (!ref.current) ref.current = createReservationStore();
16
+ const store = ref.current;
74
17
  useEffect(() => {
75
- let alive = true;
76
- fetchReservationLocations()
77
- .then((locs) => {
78
- if (!alive) return;
79
- setLocations(locs);
80
- setLocationId((id) => id ?? locs[0]?.id ?? null);
81
- })
82
- .catch((e) => {
83
- if (!alive) return;
84
- setLocations([]);
85
- setError(e instanceof Error ? e.message : String(e));
86
- });
87
- return () => {
88
- alive = false;
89
- };
90
- }, []);
91
-
92
- const location = locations?.find((l) => l.id === locationId) ?? locations?.[0] ?? null;
93
-
94
- const setPartySize = (size: number) => {
95
- const min = location?.partySizeMin ?? 1;
96
- const max = location?.partySizeMax ?? 20;
97
- setPartySizeRaw(Math.min(Math.max(size, min), max));
98
- };
99
-
100
- async function findSlots(): Promise<void> {
101
- if (!location) return;
102
- setError(null);
103
- setSlots(null);
104
- setHeld(null);
105
- setConfirmed(null);
106
- setLoading(true);
107
- try {
108
- const aroundIso = new Date(`${date}T${time}:00`).toISOString();
109
- setSlots(await fetchReservationSlots(location.id, aroundIso, partySize));
110
- } catch (e) {
111
- setSlots([]);
112
- setError(e instanceof Error ? e.message : String(e));
113
- } finally {
114
- setLoading(false);
115
- }
116
- }
117
-
118
- async function holdSlot(slot: ReservationSlot): Promise<void> {
119
- if (!location) return;
120
- setError(null);
121
- setLoading(true);
122
- try {
123
- setHeld(await holdReservation(location.id, slot.startIso, partySize));
124
- } catch (e) {
125
- setError(e instanceof Error ? e.message : String(e));
126
- } finally {
127
- setLoading(false);
128
- }
129
- }
130
-
131
- const canConfirm =
132
- !!held && reservee.firstName.trim().length > 0 && reservee.phone.trim().length > 0 && !loading;
133
-
134
- async function confirm(): Promise<void> {
135
- if (!held) return;
136
- setError(null);
137
- setLoading(true);
138
- try {
139
- setConfirmed(await completeReservation(held, reservee));
140
- } catch (e) {
141
- // An expired hold can't be reserved — send the visitor back to slot picking.
142
- setError(e instanceof Error ? e.message : String(e));
143
- setHeld(null);
144
- } finally {
145
- setLoading(false);
146
- }
147
- }
148
-
18
+ store.start();
19
+ return () => store.stop();
20
+ }, [store]);
21
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
149
22
  return {
150
- locations,
151
- location,
152
- setLocationId,
153
- date,
154
- setDate,
155
- time,
156
- setTime,
157
- partySize,
158
- setPartySize,
159
- slots,
160
- findSlots,
161
- held,
162
- holdSlot,
163
- reservee,
164
- setReserveeField: (field, value) => setReservee((r) => ({ ...r, [field]: value })),
165
- canConfirm,
166
- confirm,
167
- confirmed,
168
- reset: () => {
169
- setHeld(null);
170
- setConfirmed(null);
171
- setSlots(null);
172
- setError(null);
173
- },
174
- loading,
175
- error,
23
+ ...state,
24
+ setLocationId: store.setLocationId,
25
+ setDate: store.setDate,
26
+ setTime: store.setTime,
27
+ setPartySize: store.setPartySize,
28
+ findSlots: store.findSlots,
29
+ holdSlot: store.holdSlot,
30
+ setReserveeField: store.setReserveeField,
31
+ confirm: store.confirm,
32
+ reset: store.reset,
176
33
  };
177
34
  }
@@ -0,0 +1,155 @@
1
+ // Menu rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./menu.ts (the
2
+ // SDK, managed Astro and React) and the REST twin in references/restaurants/rest/menu.ts (fetch,
3
+ // a static site or a port to another language). The hierarchy is Menu → Sections → Items wired by
4
+ // ID ARRAYS: display structure and order live in menu.sectionIds / section.itemIds, never in a
5
+ // list response's order — the tree is stitched here in id-array order and dangling ids dropped.
6
+ // A raw entity carries `_id` (SDK) or `id` (REST); every mapper accepts both. Imports are
7
+ // type-only so a strip to JS emits no imports.
8
+ import type {
9
+ MenuData,
10
+ MenuItem,
11
+ MenuItemLabel,
12
+ MenuItemVariant,
13
+ MenuModifier,
14
+ MenuModifierGroup,
15
+ MenuSection,
16
+ } from "./types";
17
+
18
+ /** A raw Menus V1 entity as either transport returns it. */
19
+ export type Raw = Record<string, any>;
20
+ /** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
21
+ export type ImgSrc = (value: any, width: number, height: number) => string;
22
+
23
+ export const rawId = (raw: Raw | undefined | null): string => raw?._id ?? raw?.id ?? "";
24
+
25
+ /** Index raw entities by id (either spelling). */
26
+ export const byId = (arr: Raw[]): Map<string, Raw> => new Map(arr.map((e) => [rawId(e), e]));
27
+
28
+ const unique = (ids: string[]): string[] => [...new Set(ids.filter(Boolean))];
29
+
30
+ // Menu prices are decimal strings in the site currency with NO symbol; the response may carry a
31
+ // platform-formatted twin (formattedPrice) — prefer it, never invent a symbol.
32
+ export function displayPrice(priceInfo: Raw | undefined): string | null {
33
+ if (!priceInfo) return null;
34
+ return priceInfo.formattedPrice ?? priceInfo.price ?? null;
35
+ }
36
+
37
+ /** The variant ids the items reference — the second read's `variantIds`. */
38
+ export function collectVariantIds(items: Raw[]): string[] {
39
+ return unique(items.flatMap((i) => ((i.priceVariants?.variants ?? []) as Raw[]).map((v) => v.variantId)));
40
+ }
41
+
42
+ /** The modifier-group ids the items reference — the second read's `modifierGroupIds`. */
43
+ export function collectGroupIds(items: Raw[]): string[] {
44
+ return unique(items.flatMap((i) => ((i.modifierGroups ?? []) as Raw[]).map(rawId)));
45
+ }
46
+
47
+ /** The modifier ids the groups reference — the third read's `modifierIds`. */
48
+ export function collectModifierIds(groups: Raw[]): string[] {
49
+ return unique(groups.flatMap((g) => ((g.modifiers ?? []) as Raw[]).map(rawId)));
50
+ }
51
+
52
+ /** Whether any item carries a label — the labels list is read only then. */
53
+ export function anyLabels(items: Raw[]): boolean {
54
+ return items.some((i) => ((i.labels ?? []) as Raw[]).length > 0);
55
+ }
56
+
57
+ export interface MenuLookups {
58
+ variantById: Map<string, Raw>;
59
+ groupById: Map<string, Raw>;
60
+ modifierById: Map<string, Raw>;
61
+ labelById: Map<string, Raw>;
62
+ }
63
+
64
+ export function toItem(raw: Raw, lookups: MenuLookups, imgSrc: ImgSrc): MenuItem {
65
+ const variants: MenuItemVariant[] = ((raw.priceVariants?.variants ?? []) as Raw[])
66
+ .map((v): MenuItemVariant => ({
67
+ variantId: v.variantId ?? "",
68
+ name: lookups.variantById.get(v.variantId)?.name ?? "",
69
+ price: displayPrice(v.priceInfo) ?? v.price ?? "",
70
+ }))
71
+ .filter((v) => v.variantId);
72
+ const modifierGroups: MenuModifierGroup[] = ((raw.modifierGroups ?? []) as Raw[])
73
+ .map((ref) => lookups.groupById.get(rawId(ref)))
74
+ .filter((g): g is Raw => !!g)
75
+ .map((g): MenuModifierGroup => ({
76
+ id: rawId(g),
77
+ name: g.name ?? "",
78
+ required: g.rule?.required === true,
79
+ minSelections: g.rule?.minSelections ?? 0,
80
+ maxSelections: g.rule?.maxSelections ?? null,
81
+ modifiers: ((g.modifiers ?? []) as Raw[]).map((m): MenuModifier => ({
82
+ id: rawId(m),
83
+ name: lookups.modifierById.get(rawId(m))?.name ?? "",
84
+ preSelected: m.preSelected === true,
85
+ additionalCharge: m.additionalChargeInfo?.additionalCharge ?? "0",
86
+ inStock: lookups.modifierById.get(rawId(m))?.inStock !== false,
87
+ })),
88
+ }));
89
+ const labels: MenuItemLabel[] = ((raw.labels ?? []) as Raw[])
90
+ .map((ref) => lookups.labelById.get(rawId(ref)))
91
+ .filter((l): l is Raw => !!l)
92
+ .map((l): MenuItemLabel => ({ id: rawId(l), name: l.name ?? "", iconUrl: imgSrc(l.icon, 48, 48) }));
93
+ const price = displayPrice(raw.priceInfo);
94
+ return {
95
+ id: rawId(raw),
96
+ name: raw.name ?? "",
97
+ description: raw.description ?? "",
98
+ price,
99
+ marketPrice: price === null && variants.length === 0,
100
+ variants,
101
+ imageUrl: imgSrc(raw.image, 800, 800),
102
+ labels,
103
+ modifierGroups,
104
+ inStock: raw.orderSettings?.inStock !== false,
105
+ featured: raw.featured === true,
106
+ };
107
+ }
108
+
109
+ export function toSection(raw: Raw, itemById: Map<string, MenuItem>, imgSrc: ImgSrc): MenuSection {
110
+ return {
111
+ id: rawId(raw),
112
+ name: raw.name ?? "",
113
+ description: raw.description ?? "",
114
+ imageUrl: imgSrc(raw.image, 800, 800),
115
+ items: ((raw.itemIds ?? []) as string[]).map((iid) => itemById.get(iid)).filter((i): i is MenuItem => !!i),
116
+ };
117
+ }
118
+
119
+ export function toMenu(raw: Raw, sectionById: Map<string, Raw>, itemById: Map<string, MenuItem>, imgSrc: ImgSrc): MenuData {
120
+ return {
121
+ id: rawId(raw),
122
+ name: raw.name ?? "",
123
+ description: raw.description ?? "",
124
+ slug: raw.urlQueryParam ?? "",
125
+ sections: ((raw.sectionIds ?? []) as string[])
126
+ .map((sid) => sectionById.get(sid))
127
+ .filter((s): s is Raw => !!s)
128
+ .map((s) => toSection(s, itemById, imgSrc)),
129
+ };
130
+ }
131
+
132
+ /** The seven list responses' entity arrays, as either transport returns them. */
133
+ export interface MenuTreeParts {
134
+ menus: Raw[];
135
+ sections: Raw[];
136
+ items: Raw[];
137
+ variants: Raw[];
138
+ groups: Raw[];
139
+ modifiers: Raw[];
140
+ labels: Raw[];
141
+ }
142
+
143
+ /** The display-ordered tree: visible menus → their sections → their items, each item enriched. [] when no menus. */
144
+ export function assembleMenus(parts: MenuTreeParts, imgSrc: ImgSrc): MenuData[] {
145
+ if (!parts.menus.length) return [];
146
+ const lookups: MenuLookups = {
147
+ variantById: byId(parts.variants),
148
+ groupById: byId(parts.groups),
149
+ modifierById: byId(parts.modifiers),
150
+ labelById: byId(parts.labels),
151
+ };
152
+ const sectionById = byId(parts.sections);
153
+ const itemById = new Map<string, MenuItem>(parts.items.map((i) => [rawId(i), toItem(i, lookups, imgSrc)]));
154
+ return parts.menus.map((m) => toMenu(m, sectionById, itemById, imgSrc));
155
+ }
@@ -0,0 +1,89 @@
1
+ // Menu browsing as a framework-free store — the logic behind useMenus, usable from React (useMenus
2
+ // wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and render), or
3
+ // as the specification for a port. Same shape as order-store.ts: state, actions, subscribe/getState,
4
+ // emit after every change. The whole tree is fetched once; menu switching is local.
5
+ //
6
+ // SSR-friendly: seed with `initialMenus` (Astro frontmatter) and no fetch happens; a SPA passes
7
+ // nothing and `start()` fetches. One store per mounted menu surface: createMenuStore(), not a
8
+ // singleton (the order cart is the singleton — one per visitor).
9
+ import { fetchMenus } from "./menu";
10
+ import type { MenuData } from "./types";
11
+
12
+ export interface MenuStoreOptions {
13
+ initialMenus?: MenuData[];
14
+ }
15
+
16
+ /** Everything a menu surface renders from. Read it with getState() or through a subscription. */
17
+ export interface MenuState {
18
+ /** null while the first load is in flight — render skeletons, not an empty state. */
19
+ menus: MenuData[] | null;
20
+ /** Defaults to the first menu once loaded. */
21
+ activeMenuId: string | null;
22
+ /** The menu to render (its sections/items are display-ordered). */
23
+ activeMenu: MenuData | null;
24
+ error: string | null;
25
+ }
26
+
27
+ export interface MenuStore {
28
+ getState(): MenuState;
29
+ subscribe(listener: () => void): () => void;
30
+ /** Fetch the tree when no `initialMenus` were given. Call once when mounted. */
31
+ start(): void;
32
+ /** Stop reacting; drop a late response. */
33
+ stop(): void;
34
+ setActiveMenuId(id: string): void;
35
+ }
36
+
37
+ export function createMenuStore({ initialMenus }: MenuStoreOptions = {}): MenuStore {
38
+ let menus: MenuData[] | null = initialMenus ?? null;
39
+ let selectedId: string | null = initialMenus?.[0]?.id ?? null;
40
+ let error: string | null = null;
41
+ let started = false;
42
+ const listeners = new Set<() => void>();
43
+ let snapshot: MenuState | null = null;
44
+ const emit = () => {
45
+ snapshot = null;
46
+ for (const fn of listeners) fn();
47
+ };
48
+
49
+ function getState(): MenuState {
50
+ if (snapshot) return snapshot;
51
+ const activeMenu = menus ? (menus.find((m) => m.id === selectedId) ?? menus[0] ?? null) : null;
52
+ snapshot = { menus, activeMenuId: activeMenu?.id ?? null, activeMenu, error };
53
+ return snapshot;
54
+ }
55
+
56
+ return {
57
+ getState,
58
+ subscribe(fn) {
59
+ listeners.add(fn);
60
+ return () => listeners.delete(fn);
61
+ },
62
+ start() {
63
+ if (started) return;
64
+ started = true;
65
+ if (initialMenus) return;
66
+ fetchMenus()
67
+ .then((m) => {
68
+ if (!started) return;
69
+ menus = m;
70
+ selectedId ??= m[0]?.id ?? null;
71
+ emit();
72
+ })
73
+ .catch((e) => {
74
+ if (!started) return;
75
+ menus = [];
76
+ error = e instanceof Error ? e.message : String(e);
77
+ emit();
78
+ });
79
+ },
80
+ stop() {
81
+ started = false;
82
+ },
83
+ setActiveMenuId(id) {
84
+ if (id === selectedId) return;
85
+ selectedId = id;
86
+ emit();
87
+ },
88
+ };
89
+ }
@@ -1,10 +1,7 @@
1
- // Menu reads (Wix Restaurants Menus V1) — the only file that touches raw menu entities.
2
- // Everything it returns is a plain DTO from ./types. Copy as-is; extend by adding functions,
3
- // not by editing these.
4
- //
5
- // The hierarchy is Menu → Sections → Items wired by ID ARRAYS: display structure and order
6
- // live in menu.sectionIds / section.itemIds, NOT in the list* response order — fetchMenus
7
- // stitches in id-array order and drops dangling ids. Entity ids are `_id` (never `id`).
1
+ // Menu reads (Wix Restaurants Menus V1) over the SDK — the only file that touches raw menu
2
+ // entities on this transport. Everything it returns is a plain DTO from ./types. The rules and
3
+ // the tree assembly live in ./menu-core (shared with the REST twin in references/restaurants/rest/);
4
+ // this file is the transport only. Copy as-is; extend by adding functions, not by editing these.
8
5
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/menus/list-menus.md
9
6
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/sections/list-sections.md
10
7
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/items/list-items.md
@@ -23,15 +20,8 @@ import {
23
20
  } from "@wix/restaurants";
24
21
  import { wixModule } from "../sdk";
25
22
  import { imgSrc } from "../media";
26
- import type {
27
- MenuData,
28
- MenuItem,
29
- MenuItemLabel,
30
- MenuItemVariant,
31
- MenuModifier,
32
- MenuModifierGroup,
33
- MenuSection,
34
- } from "./types";
23
+ import { anyLabels, assembleMenus, collectGroupIds, collectModifierIds, collectVariantIds, type Raw } from "./menu-core";
24
+ import type { MenuData } from "./types";
35
25
 
36
26
  const menusApi = wixModule(menusModule);
37
27
  const sectionsApi = wixModule(sectionsModule);
@@ -41,63 +31,6 @@ const groupsApi = wixModule(itemModifierGroups);
41
31
  const modifiersApi = wixModule(itemModifiers);
42
32
  const labelsApi = wixModule(itemLabels);
43
33
 
44
- type Raw = Record<string, any>;
45
-
46
- // Menu prices are decimal strings in the site currency with NO symbol; the response may
47
- // carry a platform-formatted twin (formattedPrice) — prefer it, never invent a symbol.
48
- function displayPrice(priceInfo: Raw | undefined): string | null {
49
- if (!priceInfo) return null;
50
- return priceInfo.formattedPrice ?? priceInfo.price ?? null;
51
- }
52
-
53
- function toItem(
54
- raw: Raw,
55
- lookups: { variantById: Map<string, Raw>; groupById: Map<string, Raw>; modifierById: Map<string, Raw>; labelById: Map<string, Raw> },
56
- ): MenuItem {
57
- const variants: MenuItemVariant[] = (raw.priceVariants?.variants ?? [])
58
- .map((v: Raw): MenuItemVariant => ({
59
- variantId: v.variantId ?? "",
60
- name: lookups.variantById.get(v.variantId)?.name ?? "",
61
- price: displayPrice(v.priceInfo) ?? v.price ?? "",
62
- }))
63
- .filter((v: MenuItemVariant) => v.variantId);
64
- const modifierGroups: MenuModifierGroup[] = (raw.modifierGroups ?? [])
65
- .map((ref: Raw) => lookups.groupById.get(ref._id))
66
- .filter(Boolean)
67
- .map((g: Raw): MenuModifierGroup => ({
68
- id: g._id ?? "",
69
- name: g.name ?? "",
70
- required: g.rule?.required === true,
71
- minSelections: g.rule?.minSelections ?? 0,
72
- maxSelections: g.rule?.maxSelections ?? null,
73
- modifiers: (g.modifiers ?? []).map((m: Raw): MenuModifier => ({
74
- id: m._id ?? "",
75
- name: lookups.modifierById.get(m._id)?.name ?? "",
76
- preSelected: m.preSelected === true,
77
- additionalCharge: m.additionalChargeInfo?.additionalCharge ?? "0",
78
- inStock: lookups.modifierById.get(m._id)?.inStock !== false,
79
- })),
80
- }));
81
- const labels: MenuItemLabel[] = (raw.labels ?? [])
82
- .map((ref: Raw) => lookups.labelById.get(ref._id))
83
- .filter(Boolean)
84
- .map((l: Raw): MenuItemLabel => ({ id: l._id ?? "", name: l.name ?? "", iconUrl: imgSrc(l.icon, 48, 48) }));
85
- const price = displayPrice(raw.priceInfo);
86
- return {
87
- id: raw._id ?? "",
88
- name: raw.name ?? "",
89
- description: raw.description ?? "",
90
- price,
91
- marketPrice: price === null && variants.length === 0,
92
- variants,
93
- imageUrl: imgSrc(raw.image, 800, 800),
94
- labels,
95
- modifierGroups,
96
- inStock: raw.orderSettings?.inStock !== false,
97
- featured: raw.featured === true,
98
- };
99
- }
100
-
101
34
  /**
102
35
  * The site's full menu tree, assembled and display-ordered: visible menus → their sections →
103
36
  * their items, each item enriched with resolved price variants, modifier groups, and labels.
@@ -112,54 +45,27 @@ export async function fetchMenus(): Promise<MenuData[]> {
112
45
  ]);
113
46
  const menus: Raw[] = menusRes.menus ?? [];
114
47
  if (!menus.length) return [];
115
- const sections: Raw[] = sectionsRes.sections ?? [];
116
48
  const items: Raw[] = itemsRes.items ?? [];
117
-
118
- const variantIds: string[] = [
119
- ...new Set(items.flatMap((i: Raw) => (i.priceVariants?.variants ?? []).map((v: Raw) => v.variantId)).filter(Boolean)),
120
- ] as string[];
121
- const groupIds: string[] = [
122
- ...new Set(items.flatMap((i: Raw) => (i.modifierGroups ?? []).map((g: Raw) => g._id)).filter(Boolean)),
123
- ] as string[];
124
- const hasLabels = items.some((i: Raw) => (i.labels ?? []).length > 0);
125
-
49
+ const variantIds = collectVariantIds(items);
50
+ const groupIds = collectGroupIds(items);
126
51
  const [variantsRes, groupsRes, labelsRes]: Raw[] = await Promise.all([
127
52
  variantIds.length ? variantsApi.listVariants({ variantIds }) : Promise.resolve({ variants: [] }),
128
53
  groupIds.length ? groupsApi.listModifierGroups({ modifierGroupIds: groupIds }) : Promise.resolve({ modifierGroups: [] }),
129
- hasLabels ? labelsApi.listLabels() : Promise.resolve({ labels: [] }),
54
+ anyLabels(items) ? labelsApi.listLabels() : Promise.resolve({ labels: [] }),
130
55
  ]);
131
56
  const groups: Raw[] = groupsRes.modifierGroups ?? [];
132
- const modifierIds: string[] = [
133
- ...new Set(groups.flatMap((g: Raw) => (g.modifiers ?? []).map((m: Raw) => m._id)).filter(Boolean)),
134
- ] as string[];
135
- const modifiersRes: Raw = modifierIds.length
136
- ? await modifiersApi.listModifiers({ modifierIds })
137
- : { modifiers: [] };
138
-
139
- const byId = (arr: Raw[]) => new Map<string, Raw>(arr.map((e: Raw) => [e._id, e]));
140
- const lookups = {
141
- variantById: byId(variantsRes.variants ?? []),
142
- groupById: byId(groups),
143
- modifierById: byId(modifiersRes.modifiers ?? []),
144
- labelById: byId(labelsRes.labels ?? []),
145
- };
146
- const sectionById = byId(sections);
147
- const itemById = new Map<string, MenuItem>(items.map((i: Raw) => [i._id, toItem(i, lookups)]));
148
-
149
- return menus.map((m: Raw): MenuData => ({
150
- id: m._id ?? "",
151
- name: m.name ?? "",
152
- description: m.description ?? "",
153
- slug: m.urlQueryParam ?? "",
154
- sections: (m.sectionIds ?? [])
155
- .map((sid: string) => sectionById.get(sid))
156
- .filter(Boolean)
157
- .map((s: Raw): MenuSection => ({
158
- id: s._id ?? "",
159
- name: s.name ?? "",
160
- description: s.description ?? "",
161
- imageUrl: imgSrc(s.image, 800, 800),
162
- items: (s.itemIds ?? []).map((iid: string) => itemById.get(iid)).filter(Boolean) as MenuItem[],
163
- })),
164
- }));
57
+ const modifierIds = collectModifierIds(groups);
58
+ const modifiersRes: Raw = modifierIds.length ? await modifiersApi.listModifiers({ modifierIds }) : { modifiers: [] };
59
+ return assembleMenus(
60
+ {
61
+ menus,
62
+ sections: sectionsRes.sections ?? [],
63
+ items,
64
+ variants: variantsRes.variants ?? [],
65
+ groups,
66
+ modifiers: modifiersRes.modifiers ?? [],
67
+ labels: labelsRes.labels ?? [],
68
+ },
69
+ imgSrc,
70
+ );
165
71
  }