@wix/agent-skills 1.20.1 → 1.22.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 (262) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +71 -113
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
  8. package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
  9. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  10. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  11. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  12. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  13. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
  14. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
  15. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
  16. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
  17. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
  18. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  19. package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
  20. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
  21. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
  22. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  23. package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
  24. package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
  25. package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
  26. package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
  27. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  28. package/skills/wix-headless-fast/SKILL.md +144 -221
  29. package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
  30. package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +15 -15
  31. package/skills/wix-headless-fast/guides/existing-site.md +76 -0
  32. package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
  33. package/skills/wix-headless-fast/install/AGENTS.md +35 -0
  34. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  35. package/skills/wix-headless-fast/install/attach.mjs +161 -86
  36. package/skills/wix-headless-fast/install/deploy.mjs +21 -60
  37. package/skills/wix-headless-fast/install/lock.mjs +55 -0
  38. package/skills/wix-headless-fast/install/setup.mjs +259 -0
  39. package/skills/wix-headless-fast/install/templates.mjs +168 -0
  40. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  41. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  42. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
  43. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
  44. package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
  45. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -301
  46. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
  47. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
  48. package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
  49. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
  50. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
  51. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
  52. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
  53. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
  54. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
  55. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
  56. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
  57. package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
  58. package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
  59. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
  60. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
  61. package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
  62. package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
  63. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
  64. package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
  65. package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
  66. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -275
  67. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
  68. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
  69. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
  70. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
  71. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
  72. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
  73. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
  74. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
  75. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
  76. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
  77. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
  78. package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
  79. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
  80. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
  81. package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
  82. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
  83. package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
  84. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
  85. package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
  86. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -353
  87. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
  88. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
  89. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
  90. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
  91. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
  92. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
  93. package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
  94. package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
  95. package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
  96. package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
  97. package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
  98. package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
  99. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -305
  100. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
  101. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
  102. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
  103. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
  104. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
  105. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
  106. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
  107. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
  108. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
  109. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
  110. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
  111. package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
  112. package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
  113. package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
  114. package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
  115. package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
  116. package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
  117. package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
  118. package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
  119. package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
  120. package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
  121. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -338
  122. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
  123. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
  124. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
  125. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
  126. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
  127. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
  128. package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
  129. package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
  130. package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
  131. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
  132. package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
  133. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
  134. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -314
  135. package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
  136. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
  137. package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
  138. package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
  139. package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
  140. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
  141. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
  142. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
  143. package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
  144. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
  145. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
  146. package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
  147. package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
  148. package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
  149. package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
  150. package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
  151. package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
  152. package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
  153. package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
  154. package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
  155. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -313
  156. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
  157. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
  158. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
  159. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
  160. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
  161. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
  162. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
  163. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
  164. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
  165. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
  166. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
  167. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
  168. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
  169. package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
  170. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
  171. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
  172. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
  173. package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
  174. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
  175. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
  176. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -258
  177. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -294
  178. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
  179. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
  180. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
  181. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
  182. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
  183. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
  184. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
  185. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
  186. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
  187. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
  188. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
  189. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
  190. package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
  191. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
  192. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
  193. package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
  194. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
  195. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
  196. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
  197. package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
  198. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -358
  199. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
  200. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
  201. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
  202. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
  203. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
  204. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
  205. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
  206. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
  207. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
  208. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
  209. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
  210. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
  211. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
  212. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
  213. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
  214. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
  215. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
  216. package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
  217. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
  218. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
  219. package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
  220. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
  221. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
  222. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
  223. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
  224. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -479
  225. package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
  226. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
  227. package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
  228. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
  229. package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
  230. package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
  231. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
  232. package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
  233. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
  234. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
  235. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
  236. package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
  237. package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
  238. package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
  239. package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
  240. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -608
  241. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
  242. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
  243. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
  244. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
  245. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
  246. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
  247. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
  248. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
  249. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
  250. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
  251. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
  252. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -309
  253. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
  254. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
  255. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
  256. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -167
  257. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
  258. package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
  259. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
  260. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
  261. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -124
  262. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -525
