@wix/agent-skills 1.19.2 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
  3. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
  4. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
  5. package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
  6. package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
  7. package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
  8. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
  9. package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
  10. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
  11. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  12. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
  13. package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
  14. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
  15. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
  16. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
  17. package/skills/wix-headless/references/CAPABILITIES.md +2 -2
  18. package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
  19. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
  20. package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
  21. package/skills/wix-headless-fast/SKILL.md +200 -34
  22. package/skills/wix-headless-fast/entry/skill.md +10 -2
  23. package/skills/wix-headless-fast/install/attach.mjs +253 -0
  24. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  25. package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
  26. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
  27. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  28. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  29. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  30. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  32. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  33. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  34. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  35. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  36. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  37. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  38. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  39. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  40. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  41. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
  42. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  43. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  44. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  48. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  49. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  50. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  51. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  52. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  53. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  54. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  55. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
  56. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  57. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  59. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  60. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  61. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  62. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  63. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  64. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  66. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  67. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  68. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  69. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  71. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  74. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  75. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  76. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  77. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  78. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  79. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
  80. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  85. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  86. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  87. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  88. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
  100. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  104. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  107. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  112. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  113. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  114. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
  115. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  116. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  117. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  118. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  119. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  120. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  122. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  126. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  127. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  129. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
  130. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  131. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  132. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  133. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  134. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  135. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  136. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  137. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  142. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  143. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  144. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  145. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  146. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  147. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
  148. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  149. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  150. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  151. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  152. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
  153. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
  154. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
  155. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  156. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
  157. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
  158. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
  159. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  160. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
  161. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
  162. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
  163. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
  166. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
  167. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  168. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  169. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
  170. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
  171. package/skills/wix-manage/SKILL.md +4 -1
  172. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  173. package/skills/wix-manage/references/events/create-wix-event.md +46 -0
  174. package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
  175. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
  176. package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
  177. package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
  178. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
  179. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
  180. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  181. package/skills/wix-vibe-headless/platforms/base44.md +19 -4
  182. package/skills/wix-vibe-headless/platforms/generic.md +6 -0
  183. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
  184. package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
  185. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
  186. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
  187. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
  188. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
  189. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
  190. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
  191. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
  192. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
  193. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
  194. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
  195. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
  196. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
  197. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
  198. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
  199. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
  200. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
  201. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
  202. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
  203. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
  204. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
  205. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
  206. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
  207. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
  208. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
  209. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
  210. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
  211. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
  212. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
  213. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  214. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  215. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -1,227 +1,80 @@
1
- // Catalog reads (Wix Stores Catalog V3) — the only file that touches raw catalog entities.
2
- // Everything it returns is a plain DTO from ./types: images resolved, prices formatted,
3
- // variants normalized. Copy as-is; extend by adding functions, not by editing these.
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.
4
5
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
5
6
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product-by-slug.md
6
7
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/query-categories.md
7
8
  import { productsV3 } from "@wix/stores";
8
9
  import { categories as categoriesModule } from "@wix/categories";
9
10
  import { wixModule } from "../sdk";
10
- import { imgSrc } from "../media";
11
- import type {
12
- Category,
13
- ProductDetail,
14
- ProductOption,
15
- ProductModifier,
16
- ProductSummary,
17
- ProductVariant,
18
- Availability,
19
- } from "./types";
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 };
20
31
 
21
32
  const products = wixModule(productsV3);
22
33
  const categories = wixModule(categoriesModule);
23
34
 
