@wix/agent-skills 1.14.0 → 1.15.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 (268) hide show
  1. package/README.md +12 -1
  2. package/package.json +1 -1
  3. package/skills/README.md +23 -0
  4. package/skills/wix-app/SKILL.md +10 -2
  5. package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +326 -0
  6. package/skills/wix-app/references/CODE_QUALITY.md +1 -0
  7. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +124 -115
  8. package/skills/wix-app/references/DASHBOARD_PAGE.md +3 -0
  9. package/skills/wix-app/references/DATA_COLLECTION.md +5 -3
  10. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +25 -9
  11. package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
  12. package/skills/wix-app/references/STORES_VERSIONING.md +9 -211
  13. package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +115 -0
  14. package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +264 -0
  15. package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +37 -0
  16. package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +86 -0
  17. package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +119 -0
  18. package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +103 -0
  19. package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +76 -0
  20. package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +82 -0
  21. package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +89 -0
  22. package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +124 -0
  23. package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +91 -0
  24. package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +71 -0
  25. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +70 -0
  26. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +73 -0
  27. package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +98 -0
  28. package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +278 -0
  29. package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +94 -0
  30. package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +43 -0
  31. package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +66 -0
  32. package/skills/wix-app/references/auto-patterns-dashboard/views.md +76 -0
  33. package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +5 -0
  34. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
  35. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +151 -0
  36. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +54 -29
  37. package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +2 -2
  38. package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +57 -0
  39. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +23 -105
  40. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +127 -0
  41. package/skills/wix-app/references/editor-react-component/PARTS.md +2 -1
  42. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +18 -16
  43. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
  44. package/skills/wix-app/references/stores/CATEGORIES.md +22 -0
  45. package/skills/wix-app/references/stores/GET_PRODUCT.md +99 -0
  46. package/skills/wix-app/references/stores/INVENTORY.md +37 -0
  47. package/skills/wix-app/references/stores/QUERY.md +87 -0
  48. package/skills/wix-app/references/stores/WRITE.md +55 -0
  49. package/skills/wix-app/scripts/generate-auto-patterns.js +510 -0
  50. package/skills/wix-auth/SKILL.md +12 -0
  51. package/skills/wix-auth/references/device-flow.md +140 -0
  52. package/skills/wix-docs/SKILL.md +188 -0
  53. package/skills/wix-docs/references/API_SPEC_SEARCH.md +213 -0
  54. package/skills/wix-docs/references/EXTRACTING.md +120 -0
  55. package/skills/wix-headless/SKILL.md +89 -130
  56. package/skills/wix-headless/entry/bootstrap.mjs +93 -0
  57. package/skills/wix-headless/entry/skill.md +76 -0
  58. package/skills/wix-headless/references/AI_FEATURES.md +178 -0
  59. package/skills/wix-headless/references/CAPABILITIES.md +86 -0
  60. package/skills/wix-headless/references/DISCOVERY.md +34 -56
  61. package/skills/wix-headless/references/DOC_DISCOVERY.md +37 -0
  62. package/skills/wix-headless/references/FEEDBACK.md +129 -0
  63. package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
  64. package/skills/wix-headless/references/SDK_HANDOFF.md +99 -0
  65. package/skills/wix-headless/references/SEED.md +45 -187
  66. package/skills/wix-headless/references/SETUP.md +32 -103
  67. package/skills/wix-headless/references/astro.md +98 -0
  68. package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +196 -0
  69. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +236 -0
  70. package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +279 -0
  71. package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +163 -0
  72. package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +203 -0
  73. package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +222 -0
  74. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +124 -0
  75. package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +160 -0
  76. package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +118 -0
  77. package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +132 -0
  78. package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +124 -0
  79. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +188 -0
  80. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +213 -0
  81. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +161 -0
  82. package/skills/wix-headless/references/inline-recipes/setup-blog.md +175 -0
  83. package/skills/wix-headless/references/inline-recipes/setup-bookings.md +232 -0
  84. package/skills/wix-headless/references/inline-recipes/setup-cms.md +200 -0
  85. package/skills/wix-headless/references/inline-recipes/setup-events.md +196 -0
  86. package/skills/wix-headless/references/inline-recipes/setup-forms.md +174 -0
  87. package/skills/wix-headless/references/inline-recipes/setup-online-store.md +192 -0
  88. package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +155 -0
  89. package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
  90. package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +73 -0
  91. package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +197 -0
  92. package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +136 -0
  93. package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +152 -0
  94. package/skills/wix-headless/references/managed/AUTHENTICATION.md +57 -0
  95. package/skills/wix-headless/references/managed/CONNECT.md +74 -0
  96. package/skills/wix-headless/references/managed/CREATE.md +130 -0
  97. package/skills/wix-headless/references/managed/DEPLOYMENT.md +79 -0
  98. package/skills/wix-headless/references/non-astro.md +59 -0
  99. package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +22 -0
  100. package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +15 -0
  101. package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
  102. package/skills/wix-headless/references/stripe/DEPLOYMENT.md +71 -0
  103. package/skills/wix-manage/SKILL.md +29 -3
  104. package/skills/wix-manage/references/analytics/query-site-analytics.md +298 -0
  105. package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -0
  106. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
  107. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +6 -5
  108. package/skills/wix-manage/references/bookings/create-course-service.md +30 -5
  109. package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +299 -0
  110. package/skills/wix-manage/references/cms/cms-schema-management.md +6 -0
  111. package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +30 -7
  112. package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +517 -0
  113. package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +218 -0
  114. package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
  115. package/skills/wix-manage/references/sites/site-import.md +270 -0
  116. package/skills/wix-manage/references/stores/create-product-from-image.md +8 -6
  117. package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +57 -0
  118. package/skills/wix-vibe-headless/SKILL.md +142 -0
  119. package/skills/wix-vibe-headless/platforms/README.md +22 -0
  120. package/skills/wix-vibe-headless/platforms/base44.md +142 -0
  121. package/skills/wix-vibe-headless/platforms/generic.md +87 -0
  122. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +128 -0
  123. package/skills/wix-vibe-headless/references/blog/wix-blog.js +217 -0
  124. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +157 -0
  125. package/skills/wix-vibe-headless/references/bookings/wix-bookings-checkout.js +147 -0
  126. package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +217 -0
  127. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +154 -0
  128. package/skills/wix-vibe-headless/references/cms/wix-cms.js +186 -0
  129. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +142 -0
  130. package/skills/wix-vibe-headless/references/events/wix-events-browse.js +136 -0
  131. package/skills/wix-vibe-headless/references/events/wix-events-registration.js +187 -0
  132. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +211 -0
  133. package/skills/wix-vibe-headless/references/members/wix-members-auth.js +387 -0
  134. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +111 -0
  135. package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +223 -0
  136. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +127 -0
  137. package/skills/wix-vibe-headless/references/pricing-plans/wix-pricing-plans.js +157 -0
  138. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +138 -0
  139. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-menu.js +224 -0
  140. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-ordering.js +156 -0
  141. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-reservations.js +107 -0
  142. package/skills/wix-vibe-headless/references/shared/wix-client.js +186 -0
  143. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +164 -0
  144. package/skills/wix-vibe-headless/references/storefront/wix-store-cart.js +147 -0
  145. package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +146 -0
  146. package/skills/wix-headless/references/BUILD-astro.md +0 -253
  147. package/skills/wix-headless/references/BUILD-own-build.md +0 -167
  148. package/skills/wix-headless/references/BUILD.md +0 -105
  149. package/skills/wix-headless/references/DESIGN_SYSTEM.md +0 -165
  150. package/skills/wix-headless/references/DISCOVERY-connect.md +0 -55
  151. package/skills/wix-headless/references/DISCOVERY-create.md +0 -238
  152. package/skills/wix-headless/references/PLAN-connect.md +0 -19
  153. package/skills/wix-headless/references/PLAN-create.md +0 -17
  154. package/skills/wix-headless/references/PLAN.md +0 -92
  155. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +0 -300
  156. package/skills/wix-headless/references/astro/blog/BLOG_SETUP.md +0 -218
  157. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +0 -45
  158. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +0 -77
  159. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +0 -214
  160. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +0 -231
  161. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +0 -453
  162. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +0 -354
  163. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +0 -299
  164. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +0 -351
  165. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +0 -119
  166. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +0 -103
  167. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +0 -132
  168. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +0 -463
  169. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +0 -119
  170. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +0 -129
  171. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +0 -178
  172. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +0 -85
  173. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +0 -129
  174. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +0 -229
  175. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +0 -215
  176. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +0 -165
  177. package/skills/wix-headless/references/astro/templates/Layout.astro +0 -132
  178. package/skills/wix-headless/references/astro/templates/astro.config.mjs +0 -41
  179. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +0 -374
  180. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +0 -136
  181. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +0 -25
  182. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +0 -71
  183. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +0 -54
  184. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +0 -49
  185. package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +0 -245
  186. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +0 -424
  187. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +0 -176
  188. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +0 -181
  189. package/skills/wix-headless/references/astro/templates/ecom/CartBadge.tsx +0 -84
  190. package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +0 -543
  191. package/skills/wix-headless/references/astro/templates/ecom/cart.astro +0 -13
  192. package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +0 -302
  193. package/skills/wix-headless/references/astro/templates/ecom/discounts.ts +0 -71
  194. package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +0 -24
  195. package/skills/wix-headless/references/astro/templates/gift-cards/GiftCardPurchase.tsx +0 -172
  196. package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +0 -42
  197. package/skills/wix-headless/references/astro/templates/gift-cards/_nav-snippet.astro +0 -11
  198. package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +0 -220
  199. package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +0 -51
  200. package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.ts +0 -75
  201. package/skills/wix-headless/references/astro/templates/global.css +0 -288
  202. package/skills/wix-headless/references/astro/templates/stores/AddToCartButton.tsx +0 -77
  203. package/skills/wix-headless/references/astro/templates/stores/BackInStockForm.tsx +0 -176
  204. package/skills/wix-headless/references/astro/templates/stores/CategoryRail.astro +0 -69
  205. package/skills/wix-headless/references/astro/templates/stores/ProductCard.astro +0 -79
  206. package/skills/wix-headless/references/astro/templates/stores/ProductPurchase.tsx +0 -397
  207. package/skills/wix-headless/references/astro/templates/stores/SeoTags.astro +0 -21
  208. package/skills/wix-headless/references/astro/templates/stores/back-in-stock.ts +0 -67
  209. package/skills/wix-headless/references/astro/templates/stores/categories.ts +0 -155
  210. package/skills/wix-headless/references/astro/templates/stores/category/[slug].astro +0 -130
  211. package/skills/wix-headless/references/astro/templates/stores/components-stores.css +0 -400
  212. package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +0 -128
  213. package/skills/wix-headless/references/astro/templates/stores/products/index.astro +0 -101
  214. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +0 -161
  215. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +0 -32
  216. package/skills/wix-headless/references/bookings/FLOW.md +0 -246
  217. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +0 -95
  218. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +0 -355
  219. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +0 -176
  220. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +0 -71
  221. package/skills/wix-headless/references/commands/known-apps.json +0 -6
  222. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +0 -142
  223. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +0 -88
  224. package/skills/wix-headless/references/custom/blog/WIRING.md +0 -52
  225. package/skills/wix-headless/references/custom/bookings/WIRING.md +0 -197
  226. package/skills/wix-headless/references/custom/cms/WIRING.md +0 -97
  227. package/skills/wix-headless/references/custom/ecom/WIRING.md +0 -67
  228. package/skills/wix-headless/references/custom/forms/WIRING.md +0 -101
  229. package/skills/wix-headless/references/custom/stores/WIRING.md +0 -80
  230. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +0 -59
  231. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +0 -43
  232. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +0 -51
  233. package/skills/wix-headless/references/images/INSTRUCTIONS.md +0 -357
  234. package/skills/wix-headless/references/seed-recipes.md +0 -49
  235. package/skills/wix-headless/references/shared/AGENTS_MD.md +0 -47
  236. package/skills/wix-headless/references/shared/AUTHENTICATION.md +0 -108
  237. package/skills/wix-headless/references/shared/BUILD_NOISE.md +0 -8
  238. package/skills/wix-headless/references/shared/DESIGN_MD.md +0 -84
  239. package/skills/wix-headless/references/shared/DOCS_SEARCH.md +0 -41
  240. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +0 -186
  241. package/skills/wix-headless/references/shared/IMPLEMENTER.md +0 -206
  242. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +0 -97
  243. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +0 -143
  244. package/skills/wix-headless/references/shared/STYLING.md +0 -140
  245. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +0 -126
  246. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +0 -252
  247. package/skills/wix-headless/references/verticals/_schema.json +0 -74
  248. package/skills/wix-headless/references/verticals/_schema.md +0 -95
  249. package/skills/wix-headless/references/verticals/blog.md +0 -31
  250. package/skills/wix-headless/references/verticals/bookings.md +0 -41
  251. package/skills/wix-headless/references/verticals/cms.md +0 -30
  252. package/skills/wix-headless/references/verticals/ecom.md +0 -25
  253. package/skills/wix-headless/references/verticals/forms.md +0 -26
  254. package/skills/wix-headless/references/verticals/gift-cards.md +0 -24
  255. package/skills/wix-headless/references/verticals/stores.md +0 -33
  256. package/skills/wix-headless/scripts/check-manifest.mjs +0 -335
  257. package/skills/wix-headless/scripts/compose.mjs +0 -520
  258. package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -157
  259. package/skills/wix-headless/scripts/flatten.sh +0 -61
  260. package/skills/wix-headless/scripts/generate-images.mjs +0 -242
  261. package/skills/wix-headless/scripts/patch-decorative-slots.mjs +0 -154
  262. package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +0 -190
  263. package/skills/wix-headless/scripts/scaffold.sh +0 -139
  264. package/skills/wix-headless/scripts/seed-utilities.sh +0 -93
  265. package/skills/wix-headless/scripts/write-decorative-json.mjs +0 -62
  266. package/skills/wix-headless/shared-utilities/analytics.ts +0 -40
  267. package/skills/wix-headless/shared-utilities/ricos.ts +0 -179
  268. package/skills/wix-headless/shared-utilities/wix-image.ts +0 -72