@@ -1,309 +0,0 @@
1
- // Catalog rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./catalog.ts
2
- // (the SDK, managed Astro and React) and the REST twin in references/storefront/rest/catalog.ts
3
- // (fetch, a static site or a port to another language). Every rule about prices, ribbons,
4
- // ranges, media, variants lives HERE, once. A raw entity may come from the SDK (`_id`) or from
5
- // REST (`id`); the mappers accept both. Imports are type-only so a strip to JS emits no imports.
6
- import type {
7
- Availability,
8
- Category,
9
- Facet,
10
- FacetData,
11
- ProductDetail,
12
- ProductModifier,
13
- ProductOption,
14
- ProductSummary,
15
- ProductVariant,
16
- } from "./types";
17
-
18
- /** A raw Catalog V3 entity as either transport returns it. */
19
- export type Raw = Record<string, any>;
20
- /** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
21
- export type ImgSrc = (value: any, width: number, height: number) => string;
22
- /** Media identity before scaling — de-duplicate on this, never on a resolved URL. */
23
- export type MediaKey = (value: any) => string;
24
-
25
- const id = (raw: Raw | undefined): string => raw?._id ?? raw?.id ?? "";
26
-
27
- // Requested on every product read. CURRENCY → formattedAmount (without it prices render as bare
28
- // numbers); MEDIA_ITEMS_INFO → the gallery; MIN_PRICE_VARIANT + DISCOUNT_INFO → the cheapest variant
29
- // with its discounted price: a card's real price and the direct-add variant id, no per-product fetch.
30
- export const LIST_FIELDS = ["CURRENCY", "MEDIA_ITEMS_INFO", "MIN_PRICE_VARIANT", "DISCOUNT_INFO"] as const;
31
- // The detail read adds the HTML description, the variants (VARIANT_OPTION_CHOICE_NAMES — without it
32
- // variantsInfo is null), and the info sections with HTML bodies.
33
- export const DETAIL_FIELDS = [
34
- ...LIST_FIELDS,
35
- "PLAIN_DESCRIPTION",
36
- "VARIANT_OPTION_CHOICE_NAMES",
37
- "INFO_SECTION",
38
- "INFO_SECTION_PLAIN_DESCRIPTION",
39
- ] as const;
40
-
41
- // ---- sort + filter ---------------------------------------------------------------------------------
42
-
43
- export const CATALOG_SORTS = {
44
- featured: { label: "Default order" }, // the catalog's own order — no sort sent, and NOT a sales ranking
45
- priceAsc: { label: "Price: low to high" },
46
- priceHigh: { label: "Price: high to low" },
47
- name: { label: "Name: A–Z" },
48
- newest: { label: "Newest" },
49
- } as const;
50
- export type CatalogSort = keyof typeof CATALOG_SORTS;
51
-
52
- export const SORT_FIELDS: Partial<Record<CatalogSort, { fieldName: string; order: "ASC" | "DESC" }[]>> = {
53
- priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
54
- priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
55
- name: [{ fieldName: "name", order: "ASC" }],
56
- newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
57
- };
58
-
59
- export interface CatalogSearchOptions {
60
- /** 1–100, default 24. */
61
- limit?: number;
62
- /** Continues the ORIGINAL query — send it alone; any change of selection starts over without one. */
63
- cursor?: string | null;
64
- categoryId?: string | null;
65
- sort?: CatalogSort;
66
- /** Inclusive bounds on the product's minimum actual variant price, site currency. */
67
- minPrice?: number | string;
68
- maxPrice?: number | string;
69
- /** IN_STOCK only — excludes partially stocked and preorder-only products. */
70
- inStockOnly?: boolean;
71
- /** Name search, max 100 chars. */
72
- search?: string;
73
- /** Option-choice facets: products carrying ANY of these choice ids (see fetchFacetData). */
74
- choiceIds?: string[];
75
- }
76
-
77
- export function priceBound(value: number | string | undefined, name: string): number | undefined {
78
- if (value == null || value === "") return undefined;
79
- const n = Number(value);
80
- if (!Number.isFinite(n) || n < 0) throw new Error(`${name} must be a non-negative number.`);
81
- return n;
82
- }
83
-
84
- /**
85
- * The filter every catalog read shares (the page, its count, the facets) — one place, so they
86
- * can't drift. Search rejects two operators in ONE field object: price bounds are separate
87
- * conditions joined by $and. Category: `allCategoriesInfo.categories` with `$matchItems` on the
88
- * id (includes parent categories); never `$hasSome`, never V1 `collectionIds`. Facets discover
89
- * PRODUCTS carrying a choice; the picker / PDP still resolves the variant.
90
- */
91
- export function catalogFilter({ categoryId, minPrice, maxPrice, inStockOnly = false, choiceIds = [] }: CatalogSearchOptions = {}): Raw {
92
- const min = priceBound(minPrice, "minPrice"), max = priceBound(maxPrice, "maxPrice");
93
- if (min !== undefined && max !== undefined && min > max) throw new Error("minPrice must not exceed maxPrice.");
94
- const and: Raw[] = [{ visible: true }];
95
- if (categoryId) and.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
96
- if (min !== undefined) and.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
97
- if (max !== undefined) and.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
98
- if (inStockOnly) and.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
99
- if (choiceIds.length) and.push({ "options.choicesSettings.choices.choiceId": { $hasSome: choiceIds } });
100
- return { $and: and };
101
- }
102
-
103
- /** The full Search Products body (minus fields) for a page; a cursor request carries ONLY cursorPaging. */
104
- export function searchQuery(o: CatalogSearchOptions): Raw {
105
- const { limit = 24, cursor, sort = "featured", search = "" } = o;
106
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("limit must be between 1 and 100.");
107
- const paging = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
108
- if (cursor) return paging;
109
- if (!(sort in CATALOG_SORTS)) throw new Error("Unsupported catalog sort.");
110
- if (search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
111
- return {
112
- ...paging,
113
- filter: catalogFilter(o),
114
- ...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
115
- ...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
116
- };
117
- }
118
-
119
- // ---- price rules ----------------------------------------------------------------------------------
120
-
121
- /**
122
- * The price the buyer pays and the price to strike — exact precedence, shared by cards and
123
- * variants. An automatic discount (priceAfterDiscount) wins and strikes the regular actualPrice;
124
- * else actualPrice with the merchant's compareAtPrice as the "was". `!== undefined` on purpose:
125
- * a discounted price of 0 is a real price.
126
- */
127
- export function sellingPrice(price: Raw | undefined): { current?: Raw; original?: Raw } {
128
- if (price?.priceAfterDiscount !== undefined) return { current: price.priceAfterDiscount, original: price.actualPrice };
129
- return { current: price?.actualPrice, original: price?.compareAtPrice };
130
- }
131
-
132
- /** The struck price only when it is real and higher than what the buyer pays. */
133
- export function strike(original: Raw | undefined, current: Raw | undefined): string | null {
134
- const o = Number(original?.amount), c = Number(current?.amount);
135
- return original?.formattedAmount && Number.isFinite(o) && Number.isFinite(c) && o > c ? original.formattedAmount : null;
136
- }
137
-
138
- /** Every merchant ribbon, primary first, de-duplicated. A ribbon is a label, never proof of a price. */
139
- export function ribbonsOf(raw: Raw): string[] {
140
- return [raw.ribbon?.name, ...((raw.additionalRibbons ?? []) as Raw[]).map((r) => r?.name)]
141
- .filter((n, i, all): n is string => typeof n === "string" && n.length > 0 && all.indexOf(n) === i);
142
- }
143
-
144
- export function toAvailability(raw: Raw): Availability {
145
- const s = raw.inventory?.availabilityStatus;
146
- return s === "OUT_OF_STOCK" || s === "PARTIALLY_OUT_OF_STOCK" ? s : "IN_STOCK";
147
- }
148
-
149
- /**
150
- * Every distinct media entry, main first, keyed on identity — two scaled URLs of one photo are
151
- * ONE entry (the "two thumbnails for the same image" bug). Shared by the tile and the PDP.
152
- */
153
- export function mediaEntries(raw: Raw, mediaKey: MediaKey): unknown[] {
154
- const out: unknown[] = [];
155
- const seen = new Set<string>();
156
- for (const m of [raw.media?.main, ...((raw.media?.itemsInfo?.items ?? []) as Raw[])]) {
157
- const v = m?.image ?? m;
158
- const k = mediaKey(v);
159
- if (!k || seen.has(k)) continue;
160
- seen.add(k);
161
- out.push(v);
162
- }
163
- return out;
164
- }
165
-
166
- // ---- DTO mappers -----------------------------------------------------------------------------------
167
-
168
- export function toSummary(raw: Raw, imgSrc: ImgSrc, mediaKey: MediaKey): ProductSummary {
169
- const options: Raw[] = raw.options ?? [];
170
- const media = mediaEntries(raw, mediaKey);
171
- const mainUrl = imgSrc(media[0], 800, 800);
172
- const hover = media.slice(1).map((m) => imgSrc(m, 800, 800)).filter(Boolean);
173
- const availability = toAvailability(raw);
174
- // Price: the cheapest variant's selling price (discount applied); the product RANGE when variants
175
- // are priced differently — and NO struck price beside a range (a lone struck minimum claims a
176
- // saving that may not apply to the variant picked).
177
- const min = raw.actualPriceRange?.minValue, max = raw.actualPriceRange?.maxValue;
178
- const isRange = !!(min?.amount && max?.amount && min.amount !== max.amount);
179
- const minVariant: Raw | undefined = raw.variantSummary?.minPriceVariant;
180
- const { current, original } = sellingPrice(minVariant?.price);
181
- const ribbons = ribbonsOf(raw);
182
- return {
183
- id: id(raw),
184
- slug: raw.slug ?? "",
185
- name: raw.name ?? "",
186
- price: current?.formattedAmount ?? min?.formattedAmount ?? "",
187
- maxPrice: max?.formattedAmount ?? "",
188
- compareAtPrice: isRange ? null : strike(original ?? raw.compareAtPriceRange?.minValue, current ?? min),
189
- ribbon: ribbons[0] ?? null,
190
- ribbons,
191
- minPriceVariantId: minVariant ? id(minVariant) || minVariant.variantId || null : null,
192
- availability,
193
- // Out of stock but pre-orderable — label "Pre-order", not "Sold out".
194
- preorder: raw.inventory?.preorderStatus === "ENABLED" && availability === "OUT_OF_STOCK",
195
- imageUrl: mainUrl,
196
- hoverImageUrl: hover[0] ?? "",
197
- optionsSummary: options
198
- .map((o) => {
199
- const n = ((o.choicesSettings?.choices ?? []) as Raw[]).filter((c) => c.visible !== false).length;
200
- return `${n} ${String(o.name ?? "").toLowerCase()}${n === 1 ? "" : "s"}`;
201
- })
202
- .join(" · "),
203
- swatches: options
204
- .filter((o) => o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES")
205
- .flatMap((o) => (o.choicesSettings?.choices ?? []) as Raw[])
206
- .filter((c) => c.visible !== false && c.colorCode)
207
- .map((c) => String(c.colorCode)),
208
- // Addable in one click: no options and plainly in stock.
209
- quickAddable: options.length === 0 && availability === "IN_STOCK",
210
- };
211
- }
212
-
213
- export function toOptions(raw: Raw): ProductOption[] {
214
- return ((raw.options ?? []) as Raw[]).map((o) => ({
215
- id: id(o) || o.name || "",
216
- name: o.name ?? "",
217
- isColor: o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES",
218
- choices: ((o.choicesSettings?.choices ?? []) as Raw[])
219
- .filter((c) => c.visible !== false) // a retired choice the merchant no longer sells
220
- .map((c) => ({ choiceId: c.choiceId ?? "", name: c.name ?? "", colorCode: c.colorCode ?? null, inStock: c.inStock !== false })),
221
- }));
222
- }
223
-
224
- export function toModifiers(raw: Raw): ProductModifier[] {
225
- return ((raw.modifiers ?? []) as Raw[]).map((m) => ({
226
- key: m.key ?? m.freeTextSettings?.key ?? m.name ?? "",
227
- name: m.name ?? "",
228
- mandatory: m.mandatory === true,
229
- type: m.modifierRenderType === "FREE_TEXT" ? ("text" as const) : ("choices" as const),
230
- choices: ((m.choicesSettings?.choices ?? []) as Raw[]).map((c) => ({ key: c.key ?? c.name ?? "", name: c.name ?? "" })),
231
- }));
232
- }
233
-
234
- /** Variants keyed by optionName → choiceName. Buyable when in stock OR pre-orderable. */
235
- export function toVariants(raw: Raw): ProductVariant[] {
236
- return ((raw.variantsInfo?.variants ?? []) as Raw[])
237
- .filter((v) => v.visible !== false)
238
- .map((v) => {
239
- const choices: Record<string, string> = {};
240
- for (const c of (v.choices ?? []) as Raw[]) {
241
- const on = c.optionChoiceNames?.optionName, cn = c.optionChoiceNames?.choiceName;
242
- if (on && cn) choices[on] = cn;
243
- }
244
- const { current, original } = sellingPrice(v.price);
245
- return {
246
- variantId: id(v),
247
- choices,
248
- price: current?.formattedAmount ?? "",
249
- compareAtPrice: strike(original, current),
250
- inStock: v.inventoryStatus?.inStock !== false,
251
- preorderEnabled: v.inventoryStatus?.preorderEnabled === true,
252
- };
253
- });
254
- }
255
-
256
- export function toDetail(raw: Raw, imgSrc: ImgSrc, mediaKey: MediaKey): ProductDetail {
257
- return {
258
- ...toSummary(raw, imgSrc, mediaKey),
259
- descriptionHtml: raw.plainDescription ?? "", // an HTML string despite the name — render as HTML
260
- infoSections: ((raw.infoSections ?? []) as Raw[])
261
- .map((s) => ({ title: s.title ?? "", html: s.plainDescription ?? "" }))
262
- .filter((s) => s.title || s.html),
263
- // De-duplicated on media identity, then resolved once at one size.
264
- gallery: mediaEntries(raw, mediaKey).map((m) => imgSrc(m, 1200, 1200)).filter(Boolean),
265
- options: toOptions(raw),
266
- modifiers: toModifiers(raw),
267
- variants: toVariants(raw),
268
- };
269
- }
270
-
271
- export function toCategory(raw: Raw): Category {
272
- return { id: id(raw), slug: raw.slug ?? "", name: raw.name ?? "", description: raw.description ?? "" };
273
- }
274
-
275
- /** Facets + price bounds from a scope's products (the 100-product read), so the panel only offers filters that exist. */
276
- export function toFacetData(products: Raw[]): FacetData {
277
- const byName = new Map<string, Facet>();
278
- let lo = Infinity, hi = -Infinity, currency = "";
279
- for (const p of products) {
280
- const min = Number(p.actualPriceRange?.minValue?.amount), max = Number(p.actualPriceRange?.maxValue?.amount);
281
- if (Number.isFinite(min)) lo = Math.min(lo, min);
282
- if (Number.isFinite(max)) hi = Math.max(hi, max);
283
- currency = currency || p.currency || "";
284
- for (const o of (p.options ?? []) as Raw[]) {
285
- if (!o.name) continue;
286
- const facet: Facet = byName.get(o.name) ?? { name: o.name, isColor: o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES", choices: [] };
287
- for (const c of (o.choicesSettings?.choices ?? []) as Raw[]) {
288
- if (c.visible === false || !c.choiceId) continue;
289
- if (!facet.choices.some((x) => x.id === c.choiceId)) facet.choices.push({ id: c.choiceId, name: c.name ?? "", colorCode: c.colorCode ?? null });
290
- }
291
- byName.set(o.name, facet);
292
- }
293
- }
294
- return {
295
- facets: [...byName.values()].filter((f) => f.choices.length > 1),
296
- priceRange: Number.isFinite(lo) && Number.isFinite(hi) && hi > lo ? { min: Math.floor(lo), max: Math.ceil(hi), currency } : null,
297
- };
298
- }
299
-
300
- /**
301
- * Resolve the variant for the buyer's selections (optionName → choiceName). A product with no
302
- * options resolves to its single variant; with options every option must be selected and match.
303
- * null while incomplete. Selections start EMPTY — never pre-pick a first choice.
304
- */
305
- export function resolveVariant(detail: ProductDetail, selections: Record<string, string>): ProductVariant | null {
306
- if (detail.options.length === 0) return detail.variants[0] ?? null;
307
- if (!detail.options.every((o) => selections[o.name])) return null;
308
- return detail.variants.find((v) => detail.options.every((o) => v.choices[o.name] === selections[o.name])) ?? null;
309
- }
@@ -1,133 +0,0 @@
1
- // Catalog reads (Wix Stores Catalog V3) over the SDK — the only file that touches raw catalog
2
- // entities on this transport. Everything it returns is a plain DTO from ./types. The rules and
3
- // mappers live in ./catalog-core (shared with the REST twin in references/storefront/rest/); this
4
- // file is the transport only. Copy as-is; extend by adding functions, not by editing these.
5
- // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
6
- // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product-by-slug.md
7
- // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/query-categories.md
8
- import { productsV3 } from "@wix/stores";
9
- import { categories as categoriesModule } from "@wix/categories";
10
- import { wixModule } from "../sdk";
11
- import { imgSrc, mediaKey } from "../media";
12
- import {
13
- CATALOG_SORTS,
14
- DETAIL_FIELDS,
15
- LIST_FIELDS,
16
- catalogFilter,
17
- resolveVariant,
18
- searchQuery,
19
- sellingPrice,
20
- toCategory,
21
- toDetail,
22
- toFacetData,
23
- toSummary,
24
- type CatalogSearchOptions,
25
- type CatalogSort,
26
- type Raw,
27
- } from "./catalog-core";
28
- import type { Category, Facet, FacetData, ProductDetail, ProductSummary } from "./types";
29
-
30
- export { CATALOG_SORTS, resolveVariant, sellingPrice, type CatalogSearchOptions, type CatalogSort };
31
-
32
- const products = wixModule(productsV3);
33
- const categories = wixModule(categoriesModule);
34
-
35
- /**
36
- * Search visible catalog products — sorted, filtered, faceted, and searched by Wix across the
37
- * whole catalog, then cursor-paged. `nextCursor` continues the same query; start over (no cursor)
38
- * whenever any selection changes. `total` (the "N products" line) is issued with the first page.
39
- */
40
- export async function searchCatalog(o: CatalogSearchOptions = {}): Promise<{ products: ProductSummary[]; nextCursor: string | null; total: number | null }> {
41
- const query = searchQuery(o);
42
- const res: Raw = await products.searchProducts(query, { fields: LIST_FIELDS as any });
43
- let total: number | null = null;
44
- if (!o.cursor && query.filter) {
45
- try {
46
- const count: Raw = await products.countProducts({ filter: query.filter });
47
- total = typeof count?.count === "number" ? count.count : null;
48
- } catch {
49
- /* the count is a nicety — the page is still valid */
50
- }
51
- }
52
- return {
53
- products: ((res.products ?? []) as Raw[]).map((p) => toSummary(p, imgSrc, mediaKey)),
54
- nextCursor: res.pagingMetadata?.cursors?.next ?? null,
55
- total,
56
- };
57
- }
58
-
59
- /** The filterable options of the catalog (or one category). Non-fatal: [] when the read fails. */
60
- export async function fetchFacets(scope: { categoryId?: string | null } = {}): Promise<Facet[]> {
61
- return (await fetchFacetData(scope)).facets;
62
- }
63
-
64
- /**
65
- * Facets plus the scope's price bounds (lowest minimum, highest maximum across its products) — the
66
- * filter panel's slider needs both, from one 100-product read. Non-fatal: empty on failure.
67
- */
68
- export async function fetchFacetData({ categoryId }: { categoryId?: string | null } = {}): Promise<FacetData> {
69
- try {
70
- const res: Raw = await products.searchProducts(
71
- { filter: catalogFilter({ categoryId }), cursorPaging: { limit: 100 } },
72
- { fields: ["CURRENCY"] as any },
73
- );
74
- return toFacetData((res.products ?? []) as Raw[]);
75
- } catch {
76
- return { facets: [], priceRange: null };
77
- }
78
- }
79
-
80
- /** First page of visible products in the default order — a thin wrap of searchCatalog. */
81
- export async function fetchProducts({ limit = 24 } = {}): Promise<ProductSummary[]> {
82
- return (await searchCatalog({ limit })).products;
83
- }
84
-
85
- /** First page of a category — same wrap; category filtering only works through search. */
86
- export async function fetchProductsByCategory(categoryId: string, { limit = 24 } = {}): Promise<ProductSummary[]> {
87
- return (await searchCatalog({ limit, categoryId })).products;
88
- }
89
-
90
- /** Fetch one product by its URL slug, with options/modifiers/variants. Null when not found. */
91
- export async function fetchProductBySlug(slug: string): Promise<ProductDetail | null> {
92
- try {
93
- const res = await products.getProductBySlug(slug, { fields: DETAIL_FIELDS as any });
94
- return res.product ? toDetail(res.product as Raw, imgSrc, mediaKey) : null;
95
- } catch {
96
- return null;
97
- }
98
- }
99
-
100
- /**
101
- * List the store's categories for nav/filter UI, minus Wix's auto-created "all-products"
102
- * system category. The query must carry a filter condition (`.exists("name", true)` — a
103
- * tautology): a bare .find() serializes an empty filter that the API rejects on the
104
- * visitor-client path. Treat a failure as "no category nav", not a fatal error.
105
- */
106
- export async function fetchCategories(): Promise<Category[]> {
107
- try {
108
- const res = await categories
109
- .queryCategories({ treeReference: { appNamespace: "@wix/stores" } })
110
- .exists("name", true)
111
- .find();
112
- return (res.items ?? []).map((c) => toCategory(c as Raw)).filter((c) => c.slug !== "all-products");
113
- } catch {
114
- return [];
115
- }
116
- }
117
-
118
- /**
119
- * One category by its URL slug — the data a /category/[slug] page needs (its id feeds
120
- * searchCatalog / useShop, its name and description head the page). The response is WRAPPED:
121
- * { category }. Null when not found or hidden, which the page turns into a real 404 — never a
122
- * fallback to all products.
123
- */
124
- export async function fetchCategoryBySlug(slug: string): Promise<Category | null> {
125
- try {
126
- const res: Raw = await categories.getCategoryBySlug(slug, { appNamespace: "@wix/stores" } as any);
127
- const raw: Raw | undefined = res?.category;
128
- if (!raw || raw.visible === false) return null;
129
- return toCategory(raw);
130
- } catch {
131
- return null;
132
- }
133
- }
@@ -1,147 +0,0 @@
1
- // Product detail as a framework-free store — the logic behind useProductDetail, usable from React
2
- // (useProductDetail wraps it), from a static page's PDP and quick-add picker, from Vue/Svelte, or as
3
- // the specification for a port. Option selection → variant resolution → add-to-cart, plus modifier
4
- // inputs. This is where the "adds variants[0] regardless of the buyer's choice" class of bugs comes
5
- // from — always drive a purchase surface through this store, never resolve variants by hand.
6
- //
7
- // Selections start EMPTY (a buyer who never noticed a pre-picked default buys the wrong colour).
8
- // Pass the server-fetched ProductDetail as `initial` (SSR) or a `slug` (the store fetches on start).
9
- // One store per product surface: createProductDetailStore(), not a singleton.
10
- import { fetchProductBySlug, resolveVariant } from "./catalog";
11
- import { addLine } from "./cart-store";
12
- import type { ProductDetail, ProductOption, ProductVariant } from "./types";
13
-
14
- export interface ProductDetailStoreOptions {
15
- initial?: ProductDetail | null;
16
- slug?: string;
17
- }
18
-
19
- export interface OptionGroupView extends ProductOption {
20
- choices: (ProductOption["choices"][number] & { selected: boolean })[];
21
- }
22
-
23
- export interface ProductDetailState {
24
- /** null while loading (or when the slug doesn't resolve — check notFound). */
25
- product: ProductDetail | null;
26
- notFound: boolean;
27
- /** Option groups with per-choice `selected`, ready to render as pills/swatches. */
28
- optionGroups: OptionGroupView[];
29
- /** Modifier inputs keyed by modifier key. */
30
- modifierValues: Record<string, string>;
31
- /** The resolved variant; null while the selection is incomplete. */
32
- variant: ProductVariant | null;
33
- /** The RANGE ("€24.99 – €34.99", one value when equal) until every option is picked, then the variant's price. */
34
- price: string;
35
- /** Struck "was" price — only once a variant is resolved (or for a single-price product), never beside a range. */
36
- compareAtPrice: string | null;
37
- /** The resolved variant is out of stock but pre-orderable — label the action "Pre-order". */
38
- isPreorder: boolean;
39
- /** False until every option is selected and the resolved variant is in stock or pre-orderable. */
40
- canAdd: boolean;
41
- /** Why the action is disabled — neutral guidance ("Choose Size"), not an error; null when addable. */
42
- blockedReason: string | null;
43
- quantity: number;
44
- adding: boolean;
45
- error: string | null;
46
- }
47
-
48
- export interface ProductDetailStore {
49
- getState(): ProductDetailState;
50
- subscribe(listener: () => void): () => void;
51
- /** Fetch by slug when no `initial` was given. Call once when mounted. */
52
- start(): void;
53
- stop(): void;
54
- selectOption(optionName: string, choiceName: string): void;
55
- setModifier(key: string, value: string): void;
56
- setQuantity(n: number): void;
57
- /** Adds the resolved variant to the cart (the cart store opens the drawer). Throws on refusal. */
58
- add(): Promise<void>;
59
- }
60
-
61
- export function createProductDetailStore({ initial, slug }: ProductDetailStoreOptions): ProductDetailStore {
62
- let product: ProductDetail | null = initial ?? null;
63
- let notFound = false;
64
- let selections: Record<string, string> = {};
65
- let modifierValues: Record<string, string> = {};
66
- let quantity = 1;
67
- let adding = false;
68
- let error: string | null = null;
69
- let started = false;
70
- const listeners = new Set<() => void>();
71
- let snapshot: ProductDetailState | null = null;
72
- const emit = () => { snapshot = null; for (const fn of listeners) fn(); };
73
-
74
- function getState(): ProductDetailState {
75
- if (snapshot) return snapshot;
76
- const variant = product ? resolveVariant(product, selections) : null;
77
- const optionGroups: OptionGroupView[] = (product?.options ?? []).map((o) => ({
78
- ...o,
79
- choices: o.choices.map((c) => ({ ...c, selected: selections[o.name] === c.name })),
80
- }));
81
- const missingModifier = (product?.modifiers ?? []).find((m) => m.mandatory && (modifierValues[m.key] ?? "").length === 0);
82
- const missingOptions = (product?.options ?? []).filter((o) => !selections[o.name]).map((o) => o.name);
83
- const selectionComplete = missingOptions.length === 0;
84
- // In stock OR pre-orderable counts as buyable; a preorder add carries preOrderRequested.
85
- const isPreorder = !!variant && !variant.inStock && variant.preorderEnabled;
86
- const available = !!variant && (variant.inStock || variant.preorderEnabled);
87
- const canAdd = !!product && available && !missingModifier;
88
- const blockedReason: string | null = !product
89
- ? null
90
- : !selectionComplete ? `Choose ${missingOptions.join(" and ")}`
91
- : !variant ? "This combination isn't available"
92
- : !available ? "Out of stock"
93
- : missingModifier ? `Add ${missingModifier.name}`
94
- : null;
95
- // Before every option is picked, the range — never an empty price, never a lone struck minimum.
96
- const isRange = !!product && product.price !== product.maxPrice && !!product.maxPrice;
97
- const rangeDisplay = product ? (isRange ? `${product.price} – ${product.maxPrice}` : product.price) : "";
98
- snapshot = {
99
- product, notFound, optionGroups, modifierValues, variant,
100
- price: variant?.price || rangeDisplay,
101
- compareAtPrice: variant ? variant.compareAtPrice : isRange ? null : (product?.compareAtPrice ?? null),
102
- isPreorder, canAdd, blockedReason, quantity, adding, error,
103
- };
104
- return snapshot;
105
- }
106
-
107
- return {
108
- getState,
109
- subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
110
- start() {
111
- if (started) return;
112
- started = true;
113
- if (initial || !slug) return;
114
- fetchProductBySlug(slug)
115
- .then((p) => { if (!started) return; product = p; notFound = p === null; emit(); })
116
- .catch(() => { if (started) { notFound = true; emit(); } });
117
- },
118
- stop() { started = false; },
119
- selectOption(optionName, choiceName) {
120
- selections = { ...selections, [optionName]: choiceName };
121
- quantity = 1; // the ceiling belongs to the newly resolved variant
122
- emit();
123
- },
124
- setModifier(key, value) { modifierValues = { ...modifierValues, [key]: value }; emit(); },
125
- setQuantity(n) { quantity = n; emit(); },
126
- async add() {
127
- const s = getState();
128
- if (!product || !s.variant) return;
129
- adding = true; error = null; emit();
130
- try {
131
- const choiceModifiers: Record<string, string> = {};
132
- const textModifiers: Record<string, string> = {};
133
- for (const m of product.modifiers) {
134
- const value = modifierValues[m.key];
135
- if (!value) continue;
136
- if (m.type === "text") textModifiers[m.key] = value; else choiceModifiers[m.key] = value;
137
- }
138
- await addLine(product.id, s.variant.variantId, quantity, { modifierChoices: choiceModifiers, customTextFields: textModifiers, preorder: s.isPreorder });
139
- } catch (e) {
140
- error = e instanceof Error ? e.message : String(e);
141
- throw e;
142
- } finally {
143
- adding = false; emit();
144
- }
145
- },
146
- };
147
- }