24
- // Requested on every product read. CURRENCY populates formattedAmount (without it, prices render
25
- // as bare unlocalized numbers); MEDIA_ITEMS_INFO populates the gallery; the detail read adds
26
- // PLAIN_DESCRIPTION and VARIANT_OPTION_CHOICE_NAMES (which populates variantsInfo.variants).
27
- const LIST_FIELDS = ["CURRENCY", "MEDIA_ITEMS_INFO"];
28
- const DETAIL_FIELDS = [...LIST_FIELDS, "PLAIN_DESCRIPTION", "VARIANT_OPTION_CHOICE_NAMES"];
29
-
30
- type RawProduct = Record<string, any>;
31
-
32
- function toAvailability(raw: RawProduct): Availability {
33
- const status = raw.inventory?.availabilityStatus;
34
- return status === "OUT_OF_STOCK" || status === "PARTIALLY_OUT_OF_STOCK"
35
- ? status
36
- : "IN_STOCK";
37
- }
38
-
39
- function toSummary(raw: RawProduct): ProductSummary {
40
- const options: RawProduct[] = raw.options ?? [];
41
- const galleryItems: RawProduct[] = raw.media?.itemsInfo?.items ?? [];
42
- const mainUrl = imgSrc(raw.media?.main, 800, 800);
43
- const hover = galleryItems
44
- .map((m) => imgSrc(m.image ?? m, 800, 800))
45
- .filter((u) => u && u !== mainUrl);
46
- const availability = toAvailability(raw);
47
- const preorder = raw.inventory?.preorderStatus === "ENABLED" && availability === "OUT_OF_STOCK";
48
- const optionsSummary = options
49
- .map((o) => {
50
- const visible = (o.choicesSettings?.choices ?? []).filter((c: RawProduct) => c.visible !== false);
51
- return `${visible.length} ${String(o.name ?? "").toLowerCase()}${visible.length === 1 ? "" : "s"}`;
52
- })
53
- .join(" · ");
54
- return {
55
- id: raw._id ?? "",
56
- slug: raw.slug ?? "",
57
- name: raw.name ?? "",
58
- price: raw.actualPriceRange?.minValue?.formattedAmount ?? "",
59
- maxPrice: raw.actualPriceRange?.maxValue?.formattedAmount ?? "",
60
- compareAtPrice: raw.compareAtPriceRange?.minValue?.formattedAmount ?? null,
61
- ribbon: raw.ribbon?.name ?? null,
62
- availability,
63
- preorder,
64
- imageUrl: mainUrl,
65
- hoverImageUrl: hover[0] ?? "",
66
- optionsSummary,
67
- quickAddable: options.length === 0 && availability === "IN_STOCK",
68
- };
69
- }
70
-
71
- function toOptions(raw: RawProduct): ProductOption[] {
72
- return (raw.options ?? []).map((o: RawProduct) => {
73
- const isColor = o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES";
74
- return {
75
- id: o._id ?? o.id ?? o.name ?? "",
76
- name: o.name ?? "",
77
- isColor,
78
- choices: (o.choicesSettings?.choices ?? [])
79
- .filter((c: RawProduct) => c.visible !== false)
80
- .map((c: RawProduct) => ({
81
- choiceId: c.choiceId ?? "",
82
- name: c.name ?? "",
83
- colorCode: c.colorCode ?? null,
84
- inStock: c.inStock !== false,
85
- })),
86
- };
87
- });
88
- }
89
-
90
- function toModifiers(raw: RawProduct): ProductModifier[] {
91
- return (raw.modifiers ?? []).map((m: RawProduct) => ({
92
- key: m.key ?? m.freeTextSettings?.key ?? m.name ?? "",
93
- name: m.name ?? "",
94
- mandatory: m.mandatory === true,
95
- type: m.modifierRenderType === "FREE_TEXT" ? ("text" as const) : ("choices" as const),
96
- choices: (m.choicesSettings?.choices ?? []).map((c: RawProduct) => ({
97
- key: c.key ?? c.name ?? "",
98
- name: c.name ?? "",
99
- })),
100
- }));
101
- }
102
-
103
- function toVariants(raw: RawProduct): ProductVariant[] {
104
- return (raw.variantsInfo?.variants ?? []).map((v: RawProduct) => {
105
- const choices: Record<string, string> = {};
106
- for (const c of v.choices ?? []) {
107
- const names = c.optionChoiceNames;
108
- if (names?.optionName) choices[names.optionName] = names.choiceName ?? "";
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 */
109
50
  }
110
- return {
111
- variantId: v._id ?? v.variantId ?? "",
112
- choices,
113
- price: v.price?.actualPrice?.formattedAmount ?? "",
114
- compareAtPrice: v.price?.compareAtPrice?.formattedAmount ?? null,
115
- inStock: v.inventoryStatus?.inStock !== false,
116
- };
117
- });
118
- }
119
-
120
- function toDetail(raw: RawProduct): ProductDetail {
121
- const summary = toSummary(raw);
122
- const gallery = [
123
- summary.imageUrl,
124
- ...(raw.media?.itemsInfo?.items ?? []).map((m: RawProduct) => imgSrc(m.image ?? m, 1200, 1200)),
125
- ].filter((u, i, arr) => u && arr.indexOf(u) === i);
51
+ }
126
52
  return {
127
- ...summary,
128
- descriptionHtml: raw.plainDescription ?? "",
129
- gallery,
130
- options: toOptions(raw),
131
- modifiers: toModifiers(raw),
132
- variants: toVariants(raw),
53
+ products: ((res.products ?? []) as Raw[]).map((p) => toSummary(p, imgSrc, mediaKey)),
54
+ nextCursor: res.pagingMetadata?.cursors?.next ?? null,
55
+ total,
133
56
  };
