@wix/agent-skills 1.19.2 → 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 (215) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
  3. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
  4. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
  5. package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
  6. package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
  7. package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
  8. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
  9. package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
  10. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
  11. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  12. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
  13. package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
  14. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
  15. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
  16. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
  17. package/skills/wix-headless/references/CAPABILITIES.md +2 -2
  18. package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
  19. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
  20. package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
  21. package/skills/wix-headless-fast/SKILL.md +200 -34
  22. package/skills/wix-headless-fast/entry/skill.md +10 -2
  23. package/skills/wix-headless-fast/install/attach.mjs +253 -0
  24. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  25. package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
  26. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
  27. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  28. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  29. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  30. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  32. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  33. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  34. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  35. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  36. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  37. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  38. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  39. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  40. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  41. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
  42. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  43. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  44. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  48. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  49. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  50. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  51. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  52. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  53. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  54. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  55. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
  56. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  57. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  59. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  60. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  61. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  62. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  63. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  64. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  66. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  67. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  68. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  69. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  71. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  74. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  75. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  76. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  77. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  78. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  79. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
  80. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  85. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  86. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  87. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  88. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
  100. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  104. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  107. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  112. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  113. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  114. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
  115. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  116. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  117. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  118. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  119. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  120. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  122. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  126. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  127. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  129. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
  130. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  131. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  132. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  133. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  134. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  135. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  136. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  137. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  142. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  143. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  144. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  145. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  146. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  147. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
  148. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  149. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  150. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  151. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  152. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
  153. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
  154. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
  155. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  156. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
  157. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
  158. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
  159. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  160. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
  161. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
  162. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
  163. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
  166. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
  167. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  168. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  169. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
  170. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
  171. package/skills/wix-manage/SKILL.md +4 -1
  172. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  173. package/skills/wix-manage/references/events/create-wix-event.md +46 -0
  174. package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
  175. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
  176. package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
  177. package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
  178. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
  179. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
  180. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  181. package/skills/wix-vibe-headless/platforms/base44.md +19 -4
  182. package/skills/wix-vibe-headless/platforms/generic.md +6 -0
  183. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
  184. package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
  185. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
  186. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
  187. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
  188. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
  189. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
  190. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
  191. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
  192. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
  193. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
  194. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
  195. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
  196. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
  197. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
  198. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
  199. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
  200. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
  201. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
  202. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
  203. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
  204. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
  205. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
  206. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
  207. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
  208. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
  209. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
  210. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
  211. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
  212. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
  213. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  214. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  215. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -1,117 +1,35 @@
1
- // The whole registration state machine for one event, branched on event.registrationType:
2
- // TICKETING loads the tier picker and checkout() reserves → redirects to Wix's hosted
3
- // checkout; RSVP is the built-in name+email form submitted in place. All correctness (the
4
- // visitor-public tier read, the reservation payload, the redirect callbacks, rsvpV2) lives
5
- // in the data layer — this hook orchestrates; you own how it looks.
6
- import { useEffect, useMemo, useState } from "react";
7
- import { fetchTicketTiers, startTicketCheckout, submitRsvp } from "../../wix/events/registration";
8
- import type { EventDetail, RegistrationResult, TicketTier } from "../../wix/events/types";
1
+ // React binding of the registration store (wix/events/registration-store.ts) — the whole
2
+ // registration state machine for one event lives there, framework-free, branched on
3
+ // event.registrationType: TICKETING loads the tier picker and checkout() reserves → redirects to
4
+ // Wix's hosted checkout; RSVP is the built-in name+email form submitted in place. All correctness
5
+ // (the visitor-public tier read, the reservation payload, the redirect callbacks, rsvpV2) lives
6
+ // in the data layer; you own how it looks. One store per event — a new event id gets a fresh one.
7
+ import { useEffect, useRef, useSyncExternalStore } from "react";
8
+ import {
9
+ createRegistrationStore,
10
+ type RegistrationState,
11
+ type RegistrationStore,
12
+ } from "../../wix/events/registration-store";
13
+ import type { EventDetail } from "../../wix/events/types";
9
14
 
