@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,140 +1,39 @@
1
- // Product detail state: option selection → variant resolution → add-to-cart, plus modifier
2
- // inputs. This is the logic the turtle-run class of bugs comes from (adding variants[0]
3
- // regardless of the buyer's choice) — always drive a PDP through this hook.
1
+ // React binding of the product-detail store (wix/storefront/product-detail-store.ts) — option
2
+ // selection → variant resolution → add-to-cart lives there, framework-free. Always drive a PDP or
3
+ // quick-add picker through this hook (or the store): resolving variants by hand is where
4
+ // "added variants[0] regardless of the buyer's choice" comes from.
4
5
  //
5
- // SSR-friendly: pass the server-fetched ProductDetail as `initial` (Astro/Next); in a SPA
6
- // pass the slug and it fetches on mount.
7
- import { useEffect, useMemo, useState } from "react";
8
- import { fetchProductBySlug, resolveVariant } from "../../wix/storefront/catalog";
9
- import type { ProductDetail, ProductOption, ProductVariant } from "../../wix/storefront/types";
10
- import { useCart } from "./useCart";
6
+ // SSR-friendly: pass the server-fetched ProductDetail as `initial` (Astro/Next); in a SPA pass
7
+ // the slug and it fetches on mount.
8
+ import { useEffect, useRef, useSyncExternalStore } from "react";
9
+ import {
10
+ createProductDetailStore,
11
+ type ProductDetailState,
12
+ type ProductDetailStore,
13
+ type ProductDetailStoreOptions,
14
+ } from "../../wix/storefront/product-detail-store";
11
15
 
12
- export interface UseProductDetailOptions {
13
- initial?: ProductDetail | null;
14
- slug?: string;
15
- }
16
+ export { type OptionGroupView } from "../../wix/storefront/product-detail-store";
16
17
 
17
- export interface OptionGroupView extends ProductOption {
18
- choices: (ProductOption["choices"][number] & { selected: boolean })[];
19
- }
18
+ export type UseProductDetailOptions = ProductDetailStoreOptions;
20
19
 
21
- export interface UseProductDetail {
22
- /** null while loading (or when the slug doesn't resolve — check notFound). */
23
- product: ProductDetail | null;
24
- notFound: boolean;
25
- /** Option groups with per-choice `selected`, ready to render as pills/swatches. */
26
- optionGroups: OptionGroupView[];
27
- selectOption: (optionName: string, choiceName: string) => void;
28
- /** Modifier inputs keyed by modifier key. */
29
- modifierValues: Record<string, string>;
30
- setModifier: (key: string, value: string) => void;
31
- /** The resolved variant; null while the selection is incomplete. */
32
- variant: ProductVariant | null;
33
- /** Price to display right now (variant price once resolved, else the product range). */
34
- price: string;
35
- compareAtPrice: string | null;
36
- /** False until every option is selected (and the resolved variant is in stock). */
37
- canAdd: boolean;
38
- quantity: number;
39
- setQuantity: (n: number) => void;
40
- /** Adds the resolved variant to the cart (opens the drawer). Throws on refusal. */
41
- add: () => Promise<void>;
42
- adding: boolean;
43
- error: string | null;
44
- }
20
+ export type UseProductDetail = ProductDetailState &
21
+ Pick<ProductDetailStore, "selectOption" | "setModifier" | "setQuantity" | "add">;
45
22
 