134
57
  }
135
58
 
136
- // Search Products applies sort/filter/search server-side, across the WHOLE catalog, before
137
- // cursor paging — the only correct place for them. Sorting or filtering an already-fetched
138
- // page only rearranges the slice you happen to hold.
139
- // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
140
- export const CATALOG_SORTS = {
141
- featured: { label: "Default order" }, // the catalog's own order; no merchant-curated promise
142
- priceAsc: { label: "Price: low to high" },
143
- priceHigh: { label: "Price: high to low" },
144
- name: { label: "Name: A\u2013Z" },
145
- newest: { label: "Newest" },
146
- } as const;
147
- export type CatalogSort = keyof typeof CATALOG_SORTS;
148
-
149
- const SORT_FIELDS: Partial<Record<CatalogSort, { fieldName: string; order: "ASC" | "DESC" }[]>> = {
150
- priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
151
- priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
152
- name: [{ fieldName: "name", order: "ASC" }],
153
- newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
154
- };
155
-
156
- function priceBound(value: number | string | undefined, name: string): number | undefined {
157
- if (value == null || value === "") return undefined;
158
- const parsed = Number(value);
159
- if (!String(value).trim() || !Number.isFinite(parsed) || parsed < 0) {
160
- throw new Error(`${name} must be a non-negative number.`);
161
- }
162
- return parsed;
163
- }
164
-
165
- export interface CatalogSearchOptions {
166
- limit?: number;
167
- /** Continues the ORIGINAL query — pass alone; new sort/filter values start without one. */
168
- cursor?: string;
169
- categoryId?: string | null;
170
- sort?: CatalogSort;
171
- /** Bounds compare the product's minimum actual variant price, in site currency. */
172
- minPrice?: number | string;
173
- maxPrice?: number | string;
174
- inStockOnly?: boolean;
175
- /** Name search, max 100 chars. */
176
- search?: string;
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;
177
62
  }
178
63
 
179
64
  /**
180
- * Search visible catalog products — sorted, filtered, and searched by Wix across the whole
181
- * catalog, then cursor-paged. `nextCursor` continues the same query; start over (no cursor)
182
- * whenever any selection changes.
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.
183
67
  */