10
- export interface UseEventRegistration {
11
- /** TICKETING: tiers for the picker — null while loading (skeletons), [] honest empty. Other types: []. */
12
- tiers: TicketTier[] | null;
13
- /** Selected quantity per tier id (0 when untouched). */
14
- quantities: Record<string, number>;
15
- /** Clamped to 0..limitPerCheckout; ignored for tiers not on sale (saleStatus gate). */
16
- setQuantity: (tierId: string, quantity: number) => void;
17
- ticketCount: number;
18
- /** True when ≥ 1 ticket is selected — gate the checkout CTA on this. */
19
- canCheckout: boolean;
20
- /** Reserves + redirects to the Wix-hosted checkout. On "redirect" the browser is navigating. */
21
- checkout: () => Promise<RegistrationResult>;
22
- /** RSVP form state — the built-in fields, exactly these. */
23
- rsvpValues: { firstName: string; lastName: string; email: string };
24
- setRsvpValue: (field: "firstName" | "lastName" | "email", value: string) => void;
25
- /** True when every RSVP field is filled — gate the RSVP CTA on this. */
26
- canRsvp: boolean;
27
- /** attending=false only when event.rsvpResponseType is "YES_AND_NO". */
28
- rsvp: (attending?: boolean) => Promise<RegistrationResult>;
29
- submitting: boolean;
30
- /** Set after an RSVP completes (kind "rsvpConfirmed"; status may be "WAITLIST"). */
31
- confirmed: RegistrationResult | null;
32
- error: string | null;
33
- }
15
+ export type UseEventRegistration = RegistrationState &
16
+ Pick<RegistrationStore, "setQuantity" | "setRsvpValue" | "checkout" | "rsvp">;
34
17
 
35
18
  export function useEventRegistration(event: EventDetail): UseEventRegistration {
36
- const ticketed = event.registrationType === "TICKETING";
37
- const [tiers, setTiers] = useState<TicketTier[] | null>(ticketed ? null : []);
38
- const [quantities, setQuantities] = useState<Record<string, number>>({});
39
- const [rsvpValues, setRsvpValues] = useState({ firstName: "", lastName: "", email: "" });
40
- const [submitting, setSubmitting] = useState(false);
41
- const [confirmed, setConfirmed] = useState<RegistrationResult | null>(null);
42
- const [error, setError] = useState<string | null>(null);
43
-
19
+ const key = `${event.id}:${event.registrationType}`;
20
+ const ref = useRef<{ key: string; store: RegistrationStore } | null>(null);
21
+ if (!ref.current || ref.current.key !== key) ref.current = { key, store: createRegistrationStore(event) };
22
+ const store = ref.current.store;
44
23
  useEffect(() => {
45
- if (!ticketed) return;
46
- let alive = true;
47
- setTiers(null);
48
- setQuantities({});
49
- fetchTicketTiers(event.id)
50
- .then((t) => alive && setTiers(t))
51
- .catch((e) => {
52
- if (!alive) return;
53
- setTiers([]);
54
- setError(e instanceof Error ? e.message : String(e));
55
- });
56
- return () => {
57
- alive = false;
58
- };
59
- // eslint-disable-next-line react-hooks/exhaustive-deps
60
- }, [event.id, ticketed]);
61
-
62
- const setQuantity = (tierId: string, quantity: number) => {
63
- const tier = (tiers ?? []).find((t) => t.id === tierId);
64
- if (!tier || tier.saleStatus !== "SALE_STARTED") return;
65
- const clamped = Math.max(0, Math.min(quantity, tier.limitPerCheckout || 20));
66
- setQuantities((q) => ({ ...q, [tierId]: clamped }));
67
- };
68
-
69
- const ticketCount = useMemo(
70
- () => Object.values(quantities).reduce((sum, q) => sum + q, 0),
71
- [quantities],
72
- );
73
- const canRsvp = (["firstName", "lastName", "email"] as const).every(
74
- (f) => rsvpValues[f].trim().length > 0,
75
- );
76
-
77
- async function run(op: () => Promise<RegistrationResult>): Promise<RegistrationResult> {
78
- setSubmitting(true);
79
- setError(null);
80
- try {
81
- const result = await op();
82
- if (result.kind === "redirect") {
83
- window.location.href = result.url;
84
- } else {
85
- setConfirmed(result);
86
- }
87
- return result;
88
- } catch (e) {
89
- setError(e instanceof Error ? e.message : String(e));
90
- throw e;
91
- } finally {
92
- setSubmitting(false);
93
- }
94
- }
95
-
24
+ store.start();
25
+ return () => store.stop();
26
+ }, [store]);
27
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
96
28
  return {
97
- tiers,
98
- quantities,
99
- setQuantity,
100
- ticketCount,
101
- canCheckout: ticketCount > 0,
102
- checkout: () =>
103
- run(() =>
104
- startTicketCheckout(
105
- event,
106
- Object.entries(quantities).map(([tierId, quantity]) => ({ tierId, quantity })),
107
- ),
108
- ),
109
- rsvpValues,
110
- setRsvpValue: (field, value) => setRsvpValues((v) => ({ ...v, [field]: value })),
111
- canRsvp,
112
- rsvp: (attending = true) => run(() => submitRsvp(event.id, rsvpValues, attending ? "YES" : "NO")),
113
- submitting,
114
- confirmed,
115
- error,
29
+ ...state,
30
+ setQuantity: store.setQuantity,
31
+ setRsvpValue: store.setRsvpValue,
32
+ checkout: store.checkout,
33
+ rsvp: store.rsvp,
116
34
  };
