@wix/agent-skills 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +115 -12
  3. package/skills/wix-app/references/APP_MARKET_REVIEW.md +1 -1
  4. package/skills/wix-app/references/BACKEND_API.md +18 -0
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +6 -0
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +10 -2
  7. package/skills/wix-app/references/DASHBOARD_PAGE.md +32 -70
  8. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +14 -3
  9. package/skills/wix-app/references/SITE_PLUGIN.md +12 -0
  10. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
  11. package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +79 -0
  12. package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +73 -0
  13. package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +52 -0
  14. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +184 -0
  15. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +12 -11
  16. package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +1 -0
  17. package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
  18. package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +16 -8
  19. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +19 -1
  20. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +3 -0
  21. package/skills/wix-base44-connector/SKILL.md +133 -44
  22. package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
  23. package/skills/wix-base44-connector/scripts/utils.js +216 -30
  24. package/skills/wix-headless/entry/skill.md +1 -1
  25. package/skills/wix-headless/references/CAPABILITIES.md +9 -1
  26. package/skills/wix-headless/references/DESIGN.md +1 -0
  27. package/skills/wix-headless/references/DISCOVERY.md +2 -1
  28. package/skills/wix-headless/references/IMAGE_GENERATION.md +1 -0
  29. package/skills/wix-headless/references/SDK_HANDOFF.md +4 -1
  30. package/skills/wix-headless/references/SEED.md +2 -1
  31. package/skills/wix-headless/references/SETUP.md +1 -0
  32. package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +2 -2
  33. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +26 -8
  34. package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +26 -10
  35. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +8 -8
  36. package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +1 -1
  37. package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +1 -1
  38. package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
  39. package/skills/wix-headless/references/inline-recipes/setup-online-store.md +13 -0
  40. package/skills/wix-headless/references/inline-recipes/setup-rentals.md +254 -0
  41. package/skills/wix-headless/references/non-astro.md +1 -1
  42. package/skills/wix-headless-fast/SKILL.md +166 -0
  43. package/skills/wix-headless-fast/entry/skill.md +99 -0
  44. package/skills/wix-headless-fast/install/deploy.mjs +433 -0
  45. package/skills/wix-headless-fast/install/fast-path.mjs +141 -0
  46. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +137 -0
  47. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +137 -0
  48. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +113 -0
  49. package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +42 -0
  50. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +123 -0
  51. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +70 -0
  52. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +99 -0
  53. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +76 -0
  54. package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +62 -0
  55. package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +40 -0
  56. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +99 -0
  57. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +24 -0
  58. package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +16156 -0
  59. package/skills/wix-headless-fast/references/blog/seed/SEED.md +72 -0
  60. package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +287 -0
  61. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +129 -0
  62. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +143 -0
  63. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +119 -0
  64. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +138 -0
  65. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +54 -0
  66. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +206 -0
  67. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +96 -0
  68. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +70 -0
  69. package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +40 -0
  70. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +76 -0
  71. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +22 -0
  72. package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +11962 -0
  73. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +61 -0
  74. package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +302 -0
  75. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +179 -0
  76. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +83 -0
  77. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +57 -0
  78. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +151 -0
  79. package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +75 -0
  80. package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +41 -0
  81. package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +11262 -0
  82. package/skills/wix-headless-fast/references/cms/seed/SEED.md +103 -0
  83. package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +324 -0
  84. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +149 -0
  85. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +66 -0
  86. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +176 -0
  87. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +118 -0
  88. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +117 -0
  89. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +59 -0
  90. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +136 -0
  91. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +128 -0
  92. package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +61 -0
  93. package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +40 -0
  94. package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +12 -0
  95. package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +80 -0
  96. package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +21 -0
  97. package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +11884 -0
  98. package/skills/wix-headless-fast/references/events/seed/SEED.md +86 -0
  99. package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +276 -0
  100. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +150 -0
  101. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +317 -0
  102. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +269 -0
  103. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +231 -0
  104. package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +135 -0
  105. package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +10593 -0
  106. package/skills/wix-headless-fast/references/forms/seed/SEED.md +108 -0
  107. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +320 -0
  108. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +63 -0
  109. package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +60 -0
  110. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +155 -0
  111. package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +78 -0
  112. package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +47 -0
  113. package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +66 -0
  114. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +88 -0
  115. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +53 -0
  116. package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +122 -0
  117. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +50 -0
  118. package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +21 -0
  119. package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +42 -0
  120. package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +10 -0
  121. package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +11 -0
  122. package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +10329 -0
  123. package/skills/wix-headless-fast/references/members/seed/SEED.md +39 -0
  124. package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +94 -0
  125. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +134 -0
  126. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +110 -0
  127. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +90 -0
  128. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +115 -0
  129. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +59 -0
  130. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +39 -0
  131. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +58 -0
  132. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +165 -0
  133. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +55 -0
  134. package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +40 -0
  135. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +30 -0
  136. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +21 -0
  137. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +29 -0
  138. package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +10197 -0
  139. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +78 -0
  140. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +258 -0
  141. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +133 -0
  142. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +58 -0
  143. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +126 -0
  144. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +37 -0
  145. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +39 -0
  146. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +75 -0
  147. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +45 -0
  148. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +30 -0
  149. package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +41 -0
  150. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +40 -0
  151. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +21 -0
  152. package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +11226 -0
  153. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +71 -0
  154. package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +279 -0
  155. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +165 -0
  156. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +142 -0
  157. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +26 -0
  158. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +121 -0
  159. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +155 -0
  160. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +55 -0
  161. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +43 -0
  162. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +177 -0
  163. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +165 -0
  164. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +103 -0
  165. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +254 -0
  166. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +146 -0
  167. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +168 -0
  168. package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +47 -0
  169. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +22 -0
  170. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +12 -0
  171. package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +12560 -0
  172. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +81 -0
  173. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +479 -0
  174. package/skills/wix-headless-fast/references/shared/CONTENT.md +22 -0
  175. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +73 -0
  176. package/skills/wix-headless-fast/references/shared/DESIGN.md +26 -0
  177. package/skills/wix-headless-fast/references/shared/app/styles/global.css +52 -0
  178. package/skills/wix-headless-fast/references/shared/app/wix/config.ts +15 -0
  179. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +27 -0
  180. package/skills/wix-headless-fast/references/shared/app/wix/money.ts +24 -0
  181. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +76 -0
  182. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +46 -0
  183. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +46 -0
  184. package/skills/wix-headless-fast/references/shared/seed/images.mjs +145 -0
  185. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +149 -0
  186. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +26 -0
  187. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +121 -0
  188. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +100 -0
  189. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +130 -0
  190. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +48 -0
  191. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +66 -0
  192. package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +103 -0
  193. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +48 -0
  194. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +140 -0
  195. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +80 -0
  196. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +101 -0
  197. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +221 -0
  198. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +215 -0
  199. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +109 -0
  200. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +46 -0
  201. package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +56 -0
  202. package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +23 -0
  203. package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +12247 -0
  204. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +85 -0
  205. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +415 -0
  206. package/skills/wix-manage/SKILL.md +18 -2
  207. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +141 -0
  208. package/skills/wix-manage/references/google-business-profile/connect-google-business-profile.md +134 -0
  209. package/skills/wix-manage/references/google-business-profile/manage-google-business-profile-locations.md +182 -0
  210. package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +3 -0
  211. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +21 -6
  212. package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
  213. package/skills/wix-vibe-headless/SKILL.md +18 -4
  214. package/skills/wix-vibe-headless/install/deploy.cjs +12 -4
  215. package/skills/wix-vibe-headless/install/pin-agents-md.cjs +2 -1
  216. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
  217. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +3 -1
  218. package/skills/wix-vibe-headless/platforms/base44.md +23 -61
  219. package/skills/wix-vibe-headless/platforms/generic.md +6 -3
  220. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +43 -0
  221. package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +67 -0
  222. package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +128 -0
  223. package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +5 -3
  224. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +250 -0
  225. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +12 -5
  226. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +41 -0
  227. package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js +267 -0
  228. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +6 -3
  229. package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +470 -0
  230. package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +425 -0
  231. package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +187 -0
  232. package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +238 -0
  233. package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +108 -0
  234. package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1762 -0
  235. package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
  236. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +137 -84
  237. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +15 -8
  238. package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +37 -0
  239. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +18 -6
  240. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +30 -7
  241. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +46 -10
  242. package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +0 -110
  243. package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +0 -51
  244. package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +0 -49
  245. package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +0 -92
  246. package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +0 -139
