@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,476 @@
1
+ "use client"
2
+
3
+ import { useState, useMemo, useEffect } from "react"
4
+ import { HttpTypes } from "@medusajs/types"
5
+ import { updateLineItem, deleteLineItem, addToCart, retrieveCart } from "@core/data/cart"
6
+ import { getProductByHandle } from "@core/data/products"
7
+ import { trackAddToCart, trackRemoveFromCart } from "@core/analytics/ga4-ecommerce"
8
+ import { useParams } from "next/navigation"
9
+ import { isEqual } from "lodash"
10
+ import Thumbnail from "@modules/products/components/thumbnail"
11
+ import DeleteButton from "@modules/common/components/delete-button"
12
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
13
+ import Spinner from "@modules/common/icons/spinner"
14
+ import ErrorMessage from "@modules/checkout/components/error-message"
15
+ import { getProductPrice } from "@core/util/get-product-price"
16
+ import { convertToLocale } from "@core/util/money"
17
+
18
+ type CartItemCardProps = {
19
+ item: HttpTypes.StoreCartLineItem
20
+ currencyCode: string
21
+ }
22
+
23
+ const optionsAsKeymap = (
24
+ variantOptions: HttpTypes.StoreProductVariant["options"]
25
+ ) => {
26
+ return variantOptions?.reduce((acc: Record<string, string>, varopt: any) => {
27
+ acc[varopt.option_id] = varopt.value
28
+ return acc
29
+ }, {})
30
+ }
31
+
32
+ const CartItemCard = ({ item, currencyCode }: CartItemCardProps) => {
33
+ const [updating, setUpdating] = useState(false)
34
+ const [error, setError] = useState<string | null>(null)
35
+ const [options, setOptions] = useState<Record<string, string | undefined>>({})
36
+ const [freshVariant, setFreshVariant] = useState<any>(null)
37
+ const countryCode = useParams().countryCode as string
38
+
39
+ // Get product from cart item - it should be available since we updated cart retrieval
40
+ // Check both item.product and item.variant?.product
41
+ const product = (item as any).product || item.variant?.product
42
+
43
+ // Initialize options from current variant
44
+ useEffect(() => {
45
+ if (item.variant?.options) {
46
+ const variantOptions = optionsAsKeymap(item.variant.options)
47
+ setOptions(variantOptions ?? {})
48
+ }
49
+ }, [item.variant?.options])
50
+
51
+ // Fetch fresh variant data to get accurate inventory
52
+ useEffect(() => {
53
+ const fetchFreshData = async () => {
54
+ if (item.product_handle) {
55
+ const productData = await getProductByHandle(item.product_handle)
56
+ if (productData?.variants) {
57
+ const v = productData.variants.find((v: any) => v.id === item.variant_id)
58
+ if (v) {
59
+ setFreshVariant(v)
60
+ }
61
+ }
62
+ }
63
+ }
64
+ fetchFreshData()
65
+ }, [item.product_handle, item.variant_id])
66
+
67
+ // Find selected variant based on options
68
+ const selectedVariant = useMemo(() => {
69
+ if (!product?.variants || product.variants.length === 0) {
70
+ return item.variant
71
+ }
72
+
73
+ return product.variants.find((v: HttpTypes.StoreProductVariant) => {
74
+ const variantOptions = optionsAsKeymap(v.options)
75
+ return isEqual(variantOptions, options)
76
+ })
77
+ }, [product?.variants, options, item.variant])
78
+
79
+ // Get available option values
80
+ const getOptionValues = (optionId: string) => {
81
+ if (!product?.variants) return []
82
+
83
+ const values = new Set<string>()
84
+ product.variants.forEach((variant: HttpTypes.StoreProductVariant) => {
85
+ variant.options?.forEach((opt: any) => {
86
+ if (opt.option_id === optionId) {
87
+ values.add(opt.value)
88
+ }
89
+ })
90
+ })
91
+ return Array.from(values)
92
+ }
93
+
94
+ // Get product options
95
+ const productOptions = (product?.options || []) as HttpTypes.StoreProductOption[]
96
+
97
+ const changeQuantity = async (quantity: number) => {
98
+ setError(null)
99
+ setUpdating(true)
100
+
101
+ await updateLineItem({
102
+ lineId: item.id,
103
+ quantity,
104
+ })
105
+ .catch((err) => {
106
+ setError(err.message)
107
+ })
108
+ .finally(() => {
109
+ setUpdating(false)
110
+ })
111
+ }
112
+
113
+ const handleOptionChange = async (optionId: string, value: string) => {
114
+ const newOptions = {
115
+ ...options,
116
+ [optionId]: value,
117
+ }
118
+ setOptions(newOptions)
119
+
120
+ // Find variant with new options
121
+ const newVariant = product?.variants?.find((v: HttpTypes.StoreProductVariant) => {
122
+ const variantOptions = optionsAsKeymap(v.options)
123
+ return isEqual(variantOptions, newOptions)
124
+ })
125
+
126
+ if (newVariant && newVariant.id !== item.variant_id) {
127
+ setError(null)
128
+ setUpdating(true)
129
+
130
+ try {
131
+ const unitTotal = item.total ? item.total / item.quantity : 0
132
+ trackRemoveFromCart({
133
+ currency: currencyCode,
134
+ value: (item.total ?? 0) / 100,
135
+ items: [
136
+ {
137
+ item_id: item.variant_id ?? item.id,
138
+ item_name: item.title ?? "",
139
+ price: unitTotal / 100,
140
+ quantity: item.quantity,
141
+ },
142
+ ],
143
+ })
144
+ await deleteLineItem(item.id)
145
+
146
+ await addToCart({
147
+ variantId: newVariant.id,
148
+ quantity: item.quantity,
149
+ countryCode,
150
+ })
151
+ const newUnitPrice = (newVariant as any).calculated_price?.calculated_amount ?? 0
152
+ trackAddToCart({
153
+ currency: currencyCode,
154
+ value: (newUnitPrice * item.quantity) / 100,
155
+ items: [
156
+ {
157
+ item_id: newVariant.id,
158
+ item_name: product?.title ?? item.title ?? "",
159
+ price: newUnitPrice / 100,
160
+ quantity: item.quantity,
161
+ item_variant: newVariant.title ?? undefined,
162
+ },
163
+ ],
164
+ })
165
+ } catch (err: any) {
166
+ setError(err.message)
167
+ // Revert options on error
168
+ const currentOptions = item.variant?.options
169
+ setOptions(currentOptions ? (optionsAsKeymap(currentOptions) ?? {}) : {})
170
+ } finally {
171
+ setUpdating(false)
172
+ }
173
+ }
174
+ }
175
+
176
+ const maxQuantity = useMemo(() => {
177
+ const variant = freshVariant || selectedVariant || item.variant
178
+ if (!variant) return 10
179
+
180
+ // If inventory management is off or backorders are allowed, allow a high quantity
181
+ if (variant.manage_inventory === false || (variant as any).allow_backorder) {
182
+ return 100
183
+ }
184
+
185
+ // Otherwise use actual inventory quantity if available
186
+ const stock = (variant as any).inventory_quantity
187
+ if (stock === undefined || stock === null) {
188
+ return 100 // Fallback if still unknown
189
+ }
190
+
191
+ return stock
192
+ }, [freshVariant, selectedVariant, item.variant])
193
+
194
+ const isAtMax = item.quantity >= maxQuantity
195
+
196
+ // Get price information from cart item
197
+ // item.total is the total for the line item (quantity * unit_price)
198
+ // So we divide by quantity to get unit price
199
+ const unitTotal = item.total ? item.total / item.quantity : 0
200
+ const unitOriginalTotal = item.original_total ? item.original_total / item.quantity : 0
201
+
202
+ // Use variant price if available, otherwise use item total
203
+ const displayVariant = selectedVariant || item.variant
204
+ const variantPrice = displayVariant?.calculated_price?.calculated_amount
205
+ const variantOriginalPrice = displayVariant?.calculated_price?.original_amount
206
+
207
+ // Prefer variant price, fallback to item total
208
+ // Note: variant prices are already in smallest currency unit (e.g., paise for INR)
209
+ // item.total is also in smallest currency unit
210
+ const currentPriceAmount = variantPrice || unitTotal
211
+ const originalPriceAmount = variantOriginalPrice || unitOriginalTotal
212
+
213
+ const discountPercentage = originalPriceAmount && currentPriceAmount && originalPriceAmount > currentPriceAmount
214
+ ? Math.round(((originalPriceAmount - currentPriceAmount) / originalPriceAmount) * 100)
215
+ : 0
216
+
217
+ return (
218
+ <div className="bg-surface border border-gray-200 rounded-lg p-3 min-[340px]:p-3.5 min-[550px]:p-4 sm:p-4 min-[1023px]:p-3 min-[1150px]:p-4 min-[1360px]:p-4 mb-3 min-[340px]:mb-3.5 min-[550px]:mb-4 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4 relative">
219
+ {/* Remove button */}
220
+ <div className="absolute top-2.5 min-[340px]:top-3 min-[550px]:top-3 right-2.5 min-[340px]:right-3 min-[550px]:right-3 sm:top-3 sm:right-3 min-[1023px]:top-2 min-[1023px]:right-2 min-[1150px]:top-3 min-[1150px]:right-3 min-[1360px]:top-3 min-[1360px]:right-3 z-10">
221
+ <DeleteButton
222
+ id={item.id}
223
+ productId={item.product_id || ""}
224
+ thumbnail={item.thumbnail}
225
+ data-testid="product-delete-button"
226
+ onAfterDelete={() => {
227
+ const unitTotal = item.total ? item.total / item.quantity : 0
228
+ trackRemoveFromCart({
229
+ currency: currencyCode,
230
+ value: (item.total ?? 0) / 100,
231
+ items: [
232
+ {
233
+ item_id: item.variant_id ?? item.id,
234
+ item_name: item.title ?? "",
235
+ price: unitTotal / 100,
236
+ quantity: item.quantity,
237
+ },
238
+ ],
239
+ })
240
+ }}
241
+ />
242
+ </div>
243
+
244
+ <div className="flex flex-col sm:flex-row gap-3 min-[340px]:gap-3.5 min-[550px]:gap-4 sm:gap-4 min-[1023px]:gap-3 min-[1150px]:gap-4 min-[1360px]:gap-4">
245
+ {/* Product Image */}
246
+ <LocalizedClientLink
247
+ href={`/products/${item.product_handle}`}
248
+ className="flex-shrink-0 self-start"
249
+ >
250
+ <Thumbnail
251
+ thumbnail={item.thumbnail}
252
+ images={item.variant?.product?.images}
253
+ variantImages={item.variant?.images}
254
+ size="square"
255
+ className="w-20 h-20 min-[340px]:w-22 min-[340px]:h-22 min-[550px]:w-24 min-[550px]:h-24 sm:w-24 sm:h-24 min-[1023px]:w-20 min-[1023px]:h-20 min-[1150px]:w-24 min-[1150px]:h-24 min-[1360px]:w-24 min-[1360px]:h-24 rounded-lg object-cover"
256
+ />
257
+ </LocalizedClientLink>
258
+
259
+ {/* Product Details */}
260
+ <div className="flex-1 min-w-0 flex flex-col gap-2.5 min-[340px]:gap-3 min-[550px]:gap-3 sm:gap-3 min-[1023px]:gap-2 min-[1150px]:gap-3 min-[1360px]:gap-3 pr-6 min-[340px]:pr-7 min-[550px]:pr-8 sm:pr-8 min-[1023px]:pr-6 min-[1150px]:pr-8 min-[1360px]:pr-8">
261
+ {/* Brand */}
262
+ {product?.metadata?.brand && (
263
+ <p className="text-xs min-[340px]:text-xs min-[550px]:text-xs text-gray-500">{product.metadata.brand}</p>
264
+ )}
265
+
266
+ {/* Product Title */}
267
+ <h3 className="font-bold text-sm min-[340px]:text-sm min-[550px]:text-base sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-heading truncate pr-10 min-[340px]:pr-12 min-[550px]:pr-8 sm:pr-8 min-[1023px]:pr-8 min-[1150px]:pr-8 min-[1360px]:pr-8">
268
+ {item.product_title}
269
+ </h3>
270
+
271
+ {/* Variant Dropdowns */}
272
+ {!product ? (
273
+ <div className="flex gap-2.5 min-[340px]:gap-3 flex-wrap">
274
+ <div className="h-9 min-[340px]:h-9.5 min-[550px]:h-10 sm:h-10 min-[1023px]:h-9 min-[1150px]:h-10 min-[1360px]:h-10 w-full sm:w-32 min-[1023px]:w-28 min-[1150px]:w-32 min-[1360px]:w-32 bg-gray-100 animate-pulse rounded" />
275
+ <div className="h-9 min-[340px]:h-9.5 min-[550px]:h-10 sm:h-10 min-[1023px]:h-9 min-[1150px]:h-10 min-[1360px]:h-10 w-full sm:w-32 min-[1023px]:w-28 min-[1150px]:w-32 min-[1360px]:w-32 bg-gray-100 animate-pulse rounded" />
276
+ <div className="h-9 min-[340px]:h-9.5 min-[550px]:h-10 sm:h-10 min-[1023px]:h-9 min-[1150px]:h-10 min-[1360px]:h-10 w-full sm:w-24 min-[1023px]:w-20 min-[1150px]:w-24 min-[1360px]:w-24 bg-gray-100 animate-pulse rounded" />
277
+ </div>
278
+ ) : (
279
+ <div className="flex flex-col min-[340px]:flex-col min-[640px]:flex-row gap-2.5 min-[340px]:gap-3 min-[550px]:gap-3 sm:gap-2 min-[1023px]:gap-1.5 min-[1150px]:gap-2 min-[1360px]:gap-2 items-stretch min-[640px]:items-center">
280
+ {productOptions.map((option: HttpTypes.StoreProductOption) => {
281
+ const currentValue = options[option.id]
282
+ const availableValues = getOptionValues(option.id)
283
+
284
+ return (
285
+ <div key={option.id} className="relative">
286
+ <select
287
+ value={currentValue || ""}
288
+ onChange={(e) => handleOptionChange(option.id, e.target.value)}
289
+ disabled={updating}
290
+ className="appearance-none bg-surface border border-[var(--color-border)] rounded px-3 min-[340px]:px-3.5 min-[550px]:px-4 py-2 min-[340px]:py-2.5 min-[550px]:py-2.5 pr-8 min-[340px]:pr-9 min-[550px]:pr-9 text-xs min-[340px]:text-xs min-[550px]:text-sm sm:text-sm min-[1023px]:text-xs min-[1150px]:text-sm min-[1360px]:text-sm font-medium text-heading focus:outline-none focus:ring-2 focus:ring-brand-accent focus:border-transparent cursor-pointer disabled:opacity-50 w-full min-[640px]:min-w-[140px] min-[1023px]:min-w-[120px] min-[1150px]:min-w-[140px] min-[1360px]:min-w-[140px] h-9 min-[340px]:h-9.5 min-[550px]:h-10 sm:h-10 min-[1023px]:h-9 min-[1150px]:h-10 min-[1360px]:h-10"
291
+ >
292
+ {!currentValue && (
293
+ <option value="" disabled>
294
+ {option.title}: Select
295
+ </option>
296
+ )}
297
+ {availableValues.map((value) => (
298
+ <option key={value} value={value}>
299
+ {option.title}: {value}
300
+ </option>
301
+ ))}
302
+ </select>
303
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-400">
304
+ <svg
305
+ className="fill-current h-4 w-4"
306
+ xmlns="http://www.w3.org/2000/svg"
307
+ viewBox="0 0 20 20"
308
+ >
309
+ <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
310
+ </svg>
311
+ </div>
312
+ </div>
313
+ )
314
+ })}
315
+
316
+ {/* Quantity Selector Container with Absolute Messages */}
317
+ <div className="hidden min-[640px]:flex relative h-fit">
318
+ <div className="flex items-center border border-gray-300 rounded-[5px] overflow-hidden bg-surface h-8 min-[550px]:h-9 sm:h-10 min-[1023px]:h-9 min-[1150px]:h-10 min-[1360px]:h-10 w-[96px] min-[550px]:w-[108px] sm:w-[120px] relative z-0 shadow-sm">
319
+ <button
320
+ onClick={() => changeQuantity(Math.max(1, item.quantity - 1))}
321
+ disabled={updating || item.quantity <= 1}
322
+ className="w-8 min-[550px]:w-9 sm:w-10 h-full flex items-center justify-center text-heading hover:bg-surface-muted disabled:opacity-40 disabled:cursor-not-allowed font-semibold text-base min-[550px]:text-lg sm:text-xl transition-colors duration-200"
323
+ type="button"
324
+ >
325
+
326
+ </button>
327
+ <span className="w-8 min-[550px]:w-9 sm:w-10 text-center font-semibold text-heading border-x border-[var(--color-border)] h-full flex items-center justify-center text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base bg-surface">
328
+ {item.quantity}
329
+ </span>
330
+ <button
331
+ onClick={() => changeQuantity(Math.min(maxQuantity, item.quantity + 1))}
332
+ disabled={updating || item.quantity >= maxQuantity}
333
+ className="w-8 min-[550px]:w-9 sm:w-10 h-full flex items-center justify-center text-heading hover:bg-surface-muted disabled:opacity-40 disabled:cursor-not-allowed font-semibold text-base min-[550px]:text-lg sm:text-xl transition-colors duration-200"
334
+ type="button"
335
+ >
336
+ +
337
+ </button>
338
+ {updating && (
339
+ <div className="absolute inset-0 flex items-center justify-center bg-surface bg-opacity-75 z-10">
340
+ <Spinner />
341
+ </div>
342
+ )}
343
+ </div>
344
+ {isAtMax && maxQuantity > 0 && (
345
+ <p className="absolute top-[105%] left-0 text-[10px] min-[1150px]:text-[11px] text-red-500 font-bold animate-pulse whitespace-nowrap">
346
+ Maximum available stock reached
347
+ </p>
348
+ )}
349
+ {!isAtMax && maxQuantity < 6 && maxQuantity > 0 && (
350
+ <p className="absolute top-[105%] left-0 text-[10px] min-[1150px]:text-[11px] text-orange-500 font-bold whitespace-nowrap">
351
+ Only {maxQuantity - item.quantity} left in stock
352
+ </p>
353
+ )}
354
+ </div>
355
+ </div>
356
+ )}
357
+
358
+ {/* Quantity Selector and Price - Side by side at 340px-639px, aligned to left */}
359
+ <div className="flex flex-col min-[640px]:hidden gap-2 w-full mt-3 min-[340px]:mt-3.5">
360
+ <div className="flex flex-row gap-3 items-center justify-start w-full">
361
+ {/* Quantity Selector */}
362
+ <div className="flex items-center border border-gray-300 rounded-[5px] overflow-hidden bg-surface h-9 min-[340px]:h-9.5 min-[550px]:h-10 w-[108px] min-[340px]:w-[114px] min-[550px]:w-[120px] flex-shrink-0 relative z-0 shadow-sm">
363
+ <button
364
+ onClick={() => changeQuantity(Math.max(1, item.quantity - 1))}
365
+ disabled={updating || item.quantity <= 1}
366
+ className="w-9 min-[340px]:w-9.5 min-[550px]:w-10 h-full flex items-center justify-center text-heading hover:bg-surface-muted disabled:opacity-40 disabled:cursor-not-allowed font-semibold text-lg min-[340px]:text-xl min-[550px]:text-xl transition-colors duration-200"
367
+ type="button"
368
+ >
369
+
370
+ </button>
371
+ <span className="w-9 min-[340px]:w-9.5 min-[550px]:w-10 text-center font-semibold text-heading border-x border-[var(--color-border)] h-full flex items-center justify-center text-sm min-[340px]:text-sm min-[550px]:text-base bg-surface">
372
+ {item.quantity}
373
+ </span>
374
+ <button
375
+ onClick={() => changeQuantity(Math.min(maxQuantity, item.quantity + 1))}
376
+ disabled={updating || item.quantity >= maxQuantity}
377
+ className="w-9 min-[340px]:w-9.5 min-[550px]:w-10 h-full flex items-center justify-center text-heading hover:bg-surface-muted disabled:opacity-40 disabled:cursor-not-allowed font-semibold text-lg min-[340px]:text-xl min-[550px]:text-xl transition-colors duration-200"
378
+ type="button"
379
+ >
380
+ +
381
+ </button>
382
+ {updating && (
383
+ <div className="absolute inset-0 flex items-center justify-center bg-surface bg-opacity-75 z-10">
384
+ <Spinner />
385
+ </div>
386
+ )}
387
+ </div>
388
+
389
+ {/* Price Information */}
390
+ {currentPriceAmount > 0 && (
391
+ <div className="flex items-center gap-2 min-[340px]:gap-2.5 flex-wrap flex-shrink-0">
392
+ <span className="font-bold text-base min-[340px]:text-lg min-[550px]:text-xl text-heading whitespace-nowrap">
393
+ {convertToLocale({
394
+ amount: currentPriceAmount,
395
+ currency_code: currencyCode,
396
+ minimumFractionDigits: 0,
397
+ maximumFractionDigits: 0,
398
+ })}
399
+ </span>
400
+ {originalPriceAmount && originalPriceAmount > currentPriceAmount && (
401
+ <>
402
+ <span className="text-sm min-[340px]:text-base min-[550px]:text-lg text-gray-500 line-through whitespace-nowrap">
403
+ {convertToLocale({
404
+ amount: originalPriceAmount,
405
+ currency_code: currencyCode,
406
+ minimumFractionDigits: 0,
407
+ maximumFractionDigits: 0,
408
+ })}
409
+ </span>
410
+ {discountPercentage > 0 && (
411
+ <span className="text-xs min-[340px]:text-xs min-[550px]:text-sm text-brand-accent font-medium whitespace-nowrap">
412
+ ({discountPercentage}% OFF)
413
+ </span>
414
+ )}
415
+ </>
416
+ )}
417
+ </div>
418
+ )}
419
+ </div>
420
+
421
+ {/* Mobile Stock Warning - Below Selector */}
422
+ <div className="flex flex-col gap-1.5 px-1 mt-3">
423
+ {isAtMax && maxQuantity > 0 && (
424
+ <p className="text-[10px] text-red-500 font-bold animate-pulse">
425
+ Maximum stock reached
426
+ </p>
427
+ )}
428
+ {!isAtMax && maxQuantity < 6 && maxQuantity > 0 && (
429
+ <p className="text-[10px] text-orange-500 font-bold">
430
+ Only {maxQuantity - item.quantity} left
431
+ </p>
432
+ )}
433
+ </div>
434
+ </div>
435
+
436
+ {/* Price Information - Only visible at 640px+ (same as 650px view) */}
437
+ {currentPriceAmount > 0 && (
438
+ <div className="hidden min-[640px]:flex items-center gap-2 flex-wrap">
439
+ <span className="font-bold text-base min-[550px]:text-[0.9375rem] sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg text-heading">
440
+ {convertToLocale({
441
+ amount: currentPriceAmount,
442
+ currency_code: currencyCode,
443
+ minimumFractionDigits: 0,
444
+ maximumFractionDigits: 0,
445
+ })}
446
+ </span>
447
+ {originalPriceAmount && originalPriceAmount > currentPriceAmount && (
448
+ <>
449
+ <span className="text-sm min-[550px]:text-[0.8125rem] sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-gray-500 line-through">
450
+ {convertToLocale({
451
+ amount: originalPriceAmount,
452
+ currency_code: currencyCode,
453
+ minimumFractionDigits: 0,
454
+ maximumFractionDigits: 0,
455
+ })}
456
+ </span>
457
+ {discountPercentage > 0 && (
458
+ <span className="text-xs min-[550px]:text-[0.6875rem] sm:text-sm min-[1023px]:text-xs min-[1150px]:text-sm min-[1360px]:text-sm text-brand-accent font-medium">
459
+ ({discountPercentage}% OFF)
460
+ </span>
461
+ )}
462
+ </>
463
+ )}
464
+ </div>
465
+ )}
466
+
467
+ {/* Error Message */}
468
+ <ErrorMessage error={error} data-testid="product-error-message" />
469
+ </div>
470
+ </div>
471
+ </div>
472
+ )
473
+ }
474
+
475
+ export default CartItemCard
476
+
@@ -0,0 +1,73 @@
1
+ "use client"
2
+
3
+ import { IconBadge, clx } from "@medusajs/ui"
4
+ import {
5
+ SelectHTMLAttributes,
6
+ forwardRef,
7
+ useEffect,
8
+ useImperativeHandle,
9
+ useRef,
10
+ useState,
11
+ } from "react"
12
+
13
+ import ChevronDown from "@modules/common/icons/chevron-down"
14
+
15
+ type NativeSelectProps = {
16
+ placeholder?: string
17
+ errors?: Record<string, unknown>
18
+ touched?: Record<string, unknown>
19
+ } & Omit<SelectHTMLAttributes<HTMLSelectElement>, "size">
20
+
21
+ const CartItemSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(
22
+ ({ placeholder = "Select...", className, children, ...props }, ref) => {
23
+ const innerRef = useRef<HTMLSelectElement>(null)
24
+ const [isPlaceholder, setIsPlaceholder] = useState(false)
25
+
26
+ useImperativeHandle<HTMLSelectElement | null, HTMLSelectElement | null>(
27
+ ref,
28
+ () => innerRef.current
29
+ )
30
+
31
+ useEffect(() => {
32
+ if (innerRef.current && innerRef.current.value === "") {
33
+ setIsPlaceholder(true)
34
+ } else {
35
+ setIsPlaceholder(false)
36
+ }
37
+ }, [innerRef.current?.value])
38
+
39
+ return (
40
+ <div>
41
+ <IconBadge
42
+ onFocus={() => innerRef.current?.focus()}
43
+ onBlur={() => innerRef.current?.blur()}
44
+ className={clx(
45
+ "relative flex items-center txt-compact-small border text-ui-fg-base group",
46
+ className,
47
+ {
48
+ "text-ui-fg-subtle": isPlaceholder,
49
+ }
50
+ )}
51
+ >
52
+ <select
53
+ ref={innerRef}
54
+ {...props}
55
+ className="appearance-none bg-transparent border-none px-4 transition-colors duration-150 focus:border-gray-700 outline-none w-16 h-16 items-center justify-center"
56
+ >
57
+ <option disabled value="">
58
+ {placeholder}
59
+ </option>
60
+ {children}
61
+ </select>
62
+ <span className="absolute flex pointer-events-none justify-end w-8 group-hover:animate-pulse">
63
+ <ChevronDown />
64
+ </span>
65
+ </IconBadge>
66
+ </div>
67
+ )
68
+ }
69
+ )
70
+
71
+ CartItemSelect.displayName = "CartItemSelect"
72
+
73
+ export default CartItemSelect
@@ -0,0 +1,44 @@
1
+ "use client"
2
+
3
+ import { useEffect } from "react"
4
+ import { trackViewCart } from "@core/analytics/ga4-ecommerce"
5
+
6
+ type CartViewTrackerProps = {
7
+ cart: {
8
+ items?: Array<{
9
+ id: string
10
+ variant_id?: string
11
+ title?: string
12
+ quantity: number
13
+ total?: number
14
+ }>
15
+ total?: number
16
+ currency_code?: string
17
+ region?: { currency_code?: string }
18
+ } | null
19
+ }
20
+
21
+ export default function CartViewTracker({ cart }: CartViewTrackerProps) {
22
+ useEffect(() => {
23
+ const currency =
24
+ cart?.currency_code ?? (cart as any)?.region?.currency_code
25
+ if (!cart?.items?.length || !currency) return
26
+
27
+ const currencyUpper = currency.toUpperCase()
28
+ const value = (cart.total ?? 0) / 100
29
+ const items = cart.items.map((item, index) => {
30
+ const unitTotal = item.total && item.quantity ? item.total / item.quantity : 0
31
+ return {
32
+ item_id: item.variant_id ?? item.id,
33
+ item_name: item.title ?? "",
34
+ price: unitTotal / 100,
35
+ quantity: item.quantity,
36
+ index,
37
+ }
38
+ })
39
+
40
+ trackViewCart({ currency: currencyUpper, value, items })
41
+ }, [cart])
42
+
43
+ return null
44
+ }
@@ -0,0 +1,89 @@
1
+ "use client"
2
+
3
+ import { HttpTypes } from "@medusajs/types"
4
+ import { useState, useEffect } from "react"
5
+ import { getShiprocketServiceability } from "@core/data/fulfillment"
6
+ import { Truck } from "lucide-react"
7
+
8
+ interface DeliveryEstimate {
9
+ etd: string | null
10
+ isLoading: boolean
11
+ error: string | null
12
+ }
13
+
14
+ const CartDeliveryEstimate = ({ cart }: { cart: HttpTypes.StoreCart | null }) => {
15
+ const [estimate, setEstimate] = useState<DeliveryEstimate>({
16
+ etd: null,
17
+ isLoading: false,
18
+ error: null,
19
+ })
20
+
21
+ const postalCode = cart?.shipping_address?.postal_code
22
+ const firstItem = cart?.items?.[0]
23
+
24
+ useEffect(() => {
25
+ const fetchEstimate = async () => {
26
+ // Prioritize metadata if available
27
+ if (cart?.metadata?.etd) {
28
+ setEstimate({ etd: cart.metadata.etd as string, isLoading: false, error: null })
29
+ return
30
+ }
31
+
32
+ if (!postalCode || postalCode.length < 6 || !firstItem?.variant_id) {
33
+ setEstimate({ etd: null, isLoading: false, error: null })
34
+ return
35
+ }
36
+
37
+ setEstimate(prev => ({ ...prev, isLoading: true, error: null }))
38
+
39
+ try {
40
+ const data = await getShiprocketServiceability(postalCode, firstItem.variant_id, 0)
41
+
42
+ let etd = null
43
+ if (data?.etd) etd = data.etd
44
+ else if (data?.data?.etd) etd = data.data.etd
45
+ else if (data?.data?.available_courier_companies?.length > 0) etd = data.data.available_courier_companies[0].etd
46
+
47
+ if (etd) {
48
+ setEstimate({ etd, isLoading: false, error: null })
49
+ document.cookie = `_medusa_last_etd=${etd}; path=/; max-age=3600`
50
+ } else {
51
+ setEstimate({ etd: null, isLoading: false, error: "Not available" })
52
+ }
53
+ } catch (err) {
54
+ setEstimate({ etd: null, isLoading: false, error: "Error fetching" })
55
+ }
56
+ }
57
+
58
+ fetchEstimate()
59
+ }, [postalCode, firstItem?.variant_id, cart?.metadata?.etd])
60
+
61
+ if (!postalCode || postalCode.length < 6) return null
62
+
63
+ const formatDeliveryDate = (etd: string) => {
64
+ const date = new Date(etd)
65
+ return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })
66
+ }
67
+
68
+ return (
69
+ <div className="bg-white border border-gray-200 rounded-lg p-4 mb-4 shadow-sm flex items-center gap-4">
70
+ <div className="w-10 h-10 rounded-full bg-purple-50 flex items-center justify-center flex-shrink-0">
71
+ <Truck className="w-5 h-5 text-[#8B5AB1]" />
72
+ </div>
73
+ <div>
74
+ <p className="text-xs font-bold text-gray-400 uppercase tracking-widest mb-0.5">Delivery Estimate</p>
75
+ {estimate.isLoading ? (
76
+ <p className="text-sm text-gray-500 animate-pulse">Calculating...</p>
77
+ ) : estimate.etd ? (
78
+ <p className="text-sm sm:text-base text-gray-900 font-medium">
79
+ Estimated delivery by <span className="font-bold text-[#8B5AB1]">{formatDeliveryDate(estimate.etd)}</span>
80
+ </p>
81
+ ) : (
82
+ <p className="text-sm text-gray-500">Enter a valid pincode for estimate</p>
83
+ )}
84
+ </div>
85
+ </div>
86
+ )
87
+ }
88
+
89
+ export default CartDeliveryEstimate