117
35
  }
@@ -1,59 +1,24 @@
1
- // Events listing + category filter. SSR-friendly: pass server-fetched data as `initialEvents`
2
- // (Astro frontmatter / server component) and no client fetch happens; a SPA passes nothing.
3
- // The category menu is DERIVED from the loaded events (the categories management API is
4
- // admin-scope — visitors can't query it); filtering is client-side.
5
- import { useEffect, useMemo, useState } from "react";
6
- import { fetchEvents } from "../../wix/events/events";
7
- import type { EventSummary } from "../../wix/events/types";
1
+ // React binding of the events store (wix/events/events-store.ts) — the listing state machine
2
+ // lives there, framework-free; this hook subscribes to one instance per mounted listing and
3
+ // exposes its state and actions under one name. SSR-friendly: pass server-fetched data as
4
+ // `initialEvents` (Astro frontmatter / server component) and no client fetch happens; a SPA
5
+ // passes nothing. Astro islands and React SPAs use this; a static page, Vue, or Svelte uses the
6
+ // store directly.
7
+ import { useEffect, useRef, useSyncExternalStore } from "react";
8
+ import { createEventsStore, type EventsState, type EventsStore, type EventsStoreOptions } from "../../wix/events/events-store";
8
9
 
9
- export interface UseEventsOptions {
10
- initialEvents?: EventSummary[];
11
- }
12
-
13
- export interface UseEvents {
14
- /** null while the first load is in flight — render skeletons, not an empty state. */
15
- events: EventSummary[] | null;
16
- /** Unique assigned categories in listing order — render a filter bar only when > 1. */
17
- categories: { id: string; name: string }[];
18
- activeCategoryId: string | null;
19
- setActiveCategoryId: (id: string | null) => void;
20
- error: string | null;
21
- }
10
+ export type UseEventsOptions = EventsStoreOptions;
22
11
 
23
- export function useEvents({ initialEvents }: UseEventsOptions = {}): UseEvents {
24
- const [all, setAll] = useState<EventSummary[] | null>(initialEvents ?? null);
25
- const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
26
- const [error, setError] = useState<string | null>(null);
12
+ export type UseEvents = EventsState & Pick<EventsStore, "setActiveCategoryId">;
27
13
 
14
+ export function useEvents(options: UseEventsOptions = {}): UseEvents {
15
+ const ref = useRef<EventsStore | null>(null);
16
+ if (!ref.current) ref.current = createEventsStore(options);
17
+ const store = ref.current;
28
18
  useEffect(() => {
29
- let alive = true;
30
- if (!initialEvents) {
31
- fetchEvents()
32
- .then((e) => alive && setAll(e))
33
- .catch((e) => {
34
- if (!alive) return;
35
- setAll([]);
36
- setError(e instanceof Error ? e.message : String(e));
37
- });
38
- }
39
- return () => {
40
- alive = false;
41
- };
42
- // eslint-disable-next-line react-hooks/exhaustive-deps
43
- }, []);
44
-
45
- const categories = useMemo(() => {
46
- const seen = new Map<string, string>();
47
- for (const e of all ?? []) for (const c of e.categories) if (!seen.has(c.id)) seen.set(c.id, c.name);
48
- return [...seen.entries()].map(([id, name]) => ({ id, name }));
49
- }, [all]);
50
-
51
- const events = useMemo(() => {
52
- if (all === null) return null;
53
- return activeCategoryId
54
- ? all.filter((e) => e.categories.some((c) => c.id === activeCategoryId))
55
- : all;
56
- }, [all, activeCategoryId]);
57
-
58
- return { events, categories, activeCategoryId, setActiveCategoryId, error };
19
+ store.start();
20
+ return () => store.stop();
21
+ }, [store]);
22
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
23
+ return { ...state, setActiveCategoryId: store.setActiveCategoryId };
59
24
  }