46
23
  export function useProductDetail({ initial, slug }: UseProductDetailOptions): UseProductDetail {
47
- const [product, setProduct] = useState<ProductDetail | null>(initial ?? null);
48
- const [notFound, setNotFound] = useState(false);
49
- const [selections, setSelections] = useState<Record<string, string>>({});
50
- const [modifierValues, setModifierValues] = useState<Record<string, string>>({});
51
- const [quantity, setQuantity] = useState(1);
52
- const [adding, setAdding] = useState(false);
53
- const [error, setError] = useState<string | null>(null);
54
- const { addToCart } = useCart();
55
-
24
+ const ref = useRef<{ slug?: string; store: ProductDetailStore } | null>(null);
25
+ if (!ref.current || ref.current.slug !== slug) ref.current = { slug, store: createProductDetailStore({ initial, slug }) };
26
+ const store = ref.current.store;
56
27
  useEffect(() => {
57
- if (initial || !slug) return;
58
- let alive = true;
59
- fetchProductBySlug(slug)
60
- .then((p) => {
61
- if (!alive) return;
62
- setProduct(p);
63
- setNotFound(p === null);
64
- })
65
- .catch(() => alive && setNotFound(true));
66
- return () => {
67
- alive = false;
68
- };
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, [slug]);
71
-
72
- const variant = useMemo(
73
- () => (product ? resolveVariant(product, selections) : null),
74
- [product, selections],
75
- );
76
-
77
- const optionGroups: OptionGroupView[] = useMemo(
78
- () =>
79
- (product?.options ?? []).map((o) => ({
80
- ...o,
81
- choices: o.choices.map((c) => ({ ...c, selected: selections[o.name] === c.name })),
82
- })),
83
- [product, selections],
84
- );
85
-
86
- const mandatoryModifiersFilled = (product?.modifiers ?? [])
87
- .filter((m) => m.mandatory)
88
- .every((m) => (modifierValues[m.key] ?? "").length > 0);
89
-
90
- const canAdd =
91
- !!product &&
92
- product.availability !== "OUT_OF_STOCK" &&
93
- variant !== null &&
94
- variant.inStock &&
95
- mandatoryModifiersFilled;
96
-
97
- async function add(): Promise<void> {
98
- if (!product || !variant) return;
99
- setAdding(true);
100
- setError(null);
101
- try {
102
- const choiceModifiers: Record<string, string> = {};
103
- const textModifiers: Record<string, string> = {};
104
- for (const m of product.modifiers) {
105
- const value = modifierValues[m.key];
106
- if (!value) continue;
107
- if (m.type === "text") textModifiers[m.key] = value;
108
- else choiceModifiers[m.key] = value;
109
- }
110
- await addToCart(product.id, variant.variantId, quantity, {
111
- modifierChoices: choiceModifiers,
112
- customTextFields: textModifiers,
113
- });
114
- } catch (e) {
115
- setError(e instanceof Error ? e.message : String(e));
116
- throw e;
117
- } finally {
118
- setAdding(false);
119
- }
120
- }
121
-
28
+ store.start();
29
+ return () => store.stop();
30
+ }, [store]);
31
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
122
32
  return {
123
- product,
124
- notFound,
125
- optionGroups,
126
- selectOption: (optionName, choiceName) =>
127
- setSelections((s) => ({ ...s, [optionName]: choiceName })),
128
- modifierValues,
129
- setModifier: (key, value) => setModifierValues((v) => ({ ...v, [key]: value })),
130
- variant,
131
- price: variant?.price || product?.price || "",
132
- compareAtPrice: variant ? variant.compareAtPrice : (product?.compareAtPrice ?? null),
133
- canAdd,
134
- quantity,
135
- setQuantity,
136
- add,
137
- adding,
138
- error,
33
+ ...state,
34
+ selectOption: store.selectOption,
35
+ setModifier: store.setModifier,
36
+ setQuantity: store.setQuantity,
37
+ add: store.add,
139
38
  };
140
39
  }