@@ -0,0 +1,250 @@
1
+ import { wixApiRequest } from "./wix-client.js";
2
+ import { listAvailableSlots } from "./wix-bookings-services.js";
3
+
4
+ /**
5
+ * Wix Rentals — a DELTA on `wix-bookings-services.js`, not a replacement.
6
+ *
7
+ * Wix Rentals has no APIs of its own. A rental is a Bookings **service** with rentals-specific
8
+ * field values, so the catalog read, the booking form, and the whole
9
+ * `createBooking → cart → checkout` chain in `wix-bookings-checkout.js` are UNCHANGED. Only
10
+ * three things differ, and they are all here:
11
+ *
12
+ * 1. every catalog read filters by the rentals `appId`, or rentals and appointments interleave
13
+ * 2. the customer picks the LENGTH, so availability is two calls: start times, then the valid
14
+ * ends for the chosen start
15
+ * 3. price is a rate per unit, so the total for a chosen length comes from the server
16
+ *
17
+ * What makes a service a rental (from the docs' own mapping — nothing is inferred):
18
+ * type always "APPOINTMENT"
19
+ * appId always RENTALS_APP_ID
20
+ * schedule.availabilityConstraints.durationRange replaces `sessionDurations`
21
+ * — MUTUALLY EXCLUSIVE, one or the other
22
+ * primaryResourceType a resource type id, not staff — availability comes from its resources
23
+ * payment.rateType always "FIXED", charged per hour or per day
24
+ *
25
+ * ⚠️ Because the two constraint fields are mutually exclusive, `isRental()` below is a reliable
26
+ * test on the DATA. Never decide "this is a rental" from the brief, the service name, or a
27
+ * category — a 60-minute yoga class and a meeting room rented for 1–8 hours are told apart by
28
+ * which constraint the service carries, and nothing else.
29
+ *
30
+ * docs: https://dev.wix.com/docs/api-reference/business-solutions/rentals/wix-rentals-and-the-bookings-apis.md
31
+ * docs: https://dev.wix.com/docs/api-reference/business-solutions/rentals/about-wix-rentals-availability.md
32
+ */
33
+
34
+ /** The Wix Rentals app. Used twice: to filter the catalog, and as the cart's catalogReference.appId. */
35
+ export const RENTALS_APP_ID = "ff5d6eb1-65e4-4f9a-8b14-64d34c12cc2e";
36
+
37
+ /** The Wix Bookings app — the other half of a mixed site. */
38
+ export const BOOKINGS_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
39
+
40
+ /**
41
+ * Is this service a rental? True when it carries a duration RANGE the customer picks within,
42
+ * rather than a fixed session length. This is the only correct test.
43
+ * @param {object} service
44
+ * @returns {boolean}
45
+ */
46
+ export function isRental(service) {
47
+ return Boolean(service?.schedule?.availabilityConstraints?.durationRange);
48
+ }
49
+
50
+ /**
51
+ * The rental's unit and bounds, or null for an ordinary service.
52
+ * Hourly bounds are MINUTES (30–1440), daily bounds are DAYS (1–8).
53
+ * @param {object} service
54
+ * @returns {{ unit: "HOUR"|"DAY", min: number, max: number, step: number }|null}
55
+ */
56
+ export function rentalDuration(service) {
57
+ const range = service?.schedule?.availabilityConstraints?.durationRange;
58
+ if (!range) return null;
59
+ if (range.unitType === "DAY") {
60
+ return {
61
+ unit: "DAY",
62
+ min: range.dayOptions?.minDurationInDays ?? 1,
63
+ max: range.dayOptions?.maxDurationInDays ?? 1,
64
+ step: 1,
65
+ };
66
+ }
67
+ return {
68
+ unit: "HOUR",
69
+ min: range.hourOptions?.minDurationInMinutes ?? 30,
70
+ max: range.hourOptions?.maxDurationInMinutes ?? 1440,
71
+ step: 30,
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Query rentals only. On a site that has both apps an unfiltered `queryServices` returns
77
+ * haircuts next to meeting rooms, so this is not optional — it is the same query with the
78
+ * rentals `appId` on the filter.
79
+ *
80
+ * Pass `appId: BOOKINGS_APP_ID` to get the ordinary services instead, on a mixed site.
81
+ * @param {{ limit?: number, offset?: number, appId?: string }} [options]
82
+ * @returns {Promise<{ services: object[], total: number, nextOffset: number|null }>}
83
+ */
84
+ export async function queryRentals({ limit = 100, offset = 0, appId = RENTALS_APP_ID } = {}) {
85
+ const res = await wixApiRequest("/bookings/v2/services/query", {
86
+ method: "POST",
87
+ body: { query: { filter: { hidden: false, appId }, paging: { limit, offset } } },
88
+ });
89
+ const services = res?.services ?? [];
90
+ const total = res?.pagingMetadata?.total ?? services.length;
91
+ const loaded = offset + services.length;
92
+ return { services, total, nextOffset: loaded < total ? loaded : null };
93
+ }
94
+
95
+ /**
96
+ * The resource type id(s) a rental's availability comes from — read off `serviceResources`.
97
+ * Every rental availability call must pass these as `includeResourceTypeIds`, or it returns zero slots.
98
+ * @param {object} service
99
+ * @returns {string[]}
100
+ */
101
+ export function rentalResourceTypeIds(service) {
102
+ return (service?.serviceResources ?? [])
103
+ .map((sr) => sr.resourceType?._id ?? sr.resourceType?.id)
104
+ .filter(Boolean);
105
+ }
106
+
107
+ /**
108
+ * Start times for a rental — step 1 of 2.
109
+ *
110
+ * A rental is an APPOINTMENT-typed service, so this is the same availability call an appointment
111
+ * uses; what comes back are the times a rental may START. The customer then picks how long, which is
112
+ * `listEndOptions` below. Pass the whole `service` so its resource types ride along — the
113
+ * availability engine reads them to find a resource-driven service's slots.
114
+ * @param {object} service A rental service from queryRentals.
115
+ * @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
116
+ * @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
117
+ */
118
+ export function listRentalStartSlots(service, options = {}) {
119
+ const serviceId = service?._id || service?.id;
120
+ return listAvailableSlots(serviceId, { ...options, includeResourceTypeIds: rentalResourceTypeIds(service) });
121
+ }
122
+
123
+ /**
124
+ * The valid END times for a chosen start — step 2 of 2, HOURLY rentals only.
125
+ *
126
+ * Each entry is a TimeSlot sharing `localStartDate` with the request and varying only in
127
+ * `localEndDate`, sorted shortest first. The service's maximum duration always caps the list,
128
+ * so a `maxLocalEndDate` beyond it is ignored rather than rejected.
129
+ *
130
+ * ⚠️ Hourly only. A DAILY rental has no end options — its lengths are whole days, so walk
131
+ * consecutive days from the chosen start instead (`dailyEndOptions` below).
132
+ * https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slot-end-options.md
133
+ * Pass the chosen start slot's own `location`. It carries `locationType` (and an `id` on a
134
+ * multi-location site); `{ locationType: "BUSINESS" }` is enough on a single-location site.
135
+ * @param {string} serviceId
136
+ * @param {{ localStartDate: string, location: object, maxLocalEndDate?: string, timeZone?: string }} options
137
+ * @returns {Promise<{ endOptions: object[], timeZone: string|null }>}
138
+ */
139
+ export async function listEndOptions(serviceId, { localStartDate, location, maxLocalEndDate, timeZone } = {}) {
140
+ if (!localStartDate) throw new Error("listEndOptions requires localStartDate (local 'YYYY-MM-DDThh:mm:ss').");
141
+ if (!location) throw new Error("listEndOptions requires the chosen slot's location.");
142
+ const res = await wixApiRequest("/_api/service-availability/v2/time-slots/end-options", {
143
+ method: "POST",
144
+ body: {
145
+ serviceId,
146
+ localStartDate,
147
+ location,
148
+ ...(maxLocalEndDate ? { maxLocalEndDate } : {}),
149
+ ...(timeZone ? { timeZone } : {}),
150
+ },
151
+ });
152
+ return { endOptions: res?.endOptions ?? [], timeZone: res?.timeZone ?? null };
153
+ }
154
+
155
+ /**
156
+ * The lengths a DAILY rental can run for from a chosen start day — the daily counterpart to
157
+ * `listEndOptions`. A day's slot list is the availability, so this walks forward from the start
158
+ * and stops at the first day that has none: a 3-day rental needs all 3 days free, and a gap
159
+ * means the longer options are not bookable however far out the calendar goes.
160
+ *
161
+ * Returns one entry per bookable length, `{ days, localEndDate }`, shortest first.
162
+ * `localEndDate` is midnight of the day AFTER the last rented day — the end boundary is
163
+ * exclusive, which is the same convention the catalog's availability window uses.
164
+ * @param {object} service A rental service (daily).
165
+ * @param {{ localStartDate: string, timeZone?: string }} options
166
+ * @returns {Promise<{ days: number, localEndDate: string }[]>}
167
+ */
168
+ export async function dailyEndOptions(service, { localStartDate, timeZone } = {}) {
169
+ const duration = rentalDuration(service);
170
+ if (!duration || duration.unit !== "DAY") {
171
+ throw new Error("dailyEndOptions expects a DAY-unit rental — use listEndOptions for hourly.");
172
+ }
173
+ const serviceId = service._id || service.id;
174
+ const includeResourceTypeIds = rentalResourceTypeIds(service);
175
+ const startDay = localStartDate.slice(0, 10);
176
+ const dayAfter = (isoDay, n) => {
177
+ const d = new Date(`${isoDay}T00:00:00Z`);
178
+ d.setUTCDate(d.getUTCDate() + n);
179
+ return d.toISOString().slice(0, 10);
180
+ };
181
+
182
+ const out = [];
183
+ for (let days = 1; days <= duration.max; days++) {
184
+ const dayToCheck = dayAfter(startDay, days - 1);
185
+ const { slots } = await listAvailableSlots(serviceId, {
186
+ fromLocalDate: `${dayToCheck}T00:00:00`,
187
+ toLocalDate: `${dayAfter(dayToCheck, 1)}T00:00:00`,
188
+ timeZone,
189
+ limit: 1,
190
+ includeResourceTypeIds,
191
+ });
192
+ if (!slots.length) break; // a gap — every longer option is unbookable too
193
+ if (days >= duration.min) out.push({ days, localEndDate: `${dayAfter(startDay, days)}T00:00:00` });
194
+ }
195
+ return out;
196
+ }
197
+
198
+ /**
199
+ * The price for a chosen length, from the server.
200
+ *
201
+ * ⚠️ Never compute this client-side. A rental is charged as a RATE — per hour or per day — and
202
+ * hourly is calculated at per-minute granularity, so a 90-minute rental of a $10/hour room is
203
+ * $15. Multiplying a displayed base price by a rounded number of hours quietly disagrees with
204
+ * what the customer is charged at checkout.
205
+ *
206
+ * ⚠️ Omitting the local dates or the time zone does NOT error — it returns a duration-blind
207
+ * price, which is the same trap in a quieter form.
208
+ * https://dev.wix.com/docs/api-reference/business-solutions/bookings/pricing/pricing-api/preview-price.md
209
+ * @param {string} serviceId
210
+ * @param {{ localStartDate: string, localEndDate: string, timeZone: string, numberOfParticipants?: number }} options
211
+ * @returns {Promise<{ total: string, currency: string|null, raw: object }>}
212
+ */
213
+ export async function previewRentalPrice(serviceId, { localStartDate, localEndDate, timeZone, numberOfParticipants = 1 } = {}) {
214
+ if (!localStartDate || !localEndDate || !timeZone) {
215
+ throw new Error("previewRentalPrice requires localStartDate, localEndDate and timeZone — without them the price ignores the duration.");
216
+ }
217
+ const res = await wixApiRequest("/bookings/v2/pricing/v2/pricing/preview", {
218
+ method: "POST",
219
+ body: {
220
+ bookingLineItems: [{ serviceId, localStartDate, localEndDate, timeZone, numberOfParticipants }],
221
+ },
222
+ });
223
+ const info = res?.priceInfo ?? {};
224
+ const total = info.totalPrice ?? info.total ?? {};
225
+ return {
226
+ total: total.formattedValue ?? (total.value != null ? String(total.value) : ""),
227
+ currency: total.currency ?? null,
228
+ raw: info,
229
+ };
230
+ }
231
+
232
+ /**
233
+ * A rental's rate, ready to render — "$10 / hour" or "$120 / day".
234
+ * `servicePriceLabel` in `lib/serviceFacts.js` renders the same number as a flat price, which
235
+ * reads as the whole cost of the rental rather than its rate.
236
+ * @param {object} service
237
+ * @returns {string}
238
+ */
239
+ export function rentalRateLabel(service) {
240
+ const duration = rentalDuration(service);
241
+ if (!duration) return "";
242
+ const price = service?.payment?.fixed?.price;
243
+ const money =
244
+ price?.formattedValue ||
245
+ (price?.currency && price?.value != null
246
+ ? new Intl.NumberFormat(undefined, { style: "currency", currency: price.currency }).format(Number(price.value))
247
+ : "");
248
+ if (!money) return "";
249
+ return `${money} / ${duration.unit === "DAY" ? "day" : "hour"}`;
250
+ }
@@ -183,11 +183,13 @@ export async function countServices() {
183
183
  * If you render a mixed catalog, route by `service.type` via `listSlotsForService`.
184
184
  * Dates are LOCAL ("YYYY-MM-DDThh:mm:ss") in timeZone.
185
185
  * https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slots.md
186
+ * For a rental (a resource-driven service), pass its resource type id(s) as `includeResourceTypeIds`
187
+ * so the slots come back with their bookable resources; a staff appointment can omit it.
186
188
  * @param {string} serviceId
187
- * @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
189
+ * @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string, includeResourceTypeIds?: string[] }} options
188
190
  * @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
189
191
  */
190
- export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor } = {}) {
192
+ export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor, includeResourceTypeIds } = {}) {
191
193
  if (!cursor && (!fromLocalDate || !toLocalDate)) {
192
194
  throw new Error("listAvailableSlots requires fromLocalDate and toLocalDate (local 'YYYY-MM-DDThh:mm:ss').");
193
195
  }
@@ -200,6 +202,7 @@ export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate
200
202
  toLocalDate,
201
203
  timeZone: timeZone || defaultTimeZone(),
202
204
  cursorPaging: { limit },
205
+ ...(includeResourceTypeIds?.length ? { includeResourceTypeIds } : {}),
203
206
  };