@@ -0,0 +1,130 @@
1
+ // Event rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./events.ts (the
2
+ // SDK, managed Astro and React) and the REST twin in references/events/rest/events.ts (fetch, a
3
+ // static site or a port to another language). Every rule about statuses, fieldsets, prices,
4
+ // dates, images and categories lives HERE, once. A raw event may come from the SDK (`_id`,
5
+ // mainImage as a wix:image string) or from REST (`id`, mainImage as { id, url }); the mappers
6
+ // accept both. Imports are type-only so a strip to JS emits no imports.
7
+ import type { EventDetail, EventSummary, RegistrationType } from "./types";
8
+
9
+ /** A raw Events V3 entity as either transport returns it. */
10
+ export type Raw = Record<string, any>;
11
+ /** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
12
+ export type ImgSrc = (value: any, width: number, height: number) => string;
13
+
14
+ /** The Wix Events app id (reference only — frontend calls need no app-id constant). */
15
+ export const EVENTS_APP_ID = "140603ad-af8d-84a5-2c80-a0f60cb47351";
16
+
17
+ export const rawId = (raw: Raw | undefined | null): string => raw?._id ?? raw?.id ?? "";
18
+
19
+ // Fieldsets are opt-in: without REGISTRATION there's no registration type to branch on;
20
+ // without CATEGORIES the category names never arrive; DETAILS carries the formatted date
21
+ // and mainImage; TEXTS carries the long description; URLS the Wix event page url.
22
+ export const LIST_FIELDS = ["DETAILS", "REGISTRATION", "CATEGORIES"] as const;
23
+ export const DETAIL_FIELDS = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "CATEGORIES"] as const;
24
+
25
+ /** Only these are listed and linked — a past or canceled event isn't registerable. */
26
+ export const LIVE_STATUSES = ["UPCOMING", "STARTED"] as const;
27
+ export const START_DATE_FIELD = "dateAndTimeSettings.startDate";
28
+
29
+ /**
30
+ * The Query Events body for the listing: live statuses, soonest first, a positive limit.
31
+ * `paging.limit` defaults to 0 — a bare query answers `total: 2, events: []` with no error — so
32
+ * the limit is always sent. The SDK builder spells the same three parts.
33
+ */
34
+ export function listQuery({ limit = 100 } = {}): Raw {
35
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1000) throw new Error("limit must be between 1 and 1000.");
36
+ return {
37
+ filter: { status: { $in: [...LIVE_STATUSES] } },
38
+ sort: [{ fieldName: START_DATE_FIELD, order: "ASC" }],
39
+ paging: { limit },
40
+ };
41
+ }
42
+
43
+ /** "From €45" (ticketed), "Free" (RSVP / free tickets), "" when nothing applies. */
44
+ export function lowestPriceLabel(reg: Raw): string {
45
+ const type = reg.type ?? reg.initialType;
46
+ if (type === "RSVP") return "Free";
47
+ if (type !== "TICKETING") return "";
48
+ const lowest: Raw | undefined = reg.tickets?.lowestPrice;
49
+ if (!lowest) return "";
50
+ if (Number(lowest.value ?? 0) === 0) return "Free";
51
+ if (lowest.formattedValue) return `From ${lowest.formattedValue}`;
52
+ try {
53
+ return `From ${new Intl.NumberFormat(undefined, { style: "currency", currency: lowest.currency || "USD" }).format(Number(lowest.value))}`;
54
+ } catch {
55
+ return `From ${lowest.value} ${lowest.currency ?? ""}`.trim();
56
+ }
57
+ }
58
+
59
+ // event.description is Ricos rich content ({ nodes: [...] }), NOT a string — calling string
60
+ // methods on it crashes the page. Extract plain paragraphs; detailedDescription (legacy plain
61
+ // text) is the fallback.
62
+ export function toParagraphs(rich: Raw | undefined | null, legacy: string | undefined): string[] {
63
+ const collect = (nodes: Raw[] | undefined): string =>
64
+ (nodes ?? [])
65
+ .map((n: Raw) => (typeof n.textData?.text === "string" ? n.textData.text : collect(n.nodes)))
66
+ .join("");
67
+ const out: string[] = [];
68
+ for (const node of rich?.nodes ?? []) {
69
+ const text = collect([node]).trim();
70
+ if (text) out.push(text);
71
+ }
72
+ if (!out.length && legacy) out.push(legacy);
73
+ return out;
74
+ }
75
+
76
+ /**
77
+ * The main image in the form imgSrc scales: the SDK carries it as a `wix:image://` string; REST
78
+ * as `{ id, url, altText }` — rebuilt into the id form so both transports resolve one URL shape.
79
+ */
80
+ export function mainImageOf(raw: Raw): unknown {
81
+ const m = raw.mainImage;
82
+ if (m && typeof m === "object" && typeof m.id === "string" && m.id) {
83
+ return `wix:image://v1/${m.id}/${encodeURIComponent(String(m.altText || "image"))}`;
84
+ }
85
+ return m;
86
+ }
87
+
88
+ export function toSummary(raw: Raw, imgSrc: ImgSrc): EventSummary {
89
+ const reg: Raw = raw.registration ?? {};
90
+ const dts: Raw = raw.dateAndTimeSettings ?? {};
91
+ return {
92
+ id: rawId(raw), // _id on the SDK, id on REST — never a bare .id on the SDK (undefined there)
93
+ slug: raw.slug ?? "",
94
+ title: raw.title ?? "",
95
+ shortDescription: raw.shortDescription ?? "",
96
+ dateLabel: dts.formatted?.dateAndTime ?? "",
97
+ startDateIso: dts.startDate ? new Date(dts.startDate).toISOString() : "",
98
+ locationName: raw.location?.name ?? "",
99
+ locationType: raw.location?.locationTbd
100
+ ? "TBD"
101
+ : ((raw.location?.type as "VENUE" | "ONLINE") ?? "TBD"),
102
+ imageUrl: imgSrc(mainImageOf(raw), 1200, 800),
103
+ // `type` is the current flavor (can become EXTERNAL later); initialType is the immutable
104
+ // seeded one — read type first.
105
+ registrationType: (reg.type ?? reg.initialType ?? "NONE") as RegistrationType,
106
+ priceLabel: lowestPriceLabel(reg),
107
+ // The API reports tickets.soldOut true on an RSVP event too (it has no tickets at all) — sold
108
+ // out is a ticketing signal only.
109
+ soldOut: (reg.type ?? reg.initialType) === "TICKETING" && reg.tickets?.soldOut === true,
110
+ // `categories` is absent on the typed Event (an SDK type gap) — the Raw boundary reads
111
+ // the runtime field the CATEGORIES fieldset populates.
112
+ categories: ((raw.categories?.categories ?? []) as Raw[])
113
+ .map((c) => ({ id: rawId(c), name: c.name ?? "" }))
114
+ .filter((c) => c.id),
115
+ };
116
+ }
117
+
118
+ export function toDetail(raw: Raw, imgSrc: ImgSrc): EventDetail {
119
+ const summary = toSummary(raw, imgSrc);
120
+ const reg: Raw = raw.registration ?? {};
121
+ return {
122
+ ...summary,
123
+ aboutParagraphs: toParagraphs(raw.description, raw.detailedDescription),
124
+ // Only OPEN_* statuses (OPEN_RSVP, OPEN_TICKETS, OPEN_EXTERNAL, …) accept registrations.
125
+ registrationOpen: typeof reg.status === "string" && reg.status.startsWith("OPEN_"),
126
+ rsvpResponseType: reg.rsvp?.responseType === "YES_AND_NO" ? "YES_AND_NO" : "YES_ONLY",
127
+ externalUrl: reg.external?.url ?? "",
128
+ addToCalendar: { google: raw.calendarUrls?.google ?? "", ics: raw.calendarUrls?.ics ?? "" },
129
+ };
130
+ }
@@ -0,0 +1,97 @@
1
+ // The events listing as a framework-free store — the logic behind useEvents, usable from React
2
+ // (useEvents wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and
3
+ // render), or as the specification for a port. State, actions, subscribe/getState, emit after
4
+ // every change.
5
+ //
6
+ // SSR-friendly: seed with `initialEvents` and they render at once with no client fetch; a SPA
7
+ // passes nothing and `start()` fetches. The category menu is DERIVED from the loaded events (the
8
+ // categories management API is admin-scope — visitors can't query it); filtering is client-side
9
+ // over the live list, which is small by definition (only UPCOMING/STARTED events are listed).
10
+ //
11
+ // One store per mounted listing (a page can hold the index and a "next up" rail):
12
+ // createEventsStore(), not a singleton.
13
+ import { fetchEvents } from "./events";
14
+ import type { EventSummary } from "./types";
15
+
16
+ export interface EventsStoreOptions {
17
+ initialEvents?: EventSummary[];
18
+ }
19
+
20
+ /** Everything a listing surface renders from. Read it with getState() or through a subscription. */
21
+ export interface EventsState {
22
+ /** null while the first load is in flight — render skeletons, not an empty state. */
23
+ events: EventSummary[] | null;
24
+ /** Unique assigned categories in listing order — render a filter bar only when > 1. */
25
+ categories: { id: string; name: string }[];
26
+ activeCategoryId: string | null;
27
+ error: string | null;
28
+ }
29
+
30
+ export interface EventsStore {
31
+ getState(): EventsState;
32
+ subscribe(listener: () => void): () => void;
33
+ /** Fetch when no `initialEvents` were given. Call once when mounted (a browser). */
34
+ start(): void;
35
+ /** Stop reacting; drop a late response. */
36
+ stop(): void;
37
+ setActiveCategoryId(id: string | null): void;
38
+ }
39
+
40
+ export function createEventsStore({ initialEvents }: EventsStoreOptions = {}): EventsStore {
41
+ let all: EventSummary[] | null = initialEvents ?? null;
42
+ let activeCategoryId: string | null = null;
43
+ let error: string | null = null;
44
+ let started = false;
45
+ const listeners = new Set<() => void>();
46
+ let snapshot: EventsState | null = null;
47
+ const emit = () => {
48
+ snapshot = null;
49
+ for (const fn of listeners) fn();
50
+ };
51
+
52
+ function getState(): EventsState {
53
+ if (snapshot) return snapshot;
54
+ const seen = new Map<string, string>();
55
+ for (const e of all ?? []) for (const c of e.categories) if (!seen.has(c.id)) seen.set(c.id, c.name);
56
+ snapshot = {
57
+ events: all === null ? null : activeCategoryId ? all.filter((e) => e.categories.some((c) => c.id === activeCategoryId)) : all,
58
+ categories: [...seen.entries()].map(([id, name]) => ({ id, name })),
59
+ activeCategoryId,
60
+ error,
61
+ };
62
+ return snapshot;
63
+ }
64
+
65
+ return {
66
+ getState,
67
+ subscribe(fn) {
68
+ listeners.add(fn);
69
+ return () => listeners.delete(fn);
70
+ },
71
+ start() {
72
+ if (started) return;
73
+ started = true;
74
+ if (initialEvents) return;
75
+ fetchEvents()
76
+ .then((list) => {
77
+ if (!started) return;
78
+ all = list;
79
+ emit();
80
+ })
81
+ .catch((e) => {
82
+ if (!started) return;
83
+ all = [];
84
+ error = e instanceof Error ? e.message : String(e);
85
+ emit();
86
+ });
87
+ },
88
+ stop() {
89
+ started = false;
90
+ },
91
+ setActiveCategoryId(id) {
92
+ if (id === activeCategoryId) return;
93
+ activeCategoryId = id;
94
+ emit();
95
+ },
96
+ };
97
+ }
@@ -1,99 +1,18 @@
1
- // Event reads (Wix Events V3, the `wixEventsV2` module) — the only file that touches raw
2
- // event entities. Everything it returns is a plain DTO from ./types. Copy as-is; extend by
3
- // adding functions, not by editing these.
1
+ // Event reads (Wix Events V3, the `wixEventsV2` module) over the SDK — the only file that touches
2
+ // raw event entities on this transport. Everything it returns is a plain DTO from ./types. The
3
+ // rules and mappers live in ./events-core (shared with the REST twin in references/events/rest/);
4
+ // this file is the transport only. Copy as-is; extend by adding functions, not by editing these.
4
5
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/query-events.md
5
6
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/get-event-by-slug.md
6
7
  import { wixEventsV2 } from "@wix/events";
