@wix/agent-skills 1.21.0 → 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 (258) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +60 -4
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
  4. package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
  5. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
  6. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
  7. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
  8. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
  9. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
  10. package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
  11. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
  12. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
  13. package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
  14. package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
  15. package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
  16. package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
  17. package/skills/wix-headless-fast/SKILL.md +61 -223
  18. package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
  19. package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +11 -12
  20. package/skills/wix-headless-fast/guides/existing-site.md +76 -0
  21. package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
  22. package/skills/wix-headless-fast/install/AGENTS.md +6 -3
  23. package/skills/wix-headless-fast/install/attach.mjs +15 -47
  24. package/skills/wix-headless-fast/install/deploy.mjs +21 -60
  25. package/skills/wix-headless-fast/install/lock.mjs +55 -0
  26. package/skills/wix-headless-fast/install/setup.mjs +32 -12
  27. package/skills/wix-headless-fast/install/templates.mjs +168 -0
  28. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
  29. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
  30. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -303
  31. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
  32. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
  33. package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
  34. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
  35. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
  36. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
  37. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
  38. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
  39. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
  40. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
  41. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
  42. package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
  43. package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
  44. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
  45. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
  46. package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
  47. package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
  48. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
  49. package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
  50. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +0 -22
  51. package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
  52. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -277
  53. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
  54. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
  55. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
  56. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
  57. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
  58. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
  59. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
  60. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
  61. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
  62. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
  63. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
  64. package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
  65. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
  66. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
  67. package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
  68. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
  69. package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
  70. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
  71. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +0 -28
  72. package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
  73. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -355
  74. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
  75. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
  76. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
  77. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
  78. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
  79. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
  80. package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
  81. package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
  82. package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
  83. package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
  84. package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
  85. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +0 -20
  86. package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
  87. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -307
  88. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
  89. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
  90. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
  91. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
  92. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
  93. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
  94. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
  95. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
  96. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
  97. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
  98. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
  99. package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
  100. package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
  101. package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
  102. package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
  103. package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
  104. package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
  105. package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
  106. package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
  107. package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
  108. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +0 -26
  109. package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
  110. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -340
  111. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
  112. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
  113. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
  114. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
  115. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
  116. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
  117. package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
  118. package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
  119. package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
  120. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
  121. package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
  122. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +0 -20
  123. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
  124. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -316
  125. package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
  126. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
  127. package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
  128. package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
  129. package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
  130. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
  131. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
  132. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
  133. package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
  134. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
  135. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
  136. package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
  137. package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
  138. package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
  139. package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
  140. package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
  141. package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
  142. package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
  143. package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
  144. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +0 -15
  145. package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
  146. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -315
  147. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
  148. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
  149. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
  150. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
  151. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
  152. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
  153. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
  154. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
  155. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
  156. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
  157. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
  158. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
  159. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
  160. package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
  161. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
  162. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
  163. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
  164. package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
  165. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
  166. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
  167. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +0 -20
  168. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -256
  169. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -296
  170. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
  171. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
  172. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
  173. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
  174. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
  175. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
  176. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
  177. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
  178. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
  179. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
  180. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
  181. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
  182. package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
  183. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
  184. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
  185. package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
  186. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
  187. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
  188. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
  189. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +0 -21
  190. package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
  191. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -360
  192. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
  193. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
  194. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
  195. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
  196. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
  197. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
  198. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
  199. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
  200. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
  201. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
  202. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
  203. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
  204. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
  205. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
  206. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
  207. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
  208. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
  209. package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
  210. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
  211. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
  212. package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
  213. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
  214. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
  215. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
  216. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
  217. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +0 -25
  218. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -499
  219. package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
  220. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
  221. package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
  222. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
  223. package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
  224. package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
  225. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
  226. package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
  227. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
  228. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
  229. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
  230. package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
  231. package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
  232. package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
  233. package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
  234. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +0 -99
  235. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -612
  236. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
  237. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
  238. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
  239. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
  240. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
  241. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
  242. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
  243. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
  244. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
  245. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
  246. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
  247. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -314
  248. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
  249. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
  250. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
  251. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -169
  252. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
  253. package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
  254. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
  255. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
  256. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -130
  257. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +0 -38
  258. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -561
