@pradip1995/create-storefront 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/bin/create-storefront.js +239 -0
  2. package/lib/kit-next-config.js +84 -0
  3. package/package.json +32 -0
  4. package/templates/storefront/.eslintrc.json +3 -0
  5. package/templates/storefront/README.md +35 -0
  6. package/templates/storefront/check-env-variables.js +51 -0
  7. package/templates/storefront/kit-next-config.js +71 -0
  8. package/templates/storefront/next-env.d.ts +5 -0
  9. package/templates/storefront/next.config.js +25 -0
  10. package/templates/storefront/package.json +56 -0
  11. package/templates/storefront/postcss.config.js +6 -0
  12. package/templates/storefront/public/favicon.png +0 -0
  13. package/templates/storefront/src/app/[countryCode]/(checkout)/checkout/page.tsx +23 -0
  14. package/templates/storefront/src/app/[countryCode]/(checkout)/checkout/payment/page.tsx +47 -0
  15. package/templates/storefront/src/app/[countryCode]/(checkout)/layout.tsx +31 -0
  16. package/templates/storefront/src/app/[countryCode]/(checkout)/not-found.tsx +19 -0
  17. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/addresses/page.tsx +31 -0
  18. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/loading.tsx +9 -0
  19. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx +35 -0
  20. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/orders/exchange/[id]/page.tsx +47 -0
  21. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx +28 -0
  22. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/orders/return/[id]/page.tsx +66 -0
  23. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/page.tsx +22 -0
  24. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/payment-methods/page.tsx +23 -0
  25. package/templates/storefront/src/app/[countryCode]/(main)/account/@dashboard/profile/page.tsx +43 -0
  26. package/templates/storefront/src/app/[countryCode]/(main)/account/@login/default.tsx +11 -0
  27. package/templates/storefront/src/app/[countryCode]/(main)/account/@login/page.tsx +18 -0
  28. package/templates/storefront/src/app/[countryCode]/(main)/account/guest-orders/page.tsx +13 -0
  29. package/templates/storefront/src/app/[countryCode]/(main)/account/layout.tsx +22 -0
  30. package/templates/storefront/src/app/[countryCode]/(main)/account/loading.tsx +9 -0
  31. package/templates/storefront/src/app/[countryCode]/(main)/cart/loading.tsx +5 -0
  32. package/templates/storefront/src/app/[countryCode]/(main)/cart/not-found.tsx +21 -0
  33. package/templates/storefront/src/app/[countryCode]/(main)/cart/page.tsx +23 -0
  34. package/templates/storefront/src/app/[countryCode]/(main)/categories/[...category]/page.tsx +11 -0
  35. package/templates/storefront/src/app/[countryCode]/(main)/collections/[handle]/page.tsx +11 -0
  36. package/templates/storefront/src/app/[countryCode]/(main)/contact/page.tsx +21 -0
  37. package/templates/storefront/src/app/[countryCode]/(main)/guest-orders/page.tsx +12 -0
  38. package/templates/storefront/src/app/[countryCode]/(main)/help/page.tsx +28 -0
  39. package/templates/storefront/src/app/[countryCode]/(main)/layout.tsx +21 -0
  40. package/templates/storefront/src/app/[countryCode]/(main)/not-found.tsx +20 -0
  41. package/templates/storefront/src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx +5 -0
  42. package/templates/storefront/src/app/[countryCode]/(main)/order/[id]/confirmed/page.tsx +23 -0
  43. package/templates/storefront/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx +41 -0
  44. package/templates/storefront/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx +41 -0
  45. package/templates/storefront/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx +38 -0
  46. package/templates/storefront/src/app/[countryCode]/(main)/order/exchange/[id]/page.tsx +47 -0
  47. package/templates/storefront/src/app/[countryCode]/(main)/order/return/[id]/page.tsx +61 -0
  48. package/templates/storefront/src/app/[countryCode]/(main)/orders/[id]/page.tsx +33 -0
  49. package/templates/storefront/src/app/[countryCode]/(main)/page.tsx +24 -0
  50. package/templates/storefront/src/app/[countryCode]/(main)/privacy-policy/page.tsx +173 -0
  51. package/templates/storefront/src/app/[countryCode]/(main)/products/[handle]/page.tsx +193 -0
  52. package/templates/storefront/src/app/[countryCode]/(main)/reset-password/page.tsx +192 -0
  53. package/templates/storefront/src/app/[countryCode]/(main)/store/page.tsx +72 -0
  54. package/templates/storefront/src/app/[countryCode]/(main)/terms-of-use/page.tsx +179 -0
  55. package/templates/storefront/src/app/[countryCode]/(main)/wishlist/page.tsx +19 -0
  56. package/templates/storefront/src/app/api/meta/event/route.ts +63 -0
  57. package/templates/storefront/src/app/auth/customer/google/callback/page.tsx +126 -0
  58. package/templates/storefront/src/app/layout.tsx +104 -0
  59. package/templates/storefront/src/app/not-found.tsx +30 -0
  60. package/templates/storefront/src/app/opengraph-image.jpg +0 -0
  61. package/templates/storefront/src/app/robots.ts +15 -0
  62. package/templates/storefront/src/app/sitemap.ts +65 -0
  63. package/templates/storefront/src/app/twitter-image.jpg +0 -0
  64. package/templates/storefront/src/modules/account/components/account-deletion/index.tsx +160 -0
  65. package/templates/storefront/src/modules/account/components/account-info/index.tsx +145 -0
  66. package/templates/storefront/src/modules/account/components/account-nav/icons.tsx +43 -0
  67. package/templates/storefront/src/modules/account/components/account-nav/index.tsx +318 -0
  68. package/templates/storefront/src/modules/account/components/account-nav/logout-modal.tsx +92 -0
  69. package/templates/storefront/src/modules/account/components/account-nav/payment-methods-icon.tsx +9 -0
  70. package/templates/storefront/src/modules/account/components/address-book/index.tsx +47 -0
  71. package/templates/storefront/src/modules/account/components/address-card/add-address.tsx +377 -0
  72. package/templates/storefront/src/modules/account/components/address-card/edit-address-modal.tsx +468 -0
  73. package/templates/storefront/src/modules/account/components/deletion-pending-modal/index.tsx +213 -0
  74. package/templates/storefront/src/modules/account/components/forgot-password/index.tsx +1 -0
  75. package/templates/storefront/src/modules/account/components/login/index.tsx +1 -0
  76. package/templates/storefront/src/modules/account/components/order-card/index.tsx +221 -0
  77. package/templates/storefront/src/modules/account/components/order-overview/index.tsx +159 -0
  78. package/templates/storefront/src/modules/account/components/overview/index.tsx +189 -0
  79. package/templates/storefront/src/modules/account/components/profile-billing-address/index.tsx +447 -0
  80. package/templates/storefront/src/modules/account/components/profile-email/index.tsx +75 -0
  81. package/templates/storefront/src/modules/account/components/profile-form/index.tsx +416 -0
  82. package/templates/storefront/src/modules/account/components/profile-name/index.tsx +76 -0
  83. package/templates/storefront/src/modules/account/components/profile-password/index.tsx +70 -0
  84. package/templates/storefront/src/modules/account/components/profile-phone/index.tsx +185 -0
  85. package/templates/storefront/src/modules/account/components/register/index.tsx +1 -0
  86. package/templates/storefront/src/modules/account/components/return-item-selector/index.tsx +187 -0
  87. package/templates/storefront/src/modules/account/components/return-shipping-selector/index.tsx +118 -0
  88. package/templates/storefront/src/modules/account/components/transfer-request-form/index.tsx +81 -0
  89. package/templates/storefront/src/modules/account/templates/account-layout.tsx +38 -0
  90. package/templates/storefront/src/modules/account/templates/exchange-request-template.tsx +389 -0
  91. package/templates/storefront/src/modules/account/templates/guest-orders-template.tsx +123 -0
  92. package/templates/storefront/src/modules/account/templates/login-template.tsx +44 -0
  93. package/templates/storefront/src/modules/account/templates/payment-methods-template.tsx +478 -0
  94. package/templates/storefront/src/modules/account/templates/return-request-template.tsx +300 -0
  95. package/templates/storefront/src/modules/cart/components/abandoned-carts/ScrollToPendingOrdersButton.tsx +21 -0
  96. package/templates/storefront/src/modules/cart/components/abandoned-carts/index.tsx +335 -0
  97. package/templates/storefront/src/modules/cart/components/applied-promotions/index.tsx +121 -0
  98. package/templates/storefront/src/modules/cart/components/cart-delivery-selection/index.tsx +203 -0
  99. package/templates/storefront/src/modules/cart/components/cart-item-card/index.tsx +476 -0
  100. package/templates/storefront/src/modules/cart/components/cart-item-select/index.tsx +73 -0
  101. package/templates/storefront/src/modules/cart/components/cart-view-tracker/index.tsx +44 -0
  102. package/templates/storefront/src/modules/cart/components/delivery-information/index.tsx +89 -0
  103. package/templates/storefront/src/modules/cart/components/empty-cart-message/index.tsx +38 -0
  104. package/templates/storefront/src/modules/cart/components/item/index.tsx +150 -0
  105. package/templates/storefront/src/modules/cart/components/pincode-checker/index.tsx +174 -0
  106. package/templates/storefront/src/modules/cart/components/sign-in-prompt/index.tsx +26 -0
  107. package/templates/storefront/src/modules/cart/components/you-may-also-like/index.tsx +137 -0
  108. package/templates/storefront/src/modules/cart/templates/index.tsx +88 -0
  109. package/templates/storefront/src/modules/cart/templates/items.tsx +49 -0
  110. package/templates/storefront/src/modules/cart/templates/preview.tsx +51 -0
  111. package/templates/storefront/src/modules/cart/templates/summary.tsx +29 -0
  112. package/templates/storefront/src/modules/checkout/components/add-address-modal/index.tsx +390 -0
  113. package/templates/storefront/src/modules/checkout/components/address-card/index.tsx +135 -0
  114. package/templates/storefront/src/modules/checkout/components/address-select/index.tsx +116 -0
  115. package/templates/storefront/src/modules/checkout/components/addresses/index.tsx +605 -0
  116. package/templates/storefront/src/modules/checkout/components/back-link/index.tsx +32 -0
  117. package/templates/storefront/src/modules/checkout/components/billing_address/index.tsx +301 -0
  118. package/templates/storefront/src/modules/checkout/components/checkout-begin-tracker/index.tsx +45 -0
  119. package/templates/storefront/src/modules/checkout/components/checkout-leave-guard/index.tsx +109 -0
  120. package/templates/storefront/src/modules/checkout/components/checkout-shipping-tracker/index.tsx +45 -0
  121. package/templates/storefront/src/modules/checkout/components/country-select/index.tsx +50 -0
  122. package/templates/storefront/src/modules/checkout/components/discount-code/index.tsx +220 -0
  123. package/templates/storefront/src/modules/checkout/components/error-message/index.tsx +13 -0
  124. package/templates/storefront/src/modules/checkout/components/payment/index.tsx +572 -0
  125. package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +257 -0
  126. package/templates/storefront/src/modules/checkout/components/payment-button/razorpay-payment-button.tsx +136 -0
  127. package/templates/storefront/src/modules/checkout/components/payment-container/index.tsx +129 -0
  128. package/templates/storefront/src/modules/checkout/components/payment-test/index.tsx +12 -0
  129. package/templates/storefront/src/modules/checkout/components/payment-wrapper/index.tsx +50 -0
  130. package/templates/storefront/src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx +54 -0
  131. package/templates/storefront/src/modules/checkout/components/processing-overlay/index.tsx +83 -0
  132. package/templates/storefront/src/modules/checkout/components/review/index.tsx +60 -0
  133. package/templates/storefront/src/modules/checkout/components/select-address-modal/index.tsx +103 -0
  134. package/templates/storefront/src/modules/checkout/components/shipping/index.tsx +533 -0
  135. package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +521 -0
  136. package/templates/storefront/src/modules/checkout/components/submit-button/index.tsx +32 -0
  137. package/templates/storefront/src/modules/checkout/templates/checkout-form/index.tsx +38 -0
  138. package/templates/storefront/src/modules/checkout/templates/checkout-summary/index.tsx +274 -0
  139. package/templates/storefront/src/modules/common/components/breadcrumb/index.tsx +43 -0
  140. package/templates/storefront/src/modules/common/components/cart-totals/index.tsx +473 -0
  141. package/templates/storefront/src/modules/common/components/checkbox/index.tsx +98 -0
  142. package/templates/storefront/src/modules/common/components/delete-button/index.tsx +156 -0
  143. package/templates/storefront/src/modules/common/components/divider/index.tsx +9 -0
  144. package/templates/storefront/src/modules/common/components/filter-checkbox-group/index.tsx +134 -0
  145. package/templates/storefront/src/modules/common/components/filter-radio-group/index.tsx +62 -0
  146. package/templates/storefront/src/modules/common/components/input/index.tsx +79 -0
  147. package/templates/storefront/src/modules/common/components/interactive-link/index.tsx +33 -0
  148. package/templates/storefront/src/modules/common/components/line-item-options/index.tsx +26 -0
  149. package/templates/storefront/src/modules/common/components/line-item-price/index.tsx +64 -0
  150. package/templates/storefront/src/modules/common/components/line-item-unit-price/index.tsx +61 -0
  151. package/templates/storefront/src/modules/common/components/localized-client-link/index.tsx +32 -0
  152. package/templates/storefront/src/modules/common/components/login-popup/index.tsx +78 -0
  153. package/templates/storefront/src/modules/common/components/modal/index.tsx +123 -0
  154. package/templates/storefront/src/modules/common/components/native-select/index.tsx +75 -0
  155. package/templates/storefront/src/modules/common/components/obfuscated-email/index.tsx +30 -0
  156. package/templates/storefront/src/modules/common/components/product/product-rating/index.tsx +172 -0
  157. package/templates/storefront/src/modules/common/components/product/review-modal/index.tsx +333 -0
  158. package/templates/storefront/src/modules/common/components/product/share-button/index.tsx +227 -0
  159. package/templates/storefront/src/modules/common/components/product/wishlist-icon/index.tsx +46 -0
  160. package/templates/storefront/src/modules/common/components/radio/index.tsx +27 -0
  161. package/templates/storefront/src/modules/common/components/select/index.tsx +164 -0
  162. package/templates/storefront/src/modules/common/components/side-panel/index.tsx +65 -0
  163. package/templates/storefront/src/modules/common/icons/arrow-left.tsx +36 -0
  164. package/templates/storefront/src/modules/common/icons/back.tsx +37 -0
  165. package/templates/storefront/src/modules/common/icons/bancontact.tsx +26 -0
  166. package/templates/storefront/src/modules/common/icons/chevron-down.tsx +30 -0
  167. package/templates/storefront/src/modules/common/icons/delivered.tsx +29 -0
  168. package/templates/storefront/src/modules/common/icons/envelope.tsx +27 -0
  169. package/templates/storefront/src/modules/common/icons/eye-off.tsx +37 -0
  170. package/templates/storefront/src/modules/common/icons/eye.tsx +37 -0
  171. package/templates/storefront/src/modules/common/icons/fast-delivery.tsx +65 -0
  172. package/templates/storefront/src/modules/common/icons/ideal.tsx +26 -0
  173. package/templates/storefront/src/modules/common/icons/lock.tsx +31 -0
  174. package/templates/storefront/src/modules/common/icons/map-pin.tsx +37 -0
  175. package/templates/storefront/src/modules/common/icons/medusa.tsx +27 -0
  176. package/templates/storefront/src/modules/common/icons/menu.tsx +45 -0
  177. package/templates/storefront/src/modules/common/icons/nextjs.tsx +27 -0
  178. package/templates/storefront/src/modules/common/icons/package.tsx +44 -0
  179. package/templates/storefront/src/modules/common/icons/paypal.tsx +30 -0
  180. package/templates/storefront/src/modules/common/icons/phone.tsx +30 -0
  181. package/templates/storefront/src/modules/common/icons/placeholder-image.tsx +44 -0
  182. package/templates/storefront/src/modules/common/icons/refresh.tsx +51 -0
  183. package/templates/storefront/src/modules/common/icons/spinner.tsx +37 -0
  184. package/templates/storefront/src/modules/common/icons/trash.tsx +51 -0
  185. package/templates/storefront/src/modules/common/icons/user.tsx +37 -0
  186. package/templates/storefront/src/modules/common/icons/x.tsx +37 -0
  187. package/templates/storefront/src/modules/contact/templates/index.tsx +272 -0
  188. package/templates/storefront/src/modules/help/templates/index.tsx +629 -0
  189. package/templates/storefront/src/modules/home/components/dynamic-banner/index.tsx +190 -0
  190. package/templates/storefront/src/modules/home/components/featured-products/index.tsx +16 -0
  191. package/templates/storefront/src/modules/home/components/featured-products/product-rail/index.tsx +51 -0
  192. package/templates/storefront/src/modules/home/components/features/index.tsx +1 -0
  193. package/templates/storefront/src/modules/home/components/hero/index.tsx +1 -0
  194. package/templates/storefront/src/modules/home/components/loved-by-moms/index.tsx +1 -0
  195. package/templates/storefront/src/modules/home/components/new-arrivals/index.tsx +1 -0
  196. package/templates/storefront/src/modules/home/components/shop-by-age/index.tsx +1 -0
  197. package/templates/storefront/src/modules/home/components/shop-by-category/index.tsx +1 -0
  198. package/templates/storefront/src/modules/home/components/testimonials/index.tsx +1 -0
  199. package/templates/storefront/src/modules/home/components/why-choose-us/dynamic-features.tsx +93 -0
  200. package/templates/storefront/src/modules/home/components/why-choose-us/index.tsx +1 -0
  201. package/templates/storefront/src/modules/layout/components/account-dropdown/index.tsx +56 -0
  202. package/templates/storefront/src/modules/layout/components/cart-button/index.tsx +8 -0
  203. package/templates/storefront/src/modules/layout/components/cart-dropdown/index.tsx +424 -0
  204. package/templates/storefront/src/modules/layout/components/cart-mismatch-banner/index.tsx +57 -0
  205. package/templates/storefront/src/modules/layout/components/cookie-consent/index.tsx +116 -0
  206. package/templates/storefront/src/modules/layout/components/country-select/index.tsx +135 -0
  207. package/templates/storefront/src/modules/layout/components/desktop-search/index.tsx +148 -0
  208. package/templates/storefront/src/modules/layout/components/dynamic-logo/index.tsx +27 -0
  209. package/templates/storefront/src/modules/layout/components/footer-categories/index.tsx +34 -0
  210. package/templates/storefront/src/modules/layout/components/footer-contact/index.tsx +87 -0
  211. package/templates/storefront/src/modules/layout/components/footer-description/index.tsx +12 -0
  212. package/templates/storefront/src/modules/layout/components/footer-logo/index.tsx +22 -0
  213. package/templates/storefront/src/modules/layout/components/footer-newsletter/index.tsx +100 -0
  214. package/templates/storefront/src/modules/layout/components/language-select/index.tsx +192 -0
  215. package/templates/storefront/src/modules/layout/components/medusa-cta/index.tsx +21 -0
  216. package/templates/storefront/src/modules/layout/components/mobile-menu/index.tsx +296 -0
  217. package/templates/storefront/src/modules/layout/components/nav-links/index.tsx +66 -0
  218. package/templates/storefront/src/modules/layout/components/nav-wrapper/index.tsx +14 -0
  219. package/templates/storefront/src/modules/layout/components/promo-bar/index.tsx +7 -0
  220. package/templates/storefront/src/modules/layout/components/promo-bar/promo-bar-content.tsx +174 -0
  221. package/templates/storefront/src/modules/layout/components/push-notification-manager/index.tsx +191 -0
  222. package/templates/storefront/src/modules/layout/components/search-panel/index.tsx +136 -0
  223. package/templates/storefront/src/modules/layout/components/side-menu/index.tsx +144 -0
  224. package/templates/storefront/src/modules/layout/components/verification-banner/index.tsx +217 -0
  225. package/templates/storefront/src/modules/layout/components/wishlist-counter/index.tsx +17 -0
  226. package/templates/storefront/src/modules/layout/templates/footer/index.tsx +7 -0
  227. package/templates/storefront/src/modules/layout/templates/nav/index.tsx +14 -0
  228. package/templates/storefront/src/modules/order/components/cancel-order-modal/index.tsx +168 -0
  229. package/templates/storefront/src/modules/order/components/help/index.tsx +25 -0
  230. package/templates/storefront/src/modules/order/components/item/index.tsx +62 -0
  231. package/templates/storefront/src/modules/order/components/items/index.tsx +44 -0
  232. package/templates/storefront/src/modules/order/components/onboarding-cta/index.tsx +28 -0
  233. package/templates/storefront/src/modules/order/components/order-confirmation-back-handler/index.tsx +28 -0
  234. package/templates/storefront/src/modules/order/components/order-details/index.tsx +63 -0
  235. package/templates/storefront/src/modules/order/components/order-purchase-tracker/index.tsx +48 -0
  236. package/templates/storefront/src/modules/order/components/order-redesign/index.tsx +887 -0
  237. package/templates/storefront/src/modules/order/components/order-summary/index.tsx +60 -0
  238. package/templates/storefront/src/modules/order/components/payment-details/index.tsx +63 -0
  239. package/templates/storefront/src/modules/order/components/shipping-details/index.tsx +73 -0
  240. package/templates/storefront/src/modules/order/components/transfer-actions/index.tsx +81 -0
  241. package/templates/storefront/src/modules/order/components/transfer-image/index.tsx +275 -0
  242. package/templates/storefront/src/modules/order/templates/order-completed-template.tsx +233 -0
  243. package/templates/storefront/src/modules/order/templates/order-details-template.tsx +128 -0
  244. package/templates/storefront/src/modules/products/components/image-gallery/index.tsx +297 -0
  245. package/templates/storefront/src/modules/products/components/product-actions/index.tsx +1400 -0
  246. package/templates/storefront/src/modules/products/components/product-actions/mobile-actions.tsx +217 -0
  247. package/templates/storefront/src/modules/products/components/product-actions/option-select.tsx +62 -0
  248. package/templates/storefront/src/modules/products/components/product-onboarding-cta/index.tsx +30 -0
  249. package/templates/storefront/src/modules/products/components/product-preview/index.tsx +5 -0
  250. package/templates/storefront/src/modules/products/components/product-preview/price.tsx +29 -0
  251. package/templates/storefront/src/modules/products/components/product-price/index.tsx +58 -0
  252. package/templates/storefront/src/modules/products/components/product-rating/index.tsx +1 -0
  253. package/templates/storefront/src/modules/products/components/product-tabs/accordion.tsx +100 -0
  254. package/templates/storefront/src/modules/products/components/product-tabs/index.tsx +127 -0
  255. package/templates/storefront/src/modules/products/components/product-tabs/ratings-tab.tsx +598 -0
  256. package/templates/storefront/src/modules/products/components/product-view-tracker/index.tsx +53 -0
  257. package/templates/storefront/src/modules/products/components/related-products/index.tsx +152 -0
  258. package/templates/storefront/src/modules/products/components/review-modal/index.tsx +1 -0
  259. package/templates/storefront/src/modules/products/components/share-button/index.tsx +1 -0
  260. package/templates/storefront/src/modules/products/components/thumbnail/index.tsx +91 -0
  261. package/templates/storefront/src/modules/products/components/wishlist-icon/index.tsx +1 -0
  262. package/templates/storefront/src/modules/products/context/product-context.tsx +52 -0
  263. package/templates/storefront/src/modules/products/templates/index.tsx +26 -0
  264. package/templates/storefront/src/modules/products/templates/product-actions-wrapper/index.tsx +1 -0
  265. package/templates/storefront/src/modules/products/templates/product-info/index.tsx +2 -0
  266. package/templates/storefront/src/modules/shipping/components/free-shipping-price-nudge/index.tsx +283 -0
  267. package/templates/storefront/src/modules/skeletons/components/skeleton-button/index.tsx +5 -0
  268. package/templates/storefront/src/modules/skeletons/components/skeleton-card-details/index.tsx +10 -0
  269. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-item/index.tsx +35 -0
  270. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-totals/index.tsx +30 -0
  271. package/templates/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx +13 -0
  272. package/templates/storefront/src/modules/skeletons/components/skeleton-line-item/index.tsx +35 -0
  273. package/templates/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx +14 -0
  274. package/templates/storefront/src/modules/skeletons/components/skeleton-order-information/index.tsx +36 -0
  275. package/templates/storefront/src/modules/skeletons/components/skeleton-order-items/index.tsx +43 -0
  276. package/templates/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx +15 -0
  277. package/templates/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx +15 -0
  278. package/templates/storefront/src/modules/skeletons/templates/skeleton-cart-page/index.tsx +65 -0
  279. package/templates/storefront/src/modules/skeletons/templates/skeleton-order-confirmed/index.tsx +21 -0
  280. package/templates/storefront/src/modules/skeletons/templates/skeleton-product-grid/index.tsx +23 -0
  281. package/templates/storefront/src/modules/skeletons/templates/skeleton-related-products/index.tsx +25 -0
  282. package/templates/storefront/src/modules/store/components/client-paginated-products.tsx +108 -0
  283. package/templates/storefront/src/modules/store/components/mobile-filters/index.tsx +135 -0
  284. package/templates/storefront/src/modules/store/components/pagination/index.tsx +118 -0
  285. package/templates/storefront/src/modules/store/components/product-list-view-tracker/index.tsx +43 -0
  286. package/templates/storefront/src/modules/store/components/refinement-list/index.tsx +299 -0
  287. package/templates/storefront/src/modules/store/components/refinement-list/sort-products/index.tsx +120 -0
  288. package/templates/storefront/src/modules/store/components/store-header/index.tsx +67 -0
  289. package/templates/storefront/src/modules/store/templates/index.tsx +1 -0
  290. package/templates/storefront/src/modules/store/templates/paginated-products.tsx +175 -0
  291. package/templates/storefront/src/modules/wishlist/components/wishlist-item/index.tsx +797 -0
  292. package/templates/storefront/src/modules/wishlist/templates/index.tsx +176 -0
  293. package/templates/storefront/src/storefront.config.ts +12 -0
  294. package/templates/storefront/src/styles/globals.css +326 -0
  295. package/templates/storefront/src/theme/valero/blocks/home/Features/index.tsx +61 -0
  296. package/templates/storefront/src/theme/valero/blocks/home/Hero/index.tsx +102 -0
  297. package/templates/storefront/src/theme/valero/blocks/home/LovedByMoms/index.tsx +407 -0
  298. package/templates/storefront/src/theme/valero/blocks/home/NewArrivals/index.tsx +48 -0
  299. package/templates/storefront/src/theme/valero/blocks/home/ShopByAge/index.tsx +128 -0
  300. package/templates/storefront/src/theme/valero/blocks/home/ShopByCategory/index.tsx +409 -0
  301. package/templates/storefront/src/theme/valero/blocks/home/Testimonials/index.tsx +697 -0
  302. package/templates/storefront/src/theme/valero/blocks/home/WhyChooseUs/index.tsx +62 -0
  303. package/templates/storefront/src/theme/valero/layouts/MainLayoutShell.tsx +14 -0
  304. package/templates/storefront/src/theme/valero/primitives/Button.tsx +28 -0
  305. package/templates/storefront/src/theme/valero/primitives/Card.tsx +32 -0
  306. package/templates/storefront/src/theme/valero/primitives/index.ts +2 -0
  307. package/templates/storefront/src/theme/valero/slots/account/ForgotPassword/index.tsx +1 -0
  308. package/templates/storefront/src/theme/valero/slots/account/Login/index.tsx +1 -0
  309. package/templates/storefront/src/theme/valero/slots/account/LoginTemplate/index.tsx +44 -0
  310. package/templates/storefront/src/theme/valero/slots/account/Register/index.tsx +1 -0
  311. package/templates/storefront/src/theme/valero/slots/cart/CartItem/index.tsx +11 -0
  312. package/templates/storefront/src/theme/valero/slots/cart/CartSummary/index.tsx +13 -0
  313. package/templates/storefront/src/theme/valero/slots/checkout/CheckoutForm/index.tsx +1 -0
  314. package/templates/storefront/src/theme/valero/slots/checkout/CheckoutSummary/index.tsx +1 -0
  315. package/templates/storefront/src/theme/valero/slots/layout/Footer/index.tsx +104 -0
  316. package/templates/storefront/src/theme/valero/slots/layout/Nav/index.tsx +97 -0
  317. package/templates/storefront/src/theme/valero/slots/layout/PromoBar/index.tsx +19 -0
  318. package/templates/storefront/src/theme/valero/slots/layout/PromoBar/promo-bar-content.tsx +174 -0
  319. package/templates/storefront/src/theme/valero/slots/order/OrderDetails/index.tsx +12 -0
  320. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductCTASection.tsx +191 -0
  321. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductDetailsSection.tsx +137 -0
  322. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductFeaturePanel.tsx +245 -0
  323. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductHighlightsSection.tsx +98 -0
  324. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductOptionsSection.tsx +233 -0
  325. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
  326. package/templates/storefront/src/theme/valero/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
  327. package/templates/storefront/src/theme/valero/slots/product/ProductActions/index.tsx +161 -0
  328. package/templates/storefront/src/theme/valero/slots/product/ProductCard/index.tsx +132 -0
  329. package/templates/storefront/src/theme/valero/slots/product/ProductInfo/index.tsx +40 -0
  330. package/templates/storefront/src/theme/valero/templates/StorePage/index.tsx +154 -0
  331. package/templates/storefront/src/theme/valero/tokens/colors.js +16 -0
  332. package/templates/storefront/src/theme/valero/tokens/colors.ts +21 -0
  333. package/templates/storefront/src/theme/valero/tokens/fonts.ts +13 -0
  334. package/templates/storefront/src/theme/valero/tokens/index.ts +3 -0
  335. package/templates/storefront/src/theme/valero/tokens/spacing.ts +9 -0
  336. package/templates/storefront/src/theme/valero/tokens/theme.css +91 -0
  337. package/templates/storefront/tailwind.config.js +221 -0
  338. package/templates/storefront/tsconfig.json +30 -0