7
8
  import { wixModule } from "../sdk";
8
9
  import { imgSrc } from "../media";
9
- import type { EventDetail, EventSummary, RegistrationType } from "./types";
10
+ import { DETAIL_FIELDS, EVENTS_APP_ID, LIST_FIELDS, LIVE_STATUSES, START_DATE_FIELD, rawId, toDetail, toSummary, type Raw } from "./events-core";
11
+ import type { EventDetail, EventSummary } from "./types";
10
12
 
11
- const events = wixModule(wixEventsV2);
12
-
13
- /** The Wix Events app id (reference only — frontend calls need no app-id constant). */
14
- export const EVENTS_APP_ID = "140603ad-af8d-84a5-2c80-a0f60cb47351";
15
-
16
- type Raw = Record<string, any>;
17
-
18
- // Fieldsets are opt-in: without REGISTRATION there's no registration type to branch on;
19
- // without CATEGORIES the category names never arrive; DETAILS carries the formatted date
20
- // and mainImage; TEXTS carries the long description.
21
- const LIST_FIELDS = ["DETAILS", "REGISTRATION", "CATEGORIES"];
22
- const DETAIL_FIELDS = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "CATEGORIES"];
23
-
24
- function lowestPriceLabel(reg: Raw): string {
25
- const type = reg.type ?? reg.initialType;
26
- if (type === "RSVP") return "Free";
27
- if (type !== "TICKETING") return "";
28
- const lowest: Raw | undefined = reg.tickets?.lowestPrice;
29
- if (!lowest) return "";
30
- if (Number(lowest.value ?? 0) === 0) return "Free";
31
- if (lowest.formattedValue) return `From ${lowest.formattedValue}`;
32
- try {
33
- return `From ${new Intl.NumberFormat(undefined, { style: "currency", currency: lowest.currency || "USD" }).format(Number(lowest.value))}`;
34
- } catch {
35
- return `From ${lowest.value} ${lowest.currency ?? ""}`.trim();
36
- }
37
- }
38
-
39
- // event.description is Ricos rich content ({ nodes: [...] }), NOT a string — calling string
40
- // methods on it crashes the page. Extract plain paragraphs; detailedDescription (legacy plain
41
- // text) is the fallback.
42
- function toParagraphs(rich: Raw | undefined, legacy: string | undefined): string[] {
43
- const collect = (nodes: Raw[] | undefined): string =>
44
- (nodes ?? [])
45
- .map((n: Raw) => (typeof n.textData?.text === "string" ? n.textData.text : collect(n.nodes)))
46
- .join("");
47
- const out: string[] = [];
48
- for (const node of rich?.nodes ?? []) {
49
- const text = collect([node]).trim();
50
- if (text) out.push(text);
51
- }
52
- if (!out.length && legacy) out.push(legacy);
53
- return out;
54
- }
13
+ export { EVENTS_APP_ID };
55
14
 