@@ -1,190 +1,34 @@
1
- // Catalog listing for a shop surface: category filter, sort, price/stock/search filters, and
2
- // cursor paging — all applied by Wix across the whole catalog (searchCatalog), never to a page
3
- // already in hand. A changed selection starts a fresh cursor chain.
4
- //
5
- // SSR-friendly: pass `initialProducts`/`initialCategories` (Astro frontmatter) and they render
6
- // immediately — the hook still revalidates the first page once on mount to open the cursor
7
- // chain, replacing the seed without a skeleton flash.
8
- import { useCallback, useEffect, useRef, useState } from "react";
9
- import {
10
- CATALOG_SORTS,
11
- fetchCategories,
12
- searchCatalog,
13
- type CatalogSort,
14
- } from "../../wix/storefront/catalog";
15
- import type { Category, ProductSummary } from "../../wix/storefront/types";
1
+ // React binding of the shop store (wix/storefront/shop-store.ts) — the listing state machine
2
+ // lives there, framework-free; this hook subscribes to one instance per mounted listing and
3
+ // exposes its state and actions under one name. Astro islands and React SPAs use this; a static
4
+ // page, Vue, or Svelte uses the store directly.
5
+ import { useEffect, useRef, useSyncExternalStore } from "react";
6
+ import { createShopStore, type ShopState, type ShopStore, type ShopStoreOptions } from "../../wix/storefront/shop-store";
16
7
 
17
- export const SORTS = CATALOG_SORTS;
8
+ export { SORTS, type ShopFilters } from "../../wix/storefront/shop-store";
18
9
 
19
- export interface ShopFilters {
20
- minPrice?: number | string;
21
- maxPrice?: number | string;
22
- inStockOnly?: boolean;
23
- /** Name search, max 100 chars. */
24
- search?: string;
25
- }
26
-
27
- export interface UseShopOptions {
28
- initialProducts?: ProductSummary[];
29
- initialCategories?: Category[];
30
- pageSize?: number;
31
- }
32
-
33
- export interface UseShop {
34
- /** null while the first load is in flight — render skeletons, not an empty state. */
35
- products: ProductSummary[] | null;
36
- categories: Category[];
37
- /** null = "all products". */
38
- activeCategoryId: string | null;
39
- setActiveCategoryId: (id: string | null) => void;
40
- sort: CatalogSort;
41
- setSort: (sort: CatalogSort) => void;
42
- filters: ShopFilters;
43
- setFilters: (filters: ShopFilters) => void;
44
- loading: boolean;
45
- error: string | null;
46
- retry: () => void;
47
- hasMore: boolean;
48
- loadMore: () => Promise<void>;
49
- loadingMore: boolean;
50
- }
51
-
52
- interface PageState {
53
- key: string | null;
54
- products: ProductSummary[] | null;
55
- cursor: string | null;
56
- error: string | null;
57
- loadingMore: boolean;
58
- }
10
+ export type UseShopOptions = ShopStoreOptions;
59
11
 