@@ -1,314 +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
- // `variant.media` is read-only on the API: Wix derives it from the media linked to the variant's
236
- // choice (a product with several options derives it only when the choices agree). Mapped to a
237
- // URL so a PDP can show the selected variant's picture without knowing how Wix derives it.
238
- export function toVariants(raw: Raw, imgSrc: ImgSrc): ProductVariant[] {
239
- return ((raw.variantsInfo?.variants ?? []) as Raw[])
240
- .filter((v) => v.visible !== false)
241
- .map((v) => {
242
- const choices: Record<string, string> = {};
243
- for (const c of (v.choices ?? []) as Raw[]) {
244
- const on = c.optionChoiceNames?.optionName, cn = c.optionChoiceNames?.choiceName;
245
- if (on && cn) choices[on] = cn;
246
- }
247
- const { current, original } = sellingPrice(v.price);
248
- const vm = v.media ? (v.media.image ?? v.media) : null;
249
- return {
250
- variantId: id(v),
251
- choices,
252
- price: current?.formattedAmount ?? "",
253
- compareAtPrice: strike(original, current),
254
- inStock: v.inventoryStatus?.inStock !== false,
255
- preorderEnabled: v.inventoryStatus?.preorderEnabled === true,
256
- imageUrl: vm ? imgSrc(vm, 1200, 1200) || null : null,
257
- };
258
- });
259
- }
260
-
261
- export function toDetail(raw: Raw, imgSrc: ImgSrc, mediaKey: MediaKey): ProductDetail {
262
- return {
263
- ...toSummary(raw, imgSrc, mediaKey),
264
- descriptionHtml: raw.plainDescription ?? "", // an HTML string despite the name — render as HTML
265
- infoSections: ((raw.infoSections ?? []) as Raw[])
266
- .map((s) => ({ title: s.title ?? "", html: s.plainDescription ?? "" }))
267
- .filter((s) => s.title || s.html),
268
- // De-duplicated on media identity, then resolved once at one size.
269
- gallery: mediaEntries(raw, mediaKey).map((m) => imgSrc(m, 1200, 1200)).filter(Boolean),
270
- options: toOptions(raw),
271
- modifiers: toModifiers(raw),
272
- variants: toVariants(raw, imgSrc),
273
- };
274
- }
275
-
276
- export function toCategory(raw: Raw): Category {
277
- return { id: id(raw), slug: raw.slug ?? "", name: raw.name ?? "", description: raw.description ?? "" };
278
- }
279
-
280
- /** Facets + price bounds from a scope's products (the 100-product read), so the panel only offers filters that exist. */
281
- export function toFacetData(products: Raw[]): FacetData {
282
- const byName = new Map<string, Facet>();
283
- let lo = Infinity, hi = -Infinity, currency = "";
284
- for (const p of products) {
285
- const min = Number(p.actualPriceRange?.minValue?.amount), max = Number(p.actualPriceRange?.maxValue?.amount);
286
- if (Number.isFinite(min)) lo = Math.min(lo, min);
287
- if (Number.isFinite(max)) hi = Math.max(hi, max);
288
- currency = currency || p.currency || "";
289
- for (const o of (p.options ?? []) as Raw[]) {
290
- if (!o.name) continue;
291
- const facet: Facet = byName.get(o.name) ?? { name: o.name, isColor: o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES", choices: [] };
292
- for (const c of (o.choicesSettings?.choices ?? []) as Raw[]) {
293
- if (c.visible === false || !c.choiceId) continue;
294
- if (!facet.choices.some((x) => x.id === c.choiceId)) facet.choices.push({ id: c.choiceId, name: c.name ?? "", colorCode: c.colorCode ?? null });
295
- }
296
- byName.set(o.name, facet);
297
- }
298
- }
299
- return {
300
- facets: [...byName.values()].filter((f) => f.choices.length > 1),
301
- priceRange: Number.isFinite(lo) && Number.isFinite(hi) && hi > lo ? { min: Math.floor(lo), max: Math.ceil(hi), currency } : null,
302
- };
303
- }
304
-
305
- /**
306
- * Resolve the variant for the buyer's selections (optionName → choiceName). A product with no
307
- * options resolves to its single variant; with options every option must be selected and match.
308
- * null while incomplete. Selections start EMPTY — never pre-pick a first choice.
309
- */
310
- export function resolveVariant(detail: ProductDetail, selections: Record<string, string>): ProductVariant | null {
311
- if (detail.options.length === 0) return detail.variants[0] ?? null;
312
- if (!detail.options.every((o) => selections[o.name])) return null;
313
- return detail.variants.find((v) => detail.options.every((o) => v.choices[o.name] === selections[o.name])) ?? null;
314
- }
@@ -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
- }