56
- function toSummary(raw: Raw): EventSummary {
57
- const reg: Raw = raw.registration ?? {};
58
- const dts: Raw = raw.dateAndTimeSettings ?? {};
59
- return {
60
- id: raw._id ?? "", // _id, never .id — `id` is a REST-view field and is undefined here
61
- slug: raw.slug ?? "",
62
- title: raw.title ?? "",
63
- shortDescription: raw.shortDescription ?? "",
64
- dateLabel: dts.formatted?.dateAndTime ?? "",
65
- startDateIso: dts.startDate ? new Date(dts.startDate).toISOString() : "",
66
- locationName: raw.location?.name ?? "",
67
- locationType: raw.location?.locationTbd
68
- ? "TBD"
69
- : ((raw.location?.type as "VENUE" | "ONLINE") ?? "TBD"),
70
- imageUrl: imgSrc(raw.mainImage, 1200, 800),
71
- // `type` is the current flavor (can become EXTERNAL later); initialType is the immutable
72
- // seeded one — read type first.
73
- registrationType: (reg.type ?? reg.initialType ?? "NONE") as RegistrationType,
74
- priceLabel: lowestPriceLabel(reg),
75
- soldOut: reg.tickets?.soldOut === true,
76
- // `categories` is absent on the typed Event (an SDK type gap) — the Raw boundary reads
77
- // the runtime field the CATEGORIES fieldset populates.
78
- categories: (raw.categories?.categories ?? [])
79
- .map((c: Raw) => ({ id: c._id ?? "", name: c.name ?? "" }))
80
- .filter((c: { id: string }) => c.id),
81
- };
82
- }
83
-
84
- function toDetail(raw: Raw): EventDetail {
85
- const summary = toSummary(raw);
86
- const reg: Raw = raw.registration ?? {};
87
- return {
88
- ...summary,
89
- aboutParagraphs: toParagraphs(raw.description, raw.detailedDescription),
90
- // Only OPEN_* statuses (OPEN_RSVP, OPEN_TICKETS, OPEN_EXTERNAL, …) accept registrations.
91
- registrationOpen: typeof reg.status === "string" && reg.status.startsWith("OPEN_"),
92
- rsvpResponseType: reg.rsvp?.responseType === "YES_AND_NO" ? "YES_AND_NO" : "YES_ONLY",
93
- externalUrl: reg.external?.url ?? "",
94
- addToCalendar: { google: raw.calendarUrls?.google ?? "", ics: raw.calendarUrls?.ics ?? "" },
95
- };
96
- }
15
+ const events = wixModule(wixEventsV2);
97
16
 
