@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
@@ -0,0 +1,42 @@
1
+ // Post reads over REST — the twin of app/wix/blog/posts.ts. Same exports, same DTOs; the rules and
2
+ // mappers come from posts-core (the SAME file the SDK transport uses, deployed flat next to this one
3
+ // by deploy.mjs --stack static), so this file is only the transport: one fetch with a literal body
4
+ // per function. Every call here is safe from a browser with a visitor token — only PUBLISHED posts
5
+ // come back to it. Porting: keep the paths, keep the bodies, port the core once.
6
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/posts-stats/query-posts.md
7
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/posts-stats/get-post-by-slug.md
8
+ import { wixRequest } from "./client.js";
9
+ import { imgSrc } from "./media.js";
10
+ import { BLOG_APP_ID, DETAIL_FIELDSETS, feedQuery, postMetaLine, slugQuery, toDetail, toSummary, type FetchPostsOptions, type Raw } from "./posts-core.js";
11
+ import type { PostDetail, PostPage } from "./types.js";
12
+
13
+ export { BLOG_APP_ID, postMetaLine, type FetchPostsOptions };
14
+
15
+ const POSTS_QUERY = "/blog/v3/posts/query";
16
+
17
+ /**
18
+ * One page of published posts, newest first (pinned posts lead); `nextCursor` continues the same
19
+ * query — send it alone, the filters ride inside it.
20
+ * POST /blog/v3/posts/query { query: { filter?, sort, cursorPaging: { limit } } } — first page
21
+ * POST /blog/v3/posts/query { query: { cursorPaging: { limit, cursor } } } — later pages
22
+ */
23
+ export async function fetchPosts(o: FetchPostsOptions = {}): Promise<PostPage> {
24
+ const res = await wixRequest<Raw>(POSTS_QUERY, { body: { query: feedQuery(o) } });
25
+ const paging: Raw = res?.pagingMetadata ?? {};
26
+ return {
27
+ posts: ((res?.posts ?? []) as Raw[]).map((p) => toSummary(p, imgSrc)),
28
+ nextCursor: paging.hasNext ? (paging.cursors?.next ?? null) : null,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * One post by URL slug with the full body (the RICH_CONTENT + CONTENT_TEXT fieldsets — without
34
+ * them richContent and contentText are absent). Null when the slug resolves to nothing — a real
35
+ * 404 for the page, never a fallback to another post.
36
+ * POST /blog/v3/posts/query { fieldsets: ["RICH_CONTENT", "CONTENT_TEXT"], query: { filter: { slug: { $eq } }, cursorPaging: { limit: 1 } } }
37
+ */
38
+ export async function fetchPostBySlug(slug: string): Promise<PostDetail | null> {
39
+ const res = await wixRequest<Raw>(POSTS_QUERY, { body: { fieldsets: DETAIL_FIELDSETS, query: slugQuery(slug) } });
40
+ const raw: Raw | undefined = res?.posts?.[0];
41
+ return raw ? toDetail(raw, imgSrc) : null;
42
+ }
@@ -0,0 +1,62 @@
1
+ // Category + tag reads over REST — the twin of app/wix/blog/taxonomy.ts. Same exports, same DTOs;
2
+ // the mappers come from taxonomy-core (the SAME file the SDK transport uses, deployed flat next to
3
+ // this one). Taxonomy queries page by OFFSET (`paging`), unlike posts (`cursorPaging`); a blog's
4
+ // taxonomy fits in one read of 100. All four calls are visitor-safe.
5
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/category/query-categories.md
6
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/category/get-category-by-slug.md
7
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/tags/query-tags.md
8
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/blog/tags/get-tag-by-slug.md
9
+ import { wixRequest } from "./client.js";
10
+ import { imgSrc } from "./media.js";
11
+ import { CATEGORY_SORT, TAG_SORT, TAXONOMY_LIMIT, toCategory, toTag } from "./taxonomy-core.js";
12
+ import type { Raw } from "./posts-core.js";
13
+ import type { BlogCategory, BlogTag } from "./types.js";
14
+
15
+ /**
16
+ * Categories in menu order. Non-fatal: [] on failure (the feed renders without its filter row).
17
+ * POST /blog/v3/categories/query { query: { sort: [{ fieldName: "displayPosition", order: "ASC" }], paging: { limit: 100 } } }
18
+ */
19
+ export async function fetchBlogCategories(): Promise<BlogCategory[]> {
20
+ try {
21
+ const res = await wixRequest<Raw>("/blog/v3/categories/query", { body: { query: { sort: CATEGORY_SORT, paging: { limit: TAXONOMY_LIMIT } } } });
22
+ return ((res?.categories ?? []) as Raw[]).map((c) => toCategory(c, imgSrc)).filter((c) => c.id);
23
+ } catch {
24
+ return [];
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Tags, most-published-posts first. Non-fatal: [] on failure.
30
+ * POST /blog/v3/tags/query { query: { sort: [{ fieldName: "publishedPostCount", order: "DESC" }], paging: { limit: 100 } } }
31
+ */
32
+ export async function fetchBlogTags(): Promise<BlogTag[]> {
33
+ try {
34
+ const res = await wixRequest<Raw>("/blog/v3/tags/query", { body: { query: { sort: TAG_SORT, paging: { limit: TAXONOMY_LIMIT } } } });
35
+ return ((res?.tags ?? []) as Raw[]).map(toTag).filter((t) => t.id);
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
40
+
41
+ // The two by-slug getters answer in DIFFERENT envelopes ({ category } and { tag }); a missing slug
42
+ // is a 404 — mapped to null here, as the SDK transport does.
43
+
44
+ /** One category by its URL slug. Null when not found. GET /blog/v3/categories/slugs/{slug} */
45
+ export async function fetchCategoryBySlug(slug: string): Promise<BlogCategory | null> {
46
+ try {
47
+ const res = await wixRequest<Raw>(`/blog/v3/categories/slugs/${encodeURIComponent(slug)}`, { method: "GET" });
48
+ return res?.category ? toCategory(res.category, imgSrc) : null;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /** One tag by its URL slug. Null when not found. GET /blog/v3/tags/slugs/{slug} */
55
+ export async function fetchTagBySlug(slug: string): Promise<BlogTag | null> {
56
+ try {
57
+ const res = await wixRequest<Raw>(`/blog/v3/tags/slugs/${encodeURIComponent(slug)}`, { method: "GET" });
58
+ return res?.tag ? toTag(res.tag) : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
@@ -62,6 +62,15 @@ feed without covers looks broken.
62
62
  **Seeding is additive — never delete or overwrite existing content**; ask first if a cleanup
63
63
  seems needed.
64
64
 
65
+ ## Supplied content
66
+
67
+ The general rules are in `references/shared/SUPPLIED-CONTENT.md`. For a blog, each post the user
68
+ hands over is one entry. Its title becomes `title`; its body becomes `content` blocks, one block per
69
+ paragraph, heading or list, whether the body arrived as text, HTML or Markdown; a category becomes
70
+ `category` (and joins the `categories` list); tags become `tags`; their cover image becomes
71
+ `coverImageUrl`. A post with no body is a question for the user. Authors and publish dates are
72
+ not seeded — say so.
73
+
65
74
  ## Escape hatch — individual functions
66
75
  `setupBlog` composes exported steps — `installBlogApp`, `getAuthorMemberId`, `createPosts`,
67
76
  `createCategories`, `createTags`, `importImage`, `attachPostCovers`, plus `makeCtx()` —
@@ -8,18 +8,25 @@ the home page and the brand. You never write booking logic; you never skip desig
8
8
 
9
9
  ## The file map (deployed into `src/`)
10
10
 
11
- **Don't read the shipped files** — this table and the contracts below are everything you
12
- need. Open a shipped file's source only on a real fallback (runtime error / uncovered field),
13
- or to read a reference component's pattern.
11
+ **On Astro and React the shipped files are tested and work as they are** — this table and the
12
+ contracts below are everything you need to use them, so don't spend the run reading their source;
13
+ wire them and build your surfaces. Reading them is the right move when something is off (a runtime
14
+ error, a field this playbook doesn't cover) or when the brief wants a behaviour they don't offer —
15
+ then read the file that owns it and change or extend it. On `lib`, `static`, and a port the
16
+ components don't deploy at all, and each wiring section below opens with the files to read before
17
+ writing their equivalents. Files you edit: `SiteLayout.astro`, `styles/global.css`, and the two
18
+ pages' island imports. Files you **create**: your listing and booking components, plus your home page.
14
19
 
15
20
  | file | what it is |
16
21
  |---|---|
17
- | `wix/config.ts` · `wix/sdk.ts` · `wix/media.ts` · `wix/money.ts` | shared auth seam + helpers (deploy configures; nothing to set) |
18
- | `wix/bookings/types.ts` | the DTOs (`ServiceSummary`, `ServiceDetail`, `Slot`, `BookingFormField`, `BookingResult`) — contracts below |
19
- | `wix/bookings/services.ts` | `fetchServices`, `fetchServiceBySlug`, `fetchBookingCategories` |
20
- | `wix/bookings/booking.ts` | `fetchSlots`, `fetchBookingForm`, `bookService` — the exact booking sequence lives here |
21
- | `hooks/bookings/useServices.ts` | listing + category filter — contract below |
22
- | `hooks/bookings/useBookingFlow.ts` | the whole booking state machine — contract below |
22
+ | `wix/config.ts` · `wix/sdk.ts` | shared auth seam (deploy configures it — nothing to set by hand) |
23
+ | `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes)` — every `<img>` attribute for a DTO image (`src`, `srcSet`, `sizes`, lazy): `<img {...imgAttrs(s.imageUrl, "33vw")} alt={s.name} />`; `imgSrc()` / `imgSrcSet()` / `formatMoney()` underneath |
24
+ | `wix/bookings/types.ts` | the DTOs (`ServiceSummary`, `ServiceDetail`, `BookingCategory`, `Slot`, `BookingFormField`, `BookingResult`) — contracts below |
25
+ | `wix/bookings/services.ts` | `fetchServices`, `fetchServiceBySlug`, `fetchBookingCategories` — the transport; the rules and DTO mappers are in `services-core.ts` beside it (shared with the REST layer) |
26
+ | `wix/bookings/booking.ts` | `fetchSlots`, `fetchBookingForm`, `bookService` — the transport; the request bodies, the slot and form mappers, and the checkout-or-place rule are in `booking-core.ts` beside it (shared with the REST layer) |
27
+ | `wix/bookings/services-store.ts` · `booking-flow-store.ts` | the listing and booking state machines, framework-free (`createServicesStore()`, `createBookingFlowStore(service)` — `getState`/`subscribe` + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
28
+ | `hooks/bookings/useServices.ts` | React binding of `services-store.ts`: listing + category filter — contract below |
29
+ | `hooks/bookings/useBookingFlow.ts` | React binding of `booking-flow-store.ts`: the whole booking state machine — contract below |
23
30
  | `components/bookings/ServicesView.tsx` (+ `ServiceCard`) · `ServiceBookingView.tsx` | **REFERENCE implementations** — correct, plain; build your own instead of shipping them |
24
31
  | `styles/global.css` | the design system: Tailwind v4 + the `@theme` token block (shared across verticals) |
25
32
 
@@ -45,30 +52,79 @@ Astro stack additionally gets:
45
52
  Plus the **theme** (`@theme` block, one edit) and the **chrome** (`SiteLayout`, one pass).
46
53
  Style everything with Tailwind utilities on the tokens.
47
54
 
55
+ ### What a complete booking site shows (recommended defaults)
56
+
57
+ Defaults for a brief that says nothing about them; the prompt wins where it differs. Look at the
58
+ services before designing (appointments vs classes, categories, staff, prices, images) and design
59
+ for this business, not a stereotype of its category. Then, by default:
60
+
61
+ - **Home:** what the business offers and one booking action in the first screen; real services
62
+ under truthful headings; not a repeat of the listing.
63
+ - **Listing:** a real service card — image, name, duration and price, link — in the first screen;
64
+ category pills only when there is more than one category; `services === null` → skeleton tiles,
65
+ `[]` → your honest empty state.
66
+ - **Service page:** name, price, and the first bookable day's times in the first screen **at 390px
67
+ wide too** — the image is a bounded band on a phone, not a full-screen hero; week paging beside
68
+ the times; the staff filter only when `service.staff.length > 1`; the form under the times, the
69
+ CTA under the form with its label from `service.free` / `service.price`; a week with no times says
70
+ so and points to the next one.
71
+ - **Confirmed state:** rendered only from `confirmed`; a visitor returning from the hosted checkout
72
+ is not a success signal.
73
+ - **A slug that resolves to nothing** shows only the not-found state — no heading or empty picker
74
+ rendered around it.
75
+ - **Copy:** nothing the owner didn't supply — no invented reviews, availability pressure, or
76
+ guarantees; no Wix IDs or technical words in visible text.
77
+
48
78
  ### The contracts your components consume
49
79
 
80
+ Everything you need to build on the shipped code; read the source only when something is off.
81
+
50
82
  ```ts
51
83
  // ServiceSummary (tiles) — display-ready:
52
84
  // { id, slug, name, tagLine, type: "APPOINTMENT"|"CLASS", price /* "€75.00" | "Free" */,
53
- // free, durationMinutes|null, imageUrl, categoryId|null, staff: [{id,name}] }
85
+ // free, durationMinutes|null, imageUrl /* https or "" */, categoryId|null, staff: [{id,name}] }
54
86
  // ServiceDetail adds: description, formId, paymentOption, cancellationFeeEnabled.
87
+ // BookingCategory = { id, name }.
55
88
 
56
89
  // useServices({ initialServices?, initialCategories? }) →
57
90
  // { services: ServiceSummary[]|null /* null = loading → skeletons */,
58
- // categories: [{id,name}], activeCategoryId, setActiveCategoryId(id|null), error }
91
+ // categories: BookingCategory[], activeCategoryId, setActiveCategoryId(id|null), error }
92
+ // Category filtering is client-side (a bookings catalog is small and fully fetched).
59
93
 
60
94
  // useBookingFlow(service: ServiceDetail) →
61
95
  // { days: [{ dayKey, dayLabel, slots: Slot[] }] | null, // day-grouped; null = loading
62
96
  // windowStart, nextWeek(), prevWeek(), // 7-day paging (prev clamps to today)
63
97
  // staffId, setStaffId(id|undefined), // show a picker only when service.staff.length > 1
64
- // selectedSlot, setSelectedSlot(slot|null), // Slot = { startLocal, endLocal, dayKey, label, … }
65
- // formFields: [{ target, label, type, options? }], // never empty (contact-basics fallback)
98
+ // selectedSlot, setSelectedSlot(slot|null), // Slot = { startLocal, endLocal, dayKey, label, scheduleId|null, eventId|null, staff }
99
+ // formFields: [{ target, label, type, options?, required }], // never empty (contact-basics fallback); only required ones gate canBook
66
100
  // values, setValue(target, value), // inputs write here, keyed by target
67
101
  // canBook, // gate the CTA on this
68
- // book(): Promise<BookingResult>, // paid → the browser redirects to Wix checkout;
102
+ // book(): Promise<BookingResult>, // paid → the browser navigates to the Wix checkout;
69
103
  // booking, confirmed, error } // free/offline → confirmed is set (REAL success)
104
+ // A window or staff change clears selectedSlot and sets days to null while the new week loads.
105
+ // book() rejects on refusal (slot taken, invalid form) AND records .error — render it beside the CTA.
70
106
  ```
71
107
 
108
+ ### The reference files for stacks where the components don't deploy
109
+
110
+ On `lib`, `static`, and a port, nothing under `components/` or `hooks/` arrives. The state machines
111
+ behind the hooks do arrive — `wix/bookings/services-store.ts`, `booking-flow-store.ts` — so you
112
+ never rewrite them: create a store per surface, `subscribe`, render from `getState()`, call its
113
+ actions. Their `ServicesState` / `BookingFlowState` interfaces are the render contract; read those.
114
+ What you write is the rendering — tiles, the slot picker, the form, the CTA — and for that read
115
+ these first; they are tested code for exactly that behaviour:
116
+
117
+ 1. `components/bookings/ServiceBookingView.tsx` — the booking surface as working code: day
118
+ groups with slot chips, week paging controls, the staff filter only when there is more than one
119
+ staff member, one input per `formFields` entry typed from its `type` (a `<select>` when it has
120
+ `options`), the CTA disabled until `canBook`, labelled free vs priced, `error` inline, and the
121
+ confirmed state rendered only from `confirmed`.
122
+ 2. `components/bookings/ServicesView.tsx` — category pills only when `categories.length > 1`,
123
+ skeleton tiles while `services === null`, the honest empty state, and `ServiceCard`: image with
124
+ the type badge, name, tagLine, "duration · price".
125
+
126
+ All under `references/bookings/app/`.
127
+
72
128
  ### Wiring — Astro (default)
73
129
 
74
130
  1. Set the `@theme` tokens (one edit); brand `SiteLayout.astro` (one pass — merge into the
@@ -80,11 +136,92 @@ Style everything with Tailwind utilities on the tokens.
80
136
  batch multiple Writes per message.
81
137
  3. Write `pages/index.astro` (home) — it exists from the scaffold; Read it before overwriting.
82
138
 
139
+ ### Wiring — another JS framework (`--stack lib`: Vue, Svelte, Solid, plain Vite)
140
+
141
+ Read the reference files listed above before writing any surface.
142
+
143
+ `deploy.mjs bookings --stack lib` put the data layer in `src/wix/` and nothing else: `sdk.ts`
144
+ (the visitor client, configured with the public client id), `media.ts`, `money.ts`, and
145
+ `wix/bookings/` — `services.ts`, `booking.ts`, `types.ts`, the `*-core.ts` rules, and the two
146
+ stores `services-store.ts`, `booking-flow-store.ts`. None of it is React. The hooks and components
147
+ don't ship on this stack; the stores replace the hooks, and you write the components in your
148
+ framework to the contracts on this page:
149
+
150
+ - bind the stores with your framework's external-store primitive (Vue: `shallowRef` updated in
151
+ `subscribe`; Svelte: `readable(store.getState(), (set) => store.subscribe(() => set(store.getState())))`;
152
+ Solid: a signal set in `subscribe`). `createServicesStore(options)` per listing (`start()` when
153
+ mounted, `stop()` when unmounted), `createBookingFlowStore(service)` per booking surface (the
154
+ `ServiceDetail` from `fetchServiceBySlug`; `start()` in the browser only — availability is
155
+ timezone-specific). State in, actions out — exactly the hooks' contracts above;
156
+ - your tiles, slot picker, form, and CTA to the defaults in "What a complete booking site shows" —
157
+ the shipped `ServiceBookingView.tsx` and `ServicesView.tsx` are readable as behaviour specs.
158
+
159
+ Routes `/services`, `/services/:slug` (via `fetchServiceBySlug`, null → your 404); dev server on
160
+ 4321; a static build goes through `npx @wix/cli@latest release` with `site.outputDirectory`
161
+ pointing at the build folder, an SSR build is hosted by you. Service-page tags from the
162
+ `ServiceDetail` (name, tagLine, description).
163
+
164
+ ### Wiring — static site (`--stack static`, no bundler)
165
+
166
+ Read the reference files listed above before writing any surface.
167
+
168
+ `deploy.mjs bookings --stack static --out site` put the REST layer in `site/js/wix/` (browser
169
+ ESM, the `.ts` beside each `.js` for reading). Everything the visitor loads lives under `site/` —
170
+ pages, styles, `js/` — and `wix.config.json`'s `site.outputDirectory` is `"./site"`; the project
171
+ root (config, plan, seed output) is never the upload. Same function names and DTOs as the table
172
+ above, so the contracts on this page hold unchanged: `fetchServices`, `fetchServiceBySlug`,
173
+ `fetchBookingCategories` from `./js/wix/services.js`; `fetchSlots`, `fetchBookingForm`,
174
+ `bookService` from `./js/wix/booking.js`. The state machines ship too: `createServicesStore` from
175
+ `./js/wix/services-store.js` (the listing — `start()` once the page is up, `setActiveCategoryId`)
176
+ and `createBookingFlowStore(service)` from `./js/wix/booking-flow-store.js` (the booking surface —
177
+ `fetchServiceBySlug(slug)` first, then the store: days, week paging, staff filter, `formFields`,
178
+ `setValue`, `canBook`, `book()`). No components ship — you write the rendering in plain JS: one
179
+ render function per surface that reads `getState()`, called from `subscribe`, with the surface's
180
+ controls calling the store's actions. `book()` navigates the full document to the hosted checkout
181
+ for a paid service on its own (`window.location.origin` must be on the OAuth app's allowed domains
182
+ so checkout can return); for a free or pay-in-person service it sets `confirmed`. Pages are
183
+ `services.html` and `service.html?slug=…` (Wix static hosting serves files, not directories — name
184
+ the file and link to it). Set `document.title` and the meta description from the `ServiceDetail`
185
+ once it loads. The visitor token persists in `localStorage` on its own; never mint per page.
186
+ `npx @wix/cli@latest release` uploads `site/`.
187
+
188
+ ### Wiring — server-rendered, another language (Flask, Laravel, Rails, …)
189
+
190
+ Read the reference files listed above before writing any surface.
191
+
192
+ Run `deploy.mjs bookings --stack static` in the project folder anyway: `js/wix/` is both the
193
+ browser-side code and the readable spec. Then split by where the call runs. **Reads on the
194
+ server:** port `js/wix/services.ts` and `services-core.ts` to your language — the same three
195
+ functions returning the same DTO shapes as dicts, one anonymous visitor token per process for these
196
+ public reads (mint and refresh per `client.ts`) — and render the listing and the service page in
197
+ your templates to the contracts above, so service names and prices are in the HTML; page tags from
198
+ the `ServiceDetail`. **Booking in the browser:** the booking surface on
199
+ `./js/wix/booking-flow-store.js` with the rendered `ServiceDetail` (a JSON script tag, or
200
+ `fetchServiceBySlug(slug)` from `./js/wix/services.js`), exactly as the static wiring above — the
201
+ browser owns the visitor's token, so the server never handles per-visitor tokens, and the booking,
202
+ its cart, and the checkout redirect are the visitor's. Routes stay `/services`, `/services/<slug>`.
203
+ Add your public https origin to the OAuth app's allowed domains before checkout can return.
204
+
205
+ **Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted.** Same port for
206
+ the reads, run at build time with one anonymous token; the generator must emit a page for every
207
+ slug `fetchServices()` returns. Run `deploy.mjs bookings --stack static --out <build dir>` so
208
+ `js/wix/` is inside the output the pages import from, point `site.outputDirectory` at that folder,
209
+ `wix release`. Pages sit at different depths (`/`, `/services/…`): give the templates one base path
210
+ to `js/wix/` (a template variable, or root-relative `/js/wix/…`), never a relative `./js/wix/` — it
211
+ breaks one level down. The frozen listing is the first paint; availability and booking still run
212
+ client-side on the service page through `createBookingFlowStore()` from
213
+ `./js/wix/booking-flow-store.js`, exactly as on a static site, so the booking contract above
214
+ applies. Close with the live URL, the rebuild + release command, and one line for the owner:
215
+ dashboard edits to services reach the site when that command runs; availability and booking are
216
+ live regardless.
217
+
83
218
  ### Wiring — React SPA (Vite etc.)
84
219
 
85
220
  Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite
86
221
  plugins — deploy added the dep). Routes: `/services` → your listing; `/services/:slug` →
87
- fetch with `fetchServiceBySlug(slug)` client-side, then your booking surface.
222
+ fetch with `fetchServiceBySlug(slug)` client-side, then your booking surface on
223
+ `useBookingFlow(service)`. Deploy wrote the public client id into `wix/config.ts`; nothing else
224
+ to configure.
88
225
 
89
226
  ## Hard rules
90
227
 
@@ -99,31 +236,40 @@ fetch with `fetchServiceBySlug(slug)` client-side, then your booking surface.
99
236
  - **The confirmed state must reflect REAL success**: render it only from `confirmed` (set by
100
237
  the free/offline branch). A visitor returning from the hosted checkout redirect is NOT a
101
238
  success signal — don't fake a confirmation page off the return URL.
102
- - Theme via the `@theme` tokens; no parallel theme files, no hardcoded palettes.
239
+ - Where the shipped components deploy (Astro, React): theme via the `@theme` tokens, and your
240
+ markup uses Tailwind utilities on the same tokens — one design system across shipped and written
241
+ code. No parallel theme files, no hardcoded palette values. Where they don't (`lib`, `static`, a
242
+ port): style with whatever your stack does well, on one token set of your own; the rule that
243
+ survives is the token set, not Tailwind.
103
244
  - Live data or an honest empty state — never mock services, slots, or availability.
104
- - Keep the detail page's SEO pieces exactly as shipped.
245
+ - Keep the detail page's SEO pieces exactly as shipped (Astro).
246
+ - **Browsing and booking need no login.** They run on the Wix visitor session the shipped client
247
+ already holds; don't gate services or booking behind sign-in unless the brief asks for accounts.
248
+ - **Call every hook before any conditional return.** Hooks first, branches after.
105
249
 
106
250
  ## Point the user to their dashboard
107
251
 
108
- Give the owner the dashboard link plus the Bookings services/calendar pages — the deploy
109
- step's JSON printed `dashboardUrl`; append `/bookings/services` for service management.
110
- Taking real online payments needs a premium plan + a connected payment method — mention it.
252
+ Hand the owner these links — `{siteId}` is `siteId` in `wix.config.json` (the deploy JSON prints it as
253
+ `dashboardUrl`); a service id fills the placeholder from the seed result.
254
+
255
+ | page | `https://manage.wix.com/dashboard/{siteId}/` + |
256
+ |---|---|
257
+ | Services | `bookings/services` |
258
+ | Edit a service | `bookings/services/form/{serviceId}` |
259
+ | Calendar | `bookings/calendar` |
260
+ | Bookings list | `bookings/bookings/bookings-list` |
261
+ | Staff | `bookings/staff` |
262
+ | Availability | `bookings/availability` |
263
+ | Booking form | `bookings/settings/booking-form-page` |
264
+ | Policies | `bookings/settings/policies` |
265
+
266
+ Completing a booking online — free services included — needs a payment method on the site (Settings →
267
+ Accept payments; "manual payments" is enough for free and pay-in-person); taking real online payments
268
+ also needs a premium plan. Until then `book()` surfaces "the site is not accepting payments" — mention
269
+ it, don't treat it as a code failure.
111
270
 
112
271
  ## Seeding
113
272
 
114
273
  Per `seed/SEED.md` — plain-data `plan.json` into `seed-bookings.mjs` from the project root.
115
274
  Seed services that exercise the UI (an APPOINTMENT with duration+price, a free one, a CLASS
116
275
  with future sessions when it fits the business; an image per service).
117
-
118
- ## Verify (before declaring done)
119
-
120
- - [ ] `/services` renders live services SSR (view-source shows names) through YOUR components;
121
- empty catalog shows your honest empty state.
122
- - [ ] A service page shows day-grouped bookable times; week paging works; the form renders
123
- (schema or fallback) and the CTA stays disabled until a slot + all fields are set.
124
- - [ ] A FREE service books end-to-end: `book()` resolves, your confirmed state renders.
125
- - [ ] A PAID service redirects to the Wix-hosted checkout.
126
- - [ ] Service page view-source carries the SEO tags (Astro).
127
- - [ ] Card/listing/booking surfaces/home are YOUR designs on the tokens; data-layer/hook files
128
- unedited.
129
- - [ ] Dashboard links handed to the owner.
@@ -105,7 +105,7 @@ export default function ServiceBookingView({ service }: { service: ServiceDetail
105
105
  <div className="mt-6 grid max-w-md gap-3">
106
106
  {formFields.map((f) => (
107
107
  <label key={f.target} className="block">
108
- <span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-muted-foreground">{f.label}</span>
108
+ <span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-muted-foreground">{f.label}{f.required ? " *" : ""}</span>
109
109
  {f.options?.length ? (
110
110
  <select
111
111
  value={values[f.target] ?? ""}
@@ -121,6 +121,7 @@ export default function ServiceBookingView({ service }: { service: ServiceDetail
121
121
  <input
122
122
  type={f.type === "EMAIL" ? "email" : f.type === "PHONE" ? "tel" : f.type === "NUMBER" ? "number" : f.type === "URL" ? "url" : "text"}
123
123
  value={values[f.target] ?? ""}
124
+ required={f.required}
124
125
  onChange={(e) => setValue(f.target, e.target.value)}
125
126
  className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none transition-shadow focus:ring-2 focus:ring-primary"
126
127
  />
@@ -1,138 +1,32 @@
1
- // The whole booking state machine for one service: availability window (day-grouped slots,
2
- // week paging, optional staff filter), the schema-driven form, and book(). All correctness
3
- // (slot scheduleId vs eventId, ANY_RESOURCE, formSubmission, payment option, checkout-or-
4
- // place) lives in the data layer — this hook orchestrates; you own how it looks.
5
- import { useEffect, useMemo, useState } from "react";
6
- import { bookService, fetchBookingForm, fetchSlots } from "../../wix/bookings/booking";
7
- import type { BookingFormField, BookingResult, ServiceDetail, Slot } from "../../wix/bookings/types";
8
-
9
- export interface UseBookingFlow {
10
- /** Slots grouped by day, in order — [{ dayKey, dayLabel, slots }]. null while loading. */
11
- days: { dayKey: string; dayLabel: string; slots: Slot[] }[] | null;
12
- /** The 7-day window start; page with nextWeek/prevWeek (prev clamps to today). */
13
- windowStart: Date;
14
- nextWeek: () => void;
15
- prevWeek: () => void;
16
- /** Staff filter — render a picker only when service.staff.length > 1. */
17
- staffId: string | undefined;
18
- setStaffId: (id: string | undefined) => void;
19
- selectedSlot: Slot | null;
20
- setSelectedSlot: (s: Slot | null) => void;
21
- /** Schema-driven form fields (never empty — contact basics fallback). */
22
- formFields: BookingFormField[];
23
- values: Record<string, string>;
24
- setValue: (target: string, value: string) => void;
25
- /** True when a slot is selected and every field has a value. */
26
- canBook: boolean;
27
- /** Books the selected slot. On "redirect" the browser is already navigating. */
28
- book: () => Promise<BookingResult>;
29
- booking: boolean;
30
- /** Set after a free/offline booking completes. */
31
- confirmed: BookingResult | null;
32
- error: string | null;
33
- }
34
-
35
- const dayLabel = (dayKey: string): string => {
36
- try {
37
- return new Date(`${dayKey}T12:00:00`).toLocaleDateString(undefined, {
38
- weekday: "short",
39
- month: "short",
40
- day: "numeric",
41
- });
42
- } catch {
43
- return dayKey;
44
- }
45
- };
1
+ // React binding of the booking-flow store (wix/bookings/booking-flow-store.ts) — the whole booking
2
+ // state machine for one service lives there, framework-free: availability window (day-grouped
3
+ // slots, week paging, optional staff filter), the schema-driven form, and book(). All correctness
4
+ // (slot scheduleId vs eventId, ANY_RESOURCE, formSubmission, payment option, checkout-or-place)
5
+ // lives in the data layer; you own how it looks. Mount the surface client-only: availability is
6
+ // timezone/session-specific.
7
+ import { useEffect, useRef, useSyncExternalStore } from "react";
8
+ import { createBookingFlowStore, type BookingFlowState, type BookingFlowStore } from "../../wix/bookings/booking-flow-store";
9
+ import type { ServiceDetail } from "../../wix/bookings/types";
10
+
11
+ export type UseBookingFlow = BookingFlowState &
12
+ Pick<BookingFlowStore, "nextWeek" | "prevWeek" | "setStaffId" | "setSelectedSlot" | "setValue" | "book">;
46
13
 
47
14
  export function useBookingFlow(service: ServiceDetail): UseBookingFlow {
48
- const [windowStart, setWindowStart] = useState<Date>(() => new Date());
49
- const [staffId, setStaffId] = useState<string | undefined>(undefined);
50
- const [slots, setSlots] = useState<Slot[] | null>(null);
51
- const [selectedSlot, setSelectedSlot] = useState<Slot | null>(null);
52
- const [formFields, setFormFields] = useState<BookingFormField[]>([]);
53
- const [values, setValues] = useState<Record<string, string>>({});
54
- const [booking, setBooking] = useState(false);
55
- const [confirmed, setConfirmed] = useState<BookingResult | null>(null);
56
- const [error, setError] = useState<string | null>(null);
57
-
58
- useEffect(() => {
59
- let alive = true;
60
- setSlots(null);
61
- setSelectedSlot(null);
62
- fetchSlots(service, { from: windowStart, days: 7, staffId })
63
- .then((s) => alive && setSlots(s))
64
- .catch((e) => {
65
- if (!alive) return;
66
- setSlots([]);
67
- setError(e instanceof Error ? e.message : String(e));
68
- });
69
- return () => {
70
- alive = false;
71
- };
72
- // eslint-disable-next-line react-hooks/exhaustive-deps
73
- }, [service.id, windowStart, staffId]);
74
-
15
+ const ref = useRef<{ serviceId: string; store: BookingFlowStore } | null>(null);
16
+ if (!ref.current || ref.current.serviceId !== service.id) ref.current = { serviceId: service.id, store: createBookingFlowStore(service) };
17
+ const store = ref.current.store;
75
18
  useEffect(() => {
76
- let alive = true;
77
- fetchBookingForm(service.formId).then((f) => alive && setFormFields(f));
78
- return () => {
79
- alive = false;
80
- };
81
- // eslint-disable-next-line react-hooks/exhaustive-deps
82
- }, [service.formId]);
83
-
84
- const days = useMemo(() => {
85
- if (slots === null) return null;
86
- const byDay = new Map<string, Slot[]>();
87
- for (const s of slots) byDay.set(s.dayKey, [...(byDay.get(s.dayKey) ?? []), s]);
88
- return [...byDay.entries()]
89
- .sort(([a], [b]) => a.localeCompare(b))
90
- .map(([dayKey, daySlots]) => ({ dayKey, dayLabel: dayLabel(dayKey), slots: daySlots }));
91
- }, [slots]);
92
-
93
- const canBook =
94
- !!selectedSlot && formFields.every((f) => (values[f.target] ?? "").trim().length > 0);
95
-
96
- async function book(): Promise<BookingResult> {
97
- if (!selectedSlot) throw new Error("Pick a time first.");
98
- setBooking(true);
99
- setError(null);
100
- try {
101
- const result = await bookService(service, selectedSlot, values, { staffId });
102
- if (result.kind === "redirect") {
103
- window.location.href = result.url;
104
- } else {
105
- setConfirmed(result);
106
- }
107
- return result;
108
- } catch (e) {
109
- setError(e instanceof Error ? e.message : String(e));
110
- throw e;
111
- } finally {
112
- setBooking(false);
113
- }
114
- }
115
-
19
+ store.start();
20
+ return () => store.stop();
21
+ }, [store]);
22
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
116
23
  return {
117
- days,
118
- windowStart,
119
- nextWeek: () => setWindowStart((d) => new Date(d.getTime() + 7 * 24 * 3600 * 1000)),
120
- prevWeek: () =>
121
- setWindowStart((d) => {
122
- const prev = new Date(d.getTime() - 7 * 24 * 3600 * 1000);
123
- return prev < new Date() ? new Date() : prev;
124
- }),
125
- staffId,
126
- setStaffId,
127
- selectedSlot,
128
- setSelectedSlot,
129
- formFields,
130
- values,
131
- setValue: (target, value) => setValues((v) => ({ ...v, [target]: value })),
132
- canBook,
133
- book,
134
- booking,
135
- confirmed,
136
- error,
24
+ ...state,
25
+ nextWeek: store.nextWeek,
26
+ prevWeek: store.prevWeek,
27
+ setStaffId: store.setStaffId,
28
+ setSelectedSlot: store.setSelectedSlot,
29
+ setValue: store.setValue,
30
+ book: store.book,
137
31
  };
138
32
  }