@wix/agent-skills 1.20.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +71 -113
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
  8. package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
  9. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  10. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  11. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  12. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  13. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
  14. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
  15. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
  16. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
  17. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
  18. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  19. package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
  20. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
  21. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
  22. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  23. package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
  24. package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
  25. package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
  26. package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
  27. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  28. package/skills/wix-headless-fast/SKILL.md +144 -221
  29. package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
  30. package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +15 -15
  31. package/skills/wix-headless-fast/guides/existing-site.md +76 -0
  32. package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
  33. package/skills/wix-headless-fast/install/AGENTS.md +35 -0
  34. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  35. package/skills/wix-headless-fast/install/attach.mjs +161 -86
  36. package/skills/wix-headless-fast/install/deploy.mjs +21 -60
  37. package/skills/wix-headless-fast/install/lock.mjs +55 -0
  38. package/skills/wix-headless-fast/install/setup.mjs +259 -0
  39. package/skills/wix-headless-fast/install/templates.mjs +168 -0
  40. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  41. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  42. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
  43. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
  44. package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
  45. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -301
  46. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
  47. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
  48. package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
  49. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
  50. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
  51. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
  52. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
  53. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
  54. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
  55. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
  56. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
  57. package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
  58. package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
  59. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
  60. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
  61. package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
  62. package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
  63. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
  64. package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
  65. package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
  66. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -275
  67. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
  68. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
  69. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
  70. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
  71. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
  72. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
  73. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
  74. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
  75. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
  76. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
  77. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
  78. package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
  79. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
  80. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
  81. package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
  82. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
  83. package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
  84. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
  85. package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
  86. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -353
  87. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
  88. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
  89. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
  90. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
  91. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
  92. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
  93. package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
  94. package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
  95. package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
  96. package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
  97. package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
  98. package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
  99. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -305
  100. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
  101. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
  102. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
  103. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
  104. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
  105. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
  106. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
  107. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
  108. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
  109. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
  110. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
  111. package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
  112. package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
  113. package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
  114. package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
  115. package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
  116. package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
  117. package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
  118. package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
  119. package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
  120. package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
  121. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -338
  122. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
  123. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
  124. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
  125. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
  126. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
  127. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
  128. package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
  129. package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
  130. package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
  131. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
  132. package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
  133. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
  134. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -314
  135. package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
  136. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
  137. package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
  138. package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
  139. package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
  140. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
  141. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
  142. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
  143. package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
  144. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
  145. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
  146. package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
  147. package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
  148. package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
  149. package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
  150. package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
  151. package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
  152. package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
  153. package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
  154. package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
  155. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -313
  156. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
  157. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
  158. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
  159. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
  160. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
  161. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
  162. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
  163. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
  164. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
  165. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
  166. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
  167. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
  168. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
  169. package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
  170. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
  171. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
  172. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
  173. package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
  174. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
  175. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
  176. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -258
  177. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -294
  178. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
  179. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
  180. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
  181. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
  182. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
  183. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
  184. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
  185. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
  186. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
  187. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
  188. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
  189. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
  190. package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
  191. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
  192. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
  193. package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
  194. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
  195. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
  196. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
  197. package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
  198. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -358
  199. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
  200. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
  201. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
  202. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
  203. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
  204. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
  205. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
  206. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
  207. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
  208. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
  209. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
  210. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
  211. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
  212. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
  213. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
  214. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
  215. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
  216. package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
  217. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
  218. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
  219. package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
  220. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
  221. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
  222. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
  223. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
  224. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -479
  225. package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
  226. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
  227. package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
  228. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
  229. package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
  230. package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
  231. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
  232. package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
  233. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
  234. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
  235. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
  236. package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
  237. package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
  238. package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
  239. package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
  240. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -608
  241. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
  242. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
  243. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
  244. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
  245. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
  246. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
  247. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
  248. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
  249. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
  250. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
  251. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
  252. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -309
  253. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
  254. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
  255. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
  256. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -167
  257. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
  258. package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
  259. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
  260. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
  261. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -124
  262. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -525