184
- export async function searchCatalog({
185
- limit = 24,
186
- cursor,
187
- categoryId,
188
- sort = "featured",
189
- minPrice,
190
- maxPrice,
191
- inStockOnly = false,
192
- search = "",
193
- }: CatalogSearchOptions = {}): Promise<{ products: ProductSummary[]; nextCursor: string | null }> {
194
- if (!Number.isInteger(limit) || limit < 1 || limit > 100)
195
- throw new Error("limit must be between 1 and 100.");
196
- let query: RawProduct = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
197
- if (!cursor) {
198
- if (!(sort in CATALOG_SORTS)) throw new Error("Unsupported catalog sort.");
199
- const min = priceBound(minPrice, "minPrice");
200
- const max = priceBound(maxPrice, "maxPrice");
201
- if (min !== undefined && max !== undefined && min > max)
202
- throw new Error("minPrice must not exceed maxPrice.");
203
- if (search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
204
- const conditions: RawProduct[] = [{ visible: true }];
205
- if (categoryId)
206
- conditions.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
207
- // Search rejects two operators in one field object — separate bounds, joined by $and.
208
- if (min !== undefined)
209
- conditions.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
210
- if (max !== undefined)
211
- conditions.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
212
- if (inStockOnly) conditions.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
213
- query = {
214
- ...query,
215
- filter: { $and: conditions },
216
- ...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
217
- ...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
218
- };
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 };
219
77
  }
220
- const res: RawProduct = await products.searchProducts(query, { fields: LIST_FIELDS as any });
221
- return {
222
- products: (res.products ?? []).map((p: RawProduct) => toSummary(p)),
223
- nextCursor: res.pagingMetadata?.cursors?.next ?? null,
224
- };
225
78
  }
226
79
 
227
80
  /** First page of visible products in the default order — a thin wrap of searchCatalog. */
@@ -230,10 +83,7 @@ export async function fetchProducts({ limit = 24 } = {}): Promise<ProductSummary
230
83
  }
231
84
 
232
85
  /** First page of a category — same wrap; category filtering only works through search. */
233
- export async function fetchProductsByCategory(
234
- categoryId: string,
235
- { limit = 24 } = {},
236
- ): Promise<ProductSummary[]> {
86
+ export async function fetchProductsByCategory(categoryId: string, { limit = 24 } = {}): Promise<ProductSummary[]> {
237
87
  return (await searchCatalog({ limit, categoryId })).products;
238
88
  }
239
89
 
@@ -241,7 +91,7 @@ export async function fetchProductsByCategory(
241
91
  export async function fetchProductBySlug(slug: string): Promise<ProductDetail | null> {
242
92
  try {
243
93
  const res = await products.getProductBySlug(slug, { fields: DETAIL_FIELDS as any });
244
- return res.product ? toDetail(res.product as RawProduct) : null;
94
+ return res.product ? toDetail(res.product as Raw, imgSrc, mediaKey) : null;
245
95
  } catch {
246
96
  return null;
247
97
  }
@@ -259,32 +109,25 @@ export async function fetchCategories(): Promise<Category[]> {
259
109
  .queryCategories({ treeReference: { appNamespace: "@wix/stores" } })
260
110
  .exists("name", true)
261
111
  .find();
262
- return (res.items ?? [])
263
- .map((c) => ({
264
- id: (c as RawProduct)._id ?? "",
265
- slug: (c as RawProduct).slug ?? "",
266
- name: (c as RawProduct).name ?? "",
267
- }))
268
- .filter((c) => c.slug !== "all-products");
112
+ return (res.items ?? []).map((c) => toCategory(c as Raw)).filter((c) => c.slug !== "all-products");
269
113
  } catch {
270
114
  return [];
271
115
  }
272
116
  }
273
117
 
274
118
  /**
275
- * Resolve the variant for the buyer's selections (optionName -> choiceName).
276
- * A product with no options resolves to its single variant; with options, every option
277
- * must be selected and match. Returns null while the selection is incomplete.
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.
278
123
  */
279
- export function resolveVariant(
280
- detail: ProductDetail,
281
- selections: Record<string, string>,
282
- ): ProductVariant | null {
283
- if (detail.options.length === 0) return detail.variants[0] ?? null;
284
- if (!detail.options.every((o) => selections[o.name])) return null;
285
- return (
286
- detail.variants.find((v) =>
287
- detail.options.every((o) => v.choices[o.name] === selections[o.name]),
288
- ) ?? null
289
- );
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
+ }
290
133
  }
@@ -0,0 +1,147 @@
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
+ }