@wix/agent-skills 1.21.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +60 -4
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
  4. package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
  5. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
  6. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
  7. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
  8. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
  9. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
  10. package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
  11. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
  12. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
  13. package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
  14. package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
  15. package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
  16. package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
  17. package/skills/wix-headless-fast/SKILL.md +61 -223
  18. package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
  19. package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +11 -12
  20. package/skills/wix-headless-fast/guides/existing-site.md +76 -0
  21. package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
  22. package/skills/wix-headless-fast/install/AGENTS.md +6 -3
  23. package/skills/wix-headless-fast/install/attach.mjs +15 -47
  24. package/skills/wix-headless-fast/install/deploy.mjs +21 -60
  25. package/skills/wix-headless-fast/install/lock.mjs +55 -0
  26. package/skills/wix-headless-fast/install/setup.mjs +32 -12
  27. package/skills/wix-headless-fast/install/templates.mjs +168 -0
  28. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
  29. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
  30. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -303
  31. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
  32. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
  33. package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
  34. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
  35. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
  36. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
  37. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
  38. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
  39. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
  40. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
  41. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
  42. package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
  43. package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
  44. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
  45. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
  46. package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
  47. package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
  48. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
  49. package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
  50. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +0 -22
  51. package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
  52. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -277
  53. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
  54. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
  55. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
  56. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
  57. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
  58. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
  59. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
  60. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
  61. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
  62. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
  63. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
  64. package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
  65. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
  66. package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
  67. package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
  68. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
  69. package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
  70. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
  71. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +0 -28
  72. package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
  73. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -355
  74. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
  75. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
  76. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
  77. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
  78. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
  79. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
  80. package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
  81. package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
  82. package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
  83. package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
  84. package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
  85. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +0 -20
  86. package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
  87. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -307
  88. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
  89. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
  90. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
  91. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
  92. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
  93. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
  94. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
  95. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
  96. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
  97. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
  98. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
  99. package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
  100. package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
  101. package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
  102. package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
  103. package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
  104. package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
  105. package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
  106. package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
  107. package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
  108. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +0 -26
  109. package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
  110. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -340
  111. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
  112. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
  113. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
  114. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
  115. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
  116. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
  117. package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
  118. package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
  119. package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
  120. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
  121. package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
  122. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +0 -20
  123. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
  124. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -316
  125. package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
  126. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
  127. package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
  128. package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
  129. package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
  130. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
  131. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
  132. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
  133. package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
  134. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
  135. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
  136. package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
  137. package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
  138. package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
  139. package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
  140. package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
  141. package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
  142. package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
  143. package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
  144. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +0 -15
  145. package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
  146. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -315
  147. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
  148. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
  149. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
  150. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
  151. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
  152. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
  153. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
  154. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
  155. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
  156. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
  157. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
  158. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
  159. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
  160. package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
  161. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
  162. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
  163. package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
  164. package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
  165. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
  166. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
  167. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +0 -20
  168. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -256
  169. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -296
  170. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
  171. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
  172. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
  173. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
  174. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
  175. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
  176. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
  177. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
  178. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
  179. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
  180. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
  181. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
  182. package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
  183. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
  184. package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
  185. package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
  186. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
  187. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
  188. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
  189. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +0 -21
  190. package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
  191. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -360
  192. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
  193. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
  194. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
  195. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
  196. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
  197. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
  198. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
  199. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
  200. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
  201. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
  202. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
  203. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
  204. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
  205. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
  206. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
  207. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
  208. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
  209. package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
  210. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
  211. package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
  212. package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
  213. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
  214. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
  215. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
  216. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
  217. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +0 -25
  218. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -499
  219. package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
  220. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
  221. package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
  222. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
  223. package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
  224. package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
  225. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
  226. package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
  227. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
  228. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
  229. package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
  230. package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
  231. package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
  232. package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
  233. package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
  234. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +0 -99
  235. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -612
  236. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
  237. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
  238. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
  239. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
  240. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
  241. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
  242. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
  243. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
  244. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
  245. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
  246. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
  247. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -314
  248. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
  249. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
  250. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
  251. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -169
  252. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
  253. package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
  254. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
  255. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
  256. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -130
  257. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +0 -38
  258. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -561
@@ -1,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
- }