@@ -1,155 +0,0 @@
1
- // Slide-over cart with quantity stepper, remove, live subtotal, and Wix-hosted checkout.
2
- // Mount ONCE per page, as-is; opens via useCart().openCart() / CartButton. Styled from the
3
- // @theme tokens.
4
- import { useEffect, useRef } from "react";
5
- import { useCart } from "../../hooks/storefront/useCart";
6
-
7
- export default function CartDrawer() {
8
- const { cart, open, closeCart, busy, error, updateQuantity, removeLine, checkout } = useCart();
9
-
10
- // The overlay contract, done here rather than assumed from CSS: Escape closes (the backdrop
11
- // click is pointer-only), the page behind stops scrolling, focus moves into the panel on open
12
- // and returns to whatever opened it on close. Hooks run before the early return below.
13
- const panelRef = useRef<HTMLElement>(null);
14
- useEffect(() => {
15
- if (!open) return;
16
- const opener = document.activeElement;
17
- const previousOverflow = document.body.style.overflow;
18
- document.body.style.overflow = "hidden";
19
- panelRef.current?.focus();
20
- const onKey = (e: KeyboardEvent) => e.key === "Escape" && closeCart();
21
- document.addEventListener("keydown", onKey);
22
- return () => {
23
- document.removeEventListener("keydown", onKey);
24
- document.body.style.overflow = previousOverflow;
25
- if (opener instanceof HTMLElement) opener.focus();
26
- };
27
- }, [open, closeCart]);
28
-
29
- if (!open) return null;
30
- const lines = cart?.lines ?? [];
31
-
32
- return (
33
- <div
34
- className="fixed inset-0 z-50 flex justify-end bg-foreground/40 backdrop-blur-[2px]"
35
- onClick={closeCart}
36
- >
37
- <aside
38
- ref={panelRef}
39
- tabIndex={-1}
40
- role="dialog"
41
- aria-modal="true"
42
- aria-label="Cart"
43
- onClick={(e) => e.stopPropagation()}
44
- className="flex h-full w-full max-w-md flex-col bg-background text-foreground shadow-2xl outline-none"
45
- >
46
- <div className="flex items-center justify-between border-b border-border px-6 py-5">
47
- <span className="text-base font-semibold">
48
- Cart{cart && cart.itemCount > 0 ? ` (${cart.itemCount})` : ""}
49
- </span>
50
- <button
51
- type="button"
52
- aria-label="Close cart"
53
- onClick={closeCart}
54
- className="text-xl leading-none text-muted-foreground transition-colors hover:text-foreground"
55
- >
56
- ×
57
- </button>
58
- </div>
59
-
60
- <div className="flex-1 overflow-y-auto px-6 py-5">
61
- {lines.length === 0 && (
62
- <p className="py-16 text-center text-sm text-muted-foreground">Your cart is empty.</p>
63
- )}
64
- {lines.map((line) => (
65
- <div className="mb-6 flex gap-4" key={line.lineItemId}>
66
- {line.imageUrl ? (
67
- <img
68
- src={line.imageUrl}
69
- alt=""
70
- loading="lazy"
71
- className="h-16 w-16 flex-shrink-0 rounded-md bg-secondary object-cover"
72
- />
73
- ) : (
74
- <div aria-hidden="true" className="h-16 w-16 flex-shrink-0 rounded-md bg-secondary" />
75
- )}
76
- <div className="min-w-0 flex-1">
77
- <p className="text-sm font-medium">{line.productName}</p>
78
- {line.descriptionLines.map((d) => (
79
- <p className="mt-0.5 text-xs text-muted-foreground" key={d}>
80
- {d}
81
- </p>
82
- ))}
83
- {line.subscription && (
84
- <p className="mt-0.5 text-xs text-muted-foreground">Subscription: {line.subscription}</p>
85
- )}
86
- {line.status !== "IN_STOCK" && (
87
- <p className="mt-1 text-xs text-red-600">No longer available at this quantity</p>
88
- )}
89
- <div className="mt-2 flex items-center">
90
- <div className="inline-flex items-center gap-3 rounded-full border border-border px-2 py-0.5">
91
- <button
92
- type="button"
93
- aria-label="Decrease quantity"
94
- disabled={busy || line.quantity <= 1}
95
- onClick={() => updateQuantity(line.lineItemId, line.quantity - 1).catch(() => {})}
96
- className="px-1 text-base disabled:opacity-40"
97
- >
98
- −
99
- </button>
100
- <span className="text-sm tabular-nums">{line.quantity}</span>
101
- <button
102
- type="button"
103
- aria-label="Increase quantity"
104
- disabled={busy}
105
- onClick={() => updateQuantity(line.lineItemId, line.quantity + 1).catch(() => {})}
106
- className="px-1 text-base disabled:opacity-40"
107
- >
108
- +
109
- </button>
110
- </div>
111
- <button
112
- type="button"
113
- disabled={busy}
114
- onClick={() => removeLine(line.lineItemId).catch(() => {})}
115
- className="ml-3 text-xs text-muted-foreground underline transition-colors hover:text-foreground disabled:opacity-40"
116
- >
117
- Remove
118
- </button>
119
- </div>
120
- </div>
121
- <p className="text-sm font-medium">{line.linePrice}</p>
122
- </div>
123
- ))}
124
- </div>
125
-
126
- {lines.length > 0 && (
127
- <div className="border-t border-border px-6 py-5">
128
- {cart?.discount && (
129
- <div className="mb-2 flex justify-between text-sm">
130
- <span className="text-muted-foreground">Discount</span>
131
- <span>−{cart.discount}</span>
132
- </div>
133
- )}
134
- {cart?.subtotal && (
135
- <div className="mb-1 flex justify-between text-sm">
136
- <span className="text-muted-foreground">Subtotal</span>
137
- <strong className="font-semibold">{cart.subtotal}</strong>
138
- </div>
139
- )}
140
- <p className="mb-4 text-xs text-muted-foreground">Shipping and taxes are calculated at checkout.</p>
141
- {error && <p className="mb-3 text-xs text-red-600">{error}</p>}
142
- <button
143
- type="button"
144
- disabled={busy}
145
- onClick={() => checkout().catch(() => {})}
146
- className="w-full rounded-full bg-primary py-3.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
147
- >
148
- {busy ? "One moment…" : "Continue to secure checkout"}
149
- </button>
150
- </div>
151
- )}
152
- </aside>
153
- </div>
154
- );
155
- }
@@ -1,266 +0,0 @@
1
- // The gallery's filter layout — wire as-is, your grid goes inside:
2
- //
3
- // <FilterPanel shop={shop}>
4
- // …your loading / empty / error states and your grid of tiles…
5
- // </FilterPanel>
6
- //
7
- // Renders: a toolbar (result count, sort, and under `md` a Filters button); active-filter chips;
8
- // then on `md`+ a two-column layout — a 16rem sidebar of collapsible groups (Price as a two-handle
9
- // slider with numeric fields, Availability, one group per option facet with swatches or pills) beside
10
- // your results, so expanded facets never push the products below the fold. Under `md` the same
11
- // groups live in a bottom sheet: changes are staged and commit on Apply; dismissing keeps the
12
- // current results. Sidebar changes commit immediately (a price pair on release/blur, valid only).
13
- // Styled from the @theme tokens.
14
- import { useEffect, useRef, useState, type ReactNode } from "react";
15
- import { SORTS, type ShopFilters, type UseShop } from "../../hooks/storefront/useShop";
16
- import type { CatalogSort } from "../../wix/storefront/catalog";
17
-
18
- interface Draft {
19
- min: string;
20
- max: string;
21
- inStockOnly: boolean;
22
- choiceIds: string[];
23
- }
24
-
25
- const draftFrom = (shop: UseShop): Draft => ({
26
- min: String(shop.filters.minPrice ?? ""),
27
- max: String(shop.filters.maxPrice ?? ""),
28
- inStockOnly: !!shop.filters.inStockOnly,
29
- choiceIds: shop.selectedChoiceIds,
30
- });
31
-
32
- function validPricePair(min: string, max: string): boolean {
33
- if (min && !Number.isFinite(Number(min))) return false;
34
- if (max && !Number.isFinite(Number(max))) return false;
35
- if (min && max && Number(min) > Number(max)) return false;
36
- return true;
37
- }
38
-
39
- export default function FilterPanel({ shop, children }: { shop: UseShop; children?: ReactNode }) {
40
- const [sheetOpen, setSheetOpen] = useState(false);
41
- const [staged, setStaged] = useState<Draft>(() => draftFrom(shop));
42
- const panelRef = useRef<HTMLElement>(null);
43
-
44
- // The sheet is an overlay: root-level, scrim, scroll lock, Escape, focus in and back.
45
- useEffect(() => {
46
- if (!sheetOpen) return;
47
- setStaged(draftFrom(shop));
48
- const opener = document.activeElement;
49
- const previousOverflow = document.body.style.overflow;
50
- document.body.style.overflow = "hidden";
51
- panelRef.current?.focus();
52
- const onKey = (e: KeyboardEvent) => e.key === "Escape" && setSheetOpen(false);
53
- document.addEventListener("keydown", onKey);
54
- return () => {
55
- document.removeEventListener("keydown", onKey);
56
- document.body.style.overflow = previousOverflow;
57
- if (opener instanceof HTMLElement) opener.focus();
58
- };
59
- // eslint-disable-next-line react-hooks/exhaustive-deps
60
- }, [sheetOpen]);
61
-
62
- const commit = (d: Draft) => {
63
- if (!validPricePair(d.min, d.max)) return;
64
- const next: ShopFilters = {
65
- ...shop.filters,
66
- minPrice: d.min || undefined,
67
- maxPrice: d.max || undefined,
68
- inStockOnly: d.inStockOnly || undefined,
69
- };
70
- const same =
71
- next.minPrice === shop.filters.minPrice &&
72
- next.maxPrice === shop.filters.maxPrice &&
73
- !!next.inStockOnly === !!shop.filters.inStockOnly;
74
- if (!same) shop.setFilters(next);
75
- for (const id of d.choiceIds) if (!shop.selectedChoiceIds.includes(id)) shop.toggleChoice(id);
76
- for (const id of shop.selectedChoiceIds) if (!d.choiceIds.includes(id)) shop.toggleChoice(id);
77
- };
78
-
79
- const activeCount =
80
- shop.selectedChoiceIds.length + (shop.filters.inStockOnly ? 1 : 0) + (shop.filters.minPrice || shop.filters.maxPrice ? 1 : 0);
81
- const choiceName = (id: string) => shop.facets.flatMap((f) => f.choices).find((c) => c.id === id)?.name ?? id;
82
-
83
- return (
84
- <div className="flex flex-col gap-4">
85
- {/* Toolbar */}
86
- <div className="flex flex-wrap items-center justify-between gap-3">
87
- <p className="text-sm text-muted-foreground" aria-live="polite">
88
- {shop.total != null ? `${shop.total} ${shop.total === 1 ? "product" : "products"}` : ""}
89
- </p>
90
- <div className="flex items-center gap-2">
91
- <button type="button" onClick={() => setSheetOpen(true)} className="rounded-md border border-border px-3 py-1.5 text-sm md:hidden">
92
- Filters{activeCount ? ` (${activeCount})` : ""}
93
- </button>
94
- <label className="sr-only" htmlFor="shop-sort">Sort by</label>
95
- <select id="shop-sort" value={shop.sort} onChange={(e) => shop.setSort(e.target.value as CatalogSort)}
96
- className="rounded-md border border-border bg-background px-2 py-1.5 text-sm">
97
- {Object.entries(SORTS).map(([key, { label }]) => <option key={key} value={key}>{label}</option>)}
98
- </select>
99
- </div>
100
- </div>
101
-
102
- {/* Active chips */}
103
- {shop.hasActiveFilters && (
104
- <div className="flex flex-wrap items-center gap-1.5" aria-label="Active filters">
105
- {shop.selectedChoiceIds.map((id) => (
106
- <button key={id} type="button" onClick={() => shop.toggleChoice(id)} className="rounded-full bg-secondary px-2.5 py-1 text-xs text-foreground">
107
- {choiceName(id)} ×
108
- </button>
109
- ))}
110
- {shop.filters.inStockOnly && (
111
- <button type="button" onClick={() => shop.setFilters({ ...shop.filters, inStockOnly: undefined })} className="rounded-full bg-secondary px-2.5 py-1 text-xs">In stock ×</button>
112
- )}
113
- {(shop.filters.minPrice || shop.filters.maxPrice) && (
114
- <button type="button" onClick={() => shop.setFilters({ ...shop.filters, minPrice: undefined, maxPrice: undefined })} className="rounded-full bg-secondary px-2.5 py-1 text-xs">
115
- Price {shop.filters.minPrice ?? shop.priceRange?.min ?? "…"}–{shop.filters.maxPrice ?? shop.priceRange?.max ?? "…"} ×
116
- </button>
117
- )}
118
- <button type="button" onClick={shop.clearFilters} className="text-xs text-muted-foreground underline">Clear all</button>
119
- </div>
120
- )}
121
-
122
- {/* Sidebar + results */}
123
- <div className="md:grid md:grid-cols-[16rem_minmax(0,1fr)] md:gap-8">
124
- <aside className="hidden md:block" aria-label="Filters">
125
- <Groups shop={shop} draft={draftFrom(shop)} onChange={commit} immediate />
126
- </aside>
127
- <div className="min-w-0">{children}</div>
128
- </div>
129
-
130
- {/* Sheet — small screens; staged until Apply */}
131
- {sheetOpen && (
132
- <div className="fixed inset-0 z-50 flex items-end bg-foreground/40 md:hidden" onClick={() => setSheetOpen(false)}>
133
- <section ref={panelRef} tabIndex={-1} role="dialog" aria-modal="true" aria-label="Filters" onClick={(e) => e.stopPropagation()}
134
- className="flex max-h-[85vh] w-full flex-col rounded-t-2xl bg-background text-foreground outline-none">
135
- <div className="flex items-center justify-between px-5 pt-5">
136
- <h2 className="text-base font-semibold">Filters</h2>
137
- <button type="button" aria-label="Close filters" onClick={() => setSheetOpen(false)} className="text-xl leading-none">×</button>
138
- </div>
139
- <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
140
- <Groups shop={shop} draft={staged} onChange={setStaged} />
141
- </div>
142
- <div className="flex gap-2 border-t border-border bg-background px-5 py-3">
143
- <button type="button" onClick={() => setStaged({ min: "", max: "", inStockOnly: false, choiceIds: [] })}
144
- className="flex-1 rounded-full border border-border py-2.5 text-sm">Clear</button>
145
- <button type="button" onClick={() => { commit(staged); setSheetOpen(false); }}
146
- className="flex-1 rounded-full bg-primary py-2.5 text-sm font-semibold text-primary-foreground">Apply</button>
147
- </div>
148
- </section>
149
- </div>
150
- )}
151
- </div>
152
- );
153
- }
154
-
155
- // The filter groups — one markup for the sidebar (immediate commit) and the sheet (staged).
156
- function Groups({ shop, draft, onChange, immediate = false }: { shop: UseShop; draft: Draft; onChange: (d: Draft) => void; immediate?: boolean }) {
157
- const toggle = (id: string) =>
158
- onChange({ ...draft, choiceIds: draft.choiceIds.includes(id) ? draft.choiceIds.filter((x) => x !== id) : [...draft.choiceIds, id] });
159
- return (
160
- <div className="flex flex-col divide-y divide-border">
161
- <Group title="Price">
162
- <PriceControl
163
- range={shop.priceRange}
164
- min={draft.min}
165
- max={draft.max}
166
- onCommit={(min, max) => onChange({ ...draft, min, max })}
167
- immediate={immediate}
168
- />
169
- </Group>
170
- <Group title="Availability">
171
- <label className="flex items-center gap-2 text-sm">
172
- <input type="checkbox" checked={draft.inStockOnly} onChange={(e) => onChange({ ...draft, inStockOnly: e.target.checked })} />
173
- In stock only
174
- </label>
175
- </Group>
176
- {shop.facets.map((facet) => (
177
- <Group key={facet.name} title={facet.name} count={facet.choices.filter((c) => draft.choiceIds.includes(c.id)).length}>
178
- <div className="flex flex-wrap gap-1.5">
179
- {facet.choices.map((c) => {
180
- const on = draft.choiceIds.includes(c.id);
181
- return facet.isColor && c.colorCode ? (
182
- <button key={c.id} type="button" aria-pressed={on} aria-label={c.name} title={c.name} onClick={() => toggle(c.id)}
183
- className={`h-7 w-7 rounded-full border-2 ${on ? "border-foreground" : "border-border"}`} style={{ backgroundColor: c.colorCode }} />
184
- ) : (
185
- <button key={c.id} type="button" aria-pressed={on} onClick={() => toggle(c.id)}
186
- className={`rounded-full border px-3 py-1 text-sm ${on ? "border-foreground bg-foreground text-background" : "border-border bg-background text-foreground"}`}>
187
- {c.name}
188
- </button>
189
- );
190
- })}
191
- </div>
192
- </Group>
193
- ))}
194
- </div>
195
- );
196
- }
197
-
198
- // A collapsible group — native <details>, open by default, the selected count on the summary.
199
- function Group({ title, count = 0, children }: { title: string; count?: number; children: ReactNode }) {
200
- return (
201
- <details open className="group py-3">
202
- <summary className="flex cursor-pointer list-none items-center justify-between text-sm font-medium">
203
- <span>{title}{count ? <span className="ml-1.5 text-xs text-muted-foreground">({count})</span> : null}</span>
204
- <span aria-hidden="true" className="text-muted-foreground transition-transform group-open:rotate-180">⌄</span>
205
- </summary>
206
- <div className="pt-3">{children}</div>
207
- </details>
208
- );
209
- }
210
-
211
- // Two-handle range slider bounded by the scope's real prices, with the numeric pair under it.
212
- // Local drafts while dragging/typing; a VALID pair commits on release / blur / Enter (immediate mode)
213
- // or on every change (staged mode, where Apply commits later).
214
- function PriceControl({ range, min, max, onCommit, immediate }: {
215
- range: UseShop["priceRange"]; min: string; max: string; onCommit: (min: string, max: string) => void; immediate: boolean;
216
- }) {
217
- const [lo, setLo] = useState(min);
218
- const [hi, setHi] = useState(max);
219
- useEffect(() => { setLo(min); setHi(max); }, [min, max]);
220
- const bounds = range ?? null;
221
- const loN = lo === "" ? (bounds?.min ?? 0) : Number(lo);
222
- const hiN = hi === "" ? (bounds?.max ?? 0) : Number(hi);
223
- const step = bounds ? Math.max(1, Math.round((bounds.max - bounds.min) / 100)) : 1;
224
- const pct = (v: number) => (bounds ? ((v - bounds.min) / (bounds.max - bounds.min)) * 100 : 0);
225
- const publish = (a: string, b: string) => {
226
- // at the bounds the filter is "no bound" — omit it rather than sending the catalog's own extremes
227
- const A = bounds && a !== "" && Number(a) <= bounds.min ? "" : a;
228
- const B = bounds && b !== "" && Number(b) >= bounds.max ? "" : b;
229
- if (validPricePair(A, B)) onCommit(A, B);
230
- };
231
- const setFromSlider = (which: "lo" | "hi", v: number) => {
232
- const a = which === "lo" ? String(Math.min(v, hiN)) : lo;
233
- const b = which === "hi" ? String(Math.max(v, loN)) : hi;
234
- setLo(a); setHi(b);
235
- if (!immediate) publish(a, b);
236
- };
237
- const release = () => immediate && publish(lo, hi);
238
- const thumb = "pointer-events-none absolute inset-x-0 top-0 h-6 w-full appearance-none bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background [&::-moz-range-thumb]:pointer-events-auto [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-foreground [&::-moz-range-thumb]:bg-background";
239
- return (
240
- <div className="flex flex-col gap-3">
241
- {bounds && (
242
- <div className="relative h-6" aria-hidden={false}>
243
- <div className="absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 rounded bg-secondary" />
244
- <div className="absolute top-1/2 h-1 -translate-y-1/2 rounded bg-foreground" style={{ left: `${pct(loN)}%`, right: `${100 - pct(hiN)}%` }} />
245
- <input type="range" aria-label="Minimum price" min={bounds.min} max={bounds.max} step={step} value={loN}
246
- onChange={(e) => setFromSlider("lo", Number(e.target.value))} onPointerUp={release} onKeyUp={release} className={thumb} />
247
- <input type="range" aria-label="Maximum price" min={bounds.min} max={bounds.max} step={step} value={hiN}
248
- onChange={(e) => setFromSlider("hi", Number(e.target.value))} onPointerUp={release} onKeyUp={release} className={thumb} />
249
- </div>
250
- )}
251
- <div className="flex items-center gap-2">
252
- <label className="sr-only" htmlFor="filter-min">Minimum price</label>
253
- <input id="filter-min" inputMode="decimal" placeholder={bounds ? String(bounds.min) : "Min"} value={lo}
254
- onChange={(e) => { setLo(e.target.value); if (!immediate) publish(e.target.value, hi); }}
255
- onBlur={release} onKeyDown={(e) => e.key === "Enter" && release()}
256
- className="w-full min-w-0 rounded-md border border-border bg-background px-2 py-1.5 text-sm" />
257
- <span className="text-muted-foreground">–</span>
258
- <label className="sr-only" htmlFor="filter-max">Maximum price</label>
259
- <input id="filter-max" inputMode="decimal" placeholder={bounds ? String(bounds.max) : "Max"} value={hi}
260
- onChange={(e) => { setHi(e.target.value); if (!immediate) publish(lo, e.target.value); }}
261
- onBlur={release} onKeyDown={(e) => e.key === "Enter" && release()}
262
- className="w-full min-w-0 rounded-md border border-border bg-background px-2 py-1.5 text-sm" />
263
- </div>
264
- </div>
265
- );
266
- }
@@ -1,95 +0,0 @@
1
- // The purchase controls for one product — option groups (swatches for a color option, pills
2
- // otherwise, out-of-stock choices disabled), choice modifiers, an optional quantity stepper, and
3
- // the buy button gated by the hook: disabled with a plain reason ("Choose Size") until every
4
- // choice is made, "Pre-order" when the resolved variant is pre-orderable, the hook's error inline.
5
- // QuickAdd's picker and your product page mount the same component, so buying behaves the same
6
- // in the gallery and on the PDP. Wire as-is; style via the tokens.
7
- //
8
- // const d = useProductDetail({ initial }); // the PDP
9
- // <OptionPicker detail={d} showQuantity />
10
- //
11
- // Price, name, gallery, description, and layout stay yours — render d.price / d.compareAtPrice
12
- // beside this (the range until every option is picked, then the variant's price).
13
- import type { UseProductDetail } from "../../hooks/storefront/useProductDetail";
14
-
15
- export default function OptionPicker({
16
- detail: d,
17
- showQuantity = false,
18
- onAdded,
19
- }: {
20
- detail: UseProductDetail;
21
- /** The PDP shows a quantity stepper; a tile picker adds one. */
22
- showQuantity?: boolean;
23
- /** Called after a successful add (a tile picker closes itself here). */
24
- onAdded?: () => void;
25
- }) {
26
- if (!d.product) return null;
27
- return (
28
- <div className="flex flex-col gap-3">
29
- {d.optionGroups.map((g) => (
30
- <fieldset key={g.id}>
31
- <legend className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">{g.name}</legend>
32
- <div className="flex flex-wrap gap-1.5">
33
- {g.choices.map((c) =>
34
- g.isColor && c.colorCode ? (
35
- <button key={c.choiceId} type="button" aria-label={c.name} title={c.name} aria-pressed={c.selected}
36
- disabled={!c.inStock} onClick={() => d.selectOption(g.name, c.name)}
37
- className={`h-8 w-8 rounded-full border-2 disabled:opacity-30 ${c.selected ? "border-foreground" : "border-border"}`}
38
- style={{ backgroundColor: c.colorCode }} />
39
- ) : (
40
- <button key={c.choiceId} type="button" aria-pressed={c.selected} disabled={!c.inStock}
41
- onClick={() => d.selectOption(g.name, c.name)}
42
- className={`rounded-full border px-3 py-1 text-sm disabled:line-through disabled:opacity-40 ${c.selected ? "border-foreground bg-foreground text-background" : "border-border"}`}>
43
- {c.name}
44
- </button>
45
- ),
46
- )}
47
- </div>
48
- </fieldset>
49
- ))}
50
- {d.product.modifiers.filter((m) => m.type === "choices").map((m) => (
51
- <fieldset key={m.key}>
52
- <legend className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
53
- {m.name}{m.mandatory ? " *" : ""}
54
- </legend>
55
- <div className="flex flex-wrap gap-1.5">
56
- {m.choices.map((c) => (
57
- <button key={c.key} type="button" aria-pressed={d.modifierValues[m.key] === c.key}
58
- onClick={() => d.setModifier(m.key, c.key)}
59
- className={`rounded-full border px-3 py-1 text-sm ${d.modifierValues[m.key] === c.key ? "border-foreground bg-foreground text-background" : "border-border"}`}>
60
- {c.name}
61
- </button>
62
- ))}
63
- </div>
64
- </fieldset>
65
- ))}
66
- {d.product.modifiers.filter((m) => m.type === "text").map((m) => (
67
- <label key={m.key} className="flex flex-col gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
68
- {m.name}{m.mandatory ? " *" : ""}
69
- <input type="text" value={d.modifierValues[m.key] ?? ""} onChange={(e) => d.setModifier(m.key, e.target.value)}
70
- className="rounded-md border border-border bg-background px-3 py-2 text-sm normal-case tracking-normal text-foreground" />
71
- </label>
72
- ))}
73
- <div className="flex items-stretch gap-2">
74
- {showQuantity && (
75
- <div className="flex items-center rounded-full border border-border">
76
- <button type="button" aria-label="Decrease quantity" disabled={d.quantity <= 1} onClick={() => d.setQuantity(d.quantity - 1)}
77
- className="px-3 py-2 text-sm disabled:opacity-40">−</button>
78
- <span className="min-w-6 text-center text-sm tabular-nums">{d.quantity}</span>
79
- <button type="button" aria-label="Increase quantity" onClick={() => d.setQuantity(d.quantity + 1)} className="px-3 py-2 text-sm">+</button>
80
- </div>
81
- )}
82
- <button
83
- type="button"
84
- disabled={!d.canAdd || d.adding}
85
- onClick={() => d.add().then(() => onAdded?.()).catch(() => {})}
86
- className="flex-1 rounded-full bg-primary py-2.5 text-sm font-semibold text-primary-foreground disabled:opacity-50"
87
- >
88
- {d.adding ? "Adding…" : d.isPreorder ? "Pre-order" : "Add to cart"}
89
- </button>
90
- </div>
91
- {d.blockedReason && !d.canAdd && <p className="text-xs text-muted-foreground">{d.blockedReason}</p>}
92
- {d.error && <p className="text-xs text-red-600">{d.error}</p>}
93
- </div>
94
- );
95
- }
@@ -1,136 +0,0 @@
1
- // The card's purchase control — the three gallery purchase paths, decided from the product:
2
- // • no options → Direct Add: one click, the cheapest variant, quantity 1
3
- // • options / choice mods → Quick Add: a picker on the card (bottom sheet on small screens)
4
- // • free-text modifier → the product page (the gallery can't collect the text)
5
- // Mount as the LAST ROW of the tile's text block (under name and price), as a direct child of the
6
- // tile root that carries `relative flex flex-col` — the picker anchors to that root and takes its
7
- // width, and the control pins itself to the tile's bottom (mt-auto) so the action row lines up
8
- // across a grid row whether or not a neighbour carries swatches or a struck price. Never overlay
9
- // it on the image, never wrap it in a narrower positioned box.
10
- // <QuickAdd product={p} /> Wire as-is; style via the tokens.
11
- import { useEffect, useRef, useState } from "react";
12
- import { useCart } from "../../hooks/storefront/useCart";
13
- import { useProductDetail } from "../../hooks/storefront/useProductDetail";
14
- import type { ProductSummary } from "../../wix/storefront/types";
15
- import OptionPicker from "./OptionPicker";
16
-
17
- export default function QuickAdd({ product }: { product: ProductSummary }) {
18
- return (
19
- // A tile is often one big <a>. A click inside the buy control must never become a navigation
20
- // to the product page — this boundary cancels the link's default for everything below it
21
- // (the button, the picker, the sheet). Still: keep the control OUTSIDE the tile's link.
22
- <div
23
- className="contents"
24
- onClick={(e) => {
25
- e.preventDefault();
26
- e.stopPropagation();
27
- }}
28
- >
29
- <QuickAddControl product={product} />
30
- </div>
31
- );
32
- }
33
-
34
- function QuickAddControl({ product }: { product: ProductSummary }) {
35
- const { addToCart, busy } = useCart();
36
- const [open, setOpen] = useState(false);
37
- const [error, setError] = useState<string | null>(null);
38
-
39
- if (product.availability === "OUT_OF_STOCK" && !product.preorder) {
40
- return <span className="mt-auto block pt-3 text-sm text-muted-foreground">Out of stock</span>;
41
- }
42
- if (product.quickAddable) {
43
- return (
44
- <div className="mt-auto pt-3">
45
- <button
46
- type="button"
47
- disabled={busy}
48
- onClick={() =>
49
- addToCart(product.id, product.minPriceVariantId, 1).catch((e) => setError(e instanceof Error ? e.message : String(e)))
50
- }
51
- className="w-full rounded-full border border-foreground py-2 text-sm font-medium transition-colors hover:bg-foreground hover:text-background disabled:opacity-50"
52
- >
53
- {product.preorder ? "Pre-order" : "Add to cart"}
54
- </button>
55
- {error && <p className="mt-1 text-xs text-red-600">{error}</p>}
56
- </div>
57
- );
58
- }
59
- // Options to pick, or a single variant that isn't plainly in stock (pre-order, partial stock):
60
- // the picker resolves it through useProductDetail, exactly as the PDP would.
61
- return (
62
- <div className="mt-auto pt-3">
63
- <button
64
- type="button"
65
- onClick={() => setOpen(true)}
66
- className="w-full rounded-full border border-foreground py-2 text-sm font-medium transition-colors hover:bg-foreground hover:text-background"
67
- >
68
- {product.optionsSummary ? "Choose options" : product.preorder ? "Pre-order" : "Add to cart"}
69
- </button>
70
- {open && <QuickAddPicker product={product} onClose={() => setOpen(false)} />}
71
- </div>
72
- );
73
- }
74
-
75
- // The picker fetches the full product only when it opens — cards never carry PDP data. Its controls
76
- // are the shipped OptionPicker, the same component the PDP mounts.
77
- function QuickAddPicker({ product, onClose }: { product: ProductSummary; onClose: () => void }) {
78
- const d = useProductDetail({ slug: product.slug });
79
- const panelRef = useRef<HTMLElement>(null);
80
-
81
- // Overlay contract: Escape closes, scroll locks under md, focus moves in and back.
82
- useEffect(() => {
83
- const opener = document.activeElement;
84
- const isSmall = window.matchMedia("(max-width: 767px)").matches;
85
- const previousOverflow = document.body.style.overflow;
86
- if (isSmall) document.body.style.overflow = "hidden";
87
- panelRef.current?.focus();
88
- const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose();
89
- document.addEventListener("keydown", onKey);
90
- return () => {
91
- document.removeEventListener("keydown", onKey);
92
- document.body.style.overflow = previousOverflow;
93
- if (opener instanceof HTMLElement) opener.focus();
94
- };
95
- }, [onClose]);
96
-
97
- const needsPdp = (d.product?.modifiers ?? []).some((m) => m.type === "text");
98
-
99
- return (
100
- <>
101
- <div className="fixed inset-0 z-40 bg-foreground/40 md:hidden" onClick={onClose} />
102
- <section
103
- ref={panelRef}
104
- tabIndex={-1}
105
- role="dialog"
106
- aria-modal="true"
107
- aria-label={`Choose options for ${product.name}`}
108
- // md+: anchored to the tile (its nearest `relative` ancestor), the tile's full width — and never
109
- // narrower than 18rem even when mounted inside a small wrapper.
110
- className="fixed inset-x-0 bottom-0 z-50 max-h-[85vh] overflow-y-auto rounded-t-2xl bg-background p-5 text-foreground shadow-2xl outline-none md:absolute md:inset-x-auto md:bottom-0 md:right-0 md:w-[max(100%,18rem)] md:max-h-none md:rounded-lg md:p-4"
111
- >
112
- <div className="mb-3 flex items-start justify-between gap-3">
113
- <div className="min-w-0">
114
- <p className="truncate text-sm font-semibold">{product.name}</p>
115
- <p className="text-sm">
116
- {d.price}
117
- {d.compareAtPrice && <span className="ml-2 text-muted-foreground line-through">{d.compareAtPrice}</span>}
118
- </p>
119
- </div>
120
- <button type="button" aria-label="Close" onClick={onClose} className="text-xl leading-none text-muted-foreground">×</button>
121
- </div>
122
-
123
- {!d.product && !d.notFound && <p className="text-sm text-muted-foreground">Loading options…</p>}
124
- {d.notFound && <p className="text-sm text-muted-foreground">This product isn't available anymore.</p>}
125
-
126
- {d.product && needsPdp && (
127
- <a href={`/products/${product.slug}`} className="block rounded-full bg-primary py-2.5 text-center text-sm font-semibold text-primary-foreground">
128
- Customize on the product page
129
- </a>
130
- )}
131
-
132
- {d.product && !needsPdp && <OptionPicker detail={d} onAdded={onClose} />}
133
- </section>
134
- </>
135
- );
136
- }