60
- export function useShop({
61
- initialProducts,
62
- initialCategories,
63
- pageSize = 24,
64
- }: UseShopOptions = {}): UseShop {
65
- const [categories, setCategories] = useState<Category[]>(initialCategories ?? []);
66
- const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
67
- const [sort, setSort] = useState<CatalogSort>("featured");
68
- const [filters, setFilters] = useState<ShopFilters>({});
69
- const [attempt, setAttempt] = useState(0);
70
- const [page, setPage] = useState<PageState>({
71
- key: null,
72
- products: initialProducts ?? null,
73
- cursor: null,
74
- error: null,
75
- loadingMore: false,
76
- });
77
- const generation = useRef(0);
78
- const pendingMore = useRef<object | null>(null);
79
-
80
- const key = JSON.stringify([
81
- pageSize,
82
- activeCategoryId,
83
- sort,
84
- filters.minPrice ?? null,
85
- filters.maxPrice ?? null,
86
- !!filters.inStockOnly,
87
- filters.search ?? "",
88
- attempt,
89
- ]);
90
- const currentKey = useRef(key);
91
- currentKey.current = key;
92
-
93
- useEffect(() => {
94
- if (initialCategories) return;
95
- let alive = true;
96
- fetchCategories().then((list) => alive && setCategories(list)).catch(() => alive && setCategories([]));
97
- return () => {
98
- alive = false;
99
- };
100
- // eslint-disable-next-line react-hooks/exhaustive-deps
101
- }, []);
12
+ export type UseShop = ShopState &
13
+ Pick<ShopStore, "setActiveCategoryId" | "setSort" | "setFilters" | "toggleChoice" | "clearFilters" | "retry" | "loadMore">;
102
14
 
15
+ export function useShop(options: UseShopOptions = {}): UseShop {
16
+ const ref = useRef<ShopStore | null>(null);
17
+ if (!ref.current) ref.current = createShopStore(options);
18
+ const store = ref.current;
103
19
  useEffect(() => {
104
- const id = ++generation.current;
105
- let alive = true;
106
- pendingMore.current = null;
107
- // Keep the SSR seed (or the previous page) on screen while the fresh query runs;
108
- // only a selection change with no seed shows skeletons.
109
- setPage((p) => ({ key, products: p.key === null ? p.products : null, cursor: null, error: null, loadingMore: false }));
110
- const [limit, categoryId, selectedSort, minPrice, maxPrice, inStockOnly, search] = JSON.parse(key);
111
- searchCatalog({ limit, categoryId, sort: selectedSort, minPrice, maxPrice, inStockOnly, search })
112
- .then((res) => {
113
- if (alive && generation.current === id && currentKey.current === key) {
114
- setPage({ key, products: res.products, cursor: res.nextCursor, error: null, loadingMore: false });
115
- }
116
- })
117
- .catch((e) => {
118
- if (alive && generation.current === id && currentKey.current === key) {
119
- setPage({
120
- key,
121
- products: [],
122
- cursor: null,
123
- error: e instanceof Error ? e.message : "Couldn't load products.",
124
- loadingMore: false,
125
- });
126
- }
127
- });
128
- return () => {
129
- alive = false;
130
- generation.current++;
131
- };
132
- // eslint-disable-next-line react-hooks/exhaustive-deps
133
- }, [key]);
134
-
135
- const loadMore = useCallback(async () => {
136
- if (page.key !== key || !page.cursor || pendingMore.current) return;
137
- const request = { generation: generation.current, key };
138
- pendingMore.current = request; // blocks repeated clicks before React rerenders
139
- const isCurrent = () => generation.current === request.generation && currentKey.current === key;
140
- setPage((p) => ({ ...p, loadingMore: true, error: null }));
141
- try {
142
- const res = await searchCatalog({ limit: pageSize, cursor: page.cursor });
143
- if (isCurrent())
144
- setPage((p) => {
145
- const seen = new Set((p.products ?? []).map((product) => product.id));
146
- return {
147
- ...p,
148
- products: [
149
- ...(p.products ?? []),
150
- ...res.products.filter((product) => {
151
- if (seen.has(product.id)) return false;
152
- seen.add(product.id);
153
- return true;
154
- }),
155
- ],
156
- cursor: res.nextCursor,
157
- };
158
- });
159
- } catch (e) {
160
- if (isCurrent())
161
- setPage((p) => ({ ...p, error: e instanceof Error ? e.message : "Couldn't load more products." }));
162
- } finally {
163
- if (pendingMore.current === request) pendingMore.current = null;
164
- if (isCurrent()) setPage((p) => ({ ...p, loadingMore: false }));
165
- }
166
- }, [key, page.key, page.cursor, pageSize]);
167
-
168
- const retry = useCallback(() => setAttempt((n) => n + 1), []);
169
-
170
- const current = page.key === key;
171
- const seeded = page.key === null && page.products !== null; // SSR seed, first query in flight
172
- // Seeded content on screen is not "loading" — skeletons are only for a genuinely empty wait.
173
- const loading = (!current && !seeded) || (current && page.products === null);
20
+ store.start();
21
+ return () => store.stop();
22
+ }, [store]);
23
+ const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
174
24
  return {
175
- categories,
176
- activeCategoryId,
177
- setActiveCategoryId,
178
- sort,
179
- setSort,
180
- filters,
181
- setFilters,
182
- products: current || seeded ? page.products : null,
183
- loading,
184
- error: current ? page.error : null,
185
- retry,
186
- hasMore: current && !!page.cursor,
187
- loadMore,
188
- loadingMore: current && page.loadingMore,
25
+ ...state,
26
+ setActiveCategoryId: store.setActiveCategoryId,
27
+ setSort: store.setSort,
28
+ setFilters: store.setFilters,
29
+ toggleChoice: store.toggleChoice,
30
+ clearFilters: store.clearFilters,
31
+ retry: store.retry,
32
+ loadMore: store.loadMore,
189
33
  };