@@ -0,0 +1,797 @@
1
+ "use client"
2
+
3
+ import { useState, useEffect, useRef, useMemo } from "react"
4
+ import Image from "next/image"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
7
+ import { getProductPrice, getPricesForVariant } from "@core/util/get-product-price"
8
+ import { addToCart } from "@core/data/cart"
9
+ import { trackAddToCart } from "@core/analytics/ga4-ecommerce"
10
+ import { useParams, useRouter } from "next/navigation"
11
+ import { isEqual } from "lodash"
12
+ import { clx } from "@medusajs/ui"
13
+ import { addToWishlist, removeFromWishlist } from "@core/data/wishlist"
14
+ import LoginPopup from "@modules/common/components/login-popup"
15
+ import Modal from "@modules/common/components/modal"
16
+
17
+ // Sub-component for Quick Add content to avoid duplication
18
+ const QuickAddContent = ({
19
+ product,
20
+ selectedOptions,
21
+ setOptionValue,
22
+ handleAddToCart,
23
+ isAdding,
24
+ showError,
25
+ onClose,
26
+ isMobile,
27
+ selectedVariantPrice,
28
+ quantity,
29
+ setQuantity,
30
+ cart,
31
+ }: {
32
+ product: HttpTypes.StoreProduct,
33
+ selectedOptions: Record<string, string>,
34
+ setOptionValue: (o: string, v: string) => void,
35
+ handleAddToCart: (e?: React.MouseEvent) => void,
36
+ isAdding: boolean,
37
+ showError: boolean,
38
+ onClose: () => void,
39
+ isMobile: boolean,
40
+ selectedVariantPrice?: {
41
+ calculated_price: string
42
+ original_price: string
43
+ percentage_diff: string
44
+ } | null,
45
+ quantity: number,
46
+ setQuantity: (q: number) => void,
47
+ cart: HttpTypes.StoreCart | null,
48
+ }) => {
49
+ // Find selected variant for inventory check
50
+ const selectedVariant = useMemo(() => {
51
+ if (!product.variants || Object.keys(selectedOptions).length === 0) return null
52
+ return product.variants.find((v) => {
53
+ const vOptions = v.options?.reduce((acc: Record<string, string>, o: any) => {
54
+ acc[o.option_id] = o.value
55
+ return acc
56
+ }, {})
57
+ return isEqual(vOptions, selectedOptions)
58
+ })
59
+ }, [selectedOptions, product.variants])
60
+
61
+ const inStock = useMemo(() => {
62
+ if (!selectedVariant) return false
63
+ if (!selectedVariant.manage_inventory) return true
64
+ if (selectedVariant.allow_backorder) return true
65
+
66
+ // Subtract what's already in the cart
67
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant.id)?.quantity || 0
68
+ const availableStock = (selectedVariant.inventory_quantity || 0) - inCartQuantity
69
+
70
+ return availableStock > 0
71
+ }, [selectedVariant, cart])
72
+
73
+ // Auto-adjust quantity if it exceeds available stock for the selected variant
74
+ useEffect(() => {
75
+ if (selectedVariant && selectedVariant.manage_inventory && !selectedVariant.allow_backorder) {
76
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant.id)?.quantity || 0
77
+ const stock = (selectedVariant.inventory_quantity || 0) - inCartQuantity
78
+
79
+ if (quantity > stock && stock > 0) {
80
+ setQuantity(stock)
81
+ } else if (stock <= 0) {
82
+ setQuantity(1)
83
+ }
84
+ }
85
+ }, [selectedVariant, quantity, setQuantity, cart])
86
+
87
+ return (
88
+ <>
89
+ <div className="flex items-center justify-between mb-2">
90
+ <h4 className="text-[11px] font-black text-gray-500 uppercase tracking-[0.1em]">Configure Product</h4>
91
+ <button
92
+ onClick={onClose}
93
+ className="p-1 hover:bg-gray-100 rounded-full transition-colors text-gray-400 hover:text-gray-600"
94
+ >
95
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
96
+ <line x1="18" y1="6" x2="6" y2="18"></line>
97
+ <line x1="6" y1="6" x2="18" y2="18"></line>
98
+ </svg>
99
+ </button>
100
+ </div>
101
+
102
+ {/* Selected Variant Price Row with Quantity Selector or Out of Stock Badge */}
103
+ <div className="mb-4 flex items-center justify-between gap-4">
104
+ {selectedVariantPrice ? (
105
+ <div className="flex items-center gap-3">
106
+ <div className="flex flex-col">
107
+ <div className="flex items-baseline gap-2">
108
+ <span className="text-[18px] sm:text-[20px] font-black" style={{ color: '#8B5AB1' }}>
109
+ {selectedVariantPrice.calculated_price}
110
+ </span>
111
+ {selectedVariantPrice.calculated_price !== selectedVariantPrice.original_price && (
112
+ <span className="text-[12px] sm:text-[13px] text-gray-500 line-through font-semibold">
113
+ {selectedVariantPrice.original_price}
114
+ </span>
115
+ )}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ ) : (
120
+ <div className="h-7" /> // Spacer
121
+ )}
122
+
123
+ {selectedVariant && !inStock ? (
124
+ <span className="text-[10px] font-bold bg-red-50 text-red-500 px-3 py-1.5 rounded-full border border-red-100 uppercase tracking-wider shrink-0 shadow-sm">
125
+ Out of Stock
126
+ </span>
127
+ ) : (
128
+ /* Quantity Selection Area */
129
+ <div className="flex flex-col items-end gap-1">
130
+ <div className={`flex items-center bg-gray-50/80 rounded-full p-1 border transition-colors shadow-sm shrink-0 ${(() => {
131
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant?.id)?.quantity || 0
132
+ const maxQuantity = !selectedVariant?.manage_inventory || selectedVariant?.allow_backorder ? 100 : ((selectedVariant?.inventory_quantity || 0) - inCartQuantity)
133
+ return quantity >= maxQuantity ? "border-orange-200" : "border-[#8B5AB1]/30"
134
+ })()}`}>
135
+ <button
136
+ onClick={() => setQuantity(Math.max(1, quantity - 1))}
137
+ disabled={quantity <= 1}
138
+ className="w-7 h-7 flex items-center justify-center hover:bg-white rounded-full text-gray-500 transition-all active:scale-90 hover:shadow-sm disabled:opacity-30 disabled:hover:bg-transparent"
139
+ aria-label="Decrease quantity"
140
+ >
141
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round">
142
+ <line x1="5" y1="12" x2="19" y2="12"></line>
143
+ </svg>
144
+ </button>
145
+ <div className="min-w-[28px] px-1 text-center text-[12px] font-black text-[#8B5AB1]">
146
+ {quantity}
147
+ </div>
148
+ <button
149
+ onClick={() => {
150
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant?.id)?.quantity || 0
151
+ const maxQuantity = !selectedVariant?.manage_inventory || selectedVariant?.allow_backorder
152
+ ? 100
153
+ : ((selectedVariant?.inventory_quantity || 0) - inCartQuantity)
154
+ if (quantity < maxQuantity) {
155
+ setQuantity(quantity + 1)
156
+ }
157
+ }}
158
+ disabled={(() => {
159
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant?.id)?.quantity || 0
160
+ const maxQuantity = !selectedVariant?.manage_inventory || selectedVariant?.allow_backorder
161
+ ? 100
162
+ : ((selectedVariant?.inventory_quantity || 0) - inCartQuantity)
163
+ return quantity >= maxQuantity
164
+ })()}
165
+ className="w-7 h-7 flex items-center justify-center hover:bg-white rounded-full text-gray-500 transition-all active:scale-90 hover:shadow-sm disabled:opacity-30 disabled:hover:bg-transparent"
166
+ aria-label="Increase quantity"
167
+ >
168
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round">
169
+ <line x1="12" y1="5" x2="12" y2="19"></line>
170
+ <line x1="5" y1="12" x2="19" y2="12"></line>
171
+ </svg>
172
+ </button>
173
+ </div>
174
+
175
+ {/* Limit Reached Legend */}
176
+ {(() => {
177
+ const inCartQuantity = cart?.items?.find(item => item.variant_id === selectedVariant?.id)?.quantity || 0
178
+ const maxQuantity = !selectedVariant?.manage_inventory || selectedVariant?.allow_backorder ? 100 : ((selectedVariant?.inventory_quantity || 0) - inCartQuantity)
179
+ if (quantity >= maxQuantity && maxQuantity < 100) {
180
+ return (
181
+ <span className="text-[9px] font-bold text-orange-500 animate-pulse px-1">
182
+ {maxQuantity <= 0 ? "Already in bag (Max reached)" : `Only ${maxQuantity} more available`}
183
+ </span>
184
+ )
185
+ }
186
+ return null
187
+ })()}
188
+ </div>
189
+ )}
190
+ </div>
191
+
192
+ <div className="flex flex-col gap-3">
193
+ <div className="max-h-[160px] overflow-y-auto pr-1 custom-scrollbar space-y-3">
194
+ {(product.options || []).slice().sort((a, b) => {
195
+ const getOrder = (title: string) => {
196
+ const t = title.toLowerCase()
197
+ if (t.includes('color') || t.includes('colour')) return 1
198
+ if (t.includes('size')) return 2
199
+ return 3
200
+ }
201
+ return getOrder(a.title || "") - getOrder(b.title || "")
202
+ }).map((option) => (
203
+ <div key={option.id} className="flex flex-col gap-1.5">
204
+ <span className="text-[10px] font-bold text-gray-400 uppercase tracking-widest px-1">{option.title}</span>
205
+ <div className="flex flex-wrap gap-1.5 px-0.5">
206
+ {option.values?.map((v: any) => {
207
+ const isSelected = selectedOptions[option.id] === v.value
208
+ return (
209
+ <button
210
+ key={v.id}
211
+ onClick={() => setOptionValue(option.id, v.value)}
212
+ className={clx(
213
+ "min-w-[42px] h-[28px] px-2 rounded-lg text-[11px] font-black transition-all border-2",
214
+ isSelected
215
+ ? "bg-[#8B5AB1] border-[#8B5AB1] text-white shadow-[0_4px_12px_rgba(139,90,177,0.3)]"
216
+ : "bg-white border-gray-100 text-gray-600 hover:border-[#8B5AB1]/30 hover:bg-gray-50"
217
+ )}
218
+ >
219
+ {v.value}
220
+ </button>
221
+ )
222
+ })}
223
+ </div>
224
+ </div>
225
+ ))}
226
+ </div>
227
+
228
+ <button
229
+ onClick={(e) => handleAddToCart(e)}
230
+ disabled={isAdding || (!isAdding && !showError && (!selectedVariant || !inStock))}
231
+ className={clx(
232
+ "w-full h-10 rounded-xl text-[11px] font-black uppercase tracking-[0.15em] transition-all shadow-lg active:scale-[0.97] flex items-center justify-center",
233
+ showError ? "bg-red-500 animate-shake text-white" : "bg-[#8B5AB1] hover:bg-[#7a4a9a] text-white",
234
+ // Disable style if not available or out of stock
235
+ (!isAdding && !showError && (!selectedVariant || !inStock)) ? "opacity-50 cursor-not-allowed bg-gray-400 hover:bg-gray-400 shadow-none" : ""
236
+ )}
237
+ >
238
+ {isAdding
239
+ ? (
240
+ <svg className="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
241
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
242
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
243
+ </svg>
244
+ )
245
+ : showError
246
+ ? "Select All Options"
247
+ : !selectedVariant
248
+ ? "Not Available"
249
+ : !inStock
250
+ ? "Out of Stock"
251
+ : `Move to Bag${quantity > 1 ? ` (${quantity})` : ''}`}
252
+ </button>
253
+ </div>
254
+
255
+ <style jsx>{`
256
+ @keyframes shake {
257
+ 0%, 100% { transform: translateX(0); }
258
+ 25% { transform: translateX(-4px); }
259
+ 75% { transform: translateX(4px); }
260
+ }
261
+ .animate-shake {
262
+ animation: shake 0.2s ease-in-out 0s 2;
263
+ }
264
+ .custom-scrollbar::-webkit-scrollbar {
265
+ width: 3px;
266
+ }
267
+ .custom-scrollbar::-webkit-scrollbar-track {
268
+ background: #f1f1f1;
269
+ border-radius: 10px;
270
+ }
271
+ .custom-scrollbar::-webkit-scrollbar-thumb {
272
+ background: #8B5AB1;
273
+ border-radius: 10px;
274
+ }
275
+ `}</style>
276
+ </>
277
+ )
278
+ }
279
+
280
+
281
+ interface WishlistItemProps {
282
+ product: HttpTypes.StoreProduct
283
+ countryCode: string
284
+ cart: HttpTypes.StoreCart | null
285
+ onRemove: () => void
286
+ }
287
+
288
+ const WishlistItem = ({ product, countryCode, cart, onRemove }: WishlistItemProps) => {
289
+ const [isAdding, setIsAdding] = useState(false)
290
+ const [successMessage, setSuccessMessage] = useState<string | null>(null)
291
+ const { countryCode: paramCountryCode } = useParams() as { countryCode: string }
292
+ const finalCountryCode = countryCode || paramCountryCode
293
+ const router = useRouter()
294
+
295
+ const [showQuickAdd, setShowQuickAdd] = useState(false)
296
+ const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})
297
+ const [quantity, setQuantity] = useState(1)
298
+ const [showError, setShowError] = useState(false)
299
+ const [isTogglingWishlist, setIsTogglingWishlist] = useState(false)
300
+ const [showAsModal, setShowAsModal] = useState(false)
301
+
302
+ // Detect when to use Modal vs in-card overlay
303
+ useEffect(() => {
304
+ const handleResize = () => {
305
+ const width = window.innerWidth
306
+ // Use Modal for mobile (< 500px) and the specific range where clipping happens (1023px-1100px)
307
+ setShowAsModal(width < 500 || (width >= 1023 && width <= 1100))
308
+ }
309
+ handleResize()
310
+ window.addEventListener("resize", handleResize)
311
+ return () => window.removeEventListener("resize", handleResize)
312
+ }, [])
313
+
314
+ const { cheapestPrice } = getProductPrice({
315
+ product,
316
+ })
317
+
318
+ // Compute selected variant price from already-loaded data (no API call)
319
+ const selectedVariantPrice = useMemo(() => {
320
+ if (!product.variants || Object.keys(selectedOptions).length === 0) return null
321
+ const matchedVariant = product.variants.find((v) => {
322
+ const vOptions = v.options?.reduce((acc: Record<string, string>, o: any) => {
323
+ acc[o.option_id] = o.value
324
+ return acc
325
+ }, {})
326
+ return isEqual(vOptions, selectedOptions)
327
+ })
328
+ if (!matchedVariant) return null
329
+ return getPricesForVariant(matchedVariant)
330
+ }, [selectedOptions, product.variants])
331
+
332
+ const productImage = product.thumbnail || product.images?.[0]?.url
333
+
334
+ // Logic to ensure only one Quick Add is open at a time
335
+ useEffect(() => {
336
+ const handleCloseOthers = (e: any) => {
337
+ if (e.detail !== product.id) {
338
+ setShowQuickAdd(false)
339
+ setSelectedOptions({})
340
+ }
341
+ }
342
+
343
+ const handleClickOutside = (e: MouseEvent) => {
344
+ if (showQuickAdd) {
345
+ const target = e.target as HTMLElement
346
+ if (!target.closest(`[data-product-id="${product.id}"]`)) {
347
+ setShowQuickAdd(false)
348
+ setSelectedOptions({})
349
+ setQuantity(1)
350
+ }
351
+ }
352
+ }
353
+
354
+ if (showQuickAdd) {
355
+ window.addEventListener("close-other-quick-adds", handleCloseOthers)
356
+ // Only close on click-outside if NOT in modal mode
357
+ if (!showAsModal) {
358
+ document.addEventListener("mousedown", handleClickOutside)
359
+ }
360
+ }
361
+
362
+ return () => {
363
+ window.removeEventListener("close-other-quick-adds", handleCloseOthers)
364
+ document.removeEventListener("mousedown", handleClickOutside)
365
+ }
366
+ }, [showQuickAdd, product.id])
367
+
368
+ // Auto-select first options when Quick Add is shown
369
+ useEffect(() => {
370
+ if (showQuickAdd && product.options) {
371
+ const partialOptions: Record<string, string> = { ...selectedOptions }
372
+ let hasChanges = false
373
+
374
+ product.options.forEach((option) => {
375
+ // If no value is selected for this option, pick the first one
376
+ if (!partialOptions[option.id]) {
377
+ const firstValue = option.values?.[0]?.value
378
+ if (firstValue) {
379
+ partialOptions[option.id] = firstValue
380
+ hasChanges = true
381
+ }
382
+ }
383
+ })
384
+
385
+ if (hasChanges) {
386
+ setSelectedOptions(partialOptions)
387
+ }
388
+ }
389
+ }, [showQuickAdd, product.options])
390
+
391
+ const handleMoveToCart = async (e?: React.MouseEvent) => {
392
+ if (e) {
393
+ e.preventDefault()
394
+ e.stopPropagation()
395
+ }
396
+
397
+ if (!product.variants || product.variants.length === 0) {
398
+ alert("This product has no variants available")
399
+ return
400
+ }
401
+
402
+ // Case 1: Only one variant exists - add it immediately if in stock
403
+ if (product.variants.length === 1) {
404
+ const firstVariant = product.variants[0]
405
+ if (!firstVariant.id) {
406
+ alert("Product variant is not available")
407
+ return
408
+ }
409
+
410
+ const inStock = !firstVariant.manage_inventory || firstVariant.allow_backorder || (firstVariant.inventory_quantity || 0) > 0
411
+
412
+ if (!inStock) {
413
+ // If out of stock, show the prompt so user sees the message
414
+ window.dispatchEvent(new CustomEvent("close-other-quick-adds", { detail: product.id }))
415
+ setShowQuickAdd(true)
416
+ return
417
+ }
418
+
419
+ setIsAdding(true)
420
+ setSuccessMessage(null)
421
+
422
+ try {
423
+ await addToCart({
424
+ variantId: firstVariant.id,
425
+ quantity: quantity,
426
+ countryCode: finalCountryCode,
427
+ })
428
+ const currency = (firstVariant as any).calculated_price?.currency_code?.toUpperCase() ?? "USD"
429
+ const unitPrice = (firstVariant as any).calculated_price?.calculated_amount ?? 0
430
+ trackAddToCart({
431
+ currency,
432
+ value: unitPrice / 100,
433
+ items: [
434
+ {
435
+ item_id: firstVariant.id,
436
+ item_name: product.title ?? "",
437
+ price: unitPrice / 100,
438
+ quantity: quantity,
439
+ item_variant: firstVariant.title ?? undefined,
440
+ },
441
+ ],
442
+ })
443
+ setSuccessMessage("Added to bag!")
444
+ router.refresh()
445
+ setTimeout(() => {
446
+ setSuccessMessage(null)
447
+ }, 2000)
448
+ } catch (error: any) {
449
+ const errorMessage = error?.message || "Failed to add product to cart. Please try again."
450
+ alert(errorMessage)
451
+ } finally {
452
+ setIsAdding(false)
453
+ }
454
+ return
455
+ }
456
+
457
+ // Case 2: Multiple variants - show prompt first if not open
458
+ if (!showQuickAdd) {
459
+ // Dispatch event to close other overlays
460
+ window.dispatchEvent(new CustomEvent("close-other-quick-adds", { detail: product.id }))
461
+ setShowQuickAdd(true)
462
+ // Defaults will be selected by the useEffect when showQuickAdd becomes true
463
+ return
464
+ }
465
+
466
+ // Case 3: Multiple variants and prompt is already open - verify selection and add
467
+ const selectedVariant = product.variants.find((v) => {
468
+ const vOptions = v.options?.reduce((acc: Record<string, string>, o: any) => {
469
+ acc[o.option_id] = o.value
470
+ return acc
471
+ }, {})
472
+ return isEqual(vOptions, selectedOptions)
473
+ })
474
+
475
+ if (selectedVariant) {
476
+ setIsAdding(true)
477
+ setSuccessMessage(null)
478
+ try {
479
+ await addToCart({
480
+ variantId: selectedVariant.id,
481
+ quantity: quantity,
482
+ countryCode: finalCountryCode,
483
+ })
484
+ const currency = (selectedVariant as any).calculated_price?.currency_code?.toUpperCase() ?? "USD"
485
+ const unitPrice = (selectedVariant as any).calculated_price?.calculated_amount ?? 0
486
+ trackAddToCart({
487
+ currency,
488
+ value: unitPrice / 100,
489
+ items: [
490
+ {
491
+ item_id: selectedVariant.id,
492
+ item_name: product.title ?? "",
493
+ price: unitPrice / 100,
494
+ quantity: quantity,
495
+ item_variant: selectedVariant.title ?? undefined,
496
+ },
497
+ ],
498
+ })
499
+ setShowQuickAdd(false)
500
+ setSelectedOptions({})
501
+ setQuantity(1)
502
+ setSuccessMessage("Added to bag!")
503
+ router.refresh()
504
+ setTimeout(() => {
505
+ setSuccessMessage(null)
506
+ }, 2000)
507
+ } catch (error: any) {
508
+ const errorMessage = error?.message || "Failed to add product to cart. Please try again."
509
+ alert(errorMessage)
510
+ } finally {
511
+ setIsAdding(false)
512
+ }
513
+ } else {
514
+ // Shaker effect or error if they try to add without selecting
515
+ setShowError(true)
516
+ setTimeout(() => setShowError(false), 2000)
517
+ }
518
+ }
519
+
520
+ const setOptionValue = (optionId: string, value: string) => {
521
+ setSelectedOptions((prev) => ({
522
+ ...prev,
523
+ [optionId]: value,
524
+ }))
525
+ setShowError(false)
526
+ }
527
+
528
+ return (
529
+ <>
530
+ <style dangerouslySetInnerHTML={{
531
+ __html: `
532
+ @media (min-width: 340px) and (max-width: 460px) {
533
+ .wishlist-card {
534
+ min-height: 240px !important;
535
+ height: auto !important;
536
+ }
537
+ .wishlist-image-container {
538
+ padding-top: 0.75rem !important;
539
+ padding-left: 0.75rem !important;
540
+ padding-right: 0.75rem !important;
541
+ }
542
+ .wishlist-info {
543
+ padding: 0.5rem 0.75rem 0.75rem 0.75rem !important;
544
+ }
545
+ .wishlist-title {
546
+ font-size: 0.875rem !important;
547
+ margin-bottom: 0.5rem !important;
548
+ }
549
+ .wishlist-price {
550
+ font-size: 0.9375rem !important;
551
+ margin-bottom: 0.75rem !important;
552
+ }
553
+ .wishlist-button {
554
+ padding: 0.5rem 1rem !important;
555
+ font-size: 0.875rem !important;
556
+ }
557
+ }
558
+ /* Custom shake animation */
559
+ @keyframes shake {
560
+ 0%, 100% { transform: translateX(0); }
561
+ 25% { transform: translateX(-5px); }
562
+ 75% { transform: translateX(5px); }
563
+ }
564
+ .animate-shake {
565
+ animation: shake 0.2s ease-in-out 0s 2;
566
+ }
567
+ `
568
+ }} />
569
+ <div
570
+ className={clx(
571
+ "bg-white rounded-[14px] sm:rounded-[16px] shadow-sm hover:shadow-md transition-shadow overflow-hidden relative group wishlist-card w-full max-w-[280px] sm:max-w-[290px]",
572
+ product.deleted_at && "grayscale-[0.5] opacity-80"
573
+ )}
574
+ style={{ height: 'auto', minHeight: 'auto' }}
575
+ data-product-id={product.id}
576
+ >
577
+ {/* Product Image */}
578
+ <div className="relative flex items-center justify-center pt-2 sm:pt-3 px-2 sm:px-3 wishlist-image-container">
579
+ <LocalizedClientLink href={`/products/${product.handle}`} className="block w-full">
580
+ <div
581
+ className="relative rounded-[8px] sm:rounded-[10px]"
582
+ style={{
583
+ width: '100%',
584
+ aspectRatio: '1',
585
+ maxWidth: '100%',
586
+ margin: '0 auto',
587
+ overflow: 'hidden'
588
+ }}
589
+ >
590
+ {productImage ? (
591
+ <Image
592
+ src={productImage}
593
+ alt={product.title || "Product"}
594
+ width={280}
595
+ height={280}
596
+ className="object-cover w-full h-full"
597
+ />
598
+ ) : (
599
+ <div className="w-full h-full bg-gray-100 flex items-center justify-center">
600
+ <span className="text-gray-400 text-xs sm:text-sm">No Image</span>
601
+ </div>
602
+ )}
603
+ {(() => {
604
+ const percentage = cheapestPrice?.percentage_diff
605
+ if (percentage && parseInt(percentage) > 0) {
606
+ return (
607
+ <div className="absolute top-2 left-2 z-10 animate-in fade-in duration-300 transform-gpu">
608
+ <span className="bg-[#FE5FB7] text-white text-[10px] sm:text-[11px] font-bold px-2 py-0.5 rounded-full shadow-sm whitespace-nowrap">
609
+ {percentage}% OFF
610
+ </span>
611
+ </div>
612
+ )
613
+ }
614
+ return null
615
+ })()}
616
+ </div>
617
+ </LocalizedClientLink>
618
+
619
+ {/* Remove Button (X) - Top Right */}
620
+ <button
621
+ onClick={(e) => {
622
+ e.preventDefault()
623
+ e.stopPropagation()
624
+ onRemove()
625
+ }}
626
+ className="absolute top-2 right-2 sm:top-3 sm:right-3 md:top-4 md:right-4 w-6 h-6 sm:w-7 sm:h-7 bg-white rounded-full flex items-center justify-center shadow-sm hover:shadow-md hover:bg-gray-50 transition-all z-10"
627
+ aria-label="Remove from wishlist"
628
+ >
629
+ <svg
630
+ width="14"
631
+ height="14"
632
+ className="sm:w-4 sm:h-4"
633
+ viewBox="0 0 24 24"
634
+ fill="none"
635
+ stroke="currentColor"
636
+ strokeWidth="2.5"
637
+ strokeLinecap="round"
638
+ strokeLinejoin="round"
639
+ style={{ color: '#000' }}
640
+ >
641
+ <line x1="18" y1="6" x2="6" y2="18"></line>
642
+ <line x1="6" y1="6" x2="18" y2="18"></line>
643
+ </svg>
644
+ </button>
645
+ </div>
646
+
647
+ {/* Product Info */}
648
+ <div className="px-2 sm:px-3 pb-2 sm:pb-3 pt-2 md:p-3 wishlist-info">
649
+ <LocalizedClientLink href={`/products/${product.handle}`}>
650
+ <h3 className="text-xs sm:text-sm font-semibold text-gray-900 mb-1 sm:mb-1.5 truncate hover:text-purple-600 transition-colors wishlist-title">
651
+ {product.title}
652
+ </h3>
653
+ </LocalizedClientLink>
654
+
655
+ {cheapestPrice && (
656
+ <div className="flex items-baseline gap-1.5 overflow-hidden mb-2 sm:mb-2.5 wishlist-price">
657
+ <p className="text-sm sm:text-base font-semibold whitespace-nowrap" style={{ color: '#8B5AB1' }}>
658
+ {cheapestPrice.calculated_price}
659
+ </p>
660
+ {cheapestPrice.calculated_price !== cheapestPrice.original_price && (
661
+ <p className="text-[11px] sm:text-[12px] text-gray-500 line-through whitespace-nowrap font-semibold">
662
+ {cheapestPrice.original_price}
663
+ </p>
664
+ )}
665
+ </div>
666
+ )}
667
+
668
+ {/* Move to Cart or No longer available Button */}
669
+ {product.deleted_at ? (
670
+ <button
671
+ disabled
672
+ className="w-full py-2 sm:py-2.5 md:py-3 px-4 sm:px-5 md:px-6 rounded-[10px] sm:rounded-full text-sm sm:text-base font-semibold text-white bg-gray-400 cursor-not-allowed opacity-75 relative wishlist-button"
673
+ >
674
+ <span className="text-xs sm:text-sm md:text-base">No longer available</span>
675
+ </button>
676
+ ) : (
677
+ <button
678
+ onClick={handleMoveToCart}
679
+ disabled={isAdding || !!successMessage}
680
+ className="w-full py-2 sm:py-2.5 md:py-3 px-4 sm:px-5 md:px-6 rounded-[10px] sm:rounded-full text-sm sm:text-base font-semibold text-white transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-md relative wishlist-button"
681
+ style={{ backgroundColor: successMessage ? '#10b981' : '#8B5AB1' }}
682
+ data-ga-event="wishlist_move_to_bag_click"
683
+ data-ga-label={product.title || "Product"}
684
+ onMouseEnter={(e) => {
685
+ if (!isAdding && !successMessage) {
686
+ e.currentTarget.style.backgroundColor = '#7a4a9a'
687
+ e.currentTarget.style.transform = 'translateY(-1px)'
688
+ }
689
+ }}
690
+ onMouseLeave={(e) => {
691
+ if (!isAdding && !successMessage) {
692
+ e.currentTarget.style.backgroundColor = '#8B5AB1'
693
+ e.currentTarget.style.transform = 'translateY(0)'
694
+ }
695
+ }}
696
+ >
697
+ <span className="pointer-events-none flex items-center justify-center">
698
+ {isAdding ? (
699
+ <span className="flex items-center justify-center gap-2">
700
+ <svg
701
+ className="animate-spin h-3 w-3 sm:h-4 sm:w-4"
702
+ xmlns="http://www.w3.org/2000/svg"
703
+ fill="none"
704
+ viewBox="0 0 24 24"
705
+ >
706
+ <circle
707
+ className="opacity-25"
708
+ cx="12"
709
+ cy="12"
710
+ r="10"
711
+ stroke="currentColor"
712
+ strokeWidth="4"
713
+ ></circle>
714
+ <path
715
+ className="opacity-75"
716
+ fill="currentColor"
717
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
718
+ ></path>
719
+ </svg>
720
+ <span className="text-xs sm:text-sm">Adding...</span>
721
+ </span>
722
+ ) : successMessage ? (
723
+ <span className="flex items-center justify-center gap-2">
724
+ <svg
725
+ className="w-3 h-3 sm:w-4 sm:h-4"
726
+ fill="none"
727
+ stroke="currentColor"
728
+ viewBox="0 0 24 24"
729
+ >
730
+ <path
731
+ strokeLinecap="round"
732
+ strokeLinejoin="round"
733
+ strokeWidth={2}
734
+ d="M5 13l4 4L19 7"
735
+ />
736
+ </svg>
737
+ <span className="text-xs sm:text-sm">{successMessage}</span>
738
+ </span>
739
+ ) : (
740
+ <span className="text-xs sm:text-sm md:text-base">Move to Bag</span>
741
+ )}
742
+ </span>
743
+ </button>
744
+ )}
745
+ </div>
746
+
747
+ {/* Quick Add Overlay/Modal */}
748
+ {showQuickAdd && (
749
+ showAsModal ? (
750
+ <Modal isOpen={showQuickAdd} close={() => { setShowQuickAdd(false); setSelectedOptions({}); }} size="xsmall">
751
+ <Modal.Body>
752
+ <div className="py-2 text-left">
753
+ <QuickAddContent
754
+ product={product}
755
+ selectedOptions={selectedOptions}
756
+ setOptionValue={setOptionValue}
757
+ handleAddToCart={handleMoveToCart}
758
+ isAdding={isAdding}
759
+ showError={showError}
760
+ onClose={() => { setShowQuickAdd(false); setSelectedOptions({}); setQuantity(1); }}
761
+ isMobile={true}
762
+ selectedVariantPrice={selectedVariantPrice}
763
+ quantity={quantity}
764
+ setQuantity={setQuantity}
765
+ cart={cart}
766
+ />
767
+ </div>
768
+ </Modal.Body>
769
+ </Modal>
770
+ ) : (
771
+ <div
772
+ className="absolute inset-x-0 bottom-0 bg-white/95 backdrop-blur-md z-[25] border-t border-gray-100 p-4 transition-all duration-300 animate-in slide-in-from-bottom shadow-2xl rounded-t-[20px] text-left"
773
+ onClick={(e) => e.stopPropagation()}
774
+ >
775
+ <QuickAddContent
776
+ product={product}
777
+ selectedOptions={selectedOptions}
778
+ setOptionValue={setOptionValue}
779
+ handleAddToCart={handleMoveToCart}
780
+ isAdding={isAdding}
781
+ showError={showError}
782
+ onClose={() => { setShowQuickAdd(false); setSelectedOptions({}); setQuantity(1); }}
783
+ isMobile={false}
784
+ selectedVariantPrice={selectedVariantPrice}
785
+ quantity={quantity}
786
+ setQuantity={setQuantity}
787
+ cart={cart}
788
+ />
789
+ </div>
790
+ )
791
+ )}
792
+ </div>
793
+ </>
794
+ )
795
+ }
796
+
797
+ export default WishlistItem