@@ -1,397 +0,0 @@
1
- import { useState, useMemo, useEffect } from "react";
2
- import { currentCart } from "@wix/ecom";
3
- import AddToCartButton from "./AddToCartButton";
4
- import BackInStockForm from "./BackInStockForm";
5
-
6
- interface Choice { choiceId?: string | null; name?: string | null; key?: string | null; }
7
- interface Option { _id?: string | null; name?: string | null; choicesSettings?: { choices?: Choice[]; }; }
8
- // Modifiers: customization choices without separate variants/inventory/price.
9
- interface FreeTextSettings { title?: string | null; key?: string | null; maxLength?: number | null; }
10
- interface Modifier {
11
- // SDK convention: outer entity IDs are `_id` (same as Variant/Option/Product).
12
- // REST returns `id`; @wix/stores remaps to `_id` in the SDK response.
13
- _id?: string | null;
14
- name?: string | null;
15
- key?: string | null;
16
- mandatory?: boolean;
17
- modifierRenderType?: string | null; // "TEXT_CHOICES" | "SWATCH_CHOICES" | "FREE_TEXT"
18
- choicesSettings?: { choices?: Choice[] };
19
- freeTextSettings?: FreeTextSettings;
20
- }
21
- interface VariantChoice { optionChoiceIds?: { optionId?: string; choiceId?: string; }; }
22
- interface Variant {
23
- _id?: string | null;
24
- choices?: VariantChoice[];
25
- price?: { actual?: { amount?: string; formattedAmount?: string; }; };
26
- // productsV3 also exposes a per-variant inventoryStatus, but it's a STALE
27
- // cached flag — a variant with live quantity 0 can still report
28
- // `inStock: true`. Only used as a last-resort fallback when the
29
- // inventoryByVariant prop is empty. The authoritative signal is
30
- // inventoryItemsV3 — see the page-level query in PRODUCT_PAGES.md.
31
- inventoryStatus?: { inStock?: boolean; preorderEnabled?: boolean };
32
- // Future-proofing: if a future wiring passes real quantities in props
33
- // (e.g. via @wix/inventory join by variantId), maxQuantity honors them.
34
- stock?: { quantity?: number | null; inStock?: boolean; };
35
- inventoryItem?: { quantity?: number | null; inStock?: boolean; trackQuantity?: boolean };
36
- }
37
- interface Stock { trackInventory?: boolean; inventoryStatus?: string; quantity?: number | null; }
38
- interface VariantStock { quantity: number; trackQuantity: boolean; preorderEnabled: boolean }
39
-
40
- // Single `product` prop (the full productsV3 object) mirrors ProductCard's
41
- // contract so both components take the same shape. Keeps `[slug].astro`
42
- // mount sites greppable: <ProductPurchase client:load product={product}
43
- // inventoryByVariant={inventoryByVariant} />.
44
- interface ProductLike {
45
- _id?: string | null;
46
- name?: string | null;
47
- actualPriceRange?: { minValue?: { currency?: string | null } | null } | null;
48
- options?: Option[] | null;
49
- modifiers?: Modifier[] | null;
50
- variantsInfo?: { variants?: Variant[] } | null;
51
- stock?: Stock | null;
52
- }
53
- interface ProductPurchaseProps {
54
- product: ProductLike;
55
- // Live per-variant stock from inventoryItemsV3, keyed by variantId.
56
- // AUTHORITATIVE source for OOS gating. productsV3's cached inStock flag is
57
- // not reliable — see PRODUCT_PAGES.md anti-patterns table.
58
- inventoryByVariant?: Record<string, VariantStock>;
59
- // SSR probe result from getBackInStockEnabled() — gates the OOS subscribe
60
- // form. When false, OOS branches render only the existing "Out of Stock"
61
- // / "Sold Out" stock-status copy. See references/BACK_IN_STOCK.md.
62
- backInStockEnabled?: boolean;
63
- // Numeric product price for the back-in-stock subscribe call. The Wix
64
- // endpoint validates `itemDetails.price >= 0` and rejects formatted
65
- // strings; pass a number, not "$695".
66
- priceAmount?: number;
67
- }
68
-
69
- export default function ProductPurchase({
70
- product,
71
- inventoryByVariant = {},
72
- backInStockEnabled = false,
73
- priceAmount,
74
- }: ProductPurchaseProps) {
75
- const productId = product._id ?? "";
76
- const productName = product.name ?? "";
77
- const currency = product.actualPriceRange?.minValue?.currency ?? undefined;
78
- const options = product.options ?? [];
79
- const modifiers = product.modifiers ?? [];
80
- const variantsInfo = product.variantsInfo ?? { variants: [] };
81
- const stock = product.stock ?? undefined;
82
-
83
- const variants = variantsInfo.variants ?? [];
84
- const hasMeaningfulOptions = options.length > 0 && options.some(
85
- (opt) => (opt.choicesSettings?.choices?.length ?? 0) > 1,
86
- );
87
- const [selections, setSelections] = useState<Record<string, string>>({});
88
- const handleSelect = (optionId: string, choiceId: string) =>
89
- setSelections((prev) => ({ ...prev, [optionId]: choiceId }));
90
- const allSelected = hasMeaningfulOptions && options.every((opt) => opt._id && selections[opt._id]);
91
-
92
- // Modifier state. Two parallel maps keyed by modifier._id, flattened below
93
- // into the shapes cart expects.
94
- const [modifierChoice, setModifierChoice] = useState<Record<string, string>>({});
95
- const [modifierText, setModifierText] = useState<Record<string, string>>({});
96
- const handleModifierChoice = (id: string, key: string) => setModifierChoice((p) => ({ ...p, [id]: key }));
97
- const handleModifierText = (id: string, v: string) => setModifierText((p) => ({ ...p, [id]: v }));
98
-
99
- const mandatoryModifiersSatisfied = useMemo(() => modifiers.every((m) => {
100
- if (!m.mandatory) return true;
101
- const id = m._id ?? "";
102
- if (m.modifierRenderType === "FREE_TEXT") return (modifierText[id] ?? "").trim().length > 0;
103
- return !!modifierChoice[id];
104
- }), [modifiers, modifierChoice, modifierText]);
105
-
106
- // Flatten to catalogReference shapes:
107
- // options → { [modifier.key]: choice.key } for TEXT_CHOICES / SWATCH_CHOICES
108
- // customTextFields → { [freeTextSettings.key]: text } for FREE_TEXT
109
- const catalogModifierChoices = useMemo(() => {
110
- const out: Record<string, string> = {};
111
- for (const m of modifiers) {
112
- if (!m.key || !m._id || m.modifierRenderType === "FREE_TEXT") continue;
113
- const c = modifierChoice[m._id];
114
- if (c) out[m.key] = c;
115
- }
116
- return out;
117
- }, [modifiers, modifierChoice]);
118
- const catalogCustomTextFields = useMemo(() => {
119
- const out: Record<string, string> = {};
120
- for (const m of modifiers) {
121
- if (!m._id || m.modifierRenderType !== "FREE_TEXT") continue;
122
- const k = m.freeTextSettings?.key;
123
- const v = (modifierText[m._id] ?? "").trim();
124
- if (k && v) out[k] = v;
125
- }
126
- return out;
127
- }, [modifiers, modifierText]);
128
-
129
- const resolvedVariant = useMemo(() => {
130
- if (!hasMeaningfulOptions) return variants[0] ?? null;
131
- if (!allSelected) return null;
132
- return variants.find((v) =>
133
- (v.choices ?? []).every(
134
- (c) => c.optionChoiceIds?.optionId &&
135
- selections[c.optionChoiceIds.optionId] === c.optionChoiceIds.choiceId,
136
- ),
137
- ) ?? null;
138
- }, [hasMeaningfulOptions, allSelected, selections, variants]);
139
-
140
- const variantLabel = useMemo(() => {
141
- if (!hasMeaningfulOptions) return undefined;
142
- return options.map((opt) => {
143
- const choiceId = opt._id ? selections[opt._id] : undefined;
144
- const choice = opt.choicesSettings?.choices?.find((c) => c.choiceId === choiceId);
145
- return choice?.name ? `${opt.name}: ${choice.name}` : null;
146
- }).filter(Boolean).join(", ") || undefined;
147
- }, [hasMeaningfulOptions, options, selections]);
148
-
149
- const variantPrice = resolvedVariant?.price?.actual?.amount
150
- ? Number(resolvedVariant.price.actual.amount)
151
- : undefined;
152
-
153
- // Binary OOS gate:
154
- // - Source of truth = inventoryByVariant (from inventoryItemsV3).
155
- // - Fallback = productsV3's variantsInfo[].inventoryStatus.inStock
156
- // (stale, don't trust it unless the live map is empty).
157
- // - productOutOfStock short-circuits the whole render (applies to the
158
- // entire product e.g. unpublished or fully sold out).
159
- // - variantOutOfStock keeps option pills visible so the shopper can
160
- // pick a different finish; hides the stepper + button.
161
- const isVariantSoldOut = (variant: Variant | null | undefined): boolean => {
162
- if (!variant?._id) return false;
163
- const live = inventoryByVariant[variant._id];
164
- if (live) {
165
- if (!live.trackQuantity) return false;
166
- if (live.preorderEnabled) return false;
167
- return live.quantity <= 0;
168
- }
169
- return (
170
- variant.inventoryStatus?.inStock === false &&
171
- variant.inventoryStatus?.preorderEnabled !== true
172
- );
173
- };
174
- const productOutOfStock = stock?.inventoryStatus === "OUT_OF_STOCK";
175
- const variantOutOfStock = isVariantSoldOut(resolvedVariant);
176
- const isOutOfStock = productOutOfStock;
177
- // maxQuantity caps the stepper. Primary source: inventoryByVariant (live
178
- // from inventoryItemsV3). Fallbacks cover partial wirings. The cart
179
- // backend still enforces stock on add-to-cart — this clamp just avoids
180
- // letting shoppers build a quantity they can't actually buy.
181
- const maxQuantity = useMemo(() => {
182
- const live = resolvedVariant?._id ? inventoryByVariant[resolvedVariant._id] : undefined;
183
- if (live) {
184
- if (!live.trackQuantity) return 99;
185
- if (live.preorderEnabled) return 99;
186
- return Math.max(1, live.quantity);
187
- }
188
- if (stock?.trackInventory === false) return 99;
189
- if (resolvedVariant?.stock?.quantity != null) return resolvedVariant.stock.quantity;
190
- if (resolvedVariant?.inventoryItem?.quantity != null) return resolvedVariant.inventoryItem.quantity;
191
- if (stock?.quantity != null) return stock.quantity;
192
- return 99;
193
- }, [inventoryByVariant, stock, resolvedVariant]);
194
- // Count how much of each variant is already in the cart so the stepper cap
195
- // reflects "stock minus already-in-cart" instead of raw stock. Wix enforces
196
- // the same rule at add-to-cart time — this is just so the UI doesn't let
197
- // the shopper think they can add "max" after already adding "max".
198
- const [qtyInCartByVariant, setQtyInCartByVariant] = useState<Record<string, number>>({});
199
- useEffect(() => {
200
- let cancelled = false;
201
- const syncCart = async () => {
202
- try {
203
- const cart = await currentCart.getCurrentCart();
204
- if (cancelled) return;
205
- const tally: Record<string, number> = {};
206
- for (const item of cart.lineItems ?? []) {
207
- const ref = item.catalogReference as any;
208
- if (ref?.catalogItemId !== productId) continue;
209
- const vId = ref?.options?.variantId as string | undefined;
210
- if (!vId) continue;
211
- tally[vId] = (tally[vId] ?? 0) + (item.quantity ?? 0);
212
- }
213
- setQtyInCartByVariant(tally);
214
- } catch { if (!cancelled) setQtyInCartByVariant({}); }
215
- };
216
- syncCart();
217
- const handler = () => syncCart();
218
- window.addEventListener("cart-updated", handler);
219
- return () => { cancelled = true; window.removeEventListener("cart-updated", handler); };
220
- }, [productId]);
221
-
222
- const qtyInCartForResolved = resolvedVariant?._id ? qtyInCartByVariant[resolvedVariant._id] ?? 0 : 0;
223
- const effectiveMax = Math.max(0, maxQuantity - qtyInCartForResolved);
224
- const alreadyMaxedInCart = qtyInCartForResolved > 0 && effectiveMax === 0;
225
-
226
- const [quantity, setQuantity] = useState(1);
227
- // When max shrinks (variant switch, or cart sync), clamp the current selection.
228
- useEffect(() => { setQuantity((q) => Math.max(1, Math.min(q, Math.max(1, effectiveMax)))); }, [effectiveMax]);
229
-
230
- const quantitySelector = !isOutOfStock && (
231
- <div className="quantity-selector">
232
- <button className="quantity-btn" onClick={() => setQuantity((q) => Math.max(1, q - 1))} disabled={quantity <= 1}>−</button>
233
- <span className="quantity-value">{quantity}</span>
234
- <button className="quantity-btn" onClick={() => setQuantity((q) => Math.min(effectiveMax, q + 1))} disabled={quantity >= effectiveMax}>+</button>
235
- </div>
236
- );
237
-
238
- const inCartNote = qtyInCartForResolved > 0 && (
239
- <p className="stock-status" aria-live="polite">{qtyInCartForResolved} already in cart</p>
240
- );
241
-
242
- // Modifier selectors — rendered between options and quantity in both branches.
243
- const modifierSelectors = modifiers.length > 0 && (
244
- <>
245
- {modifiers.map((m) => {
246
- const id = m._id ?? "";
247
- const req = m.mandatory === true;
248
- if (m.modifierRenderType === "FREE_TEXT") {
249
- return (
250
- <div key={id} className="option-group">
251
- <div className="option-label">{m.name}{req && <span aria-hidden="true"> *</span>}</div>
252
- <textarea className="modifier-text"
253
- value={modifierText[id] ?? ""}
254
- onChange={(e) => handleModifierText(id, e.target.value)}
255
- placeholder={m.freeTextSettings?.title ?? ""}
256
- maxLength={m.freeTextSettings?.maxLength ?? undefined}
257
- rows={2} aria-label={m.name ?? ""} aria-required={req || undefined}
258
- />
259
- </div>
260
- );
261
- }
262
- const choices = m.choicesSettings?.choices ?? [];
263
- return (
264
- <div key={id} className="option-group">
265
- <div className="option-label">{m.name}{req && <span aria-hidden="true"> *</span>}</div>
266
- <div className="option-choices">
267
- {choices.map((c) => {
268
- const k = c.key ?? c.choiceId ?? "";
269
- const sel = modifierChoice[id] === k;
270
- return (
271
- <button key={k} type="button" onClick={() => handleModifierChoice(id, k)}
272
- className={`option-pill${sel ? " selected" : ""}`} aria-pressed={sel}>
273
- {c.name}
274
- </button>
275
- );
276
- })}
277
- </div>
278
- </div>
279
- );
280
- })}
281
- </>
282
- );
283
-
284
- // Render the back-in-stock subscribe form in OOS branches when the
285
- // dashboard probe is on. Variant-aware: pass the resolved variantId when
286
- // a single variant is OOS, omit it when the whole product is OOS. The
287
- // form sends only required fields per the bare-fields rule
288
- // (BACK_IN_STOCK.md) — no itemUrl, no image.
289
- const renderBackInStockForm = (variantIdForForm?: string) =>
290
- backInStockEnabled ? (
291
- <BackInStockForm
292
- productId={productId}
293
- variantId={variantIdForForm}
294
- productName={productName}
295
- productPrice={variantPrice ?? priceAmount ?? 0}
296
- variantLabel={variantIdForForm ? variantLabel : undefined}
297
- />
298
- ) : null;
299
-
300
- if (isOutOfStock) {
301
- return (
302
- <div className="purchase-area">
303
- <p className="stock-status">Out of Stock</p>
304
- {renderBackInStockForm()}
305
- </div>
306
- );
307
- }
308
-
309
- if (!hasMeaningfulOptions) {
310
- const singleVariant = variants[0];
311
- const singleVariantOOS = isVariantSoldOut(singleVariant);
312
- if (singleVariantOOS) {
313
- return (
314
- <div className="purchase-area">
315
- <p className="stock-status">Sold Out</p>
316
- {renderBackInStockForm(singleVariant?._id ?? undefined)}
317
- </div>
318
- );
319
- }
320
- return (
321
- <div className="purchase-area">
322
- {modifierSelectors}
323
- {inCartNote}
324
- {alreadyMaxedInCart ? (
325
- <p className="stock-status">Maximum quantity already in cart</p>
326
- ) : (
327
- <>
328
- {quantitySelector}
329
- <AddToCartButton
330
- productId={productId} productName={productName}
331
- price={variantPrice} currency={currency}
332
- variantId={singleVariant?._id ?? undefined}
333
- quantity={quantity}
334
- modifierChoices={catalogModifierChoices}
335
- customTextFields={catalogCustomTextFields}
336
- disabled={!mandatoryModifiersSatisfied}
337
- />
338
- </>
339
- )}
340
- </div>
341
- );
342
- }
343
-
344
- return (
345
- <div className="purchase-area">
346
- {options.map((option) => {
347
- const choices = option.choicesSettings?.choices ?? [];
348
- const optionId = option._id ?? "";
349
- return (
350
- <div key={optionId} className="option-group">
351
- <div className="option-label">{option.name}</div>
352
- <div className="option-choices">
353
- {choices.map((choice) => {
354
- const choiceId = choice.choiceId ?? "";
355
- const isSelected = selections[optionId] === choiceId;
356
- return (
357
- <button key={choiceId} onClick={() => handleSelect(optionId, choiceId)}
358
- className={`option-pill${isSelected ? " selected" : ""}`}>
359
- {choice.name}
360
- </button>
361
- );
362
- })}
363
- </div>
364
- </div>
365
- );
366
- })}
367
- {modifierSelectors}
368
- {variantOutOfStock ? (
369
- <>
370
- {renderBackInStockForm(resolvedVariant?._id ?? undefined)}
371
- <p className="stock-status back-in-stock-alt">
372
- Or try another option above.
373
- </p>
374
- </>
375
- ) : alreadyMaxedInCart ? (
376
- <>
377
- {inCartNote}
378
- <p className="stock-status">Maximum quantity already in cart</p>
379
- </>
380
- ) : (
381
- <>
382
- {inCartNote}
383
- {quantitySelector}
384
- <AddToCartButton
385
- productId={productId} productName={productName}
386
- price={variantPrice} currency={currency}
387
- variantId={resolvedVariant?._id ?? undefined}
388
- variantName={variantLabel} quantity={quantity}
389
- modifierChoices={catalogModifierChoices}
390
- customTextFields={catalogCustomTextFields}
391
- disabled={!resolvedVariant || !mandatoryModifiersSatisfied}
392
- />
393
- </>
394
- )}
395
- </div>
396
- );
397
- }
@@ -1,21 +0,0 @@
1
- ---
2
- interface SeoTag {
3
- type?: string;
4
- props?: Record<string, any> | null;
5
- children?: string;
6
- disabled?: boolean;
7
- }
8
- interface Props { tags?: SeoTag[]; }
9
- const { tags = [] } = Astro.props;
10
- const visibleTags = tags.filter((t) => !t.disabled);
11
- ---
12
- {
13
- visibleTags.map((tag) => {
14
- const props = tag.props ?? {};
15
- if (tag.type === "title") return <title set:html={tag.children ?? ""} />;
16
- if (tag.type === "meta") return <meta {...props} />;
17
- if (tag.type === "link") return <link {...props} />;
18
- if (tag.type === "script") return <script {...props} is:inline set:html={tag.children ?? ""} />;
19
- return null;
20
- })
21
- }
@@ -1,67 +0,0 @@
1
- // SSR-only probe for the Wix back-in-stock service. Returns true when the
2
- // dashboard's "Start Collecting Requests" toggle is on for Wix Stores;
3
- // `[slug].astro` passes the boolean through to ProductPurchase, which
4
- // renders the subscribe form in OOS branches when the prop is true.
5
- //
6
- // Wix's back-in-stock service identifies the Stores integration by the
7
- // Stores SUB-PAGE registration id (1380b703-…), NOT by the install/catalog
8
- // appId (215238eb-…) that the public docs and other catalog endpoints use.
9
- // Against a Catalog-V3 site the request-collection endpoints behave as follows:
10
- // - PUT /settings returned `collectionStates: [{appId: "1380b703-…",
11
- // collectingRequests: true}]`.
12
- // - POST /settings/start-collecting with `215238eb-…` returned
13
- // 428 NOT_SUPPORTED_APP_DEF_ID.
14
- // - POST /back-in-stock-notification-requests with catalogReference.appId
15
- // `215238eb-…` returned 428 REQUEST_COLLECTION_DISABLED.
16
- // - POST /back-in-stock-notification-requests with catalogReference.appId
17
- // `1380b703-…` succeeded and returned a RECEIVED request.
18
- // Treat 1380b703-… as the canonical id for this service. The install/
19
- // catalog id 215238eb-… is exported in case other endpoints need it.
20
- //
21
- // The matching subscribe call goes through @wix/ecom's
22
- // backInStockNotifications.createBackInStockNotificationRequest from the
23
- // React island directly — visitor scope is enough, no client-side
24
- // elevation, no Astro API route. The cloud-provider-fetch-adapter that
25
- // ships with the Wix scaffold returns 403 to the browser on app-defined
26
- // POST routes; the SDK call avoids that surface entirely. So this file
27
- // exports only the SSR probe — there's no REQUESTS_URL constant.
28
-
29
- import { auth, httpClient } from "@wix/essentials";
30
-
31
- export const WIX_STORES_BACK_IN_STOCK_APP_ID =
32
- "1380b703-ce81-ff05-f115-39571d94dfcd";
33
- export const WIX_STORES_INSTALL_APP_ID =
34
- "215238eb-22a5-4c36-9e7b-e7c08025e04e";
35
-
36
- export const SETTINGS_URL =
37
- "https://www.wixapis.com/back-in-stock-service/v1/back-in-stock-notification-requests/settings";
38
-
39
- interface BackInStockCollectionState {
40
- appId?: string;
41
- collectingRequests?: boolean;
42
- }
43
-
44
- let cached: Promise<boolean> | null = null;
45
-
46
- export async function getBackInStockEnabled(): Promise<boolean> {
47
- if (cached) return cached;
48
- cached = (async () => {
49
- try {
50
- const elevated = auth.elevate(httpClient.fetchWithAuth);
51
- const res = await elevated(SETTINGS_URL, { method: "PUT" });
52
- if (!res.ok) return false;
53
- const data = await res.json();
54
- const states: BackInStockCollectionState[] =
55
- data?.settings?.collectionStates ?? [];
56
- return states.some(
57
- (s) =>
58
- s.appId === WIX_STORES_BACK_IN_STOCK_APP_ID &&
59
- s.collectingRequests === true,
60
- );
61
- } catch (err) {
62
- console.error("[back-in-stock] getSettings probe failed:", err);
63
- return false;
64
- }
65
- })();
66
- return cached;
67
- }
@@ -1,155 +0,0 @@
1
- // Wix Stores Categories — visible, in-use categories under the @wix/stores
2
- // tree. Drops the auto-provisioned "All Products" root and any category
3
- // with zero items so the storefront only surfaces curator-meaningful
4
- // buckets. Returns [] on any failure so consumers can render gracefully.
5
- //
6
- // Only one category-tree is in scope here: @wix/stores. Sites with multiple
7
- // stores trees (B2B, marketplaces) should pass an explicit treeKey.
8
-
9
- // The official `@wix/categories` package re-exports these functions; the
10
- // auto_sdk module is already on disk via every other @wix/* package's
11
- // transitive deps, so no extra `npm install` is needed.
12
- import * as categories from "@wix/auto_sdk_categories_categories";
13
- import { productsV3 } from "@wix/stores";
14
-
15
- const STORES_NAMESPACE = "@wix/stores";
16
- const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
17
- const ALL_PRODUCTS_HANDLE = "online_stores_all_products";
18
-
19
- // Module-level TTL cache — opportunistic across requests on warm worker
20
- // isolates, harmless on cold starts. Categories rarely change; 5 min is fine.
21
- // Errors are NOT cached so a transient failure doesn't lock out the listing.
22
- const CATEGORIES_TTL_MS = 5 * 60 * 1000;
23
- let categoriesCache: { at: number; data: StoreCategory[]; bySlug: Map<string, StoreCategory> } | null = null;
24
- let inflightCategories: Promise<StoreCategory[]> | null = null;
25
-
26
- // Per-category product-ID list — cached under the same TTL so the
27
- // 2-call listProductsInCategory pipeline doesn't re-fetch IDs on every page.
28
- const categoryItemIdsCache = new Map<string, { at: number; ids: string[] }>();
29
-
30
- export interface StoreCategory {
31
- id: string;
32
- name: string;
33
- slug: string;
34
- description?: string;
35
- itemCounter: number;
36
- imageUrl?: string;
37
- }
38
-
39
- function toStoreCategory(c: any): StoreCategory | null {
40
- if (!c?._id || !c?.slug || !c?.name) return null;
41
- return {
42
- id: c._id,
43
- name: c.name,
44
- slug: c.slug,
45
- description: c.description ?? undefined,
46
- itemCounter: typeof c.itemCounter === "number" ? c.itemCounter : 0,
47
- imageUrl: c.image?.url ?? undefined,
48
- };
49
- }
50
-
51
- async function fetchCategories(): Promise<StoreCategory[]> {
52
- // The SDK builder rejects empty filter expressions with INVALID_FILTER.
53
- // `.eq("visible", true)` is the constraint we want anyway and satisfies
54
- // the validator.
55
- const res = await categories
56
- .queryCategories({
57
- treeReference: { appNamespace: STORES_NAMESPACE },
58
- })
59
- .eq("visible", true)
60
- .ascending("name")
61
- .limit(100)
62
- .find();
63
- return (res.items ?? [])
64
- .filter((c: any) => c?.handle !== ALL_PRODUCTS_HANDLE)
65
- .filter((c: any) => typeof c?.itemCounter === "number" && c.itemCounter > 0)
66
- .map(toStoreCategory)
67
- .filter((c): c is StoreCategory => c !== null);
68
- }
69
-
70
- export async function listStoreCategories(): Promise<StoreCategory[]> {
71
- if (categoriesCache && Date.now() - categoriesCache.at < CATEGORIES_TTL_MS) {
72
- return categoriesCache.data;
73
- }
74
- if (inflightCategories) return inflightCategories;
75
- inflightCategories = (async () => {
76
- try {
77
- const data = await fetchCategories();
78
- const bySlug = new Map(data.map((c) => [c.slug, c]));
79
- categoriesCache = { at: Date.now(), data, bySlug };
80
- return data;
81
- } catch (err) {
82
- console.error("[categories] list failed:", err);
83
- return [];
84
- } finally {
85
- inflightCategories = null;
86
- }
87
- })();
88
- return inflightCategories;
89
- }
90
-
91
- export async function getCategoryBySlug(slug: string): Promise<StoreCategory | null> {
92
- const all = await listStoreCategories();
93
- return categoriesCache?.bySlug.get(slug) ?? all.find((c) => c.slug === slug) ?? null;
94
- }
95
-
96
- async function fetchCategoryItemIds(categoryId: string): Promise<string[]> {
97
- try {
98
- const res = await categories.listItemsInCategory(
99
- categoryId,
100
- { appNamespace: STORES_NAMESPACE },
101
- );
102
- return (res.items ?? [])
103
- .map((it: any) => it?.catalogItemId)
104
- .filter((id: any): id is string => typeof id === "string" && id.length > 0);
105
- } catch (err) {
106
- console.error("[categories] listItemsInCategory failed:", err);
107
- return [];
108
- }
109
- }
110
-
111
- async function getCategoryItemIds(categoryId: string): Promise<string[]> {
112
- const cached = categoryItemIdsCache.get(categoryId);
113
- if (cached && Date.now() - cached.at < CATEGORIES_TTL_MS) return cached.ids;
114
- const ids = await fetchCategoryItemIds(categoryId);
115
- categoryItemIdsCache.set(categoryId, { at: Date.now(), ids });
116
- return ids;
117
- }
118
-
119
- export interface ProductPage {
120
- items: any[];
121
- nextCursor: string | null;
122
- prevCursor: string | null;
123
- }
124
-
125
- // Cursor-paginated product list for a single category. Two calls under the
126
- // hood — there is no Wix endpoint that does category filter + cursor paging
127
- // in one shot. The cursor we surface is the productsV3 cursor, so paging is
128
- // stable as long as the category membership doesn't change mid-session.
129
- export async function listProductsInCategory(
130
- categoryId: string,
131
- opts: { limit: number; cursor?: string },
132
- ): Promise<ProductPage> {
133
- try {
134
- const ids = await getCategoryItemIds(categoryId);
135
- if (ids.length === 0) {
136
- return { items: [], nextCursor: null, prevCursor: null };
137
- }
138
- let builder = productsV3
139
- .queryProducts({ fields: ["CURRENCY"] })
140
- .in("_id", ids)
141
- .limit(opts.limit);
142
- if (opts.cursor) builder = builder.skipTo(opts.cursor);
143
- const result = await builder.find();
144
- return {
145
- items: result.items ?? [],
146
- nextCursor: result.cursors?.next ?? null,
147
- prevCursor: result.cursors?.prev ?? null,
148
- };
149
- } catch (err) {
150
- console.error(`[categories] listProductsInCategory(${categoryId}) failed:`, err);
151
- return { items: [], nextCursor: null, prevCursor: null };
152
- }
153
- }
154
-
155
- export { STORES_APP_ID };