204
207
  const res = await wixApiRequest("/_api/service-availability/v2/time-slots", { method: "POST", body });
205
208
  return {
@@ -287,7 +290,11 @@ export async function listEventTimeSlots(serviceIds, { fromLocalDate, toLocalDat
287
290
  export async function listSlotsForService(service, options = {}) {
288
291
  const serviceId = service?._id || service?.id;
289
292
  if (!serviceId) throw new Error("listSlotsForService requires a service with an id.");
290
- return service?.type === "CLASS" || service?.type === "COURSE"
291
- ? listEventTimeSlots(serviceId, options)
292
- : listAvailableSlots(serviceId, options);
293
+ if (service?.type === "CLASS" || service?.type === "COURSE") return listEventTimeSlots(serviceId, options);
294
+ // For a rental, name its resource type(s) so the slots come back; reading them off the service
295
+ // keeps this call type-agnostic (a staff appointment just yields an empty list here).
296
+ const includeResourceTypeIds =
297
+ options.includeResourceTypeIds ??
298
+ (service?.serviceResources ?? []).map((sr) => sr.resourceType?._id ?? sr.resourceType?.id).filter(Boolean);
299
+ return listAvailableSlots(serviceId, { ...options, includeResourceTypeIds });
293
300
  }
@@ -101,6 +101,47 @@ await seed.attachServiceImage(ctx, { serviceId: services[0].id, revision: servic
101
101
  Both bulk calls report per-item `success`/`error` — retry only the failed items **once** with the
102
102
  same body; don't loop and don't re-create the ones that already succeeded.
103
103
 
104
+ ## Rentals
105
+
106
+ `seed-rentals.js` is the rentals half — the same transport, a different create order and payload.
107
+ Wix Rentals has no APIs of its own, so a rental is a Bookings service with rentals-specific
108
+ field values.
109
+
110
+ ```js
111
+ const seed = require("…/references/bookings/seed/seed-rentals.js");
112
+ await seed.setupRentals(ctx, {
113
+ resourceTypeName: "Meeting rooms",
114
+ resources: ["Room A", "Room B"], // parallel capacity = MORE RESOURCES
115
+ rentals: [
116
+ { name: "Meeting room, hourly", description: "…", price: 25, unit: "HOUR", min: 60, max: 480 },
117
+ { name: "Meeting room, daily", description: "…", price: 180, unit: "DAY", min: 1, max: 5 },
118
+ ],
119
+ });
120
+ // → { resourceType, resources, services, ok, problems }
121
+ ```
122
+
123
+ `price` is a RATE — per hour or per day — not the cost of a rental.
124
+ Hourly `min`/`max` are MINUTES (30–1440); daily are DAYS (1–8).
125
+
126
+ **`ok` must be true.** `problems` is the read-back check: a service created without `appId` or
127
+ without `durationRange` still returns 200 and is simply a plain Bookings service from then on.
128
+
129
+ ### The four traps this seed handles
130
+
131
+ - **Install Rentals ONLY.** It provisions the Bookings infrastructure it runs on; installing
132
+ Wix Bookings as well is redundant.
133
+ - **`appId` is immutable after create.** A service created without it is a plain Bookings
134
+ service forever — there is no update that converts it.
135
+ - **Order is load-bearing: resource type → resources → services.** A service whose resource
136
+ type holds no resources has permanently empty availability, and nothing errors.
137
+ - **Rental services do not use categories.** Unlike bookings, do not create or assign one.
138
+ - **`serviceResources` is required** and the docs' Create Service parameter list omits it.
139
+ Without it the create fails `MISSING_APPOINTMENT_RESOURCES`, whose message sends you to check
140
+ the resource type's contents — a dead end, since it fails even when the type is populated.
141
+
142
+ Resources are seeded **24/7** (no working hours) so a multi-day rental stays one booking rather
143
+ than a multi-service group. Pass `workingHours` only when the brief names opening hours.
144
+
104
145
  ## Reference
105
146
  If a call returns a shape you didn't expect, or you need an operation this module doesn't cover,
106
147
  use the **`wix-docs`** skill to search + read the live Wix API reference — never guess. The
@@ -0,0 +1,267 @@
1
+ // Rentals seed helpers — run at BUILD TIME via exec_tool (NOT shipped in the app).
2
+ // A DELTA on seed-bookings.js: Wix Rentals has no APIs of its own, so a rental is a Bookings
3
+ // service carrying rentals-specific field values. Everything about the transport, images and
4
+ // error handling is the same; only the create order and the service payload differ.
5
+ //
6
+ // Live-verified end to end on fresh headless sites (install → resource type → resources → service →
7
+ // time-slots → end-options).
8
+ //
9
+ // Usage (build-time exec_tool) — prefer `setupRentals`, which runs the whole flow in the one order
10
+ // that works and threads the resource ids into each service for you:
11
+ // const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
12
+ // const seed = require("/app/.agents/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js");
13
+ // const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
14
+ // await seed.setupRentals(ctx, {
15
+ // resourceTypeName: "Meeting rooms",
16
+ // resources: ["Room A", "Room B"], // capacity = MORE RESOURCES
17
+ // rentals: [
18
+ // { name: "Meeting room, hourly", description: "…", price: 25, unit: "HOUR", min: 60, max: 480 },
19
+ // { name: "Meeting room, daily", description: "…", price: 180, unit: "DAY", min: 1, max: 5 },
20
+ // ],
21
+ // });
22
+ // // Calling the steps by hand instead? ORDER IS LOAD-BEARING (resource type → resources → service),
23
+ // // and each rental spec must carry `resourceTypeId` AND `resourceIds` (the ids from createResources).
24
+ //
25
+ // What a bookable rental needs, and setupRentals sets every one:
26
+ // 1. `appId` on the service at create time (it is immutable, so set it here rather than later).
27
+ // 2. Resources created before the service, and their ids listed in `serviceResources[].resourceIds`.
28
+ // 3. No category — a rental is surfaced by its `appId`, not by a Bookings category.
29
+ //
30
+ // Images are reused from seed-bookings.js — require both.
31
+ // Source recipe: wix-headless/references/inline-recipes/setup-rentals.md.
32
+
33
+ const API = "https://www.wixapis.com";
34
+
35
+ /** The Wix Rentals app — adds resource types and duration ranges on top of Bookings. */
36
+ const RENTALS_APP_ID = "ff5d6eb1-65e4-4f9a-8b14-64d34c12cc2e";
37
+
38
+ async function req(ctx, path, { method = "POST", body } = {}) {
39
+ const res = await fetch(API + path, {
40
+ method,
41
+ headers: {
42
+ Authorization: `Bearer ${ctx.token}`,
43
+ "wix-site-id": ctx.siteId,
44
+ "Content-Type": "application/json",
45
+ },
46
+ body: body ? JSON.stringify(body) : undefined,
47
+ });
48
+ const json = await res.json().catch(() => ({}));
49
+ if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json).slice(0, 400)}`);
50
+ return json;
51
+ }
52
+
53
+ /**
54
+ * Install Wix Rentals. Idempotent. Rentals provisions the Bookings infrastructure it runs on, so a
55
+ * rental's availability comes from a Rentals-only install — Wix Bookings does not need to be added.
56
+ */
57
+ async function installRentalsApp(ctx) {
58
+ try {
59
+ await req(ctx, "/apps-installer-service/v1/app-instance/install", {
60
+ body: {
61
+ tenant: { tenantType: "SITE", id: ctx.siteId },
62
+ appInstance: { appDefId: RENTALS_APP_ID, enabled: true },
63
+ },
64
+ });
65
+ } catch {
66
+ /* already installed is fine */
67
+ }
68
+ }
69
+
70
+ /**
71
+ * A resource TYPE — the kind of thing being rented ("Meeting rooms", "Vans", "Cameras").
72
+ * A rental service points at one of these; the individual rentable items live inside it.
73
+ * @returns {Promise<{ id: string, name: string }>}
74
+ */
75
+ async function createResourceType(ctx, name) {
76
+ const res = await req(ctx, "/bookings/v2/resources/resource-types", { body: { resourceType: { name } } });
77
+ const type = res?.resourceType ?? {};
78
+ return { id: type.id ?? type._id, name: type.name ?? name };
79
+ }
80
+
81
+ /** Existing resource types, so a re-run reuses one instead of making a duplicate. */
82
+ async function queryResourceTypes(ctx) {
83
+ const res = await req(ctx, "/bookings/v2/resources/resource-types/query", { body: { query: {} } });
84
+ return (res?.resourceTypes ?? []).map((t) => ({ id: t.id ?? t._id, name: t.name }));
85
+ }
86
+
87
+ /** Get a resource type by name, creating it only if it isn't there. Additive. */
88
+ async function ensureResourceType(ctx, name) {
89
+ const existing = (await queryResourceTypes(ctx)).find((t) => t.name === name);
90
+ return existing ?? createResourceType(ctx, name);
91
+ }
92
+
93
+ /**
94
+ * The individual rentable items inside a type — Room A, Room B, Van 1.
95
+ *
96
+ * ⚠️ PARALLEL CAPACITY COMES FROM MORE RESOURCES, not from a capacity number: two rooms that
97
+ * can be booked at the same time are two resources.
98
+ *
99
+ * ⚠️ Seeded 24/7 on purpose — no `workingHoursSchedules`. A resource with working hours makes a
100
+ * multi-day rental span several bookable windows, which Wix then models as a multi-service
101
+ * group booking instead of one booking. Pass `workingHours` only when the brief names opening
102
+ * hours and you accept that.
103
+ * @returns {Promise<{ id: string, name: string }[]>}
104
+ */
105
+ async function createResources(ctx, resourceTypeId, names, { workingHours } = {}) {
106
+ const out = [];
107
+ for (const name of names) {
108
+ const res = await req(ctx, "/bookings/v2/resources", {
109
+ body: {
110
+ resource: {
111
+ name,
112
+ // Link the resource to its type with a top-level `typeId` (a bare GUID).
113
+ typeId: resourceTypeId,
114
+ ...(workingHours ? { workingHoursSchedules: workingHours } : {}),
115
+ },
116
+ },
117
+ });
118
+ const r = res?.resource ?? {};
119
+ out.push({ id: r.id ?? r._id, name: r.name ?? name });
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /** The Wix Rentals default booking form. Provisioned by the Rentals app; the id is the same on every site. */
125
+ const RENTALS_FORM_ID = "3a2ea2ce-91f4-4617-ab24-629933c0c31a";
126
+
127
+ /**
128
+ * Build one rental service payload.
129
+ *
130
+ * Field values that MAKE it a rental (all required together):
131
+ * type: "APPOINTMENT" — rentals are always appointment-typed
132
+ * appId: RENTALS_APP_ID — immutable after create
133
+ * durationRange — replaces sessionDurations; the two are mutually exclusive
134
+ * serviceResources + — which resource type AND which resources supply availability
135
+ * primaryResourceType
136
+ *
137
+ * Hourly bounds are MINUTES (30–1440); daily bounds are DAYS (1–8). One service = one unit
138
+ * type: to rent the same room by the hour AND by the day, create two services.
139
+ * @param {object} s spec + `resourceTypeId` and `resourceIds` (the ids from createResources).
140
+ */
141
+ function buildRental(s) {
142
+ const unit = s.unit === "DAY" ? "DAY" : "HOUR";
143
+ const durationRange =
144
+ unit === "DAY"
145
+ ? { unitType: "DAY", dayOptions: { minDurationInDays: s.min ?? 1, maxDurationInDays: s.max ?? 5 } }
146
+ : { unitType: "HOUR", hourOptions: { minDurationInMinutes: s.min ?? 60, maxDurationInMinutes: s.max ?? 480 } };
147
+
148
+ return {
149
+ type: "APPOINTMENT",
150
+ appId: RENTALS_APP_ID,
151
+ name: s.name,
152
+ description: s.description ?? "",
153
+ ...(s.tagLine ? { tagLine: s.tagLine } : {}),
154
+ hidden: false,
155
+ defaultCapacity: 1,
156
+ // Use the Wix Rentals default booking form so checkout collects contact details.
157
+ form: { id: RENTALS_FORM_ID },
158
+ // Make the rental bookable online.
159
+ onlineBooking: { enabled: true, requireManualApproval: false, allowMultipleRequests: false },
160
+ // A rental is priced as a RATE per unit of time — the number below is per hour or per day,
161
+ // and Wix multiplies it by the length the customer picks.
162
+ payment: {
163
+ rateType: "FIXED",
164
+ fixed: { price: { value: String(s.price ?? 0), currency: s.currency ?? "USD" } },
165
+ options: { online: true, inPerson: false },
166
+ },
167
+ schedule: { availabilityConstraints: { durationRange } },
168
+ // Name the resource type AND its concrete resource ids — availability comes from the listed ids.
169
+ serviceResources: [{ resourceType: { id: s.resourceTypeId }, resourceIds: { values: s.resourceIds ?? [] } }],
170
+ // The resource type as a bare GUID string, and one of the types named in serviceResources above.
171
+ primaryResourceType: s.resourceTypeId,
172
+ locations: [{ type: "BUSINESS" }],
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Create rental services. Additive — an existing service with the same name is left alone.
178
+ * @returns {Promise<{ id: string, name: string, unit: string, revision: string }[]>}
179
+ */
180
+ async function createRentals(ctx, rentals) {
181
+ const existing = await req(ctx, "/bookings/v2/services/query", {
182
+ body: { query: { filter: { appId: RENTALS_APP_ID } } },
183
+ }).catch(() => ({ services: [] }));
184
+ const byName = new Map((existing.services ?? []).map((s) => [s.name, s]));
185
+
186
+ const out = [];
187
+ for (const spec of rentals) {
188
+ const already = byName.get(spec.name);
189
+ if (already) {
190
+ out.push({
191
+ id: already.id ?? already._id,
192
+ name: already.name,
193
+ unit: already.schedule?.availabilityConstraints?.durationRange?.unitType ?? null,
194
+ revision: already.revision,
195
+ created: false,
196
+ });
197
+ continue;
198
+ }
199
+ const res = await req(ctx, "/bookings/v2/services", { body: { service: buildRental(spec) } });
200
+ const svc = res?.service ?? {};
201
+ out.push({
202
+ id: svc.id ?? svc._id,
203
+ name: svc.name ?? spec.name,
204
+ unit: svc.schedule?.availabilityConstraints?.durationRange?.unitType ?? null,
205
+ revision: svc.revision,
206
+ created: true,
207
+ });
208
+ }
209
+ return out;
210
+ }
211
+
212
+ /**
213
+ * Read the rentals back and confirm each one is actually a rental. A service created without
214
+ * `appId` or without `durationRange` still returns 200 — it is simply a plain Bookings service
215
+ * from then on, permanently, and this is the only check that catches it.
216
+ * @returns {Promise<{ ok: boolean, problems: string[] }>}
217
+ */
218
+ async function verifyRentals(ctx, expectedNames) {
219
+ const res = await req(ctx, "/bookings/v2/services/query", {
220
+ body: { query: { filter: { appId: RENTALS_APP_ID } } },
221
+ });
222
+ const live = new Map((res?.services ?? []).map((s) => [s.name, s]));
223
+ const problems = [];
224
+ for (const name of expectedNames) {
225
+ const svc = live.get(name);
226
+ if (!svc) {
227
+ problems.push(`${name}: not returned by an appId-filtered query — it was not created as a rental`);
228
+ continue;
229
+ }
230
+ if (!svc.schedule?.availabilityConstraints?.durationRange) {
231
+ problems.push(`${name}: no durationRange — created as a plain service, and appId is immutable`);
232
+ }
233
+ if (!svc.primaryResourceType) problems.push(`${name}: no primaryResourceType — availability will be empty`);
234
+ if (!svc.serviceResources?.some((sr) => sr.resourceIds?.values?.length))
235
+ problems.push(`${name}: serviceResources carry no resourceIds — availability will be empty`);
236
+ }
237
+ return { ok: problems.length === 0, problems };
238
+ }
239
+
240
+ /**
241
+ * The whole rentals seed, in the one order that works.
242
+ * @param {object} ctx
243
+ * @param {{ resourceTypeName: string, resources: string[], rentals: object[] }} plan
244
+ */
245
+ async function setupRentals(ctx, { resourceTypeName, resources = [], rentals = [] }) {
246
+ await installRentalsApp(ctx);
247
+ const type = await ensureResourceType(ctx, resourceTypeName);
248
+ const created = await createResources(ctx, type.id, resources);
249
+ // Every rental service must list the concrete resource ids, or its availability is empty (see buildRental).
250
+ const resourceIds = created.map((c) => c.id);
251
+ const services = await createRentals(ctx, rentals.map((r) => ({ ...r, resourceTypeId: type.id, resourceIds })));
252
+ const check = await verifyRentals(ctx, rentals.map((r) => r.name));
253
+ return { resourceType: type, resources: created, services, ...check };
254
+ }
255
+
256
+ module.exports = {
257
+ RENTALS_APP_ID,
258
+ installRentalsApp,
259
+ createResourceType,
260
+ queryResourceTypes,
261
+ ensureResourceType,
262
+ createResources,
263
+ buildRental,
264
+ createRentals,
265
+ verifyRentals,
266
+ setupRentals,
267
+ };
@@ -2,9 +2,12 @@
2
2
 
3
3
  Unlike the other verticals, CMS ships **no UI**. A CMS is schema-driven — every collection is
4
4
  different — so there are no meaningful "list/detail" components to hand you. You **build the UI
5
- yourself** (list, detail, home, forms) in your own framework and design tokens, calling the shipped
6
- **utils**. Everything talks to Wix directly over the public `WIX_CLIENT_ID` (anonymous visitor token)
7
- using the official Wix Data endpoints — never hand-build a Wix Data URL, never mock items.
5
+ yourself** (list, detail, home) in your own framework and design tokens, calling the shipped
6
+ **utils**. **A visitor-fillable form is the `forms` vertical, not `cms`** — come here only when the
7
+ app must read the entries back (a public gallery, a listing, a member's "my submissions"); a visitor
8
+ cannot read Forms submissions. Everything talks to Wix directly over the public `WIX_CLIENT_ID`
9
+ (anonymous visitor token) using the official Wix Data endpoints — never hand-build a Wix Data URL,
10
+ never mock items.
8
11
 
9
12
  ## What ships (utils only)
10
13