98
17
  /** List live (UPCOMING/STARTED) events, soonest first, mapped to grid-ready DTOs. */
99
18
  export async function fetchEvents({ limit = 100 } = {}): Promise<EventSummary[]> {
@@ -102,11 +21,11 @@ export async function fetchEvents({ limit = 100 } = {}): Promise<EventSummary[]>
102
21
  // which also returns zero events with no error.
103
22
  const res = await events
104
23
  .queryEvents({ fields: LIST_FIELDS as any })
105
- .in("status", ["UPCOMING", "STARTED"]) // never list or link a past event — it isn't registerable
106
- .ascending("dateAndTimeSettings.startDate")
24
+ .in("status", [...LIVE_STATUSES]) // never list or link a past event — it isn't registerable
25
+ .ascending(START_DATE_FIELD as any)
107
26
  .limit(limit)
108
27
  .find();
109
- return (res.items ?? []).map((e: Raw) => toSummary(e));
28
+ return (res.items ?? []).map((e: Raw) => toSummary(e, imgSrc));
110
29
  }
111
30
 
112
31
  /** Fetch one event by its URL slug. Null when not found. */
@@ -115,7 +34,7 @@ export async function fetchEventBySlug(slug: string): Promise<EventDetail | null
115
34
  // getEventBySlug returns a WRAPPED { event } — unlike getEvent below.
116
35
  const res: Raw = await events.getEventBySlug(slug, { fields: DETAIL_FIELDS as any });
117
36
  const raw = res?.event;
118
- return raw ? toDetail(raw as Raw) : null;
37
+ return raw ? toDetail(raw as Raw, imgSrc) : null;
119
38
  } catch {
120
39
  return null;
121
40
  }
@@ -129,7 +48,7 @@ export async function fetchEventBySlug(slug: string): Promise<EventDetail | null
129
48
  export async function fetchEventById(eventId: string): Promise<EventDetail | null> {
130
49
  try {
131
50
  const raw: Raw = await events.getEvent(eventId, { fields: DETAIL_FIELDS as any });
132
- return raw?._id ? toDetail(raw) : null;
51
+ return rawId(raw) ? toDetail(raw, imgSrc) : null;
133
52
  } catch {
134
53
  return null;
135
54
  }