190
34
  }
@@ -0,0 +1,121 @@
1
+ // Cart rules and DTO mapping — transport-agnostic, imported by both ./cart.ts (SDK) and the REST
2
+ // twin in references/storefront/rest/cart.ts (fetch). Raw Cart V2 entities may carry `_id` (SDK)
3
+ // or `id` (REST). Has its own formatMoney (a copy of ../money.ts) so it stands alone when stripped.
4
+ import type { Cart, CartLine } from "./types";
5
+ import type { ImgSrc, Raw } from "./catalog-core";
6
+
7
+ /** Public app id of the Wix Stores catalog — required inside every catalogReference. */
8
+ export const WIX_STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
9
+
10
+ export const rawId = (raw: Raw | undefined | null): string => raw?._id ?? raw?.id ?? "";
11
+
12
+ /**
13
+ * Cart V2 money is ConvertedMoney { amount, convertedAmount } with NO formatted string, and the
14
+ * currency lives on the cart, not on the money. Format with the buyer's display currency when
15
+ * present, else the site's. Never hardcode "$" or assume USD.
16
+ */
17
+ export function formatMoney(money: Raw | null | undefined, currencyCode: string | null | undefined): string {
18
+ const value = money?.convertedAmount ?? money?.amount;
19
+ if (value == null || value === "") return "";
20
+ const currency = currencyCode || "USD";
21
+ try {
22
+ return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(Number(value));
23
+ } catch {
24
+ return `${value} ${currency}`;
25
+ }
26
+ }
27
+
28
+ export const cartCurrency = (raw: Raw | null | undefined): string =>
29
+ raw?.customerInfo?.currencyCode ?? raw?.businessInfo?.currencyCode ?? "";
30
+
31
+ /**
32
+ * "Monthly plan · every 2 months · 6 payments" from the line's subscriptionInfo (Cart V2 carries
33
+ * the plan on the line); "" for a one-time purchase. Every word comes from the plan — a
34
+ * subscription line must read as one in the cart, never a generic "recurring".
35
+ */
36
+ export function subscriptionTerms(info: Raw | undefined): string {
37
+ if (!info) return "";
38
+ const s: Raw = info.subscriptionSettings ?? {};
39
+ const unit: Record<string, string> = { DAY: "day", WEEK: "week", MONTH: "month", YEAR: "year" };
40
+ const parts: string[] = [];
41
+ const title = info.title?.original ?? info.title?.translated ?? "";
42
+ if (title) parts.push(title);
43
+ const u = unit[String(s.frequency ?? "")];
44
+ if (u) parts.push(`every ${s.interval && s.interval > 1 ? `${s.interval} ${u}s` : u}`);
45
+ if (!s.autoRenewal && s.billingCycles) parts.push(`${s.billingCycles} payments`);
46
+ return parts.join(" · ");
47
+ }
48
+
49
+ export function toLine(raw: Raw, currency: string, imgSrc: ImgSrc): CartLine {
50
+ return {
51
+ lineItemId: rawId(raw), // the LINE id — what update/remove take, never the product id
52
+ productName: raw.name?.original ?? "",
53
+ quantity: raw.quantityInfo?.confirmedQuantity ?? 0,
54
+ unitPrice: formatMoney(raw.pricing?.unitPrice, currency),
55
+ linePrice: formatMoney(raw.pricing?.totalPrice, currency),
56
+ imageUrl: imgSrc(raw.attributes?.image, 300, 300),
57
+ descriptionLines: ((raw.attributes?.descriptionLines ?? []) as Raw[])
58
+ .map((d) => {
59
+ const label = d.name?.original ?? "", value = d.plainText?.original ?? d.colorInfo?.original ?? "";
60
+ return label && value ? `${label}: ${value}` : value || label;
61
+ })
62
+ .filter(Boolean),
63
+ status: raw.status ?? "IN_STOCK", // not IN_STOCK → the line can't be checked out as-is
64
+ subscription: subscriptionTerms(raw.subscriptionInfo),
65
+ };
66
+ }
67
+
68
+ export function toCart(raw: Raw | null, subtotal: string, discount: string, imgSrc: ImgSrc): Cart {
69
+ const currency = cartCurrency(raw);
70
+ const lines = ((raw?.lineItems ?? []) as Raw[]).map((l) => toLine(l, currency, imgSrc));
71
+ return { lines, itemCount: lines.reduce((n, l) => n + l.quantity, 0), subtotal, discount, currency };
72
+ }
73
+
74
+ /** The after-discount subtotal and the CART-level discount from an estimate; "" when unknown. */
75
+ export function summaryTotals(estimate: Raw | null | undefined, raw: Raw): { subtotal: string; discount: string } {
76
+ const s = estimate?.summary?.priceSummary;
77
+ const currency = cartCurrency(raw);
78
+ const d = s?.discount;
79
+ return {
80
+ subtotal: formatMoney(s?.subtotal, currency),
81
+ discount: Number(d?.convertedAmount ?? d?.amount ?? 0) > 0 ? formatMoney(d, currency) : "",
82
+ };
83
+ }
84
+
85
+ /** The catalogReference.options object for an add — omits every key the buyer didn't use. */
86
+ export function addOptions({ variantId, modifierChoices, customTextFields, subscriptionOptionId, preorder }: {
87
+ variantId?: string | null; modifierChoices?: Record<string, string>; customTextFields?: Record<string, string>;
88
+ subscriptionOptionId?: string; preorder?: boolean;
89
+ }): Raw {
90
+ const o: Raw = {};
91
+ if (variantId) o.variantId = variantId; // REQUIRED for any product with options
92
+ if (modifierChoices && Object.keys(modifierChoices).length) o.options = modifierChoices; // MODIFIERS only, never Size/Color
93
+ if (customTextFields && Object.keys(customTextFields).length) o.customTextFields = customTextFields;
94
+ if (subscriptionOptionId) o.subscriptionOptionId = subscriptionOptionId;
95
+ if (preorder) o.preOrderRequested = true;
96
+ return o;
97
+ }
98
+
99
+ /** Read the add result — a refused add still returns 200: a line whose status isn't IN_STOCK, or no line, is a refusal. */
100
+ export function assertAdded(cart: Raw | null | undefined, productId: string, variantId?: string | null): void {
101
+ const line = ((cart?.lineItems ?? []) as Raw[]).find(
102
+ (l) => l.source?.catalogReference?.catalogItemId === productId && (!variantId || l.source?.catalogReference?.options?.variantId === variantId),
103
+ );
104
+ if (line?.status && line.status !== "IN_STOCK") {
105
+ throw new Error(`This item isn't available right now (${String(line.status).toLowerCase().replace(/_/g, " ")}).`);
106
+ }
107
+ if (!line || line.quantityInfo?.confirmedQuantity === 0) {
108
+ throw new Error("The item couldn't be added. Make sure every required selection was made (options for a product with variants, and all mandatory customizations).");
109
+ }
110
+ }
111
+
112
+ /** Refuse checkout for an empty cart or any line not IN_STOCK — say which. */
113
+ export function assertCheckoutable(raw: Raw | null): void {
114
+ const lines: Raw[] = raw?.lineItems ?? [];
115
+ if (!lines.length) throw new Error("Your cart is empty.");
116
+ const unavailable = lines.filter((l) => l.status && l.status !== "IN_STOCK");
117
+ if (unavailable.length) {
118
+ throw new Error(`Some items are no longer available: ${unavailable.map((l) => l.name?.original).filter(Boolean).join(", ")}.`);
119
+ }
120
+ if (!rawId(raw)) throw new Error("Checkout couldn't start: the cart has no id.");
121
+ }