@wix/agent-skills 1.15.1 → 1.16.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 (322) hide show
  1. package/README.md +3 -7
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +4 -1
  4. package/skills/wix-app/references/APP_TOOLS.md +134 -0
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +1 -1
  6. package/skills/wix-app/references/DASHBOARD_PAGE.md +0 -10
  7. package/skills/wix-app/references/DATA_COLLECTION.md +1 -1
  8. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +28 -9
  9. package/skills/wix-app/references/SERVICE_PLUGIN.md +12 -10
  10. package/skills/wix-app/references/app-tools/TOOLS.md +75 -0
  11. package/skills/wix-app/references/dashboard-page/DASHBOARD_API.md +2 -2
  12. package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +172 -0
  13. package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +138 -0
  14. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +23 -2
  15. package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +29 -0
  16. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +1 -1
  17. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +27 -2
  18. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +5 -3
  19. package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +18 -1
  20. package/skills/wix-app/references/editor-react-component/PARTS.md +15 -2
  21. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +2 -3
  22. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +13 -9
  23. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +4 -0
  24. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  25. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +3 -1
  26. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +4 -0
  27. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +54 -0
  28. package/skills/wix-app/references/stores/QUERY.md +32 -0
  29. package/skills/wix-docs/SKILL.md +35 -3
  30. package/skills/wix-headless/SKILL.md +3 -1
  31. package/skills/wix-headless/references/CONTENT.md +36 -0
  32. package/skills/wix-headless/references/DESIGN.md +54 -0
  33. package/skills/wix-headless/references/DISCOVERY.md +24 -0
  34. package/skills/wix-headless/references/IMAGE_GENERATION.md +10 -0
  35. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +3 -2
  36. package/skills/wix-headless/references/inline-recipes/setup-online-store.md +32 -23
  37. package/skills/wix-headless/references/managed/CONNECT.md +1 -1
  38. package/skills/wix-headless/references/managed/CREATE.md +14 -0
  39. package/skills/wix-headless/references/managed/DEPLOYMENT.md +25 -0
  40. package/skills/wix-manage/SKILL.md +123 -7
  41. package/skills/wix-manage/references/analytics/analytics-dashboard-navigation.md +41 -0
  42. package/skills/wix-manage/references/app-installation/app-installation-dashboard-navigation.md +28 -0
  43. package/skills/wix-manage/references/blog/blog-dashboard-navigation.md +56 -0
  44. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +31 -6
  45. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -1
  46. package/skills/wix-manage/references/bookings/bookings-dashboard-navigation.md +63 -0
  47. package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +10 -1
  48. package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +44 -0
  49. package/skills/wix-manage/references/cms/cms-data-items-crud.md +31 -3
  50. package/skills/wix-manage/references/cms/cms-publishing-flow.md +287 -0
  51. package/skills/wix-manage/references/cms/cms-schema-management.md +27 -1
  52. package/skills/wix-manage/references/contacts/contacts-dashboard-navigation.md +46 -0
  53. package/skills/wix-manage/references/contacts/create-a-contact.md +82 -0
  54. package/skills/wix-manage/references/contacts/update-a-contact.md +119 -0
  55. package/skills/wix-manage/references/dashboard-navigation/dashboard-navigation.md +82 -0
  56. package/skills/wix-manage/references/dashboard-navigation/routes.json +1733 -0
  57. package/skills/wix-manage/references/domains/domains-dashboard-navigation.md +32 -0
  58. package/skills/wix-manage/references/ecommerce/ecom-pricing.md +4 -4
  59. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  60. package/skills/wix-manage/references/ecommerce/gift-cards/ecom-gift-cards-goal-sell-gift-cards.md +338 -0
  61. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-discount-rule.md +42 -78
  62. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-bundle-and-save.md +1 -1
  63. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-stock-mover.md +1 -1
  64. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-upsell-boost.md +1 -1
  65. package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +90 -92
  66. package/skills/wix-manage/references/events/create-wix-event.md +176 -0
  67. package/skills/wix-manage/references/events/manage-wix-events.md +174 -0
  68. package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +44 -0
  69. package/skills/wix-manage/references/get-paid/get-paid-dashboard-navigation.md +52 -0
  70. package/skills/wix-manage/references/google-ads/billing-and-payment.md +80 -0
  71. package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +212 -0
  72. package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +110 -0
  73. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +25 -0
  74. package/skills/wix-manage/references/google-ads/install-and-create-account.md +155 -0
  75. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +63 -0
  76. package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +155 -0
  77. package/skills/wix-manage/references/marketing/marketing-dashboard-navigation.md +52 -0
  78. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +99 -1
  79. package/skills/wix-manage/references/pricing-plans/pricing-plans-dashboard-navigation.md +45 -0
  80. package/skills/wix-manage/references/restaurants/restaurants-dashboard-navigation.md +68 -0
  81. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +94 -125
  82. package/skills/wix-manage/references/site-properties/change-payment-currency-site-properties.md +48 -9
  83. package/skills/wix-manage/references/site-properties/site-properties-dashboard-navigation.md +31 -0
  84. package/skills/wix-manage/references/sites/create-headless-site.md +52 -0
  85. package/skills/wix-manage/references/sites/create-site-from-template.md +2 -25
  86. package/skills/wix-manage/references/sites/site-import.md +163 -190
  87. package/skills/wix-manage/references/sites/sites-dashboard-navigation.md +29 -0
  88. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +17 -1
  89. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +26 -8
  90. package/skills/wix-manage/references/stores/stores-dashboard-navigation.md +65 -0
  91. package/skills/wix-manage/references/stores/update-product-with-options.md +39 -7
  92. package/skills/wix-replatform/.publish-manifest.json +8 -0
  93. package/skills/wix-replatform/SKILL.md +693 -0
  94. package/skills/wix-replatform/lib/artifact-freshness.js +215 -0
  95. package/skills/wix-replatform/lib/completion-report.js +200 -0
  96. package/skills/wix-replatform/lib/config-env.js +59 -0
  97. package/skills/wix-replatform/lib/execution-manifest.js +185 -0
  98. package/skills/wix-replatform/lib/execution-state.js +173 -0
  99. package/skills/wix-replatform/lib/import-recovery.js +313 -0
  100. package/skills/wix-replatform/lib/local-state.js +642 -0
  101. package/skills/wix-replatform/lib/orchestration-preflight.js +143 -0
  102. package/skills/wix-replatform/lib/orchestration-router.js +122 -0
  103. package/skills/wix-replatform/lib/orchestration-state.js +455 -0
  104. package/skills/wix-replatform/lib/progress-log.js +224 -0
  105. package/skills/wix-replatform/lib/progress-log.sh +140 -0
  106. package/skills/wix-replatform/lib/url-preservation-state.js +391 -0
  107. package/skills/wix-replatform/resources/rp-discovery/SKILL.md +184 -0
  108. package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +529 -0
  109. package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +316 -0
  110. package/skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh +64 -0
  111. package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +1114 -0
  112. package/skills/wix-replatform/resources/rp-mapper/SKILL.md +430 -0
  113. package/skills/wix-replatform/resources/rp-mapper/lib/mapping-resolve.js +217 -0
  114. package/skills/wix-replatform/resources/rp-mapper/scripts/resolve-mapping.js +148 -0
  115. package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +378 -0
  116. package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +183 -0
  117. package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +478 -0
  118. package/skills/wix-replatform/resources/rp-source-csv/lib/canonical-record.js +136 -0
  119. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
  120. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fingerprint.js +310 -0
  121. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-layout.js +1081 -0
  122. package/skills/wix-replatform/resources/rp-source-csv/lib/csv-parse.js +618 -0
  123. package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +949 -0
  124. package/skills/wix-replatform/resources/rp-source-csv/source-schema.example.json +196 -0
  125. package/skills/wix-replatform/resources/rp-source-csv/vendors/bigcommerce.json +66 -0
  126. package/skills/wix-replatform/resources/rp-source-csv/vendors/magento.json +60 -0
  127. package/skills/wix-replatform/resources/rp-source-csv/vendors/shopify.json +95 -0
  128. package/skills/wix-replatform/resources/rp-source-csv/vendors/woocommerce.json +82 -0
  129. package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +256 -0
  130. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +249 -0
  131. package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +444 -0
  132. package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +774 -0
  133. package/skills/wix-replatform/resources/rp-source-wordpress/source-schema.example.json +71 -0
  134. package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +632 -0
  135. package/skills/wix-replatform/resources/rp-target-wix/SPEC-product-reviews-and-blog-authorship.md +330 -0
  136. package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +21 -0
  137. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +19 -0
  138. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +37 -0
  139. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +37 -0
  140. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +39 -0
  141. package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +35 -0
  142. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +14 -0
  143. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +35 -0
  144. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +36 -0
  145. package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +35 -0
  146. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +14 -0
  147. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +131 -0
  148. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +37 -0
  149. package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +37 -0
  150. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +21 -0
  151. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +37 -0
  152. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +37 -0
  153. package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +97 -0
  154. package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +528 -0
  155. package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +14 -0
  156. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +38 -0
  157. package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +37 -0
  158. package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +13 -0
  159. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +37 -0
  160. package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +53 -0
  161. package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +84 -0
  162. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +25 -0
  163. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +37 -0
  164. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +37 -0
  165. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +38 -0
  166. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +37 -0
  167. package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +90 -0
  168. package/skills/wix-replatform/resources/rp-target-wix/lib/audit-summary.js +64 -0
  169. package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +141 -0
  170. package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +384 -0
  171. package/skills/wix-replatform/resources/rp-target-wix/lib/ndjson.js +260 -0
  172. package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +238 -0
  173. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +237 -0
  174. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +238 -0
  175. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +2219 -0
  176. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +31 -0
  177. package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge.js +100 -0
  178. package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +107 -0
  179. package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +344 -0
  180. package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +250 -0
  181. package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +1927 -0
  182. package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +83 -0
  183. package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +141 -0
  184. package/skills/wix-replatform/scripts/artifact-freshness.js +76 -0
  185. package/skills/wix-replatform/scripts/execution-manifest-validate.js +43 -0
  186. package/skills/wix-replatform/scripts/execution-state-prepare.js +128 -0
  187. package/skills/wix-replatform/scripts/orchestration-preflight.js +48 -0
  188. package/skills/wix-replatform/scripts/orchestration-route.js +46 -0
  189. package/skills/wix-replatform/scripts/orchestration-state.js +245 -0
  190. package/skills/wix-vibe-headless/SKILL.md +48 -25
  191. package/skills/wix-vibe-headless/install/deploy.cjs +95 -0
  192. package/skills/wix-vibe-headless/install/pin-agents-md.cjs +34 -0
  193. package/skills/wix-vibe-headless/platforms/README.md +8 -4
  194. package/skills/wix-vibe-headless/platforms/base44.md +133 -98
  195. package/skills/wix-vibe-headless/platforms/generic.md +99 -64
  196. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +230 -119
  197. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +43 -0
  198. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +25 -0
  199. package/skills/wix-vibe-headless/references/blog/app/components/PostGrid.jsx +15 -0
  200. package/skills/wix-vibe-headless/references/blog/app/context/TaxonomyContext.jsx +35 -0
  201. package/skills/wix-vibe-headless/references/blog/app/hooks/usePostDetail.js +38 -0
  202. package/skills/wix-vibe-headless/references/blog/app/pages/Blog.jsx +40 -0
  203. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +54 -0
  204. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +65 -0
  205. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +50 -0
  206. package/skills/wix-vibe-headless/references/blog/seed/SEED.md +103 -0
  207. package/skills/wix-vibe-headless/references/blog/seed/seed-blog.js +270 -0
  208. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +260 -149
  209. package/skills/wix-vibe-headless/references/bookings/app/components/BookingForm.jsx +65 -0
  210. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +47 -0
  211. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceGrid.jsx +13 -0
  212. package/skills/wix-vibe-headless/references/bookings/app/components/SlotPicker.jsx +126 -0
  213. package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +108 -0
  214. package/skills/wix-vibe-headless/references/bookings/app/hooks/useServices.js +58 -0
  215. package/skills/wix-vibe-headless/references/bookings/app/lib/serviceFacts.js +95 -0
  216. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +94 -0
  217. package/skills/wix-vibe-headless/references/bookings/app/pages/Services.jsx +89 -0
  218. package/skills/wix-vibe-headless/references/bookings/{wix-bookings-checkout.js → app/rest/wix-bookings-checkout.js} +1 -1
  219. package/skills/wix-vibe-headless/references/bookings/{wix-bookings-services.js → app/rest/wix-bookings-services.js} +87 -11
  220. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +117 -0
  221. package/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.js +333 -0
  222. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +169 -147
  223. package/skills/wix-vibe-headless/references/cms/app/collection.config.js +31 -0
  224. package/skills/wix-vibe-headless/references/cms/app/lib/wixImage.js +18 -0
  225. package/skills/wix-vibe-headless/references/cms/{wix-cms.js → app/rest/wix-cms.js} +47 -2
  226. package/skills/wix-vibe-headless/references/cms/seed/SEED.md +145 -0
  227. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +228 -131
  228. package/skills/wix-vibe-headless/references/events/app/components/CategoryFilter.jsx +23 -0
  229. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +43 -0
  230. package/skills/wix-vibe-headless/references/events/app/components/EventGrid.jsx +13 -0
  231. package/skills/wix-vibe-headless/references/events/app/components/EventRegistration.jsx +29 -0
  232. package/skills/wix-vibe-headless/references/events/app/components/RsvpForm.jsx +57 -0
  233. package/skills/wix-vibe-headless/references/events/app/components/TicketPicker.jsx +78 -0
  234. package/skills/wix-vibe-headless/references/events/app/hooks/useEventDetail.js +27 -0
  235. package/skills/wix-vibe-headless/references/events/app/hooks/useEventsList.js +54 -0
  236. package/skills/wix-vibe-headless/references/events/app/hooks/useRsvpForm.js +40 -0
  237. package/skills/wix-vibe-headless/references/events/app/hooks/useTicketing.js +81 -0
  238. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +48 -0
  239. package/skills/wix-vibe-headless/references/events/app/pages/Events.jsx +36 -0
  240. package/skills/wix-vibe-headless/references/events/{wix-events-browse.js → app/rest/wix-events-browse.js} +1 -1
  241. package/skills/wix-vibe-headless/references/events/seed/SEED.md +107 -0
  242. package/skills/wix-vibe-headless/references/events/seed/seed-events.js +251 -0
  243. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +276 -188
  244. package/skills/wix-vibe-headless/references/members/app/components/LoginForm.jsx +101 -0
  245. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +33 -0
  246. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +21 -0
  247. package/skills/wix-vibe-headless/references/members/app/components/SocialButtons.jsx +29 -0
  248. package/skills/wix-vibe-headless/references/members/app/context/MemberContext.jsx +54 -0
  249. package/skills/wix-vibe-headless/references/members/app/hooks/useLoginForm.js +77 -0
  250. package/skills/wix-vibe-headless/references/members/app/pages/Account.jsx +36 -0
  251. package/skills/wix-vibe-headless/references/members/app/pages/Callback.jsx +32 -0
  252. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +35 -0
  253. package/skills/wix-vibe-headless/references/members/{wix-members-auth.js → app/rest/wix-members-auth.js} +6 -3
  254. package/skills/wix-vibe-headless/references/members/install/Register.jsx +22 -0
  255. package/skills/wix-vibe-headless/references/members/seed/SEED.md +22 -0
  256. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +233 -104
  257. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +31 -0
  258. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionGrid.jsx +15 -0
  259. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +39 -0
  260. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectGrid.jsx +15 -0
  261. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +29 -0
  262. package/skills/wix-vibe-headless/references/portfolio/app/hooks/useCollectionProjects.js +35 -0
  263. package/skills/wix-vibe-headless/references/portfolio/app/hooks/usePortfolioGallery.js +27 -0
  264. package/skills/wix-vibe-headless/references/portfolio/app/hooks/useProjectDetail.js +25 -0
  265. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +39 -0
  266. package/skills/wix-vibe-headless/references/portfolio/app/pages/Portfolio.jsx +27 -0
  267. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +60 -0
  268. package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +94 -0
  269. package/skills/wix-vibe-headless/references/portfolio/seed/seed-portfolio.js +236 -0
  270. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +240 -117
  271. package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +35 -0
  272. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +48 -0
  273. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanGrid.jsx +16 -0
  274. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanPrice.jsx +35 -0
  275. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/useMyPlans.js +25 -0
  276. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlanDetail.js +28 -0
  277. package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlans.js +30 -0
  278. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/MyPlans.jsx +30 -0
  279. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +53 -0
  280. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/Plans.jsx +28 -0
  281. package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +86 -0
  282. package/skills/wix-vibe-headless/references/pricing-plans/seed/seed-pricing-plans.js +251 -0
  283. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +248 -124
  284. package/skills/wix-vibe-headless/references/restaurants/app/components/ItemDialog.jsx +81 -0
  285. package/skills/wix-vibe-headless/references/restaurants/app/components/MenuItemCard.jsx +71 -0
  286. package/skills/wix-vibe-headless/references/restaurants/app/components/MenuList.jsx +43 -0
  287. package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartButton.jsx +21 -0
  288. package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +60 -0
  289. package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +63 -0
  290. package/skills/wix-vibe-headless/references/restaurants/app/hooks/useItemOrder.js +32 -0
  291. package/skills/wix-vibe-headless/references/restaurants/app/hooks/useReservation.js +73 -0
  292. package/skills/wix-vibe-headless/references/restaurants/app/pages/Menu.jsx +28 -0
  293. package/skills/wix-vibe-headless/references/restaurants/app/pages/Reservations.jsx +102 -0
  294. package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +180 -0
  295. package/skills/wix-vibe-headless/references/restaurants/seed/seed-restaurants.js +452 -0
  296. package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +56 -0
  297. package/skills/wix-vibe-headless/references/shared/{wix-client.js → app/rest/wix-client.js} +6 -8
  298. package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +6 -0
  299. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +263 -157
  300. package/skills/wix-vibe-headless/references/storefront/app/components/CartButton.jsx +20 -0
  301. package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +150 -0
  302. package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +167 -0
  303. package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +51 -0
  304. package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +54 -0
  305. package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +119 -0
  306. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +71 -0
  307. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +109 -0
  308. package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +98 -0
  309. package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +27 -0
  310. package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +139 -0
  311. package/skills/wix-vibe-headless/references/storefront/app/pages/Shop.jsx +75 -0
  312. package/skills/wix-vibe-headless/references/storefront/{wix-store-cart.js → app/rest/wix-store-cart.js} +1 -1
  313. package/skills/wix-vibe-headless/references/storefront/{wix-store-catalog.js → app/rest/wix-store-catalog.js} +20 -1
  314. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +111 -0
  315. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +299 -0
  316. /package/skills/wix-vibe-headless/references/blog/{wix-blog.js → app/rest/wix-blog.js} +0 -0
  317. /package/skills/wix-vibe-headless/references/events/{wix-events-registration.js → app/rest/wix-events-registration.js} +0 -0
  318. /package/skills/wix-vibe-headless/references/portfolio/{wix-portfolio.js → app/rest/wix-portfolio.js} +0 -0
  319. /package/skills/wix-vibe-headless/references/pricing-plans/{wix-pricing-plans.js → app/rest/wix-pricing-plans.js} +0 -0
  320. /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-menu.js → app/rest/wix-restaurants-menu.js} +0 -0
  321. /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-ordering.js → app/rest/wix-restaurants-ordering.js} +0 -0
  322. /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-reservations.js → app/rest/wix-restaurants-reservations.js} +0 -0
@@ -0,0 +1,150 @@
1
+ // Slide-over cart. Reads everything from useCart(); mutate by lineItem.id (NOT catalogItemId),
2
+ // check out via the context (redirect-session URL). Styled with base44 design tokens (shadcn Tailwind classes).
3
+ //
4
+ // Load-bearing beyond the styling:
5
+ // • prices come from the SERVER cart — `price` is post-discount, `fullPrice` is before it, so the
6
+ // two together are the strikethrough. Never compute a total in the client: tax, shipping and
7
+ // promotions are resolved server-side, so `subtotal` is the only figure safe to show pre-checkout.
8
+ // Money objects carry { amount, convertedAmount, formattedAmount, formattedConvertedAmount }; use a
9
+ // formatted one so the currency symbol and grouping come from Wix. Prefer `subtotalAfterDiscounts`
10
+ // over `subtotal` — the two are equal until a cart-level coupon applies, and then `subtotal` is the
11
+ // pre-discount figure, which would quote the buyer more than they'll pay.
12
+ // • `availability.quantityAvailable` caps the stepper, so exceeding stock is refused here rather than
13
+ // at checkout.
14
+ // • `availability.status` is flagged per line here because checkout() refuses the whole cart when
15
+ // any item isn't AVAILABLE — showing it on the row is what makes that refusal understandable.
16
+ import { useEffect } from "react";
17
+ import { useCart } from "@/context/CartContext";
18
+ import { storeImage } from "@/lib/storeImage";
19
+
20
+ export default function CartDrawer() {
21
+ const { cart, isOpen, setIsOpen, removeItem, updateQuantity, checkout, loading, error, clearError } = useCart();
22
+ const lineItems = cart?.lineItems ?? [];
23
+ const money = (m) => m?.formattedConvertedAmount || m?.formattedAmount;
24
+ const subtotal = money(cart?.subtotalAfterDiscounts) || money(cart?.subtotal);
25
+ const discount = money(cart?.discount);
26
+ const hasDiscount = (cart?.appliedDiscounts?.length || 0) > 0 && Number(cart?.discount?.amount) > 0;
27
+ const unavailable = lineItems.filter((li) => li.availability?.status && li.availability.status !== "AVAILABLE");
28
+
29
+ // Escape closes the drawer while it's open — expected of anything modal, and the only way out for
30
+ // keyboard users (the backdrop click is pointer-only).
31
+ useEffect(() => {
32
+ if (!isOpen) return;
33
+ const onKey = (e) => e.key === "Escape" && setIsOpen(false);
34
+ document.addEventListener("keydown", onKey);
35
+ return () => document.removeEventListener("keydown", onKey);
36
+ }, [isOpen, setIsOpen]);
37
+
38
+ if (!isOpen) return null;
39
+
40
+ return (
41
+ <div onClick={() => setIsOpen(false)} className="fixed inset-0 z-50 bg-black/40 flex justify-end">
42
+ <aside onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Your cart"
43
+ className="w-[min(420px,100%)] h-full flex flex-col bg-background text-foreground border-l border-border font-body">
44
+ <header className="flex justify-between items-center p-4 border-b border-border">
45
+ <strong className="font-display">Your cart{lineItems.length ? ` (${lineItems.length})` : ""}</strong>
46
+ <button onClick={() => setIsOpen(false)} aria-label="Close cart"
47
+ className="border-none bg-transparent cursor-pointer text-xl text-muted-foreground">×</button>
48
+ </header>
49
+
50
+ {error && (
51
+ <div role="alert" className="m-4 mb-0 p-3 rounded-sm border border-destructive/40 bg-destructive/10 text-sm">
52
+ <div className="flex items-start gap-2">
53
+ <span className="flex-1">{error}</span>
54
+ <button onClick={clearError} aria-label="Dismiss"
55
+ className="border-none bg-transparent cursor-pointer text-muted-foreground leading-none">×</button>
56
+ </div>
57
+ </div>
58
+ )}
59
+
60
+ <div className="flex-1 overflow-y-auto p-4">
61
+ {lineItems.length === 0 ? (
62
+ <div className="h-full flex flex-col items-center justify-center text-center gap-3 py-10">
63
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"
64
+ className="text-muted-foreground/40" aria-hidden="true">
65
+ <path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z" /><path d="M3 6h18" />
66
+ <path d="M16 10a4 4 0 0 1-8 0" />
67
+ </svg>
68
+ <p className="m-0 text-muted-foreground">Your cart is empty.</p>
69
+ <button onClick={() => setIsOpen(false)}
70
+ className="border border-border bg-card text-foreground rounded-sm py-2 px-4 cursor-pointer text-sm">Continue shopping</button>
71
+ </div>
72
+ ) : lineItems.map((item) => {
73
+ const image = storeImage(item.image?.url);
74
+ const struck = item.fullPrice?.formattedAmount;
75
+ const paid = item.price?.formattedAmount;
76
+ const isOut = item.availability?.status && item.availability.status !== "AVAILABLE";
77
+ const stock = item.availability?.quantityAvailable;
78
+ const atStockLimit = Number.isFinite(stock) && item.quantity >= stock;
79
+ return (
80
+ <div key={item.id} className="flex gap-3 py-3 px-0 border-b border-border">
81
+ <div className="w-16 h-16 shrink-0 rounded-sm bg-card overflow-hidden">
82
+ {image && <img src={image} alt={item.productName?.original || ""} className="w-full h-full object-cover" />}
83
+ </div>
84
+ <div className="flex-1 flex flex-col gap-1 min-w-0">
85
+ <span className="font-semibold truncate">{item.productName?.original}</span>
86
+ {item.descriptionLines?.map((dl, i) => (
87
+ <small key={i} className="text-muted-foreground">
88
+ {dl.name?.original}: {dl.plainText?.original || dl.colorInfo?.original}
89
+ </small>
90
+ ))}
91
+ {isOut ? (
92
+ <small className="text-destructive">
93
+ {item.availability.status === "NOT_AVAILABLE" ? "No longer available" : "Not enough stock"}
94
+ </small>
95
+ ) : atStockLimit ? (
96
+ <small className="text-muted-foreground">Only {stock} left</small>
97
+ ) : null}
98
+ <div className="flex items-center gap-2 mt-1">
99
+ <QtyButton disabled={loading || item.quantity <= 1} label="Decrease quantity"
100
+ onClick={() => updateQuantity(item.id, item.quantity - 1)}>−</QtyButton>
101
+ <span className="min-w-5 text-center" aria-label={`Quantity ${item.quantity}`}>{item.quantity}</span>
102
+ <QtyButton disabled={loading || atStockLimit} label="Increase quantity"
103
+ onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</QtyButton>
104
+ <button onClick={() => removeItem(item.id)} disabled={loading}
105
+ className="ml-auto border-none bg-transparent cursor-pointer text-muted-foreground text-[13px] underline disabled:opacity-50">Remove</button>
106
+ </div>
107
+ </div>
108
+ <div className="flex flex-col items-end shrink-0">
109
+ <span className="font-semibold">{paid}</span>
110
+ {struck && struck !== paid && (
111
+ <span className="text-muted-foreground line-through text-[13px]">{struck}</span>
112
+ )}
113
+ </div>
114
+ </div>
115
+ );
116
+ })}
117
+ </div>
118
+
119
+ {lineItems.length > 0 && (
120
+ <footer className="p-4 border-t border-border flex flex-col gap-3">
121
+ {hasDiscount && (
122
+ <div className="flex justify-between items-baseline text-sm">
123
+ <span className="text-muted-foreground">Discount</span>
124
+ <span>−{discount}</span>
125
+ </div>
126
+ )}
127
+ {subtotal && (
128
+ <div className="flex justify-between items-baseline">
129
+ <span className="text-muted-foreground">Subtotal</span>
130
+ <strong className="text-[17px]">{subtotal}</strong>
131
+ </div>
132
+ )}
133
+ <p className="m-0 text-[12px] text-muted-foreground">Shipping and taxes are calculated at checkout.</p>
134
+ <button disabled={loading || unavailable.length > 0} onClick={checkout}
135
+ className="w-full p-3 bg-primary text-primary-foreground border-none rounded-sm text-[15px] font-semibold cursor-pointer disabled:cursor-not-allowed disabled:opacity-60">
136
+ {loading ? "Working…" : unavailable.length > 0 ? "Remove unavailable items to continue" : "Checkout"}
137
+ </button>
138
+ </footer>
139
+ )}
140
+ </aside>
141
+ </div>
142
+ );
143
+ }
144
+
145
+ function QtyButton({ children, onClick, disabled, label }) {
146
+ return (
147
+ <button onClick={onClick} disabled={disabled} aria-label={label}
148
+ className="w-7 h-7 cursor-pointer leading-none bg-card text-foreground border border-border rounded-sm disabled:opacity-40 disabled:cursor-not-allowed">{children}</button>
149
+ );
150
+ }
@@ -0,0 +1,167 @@
1
+ // Grid tile. Styled with base44 design tokens (shadcn Tailwind classes: bg-card / text-foreground /
2
+ // border-border) — re-skin via the app's design tokens (src/index.css :root/.dark), not this JSX.
3
+ // The price / stock / ribbon field paths are load-bearing; image URLs go through lib/storeImage so the
4
+ // grid, the PDP gallery and the cart all normalise them the same way.
5
+ //
6
+ // Everything here comes from the list query — the tile costs no extra request. What the list query
7
+ // does NOT carry is a stock COUNT (inventory has availabilityStatus only), so "Limited stock" is as
8
+ // precise as a tile can honestly get; "Only 2 left" would need per-variant inventory per product.
9
+ import { useState } from "react";
10
+ import { Link } from "react-router-dom";
11
+ import { productGallery, productImage } from "@/lib/storeImage";
12
+ import { useCart } from "@/context/CartContext";
13
+
14
+ // Percent off, from the raw amounts — the formatted strings ("€129.00") can't be subtracted.
15
+ function discountPercent(product) {
16
+ const now = Number(product?.actualPriceRange?.minValue?.amount);
17
+ const was = Number(product?.compareAtPriceRange?.minValue?.amount);
18
+ if (!now || !was || was <= now) return null;
19
+ return Math.round(((was - now) / was) * 100);
20
+ }
21
+
22
+ // What the buyer gets to choose, previewed on the tile: colour options become real dots (the colours
23
+ // say more than "3 colors" could), everything else becomes "4 sizes". Pluralised from the option's own
24
+ // name, so the wording follows the merchant's catalogue rather than a hardcoded English word.
25
+ function optionsPreview(product) {
26
+ const labels = [], colors = [];
27
+ for (const o of product?.options || []) {
28
+ const choices = (o.choicesSettings?.choices || []).filter((c) => c.visible !== false);
29
+ if (!choices.length) continue;
30
+ const swatches = choices.map((c) => c.colorCode).filter(Boolean);
31
+ if (swatches.length) { colors.push(...swatches); continue; }
32
+ const n = o.name.toLowerCase();
33
+ labels.push(`${choices.length} ${choices.length === 1 || n.endsWith("s") ? n : `${n}s`}`);
34
+ }
35
+ return { label: labels.join(" · "), colors };
36
+ }
37
+
38
+ // One rule for every chip on the tile, so a new badge has an obvious home instead of a fresh colour:
39
+ // promotional shouts in the brand colour, informational sits quietly on a card surface, and only a
40
+ // blocking state is destructive. Each background travels with its own foreground token.
41
+ const badge = "px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide rounded-full";
42
+ const badgePromo = `${badge} bg-primary text-primary-foreground`;
43
+ const badgeInfo = `${badge} bg-card text-foreground border border-border`;
44
+ const badgeBlocked = `${badge} bg-destructive text-destructive-foreground`;
45
+
46
+ export default function ProductCard({ product }) {
47
+ const { addToCart } = useCart();
48
+ const [adding, setAdding] = useState(false);
49
+
50
+ const image = productImage(product);
51
+ const hoverImage = productGallery(product)[1]?.url;
52
+ const price = product?.actualPriceRange?.minValue?.formattedAmount;
53
+ const priceMax = product?.actualPriceRange?.maxValue?.formattedAmount;
54
+ const compareAt = product?.compareAtPriceRange?.minValue?.formattedAmount;
55
+
56
+ const status = product?.inventory?.availabilityStatus;
57
+ const soldOut = status === "OUT_OF_STOCK";
58
+ const preorder = product?.inventory?.preorderStatus === "ENABLED";
59
+ const discount = discountPercent(product);
60
+ // A merchant-set ribbon ("New", "Best Seller") is the catalogue's own badge, so it beats anything
61
+ // derived here — a "new" label computed from createdDate would flag the whole catalogue at once
62
+ // right after seeding. Suppressed when a discount badge is showing, since that ribbon is "Sale".
63
+ const ribbon = product?.ribbon?.name;
64
+ const { label: optionLabel, colors } = optionsPreview(product);
65
+ const hasOptions = (product?.options?.length || 0) > 0;
66
+
67
+ // No options means a single variant, which the cart resolves from the product id alone — so the
68
+ // tile can add straight to the cart with no extra request. The drawer opening is the confirmation
69
+ // (and carries the reason when it fails), which is why there's no "Added ✓" state to fake here.
70
+ const quickAdd = async () => {
71
+ setAdding(true);
72
+ await addToCart(product.id);
73
+ setAdding(false);
74
+ };
75
+
76
+ return (
77
+ <div className="group relative flex flex-col bg-card border border-border rounded-lg overflow-hidden shadow-sm">
78
+ <div className="relative aspect-square bg-background">
79
+ <Link to={`/product/${product.slug}`} aria-label={product.name} className="block w-full h-full">
80
+ {image ? (
81
+ <>
82
+ {/* Fade out only when there IS a second shot behind it — a one-photo product would
83
+ otherwise hover to an empty frame. */}
84
+ <img src={image} alt={product.name} loading="lazy"
85
+ className={`w-full h-full object-cover ${hoverImage ? "transition-opacity duration-300 md:group-hover:opacity-0" : ""}`} />
86
+ {/* Second catalogue shot on hover. `hidden` below md so phones never fetch it. */}
87
+ {hoverImage && (
88
+ <img src={hoverImage} alt="" aria-hidden="true" loading="lazy"
89
+ className="hidden md:block absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
90
+ )}
91
+ </>
92
+ ) : (
93
+ <div className="w-full h-full bg-muted flex items-center justify-center" aria-hidden="true">
94
+ <svg viewBox="0 0 24 24" className="w-8 h-8 text-muted-foreground/40" fill="none" stroke="currentColor" strokeWidth="1.5">
95
+ <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><path d="m21 15-5-5L5 21" />
96
+ </svg>
97
+ </div>
98
+ )}
99
+ </Link>
100
+
101
+ <div className="absolute top-2 left-2 flex flex-col items-start gap-1 pointer-events-none">
102
+ {soldOut && preorder && <span className={badgeInfo}>Pre-order</span>}
103
+ {soldOut && !preorder && <span className={badgeBlocked}>Sold out</span>}
104
+ {status === "PARTIALLY_OUT_OF_STOCK" && <span className={badgeInfo}>Limited stock</span>}
105
+ </div>
106
+ {/* Discount and ribbon are mutually exclusive, so both can claim the promotional slot. */}
107
+ <div className="absolute top-2 right-2 pointer-events-none">
108
+ {discount ? <span className={badgePromo}>−{discount}%</span>
109
+ : ribbon ? <span className={badgePromo}>{ribbon}</span> : null}
110
+ </div>
111
+
112
+ {/* Sold out with no pre-order has nothing to offer, so no control appears at all. */}
113
+ {(!soldOut || preorder) && (
114
+ <div className="absolute inset-x-2 bottom-2 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100">
115
+ {/* Same tokens as the PDP's Add-to-cart button: one action, one treatment, so the tile's
116
+ CTA is the brand colour rather than "whatever the text colour is". */}
117
+ {hasOptions || soldOut ? (
118
+ <Link to={`/product/${product.slug}`}
119
+ className="block w-full text-center py-2.5 px-4 rounded-full bg-primary text-primary-foreground no-underline text-sm font-semibold">
120
+ {soldOut ? "Pre-order" : "Choose options"}
121
+ </Link>
122
+ ) : (
123
+ <button onClick={quickAdd} disabled={adding}
124
+ className="w-full py-2.5 px-4 rounded-full bg-primary text-primary-foreground border-none text-sm font-semibold cursor-pointer transition-opacity hover:opacity-90 disabled:opacity-60 disabled:cursor-wait">
125
+ {adding ? "Adding…" : "Quick add"}
126
+ </button>
127
+ )}
128
+ </div>
129
+ )}
130
+ </div>
131
+
132
+ <div className="p-3 flex flex-col gap-1">
133
+ {/* Side by side once the tile is wide enough; stacked on a phone's two-up grid, where a long
134
+ name wrapping to three lines against a top-right price reads as broken. */}
135
+ <div className="flex flex-col gap-1 md:flex-row md:items-baseline md:justify-between md:gap-3">
136
+ {/* Clamped to two lines: a 220px tile turns a long product name into three or four ragged
137
+ lines beside a top-right price. The full name is on the tile's link and the PDP. */}
138
+ <h3 className="m-0 font-display text-[15px] font-semibold line-clamp-2">
139
+ <Link to={`/product/${product.slug}`} className="no-underline text-foreground">{product.name}</Link>
140
+ </h3>
141
+ <div className="flex gap-2 items-baseline flex-wrap md:justify-end">
142
+ {/* A product whose variants differ in price spans a range — showing only minValue reads as
143
+ the price of everything, then the PDP appears to raise it once a variant is picked. */}
144
+ <span className="font-semibold whitespace-nowrap">{priceMax && priceMax !== price ? `${price} – ${priceMax}` : price}</span>
145
+ {compareAt && compareAt !== price && (
146
+ <span className="text-muted-foreground line-through text-[13px] whitespace-nowrap">{compareAt}</span>
147
+ )}
148
+ </div>
149
+ </div>
150
+
151
+ {(colors.length > 0 || optionLabel) && (
152
+ <div className="flex items-center gap-2 text-[13px] text-muted-foreground min-h-[18px]">
153
+ {colors.length > 0 && (
154
+ <span className="flex items-center gap-1" aria-label={`${colors.length} colors available`}>
155
+ {colors.slice(0, 5).map((c, i) => (
156
+ <span key={`${c}-${i}`} className="w-3 h-3 rounded-full ring-1 ring-inset ring-black/20" style={{ backgroundColor: c }} />
157
+ ))}
158
+ {colors.length > 5 && <span>+{colors.length - 5}</span>}
159
+ </span>
160
+ )}
161
+ {optionLabel && <span>{optionLabel}</span>}
162
+ </div>
163
+ )}
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,51 @@
1
+ // PDP image gallery — main image plus thumbnails from productGallery(product), which puts
2
+ // media.main first and drops the duplicate of it that media.itemsInfo.items carries. A product with a
3
+ // single image renders just the main frame, so this is safe for a one-photo catalog.
4
+ // Styled with base44 design tokens (shadcn Tailwind classes).
5
+ import { useEffect, useState } from "react";
6
+
7
+ export default function ProductGallery({ images, name, focusUrl }) {
8
+ const [index, setIndex] = useState(0);
9
+ // Reset when navigating between products — the component stays mounted across a slug change.
10
+ // `images` must keep a stable identity per product (the PDP memoises it), or every parent render —
11
+ // a quantity bump, an option click — would land here and throw away the image the buyer picked.
12
+ useEffect(() => { setIndex(0); }, [images]);
13
+
14
+ // Picking a colour swatch shows that colour: focusUrl is the choice's linked photo. Runs after the
15
+ // reset effect, so a first paint with a colour pre-selected opens on the right image.
16
+ useEffect(() => {
17
+ const i = images?.findIndex((img) => img.url === focusUrl) ?? -1;
18
+ if (i >= 0) setIndex(i);
19
+ }, [focusUrl, images]);
20
+
21
+ const current = images?.[index];
22
+
23
+ return (
24
+ <div className="flex flex-col gap-3">
25
+ <div className="aspect-square bg-card rounded-lg overflow-hidden">
26
+ {current ? (
27
+ <img src={current.url} alt={current.altText || name} className="w-full h-full object-cover" />
28
+ ) : (
29
+ <div className="w-full h-full bg-muted flex items-center justify-center" aria-hidden="true">
30
+ <svg viewBox="0 0 24 24" className="w-10 h-10 text-muted-foreground/40" fill="none" stroke="currentColor" strokeWidth="1.5">
31
+ <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><path d="m21 15-5-5L5 21" />
32
+ </svg>
33
+ </div>
34
+ )}
35
+ </div>
36
+
37
+ {images?.length > 1 && (
38
+ <div className="flex gap-2 flex-wrap" role="group" aria-label={`${name} images`}>
39
+ {images.map((img, i) => (
40
+ <button key={img.url} onClick={() => setIndex(i)} aria-label={`Show image ${i + 1}`} aria-current={i === index}
41
+ className={`w-16 h-16 rounded-sm overflow-hidden cursor-pointer bg-card border ${
42
+ i === index ? "border-primary" : "border-border"
43
+ }`}>
44
+ <img src={img.url} alt="" loading="lazy" className="w-full h-full object-cover" />
45
+ </button>
46
+ ))}
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,54 @@
1
+ // Responsive product grid, with the two states a fresh store spends most of its life in: loading and
2
+ // empty. Styled with base44 design tokens (shadcn Tailwind classes).
3
+ //
4
+ // Two columns on phones — a 220px minmax leaves room for only one, and a one-up catalog scrolls
5
+ // forever — then auto-fill from md up.
6
+ //
7
+ // The empty state carries weight here — a catalog is seeded separately from the build, so it is
8
+ // routinely empty when the merchant first opens the page. Say why, rather than showing a bare line.
9
+ import ProductCard from "./ProductCard";
10
+
11
+ // Skeleton tiles match ProductCard's aspect-square + two text lines, so nothing shifts when the real
12
+ // products land.
13
+ export function ProductGridSkeleton({ count = 8 }) {
14
+ return (
15
+ <div className="grid gap-4 grid-cols-2 md:[grid-template-columns:repeat(auto-fill,minmax(220px,1fr))]" aria-hidden="true">
16
+ {Array.from({ length: count }).map((_, i) => (
17
+ <div key={i} className="bg-card border border-border rounded-lg overflow-hidden">
18
+ <div className="aspect-square bg-muted animate-pulse" />
19
+ <div className="p-3 flex flex-col gap-2">
20
+ <div className="flex justify-between gap-3">
21
+ <div className="h-3.5 w-1/2 bg-muted rounded-sm animate-pulse" />
22
+ <div className="h-3.5 w-1/5 bg-muted rounded-sm animate-pulse" />
23
+ </div>
24
+ <div className="h-3 w-1/3 bg-muted rounded-sm animate-pulse" />
25
+ </div>
26
+ </div>
27
+ ))}
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export default function ProductGrid({ products, loading, empty = "No products yet.", emptyHint }) {
33
+ if (loading) return <ProductGridSkeleton />;
34
+
35
+ if (!products?.length) {
36
+ return (
37
+ <div className="flex flex-col items-center justify-center text-center gap-3 py-16 px-4">
38
+ <svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"
39
+ className="text-muted-foreground/40" aria-hidden="true">
40
+ <path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z" /><path d="M3 6h18" />
41
+ <path d="M16 10a4 4 0 0 1-8 0" />
42
+ </svg>
43
+ <h2 className="font-display m-0 text-lg font-semibold">{empty}</h2>
44
+ {emptyHint && <p className="m-0 text-muted-foreground max-w-[46ch]">{emptyHint}</p>}
45
+ </div>
46
+ );
47
+ }
48
+
49
+ return (
50
+ <div className="grid gap-4 grid-cols-2 md:[grid-template-columns:repeat(auto-fill,minmax(220px,1fr))]">
51
+ {products.map((p) => <ProductCard key={p.id} product={p} />)}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,119 @@
1
+ // PDP choice controls: one OptionSelector per product.options[] (variant choices) and one
2
+ // ModifierSelector per product.modifiers[] (TEXT_CHOICES → buttons, FREE_TEXT → input). Driven by
3
+ // useProductDetail — pass its selection state/handlers in. Styled with base44 design tokens (shadcn Tailwind classes).
4
+
5
+ const chipBase = "py-1.5 px-3 cursor-pointer text-sm font-body border rounded-sm";
6
+ const chipIdle = "border-border bg-card text-foreground";
7
+ const chipActive = "border-primary bg-primary text-primary-foreground";
8
+ const label = "block mb-1.5 text-[13px] font-semibold text-muted-foreground";
9
+
10
+ // A colour/swatch option renders as swatches, a text option as pills — the render type is on the
11
+ // option (`optionRenderType`) and mirrored per choice (`choiceType`: ONE_COLOR vs CHOICE_TEXT).
12
+ // Colour choices carry `colorCode` (hex) and usually `linkedMedia`, so a swatch is real colour, and
13
+ // picking one moves the gallery to that colour's photo.
14
+ const isColorChoice = (option, choice) =>
15
+ option?.optionRenderType === "COLOR_CHOICES" ||
16
+ option?.optionRenderType === "SWATCH_CHOICES" ||
17
+ choice?.choiceType === "ONE_COLOR";
18
+
19
+ function OptionSelector({ option, selected, onSelect }) {
20
+ // `visible: false` is a choice the merchant retired — it stays in the payload, so filter it out or
21
+ // the buyer can pick something the catalogue no longer offers.
22
+ const choices = (option.choicesSettings?.choices || []).filter((c) => c.visible !== false);
23
+ if (!choices.length) return null;
24
+ const swatches = choices.some((c) => isColorChoice(option, c) && c.colorCode);
25
+
26
+ return (
27
+ <div className="mb-4">
28
+ <label className={label}>
29
+ {option.name}
30
+ {/* With swatches the chosen colour's name isn't otherwise on screen — echo it. */}
31
+ {swatches && selected && (
32
+ <span className="ml-1.5 font-normal text-foreground">
33
+ {choices.find((c) => c.choiceId === selected)?.name}
34
+ </span>
35
+ )}
36
+ </label>
37
+ <div className="flex flex-wrap gap-2">
38
+ {choices.map((c) => {
39
+ const active = selected === c.choiceId;
40
+ const out = c.inStock === false;
41
+ if (swatches && c.colorCode) {
42
+ return (
43
+ <button
44
+ key={c.choiceId}
45
+ type="button"
46
+ disabled={out}
47
+ onClick={() => onSelect(option.id, c.choiceId)}
48
+ aria-pressed={active}
49
+ aria-label={`${c.name}${out ? " (out of stock)" : ""}`}
50
+ title={c.name}
51
+ className={`relative w-9 h-9 rounded-full cursor-pointer transition-shadow ring-1 ring-inset ring-black/15 ${
52
+ active ? "ring-2 ring-offset-2 ring-offset-background ring-primary" : ""
53
+ } ${out ? "opacity-40 cursor-not-allowed" : "hover:ring-2 hover:ring-offset-1 hover:ring-offset-background hover:ring-border"}`}
54
+ style={{ backgroundColor: c.colorCode }}
55
+ >
56
+ {/* A struck line reads as "unavailable" without hiding which colour it was. */}
57
+ {out && (
58
+ <span aria-hidden="true" className="absolute inset-0 grid place-items-center">
59
+ <span className="w-full h-px bg-destructive rotate-45" />
60
+ </span>
61
+ )}
62
+ </button>
63
+ );
64
+ }
65
+ return (
66
+ <button
67
+ key={c.choiceId}
68
+ type="button"
69
+ disabled={out}
70
+ aria-pressed={active}
71
+ onClick={() => onSelect(option.id, c.choiceId)}
72
+ className={`${chipBase} ${active ? chipActive : chipIdle} ${out ? "opacity-40 line-through" : ""}`}
73
+ >
74
+ {c.name}
75
+ </button>
76
+ );
77
+ })}
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ function ModifierSelector({ modifier, value, onChange }) {
84
+ const key = modifier.modifierRenderType === "FREE_TEXT" ? modifier.freeTextSettings?.key : modifier.key;
85
+ if (modifier.modifierRenderType === "FREE_TEXT") {
86
+ return (
87
+ <div className="mb-4">
88
+ <label className={label}>{modifier.name}{modifier.mandatory && " *"}</label>
89
+ <input value={value || ""} onChange={(e) => onChange(key, e.target.value)}
90
+ className="w-full py-2 px-3 font-body border border-input rounded-sm bg-background text-foreground" />
91
+ </div>
92
+ );
93
+ }
94
+ return (
95
+ <div className="mb-4">
96
+ <label className={label}>{modifier.name}{modifier.mandatory && " *"}</label>
97
+ <div className="flex flex-wrap gap-2">
98
+ {modifier.choicesSettings?.choices?.map((c) => (
99
+ <button key={c.key} aria-pressed={value === c.key} onClick={() => onChange(key, c.key)}
100
+ className={`${chipBase} ${value === c.key ? chipActive : chipIdle}`}>{c.name}</button>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ export default function VariantPicker({ options, modifiers, selectedOptions, selectOption, modifierValues, setModifier }) {
108
+ return (
109
+ <>
110
+ {options.map((o) => (
111
+ <OptionSelector key={o.id} option={o} selected={selectedOptions[o.id]} onSelect={selectOption} />
112
+ ))}
113
+ {modifiers.map((m) => (
114
+ <ModifierSelector key={m.key || m.freeTextSettings?.key} modifier={m}
115
+ value={modifierValues[m.key || m.freeTextSettings?.key]} onChange={setModifier} />
116
+ ))}
117
+ </>
118
+ );
119
+ }
@@ -0,0 +1,71 @@
1
+ // Cart state — mirrors the Wix SERVER cart (never a local copy). Wrap the app in <CartProvider>;
2
+ // every component reads useCart(). Data wiring is correct as-is — do not restyle or re-derive it.
3
+ import { createContext, useContext, useState, useEffect, useCallback } from "react";
4
+ import {
5
+ getCurrentCart,
6
+ addToCart as apiAdd,
7
+ removeFromCart as apiRemove,
8
+ updateCartItemQuantity as apiQty,
9
+ checkout as apiCheckout,
10
+ } from "@/rest/wix-store-cart";
11
+
12
+ const CartContext = createContext(null);
13
+
14
+ export function CartProvider({ children }) {
15
+ const [cart, setCart] = useState(null);
16
+ const [isOpen, setIsOpen] = useState(false);
17
+ const [loading, setLoading] = useState(false);
18
+ // The cart helpers throw on refusal with a buyer-readable reason — an empty cart, or line items
19
+ // whose availability.status isn't AVAILABLE. Hold the message so the drawer can show it; without
20
+ // this the rejection is unhandled and the shopper sees the spinner stop with nothing said.
21
+ const [error, setError] = useState(null);
22
+
23
+ const refreshCart = useCallback(async () => setCart(await getCurrentCart()), []);
24
+ useEffect(() => {
25
+ refreshCart();
26
+ const onVisible = () => document.visibilityState === "visible" && refreshCart();
27
+ document.addEventListener("visibilitychange", onVisible);
28
+ return () => document.removeEventListener("visibilitychange", onVisible);
29
+ }, [refreshCart]);
30
+
31
+ const itemCount = (cart?.lineItems ?? []).reduce((n, li) => n + (li.quantity || 0), 0);
32
+
33
+ // Every mutation runs through here so a rejection always lands somewhere visible. `run` keeps the
34
+ // cart untouched on failure — the server stays the source of truth — and re-reads it for anything
35
+ // that may have changed underneath (an item selling out between page load and checkout).
36
+ const run = async (fn, { reread = false } = {}) => {
37
+ setLoading(true);
38
+ setError(null);
39
+ try {
40
+ return await fn();
41
+ } catch (e) {
42
+ setError(e?.message || "Something went wrong. Please try again.");
43
+ if (reread) await refreshCart().catch(() => {});
44
+ return null;
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ };
49
+
50
+ const addToCart = async (id, variantId, qty = 1, extras) =>
51
+ run(async () => {
52
+ setCart(await apiAdd(id, variantId, qty, extras));
53
+ setIsOpen(true);
54
+ });
55
+ const removeItem = (lineItemId) => run(async () => setCart(await apiRemove(lineItemId)));
56
+ const updateQuantity = (lineItemId, qty) => run(async () => setCart(await apiQty(lineItemId, qty)));
57
+ const checkout = () =>
58
+ run(async () => { window.location.href = await apiCheckout(); }, { reread: true });
59
+
60
+ return (
61
+ <CartContext.Provider value={{ cart, itemCount, isOpen, setIsOpen, loading, error, clearError: () => setError(null), addToCart, removeItem, updateQuantity, checkout, refreshCart }}>
62
+ {children}
63
+ </CartContext.Provider>
64
+ );
65
+ }
66
+
67
+ export function useCart() {
68
+ const ctx = useContext(CartContext);
69
+ if (!ctx) throw new Error("useCart must be used within <CartProvider>");
70
+ return ctx;
71
+ }