@salesforce/retail-react-app 1.0.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/.eslintignore +7 -0
  2. package/.eslintrc.js +25 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc.yaml +7 -0
  5. package/CHANGELOG.md +173 -0
  6. package/LICENSE +14 -0
  7. package/README.md +48 -0
  8. package/app/assets/svg/account.svg +3 -0
  9. package/app/assets/svg/alert.svg +3 -0
  10. package/app/assets/svg/basket.svg +3 -0
  11. package/app/assets/svg/brand-logo.svg +10 -0
  12. package/app/assets/svg/cc-amex.svg +7 -0
  13. package/app/assets/svg/cc-cvv.svg +8 -0
  14. package/app/assets/svg/cc-discover.svg +14 -0
  15. package/app/assets/svg/cc-mastercard.svg +8 -0
  16. package/app/assets/svg/cc-visa.svg +11 -0
  17. package/app/assets/svg/check-circle.svg +3 -0
  18. package/app/assets/svg/check.svg +3 -0
  19. package/app/assets/svg/chevron-down.svg +3 -0
  20. package/app/assets/svg/chevron-left.svg +3 -0
  21. package/app/assets/svg/chevron-right.svg +3 -0
  22. package/app/assets/svg/chevron-up.svg +3 -0
  23. package/app/assets/svg/close.svg +3 -0
  24. package/app/assets/svg/dashboard.svg +4 -0
  25. package/app/assets/svg/figma-logo.svg +14 -0
  26. package/app/assets/svg/file.svg +3 -0
  27. package/app/assets/svg/filter.svg +3 -0
  28. package/app/assets/svg/flag-ca.svg +5 -0
  29. package/app/assets/svg/flag-cn.svg +19 -0
  30. package/app/assets/svg/flag-fr.svg +19 -0
  31. package/app/assets/svg/flag-gb.svg +16 -0
  32. package/app/assets/svg/flag-it.svg +29 -0
  33. package/app/assets/svg/flag-jp.svg +10 -0
  34. package/app/assets/svg/flag-us.svg +7 -0
  35. package/app/assets/svg/github-logo.svg +40 -0
  36. package/app/assets/svg/hamburger.svg +8 -0
  37. package/app/assets/svg/heart-solid.svg +7 -0
  38. package/app/assets/svg/heart.svg +3 -0
  39. package/app/assets/svg/info.svg +3 -0
  40. package/app/assets/svg/like.svg +4 -0
  41. package/app/assets/svg/location.svg +3 -0
  42. package/app/assets/svg/lock.svg +3 -0
  43. package/app/assets/svg/paypal.svg +19 -0
  44. package/app/assets/svg/plug.svg +3 -0
  45. package/app/assets/svg/plus.svg +3 -0
  46. package/app/assets/svg/receipt.svg +3 -0
  47. package/app/assets/svg/search.svg +8 -0
  48. package/app/assets/svg/signout.svg +3 -0
  49. package/app/assets/svg/social-facebook.svg +3 -0
  50. package/app/assets/svg/social-instagram.svg +3 -0
  51. package/app/assets/svg/social-pinterest.svg +4 -0
  52. package/app/assets/svg/social-twitter.svg +3 -0
  53. package/app/assets/svg/social-youtube.svg +3 -0
  54. package/app/assets/svg/user.svg +3 -0
  55. package/app/assets/svg/visibility-off.svg +5 -0
  56. package/app/assets/svg/visibility.svg +3 -0
  57. package/app/components/_app/index.jsx +401 -0
  58. package/app/components/_app/index.test.js +85 -0
  59. package/app/components/_app/partials/above-header.jsx +10 -0
  60. package/app/components/_app-config/index.jsx +125 -0
  61. package/app/components/_app-config/index.test.js +77 -0
  62. package/app/components/_error/index.jsx +142 -0
  63. package/app/components/_error/index.test.js +25 -0
  64. package/app/components/action-card/index.jsx +75 -0
  65. package/app/components/address-display/index.jsx +30 -0
  66. package/app/components/basic-tile/index.jsx +65 -0
  67. package/app/components/basic-tile/index.test.js +23 -0
  68. package/app/components/breadcrumb/index.jsx +67 -0
  69. package/app/components/breadcrumb/index.test.js +30 -0
  70. package/app/components/confirmation-modal/index.jsx +111 -0
  71. package/app/components/confirmation-modal/index.test.js +98 -0
  72. package/app/components/drawer-menu/index.jsx +405 -0
  73. package/app/components/drawer-menu/index.test.js +33 -0
  74. package/app/components/dynamic-image/index.jsx +56 -0
  75. package/app/components/field/index.jsx +161 -0
  76. package/app/components/footer/index.jsx +269 -0
  77. package/app/components/footer/index.test.js +22 -0
  78. package/app/components/forms/address-fields.jsx +49 -0
  79. package/app/components/forms/credit-card-fields.jsx +149 -0
  80. package/app/components/forms/form-action-buttons.jsx +55 -0
  81. package/app/components/forms/login-fields.jsx +31 -0
  82. package/app/components/forms/password-requirements.jsx +99 -0
  83. package/app/components/forms/post-checkout-registration-fields.jsx +43 -0
  84. package/app/components/forms/profile-fields.jsx +36 -0
  85. package/app/components/forms/promo-code-fields.jsx +43 -0
  86. package/app/components/forms/registration-fields.jsx +42 -0
  87. package/app/components/forms/reset-password-fields.jsx +31 -0
  88. package/app/components/forms/state-province-options.jsx +75 -0
  89. package/app/components/forms/update-password-fields.jsx +49 -0
  90. package/app/components/forms/useAddressFields.jsx +196 -0
  91. package/app/components/forms/useCreditCardFields.jsx +146 -0
  92. package/app/components/forms/useLoginFields.jsx +52 -0
  93. package/app/components/forms/useProfileFields.jsx +95 -0
  94. package/app/components/forms/usePromoCodeFields.jsx +39 -0
  95. package/app/components/forms/useRegistrationFields.jsx +136 -0
  96. package/app/components/forms/useResetPasswordFields.jsx +40 -0
  97. package/app/components/forms/useUpdatePasswordFields.jsx +89 -0
  98. package/app/components/header/index.jsx +290 -0
  99. package/app/components/header/index.test.js +217 -0
  100. package/app/components/hero/index.jsx +84 -0
  101. package/app/components/hero/index.test.js +40 -0
  102. package/app/components/icons/index.jsx +158 -0
  103. package/app/components/icons/index.test.js +20 -0
  104. package/app/components/image-gallery/index.jsx +176 -0
  105. package/app/components/image-gallery/index.test.js +485 -0
  106. package/app/components/item-variant/index.jsx +33 -0
  107. package/app/components/item-variant/item-attributes.jsx +107 -0
  108. package/app/components/item-variant/item-image.jsx +73 -0
  109. package/app/components/item-variant/item-name.jsx +28 -0
  110. package/app/components/item-variant/item-price.jsx +117 -0
  111. package/app/components/link/index.jsx +32 -0
  112. package/app/components/link/index.test.js +72 -0
  113. package/app/components/links-list/index.jsx +89 -0
  114. package/app/components/links-list/index.test.js +62 -0
  115. package/app/components/list-menu/index.jsx +280 -0
  116. package/app/components/list-menu/index.test.js +44 -0
  117. package/app/components/loading-spinner/index.jsx +46 -0
  118. package/app/components/locale-selector/index.jsx +124 -0
  119. package/app/components/locale-selector/index.test.js +37 -0
  120. package/app/components/locale-text/index.jsx +97 -0
  121. package/app/components/locale-text/index.test.js +36 -0
  122. package/app/components/login/index.jsx +96 -0
  123. package/app/components/nested-accordion/index.jsx +185 -0
  124. package/app/components/nested-accordion/index.test.js +98 -0
  125. package/app/components/offline-banner/index.jsx +40 -0
  126. package/app/components/offline-banner/index.test.js +15 -0
  127. package/app/components/offline-boundary/index.jsx +104 -0
  128. package/app/components/offline-boundary/index.test.js +123 -0
  129. package/app/components/order-summary/index.jsx +331 -0
  130. package/app/components/page-action-placeholder/index.jsx +50 -0
  131. package/app/components/pagination/index.jsx +134 -0
  132. package/app/components/pagination/index.test.js +25 -0
  133. package/app/components/product-item/index.jsx +146 -0
  134. package/app/components/product-item/index.test.js +38 -0
  135. package/app/components/product-scroller/index.jsx +172 -0
  136. package/app/components/product-scroller/index.test.js +98 -0
  137. package/app/components/product-tile/index.jsx +195 -0
  138. package/app/components/product-tile/index.test.js +96 -0
  139. package/app/components/product-view/index.jsx +538 -0
  140. package/app/components/product-view/index.test.js +224 -0
  141. package/app/components/product-view-modal/index.jsx +48 -0
  142. package/app/components/product-view-modal/index.test.js +72 -0
  143. package/app/components/promo-code/index.jsx +162 -0
  144. package/app/components/promo-popover/index.jsx +83 -0
  145. package/app/components/quantity-picker/index.jsx +58 -0
  146. package/app/components/radio-card/index.jsx +75 -0
  147. package/app/components/recommended-products/index.jsx +227 -0
  148. package/app/components/register/index.jsx +114 -0
  149. package/app/components/reset-password/index.jsx +87 -0
  150. package/app/components/responsive/index.jsx +29 -0
  151. package/app/components/scroll-to-top/index.jsx +24 -0
  152. package/app/components/scroll-to-top/index.test.js +46 -0
  153. package/app/components/search/index.jsx +279 -0
  154. package/app/components/search/index.test.js +127 -0
  155. package/app/components/search/partials/recent-searches.jsx +76 -0
  156. package/app/components/search/partials/search-suggestions.jsx +45 -0
  157. package/app/components/search/partials/suggestions.jsx +43 -0
  158. package/app/components/section/index.jsx +68 -0
  159. package/app/components/seo/index.jsx +33 -0
  160. package/app/components/social-icons/index.jsx +101 -0
  161. package/app/components/social-icons/index.test.js +30 -0
  162. package/app/components/swatch-group/index.jsx +77 -0
  163. package/app/components/swatch-group/index.test.js +136 -0
  164. package/app/components/swatch-group/swatch.jsx +94 -0
  165. package/app/components/toggle-card/index.jsx +97 -0
  166. package/app/components/with-registration/index.jsx +58 -0
  167. package/app/components/with-registration/index.test.js +85 -0
  168. package/app/constants.js +109 -0
  169. package/app/contexts/index.js +92 -0
  170. package/app/hooks/einstein-mock-data.js +916 -0
  171. package/app/hooks/index.js +17 -0
  172. package/app/hooks/use-add-to-cart-modal.js +344 -0
  173. package/app/hooks/use-add-to-cart-modal.test.js +625 -0
  174. package/app/hooks/use-auth-modal.js +337 -0
  175. package/app/hooks/use-auth-modal.test.js +365 -0
  176. package/app/hooks/use-currency.js +20 -0
  177. package/app/hooks/use-currency.test.js +41 -0
  178. package/app/hooks/use-current-basket.js +39 -0
  179. package/app/hooks/use-current-customer.js +29 -0
  180. package/app/hooks/use-derived-product.js +77 -0
  181. package/app/hooks/use-derived-product.test.js +69 -0
  182. package/app/hooks/use-einstein.js +512 -0
  183. package/app/hooks/use-einstein.test.js +224 -0
  184. package/app/hooks/use-intersection-observer.js +64 -0
  185. package/app/hooks/use-limit-urls.js +31 -0
  186. package/app/hooks/use-limit-urls.test.js +40 -0
  187. package/app/hooks/use-multi-site.js +36 -0
  188. package/app/hooks/use-multi-site.test.js +53 -0
  189. package/app/hooks/use-navigation.js +37 -0
  190. package/app/hooks/use-navigation.test.js +109 -0
  191. package/app/hooks/use-page-urls.js +35 -0
  192. package/app/hooks/use-page-urls.test.js +39 -0
  193. package/app/hooks/use-pdp-search-params.js +16 -0
  194. package/app/hooks/use-pdp-search-params.test.js +52 -0
  195. package/app/hooks/use-previous.js +17 -0
  196. package/app/hooks/use-product-view-modal.js +93 -0
  197. package/app/hooks/use-product-view-modal.test.js +172 -0
  198. package/app/hooks/use-search-params.js +96 -0
  199. package/app/hooks/use-search-params.test.js +91 -0
  200. package/app/hooks/use-sort-urls.js +33 -0
  201. package/app/hooks/use-sort-urls.test.js +42 -0
  202. package/app/hooks/use-toast.js +68 -0
  203. package/app/hooks/use-toast.test.js +58 -0
  204. package/app/hooks/use-variant.js +32 -0
  205. package/app/hooks/use-variant.test.js +81 -0
  206. package/app/hooks/use-variation-attributes.js +138 -0
  207. package/app/hooks/use-variation-attributes.test.js +119 -0
  208. package/app/hooks/use-variation-params.js +31 -0
  209. package/app/hooks/use-variation-params.test.js +73 -0
  210. package/app/hooks/use-wish-list.js +42 -0
  211. package/app/main.jsx +14 -0
  212. package/app/mocks/basket-with-suit.js +146 -0
  213. package/app/mocks/empty-basket.js +39 -0
  214. package/app/mocks/mock-data.js +5632 -0
  215. package/app/mocks/product-set-winter-lookM.js +1224 -0
  216. package/app/mocks/searchResults.js +144 -0
  217. package/app/mocks/variant-750518699578M.js +434 -0
  218. package/app/page-designer/README.md +102 -0
  219. package/app/page-designer/assets/image-tile/index.jsx +51 -0
  220. package/app/page-designer/assets/image-tile/index.test.js +30 -0
  221. package/app/page-designer/assets/image-with-text/index.jsx +140 -0
  222. package/app/page-designer/assets/image-with-text/index.test.js +38 -0
  223. package/app/page-designer/assets/index.js +9 -0
  224. package/app/page-designer/index.js +10 -0
  225. package/app/page-designer/layouts/carousel/index.jsx +222 -0
  226. package/app/page-designer/layouts/carousel/index.test.js +43 -0
  227. package/app/page-designer/layouts/index.js +14 -0
  228. package/app/page-designer/layouts/mobileGrid1r1c/index.jsx +36 -0
  229. package/app/page-designer/layouts/mobileGrid1r1c/index.test.js +35 -0
  230. package/app/page-designer/layouts/mobileGrid2r1c/index.jsx +37 -0
  231. package/app/page-designer/layouts/mobileGrid2r1c/index.test.js +47 -0
  232. package/app/page-designer/layouts/mobileGrid2r2c/index.jsx +37 -0
  233. package/app/page-designer/layouts/mobileGrid2r2c/index.test.js +71 -0
  234. package/app/page-designer/layouts/mobileGrid2r3c/index.jsx +37 -0
  235. package/app/page-designer/layouts/mobileGrid2r3c/index.test.js +95 -0
  236. package/app/page-designer/layouts/mobileGrid3r1c/index.jsx +37 -0
  237. package/app/page-designer/layouts/mobileGrid3r1c/index.test.js +59 -0
  238. package/app/page-designer/layouts/mobileGrid3r2c/index.jsx +37 -0
  239. package/app/page-designer/layouts/mobileGrid3r2c/index.test.js +95 -0
  240. package/app/page-designer/utils.js +14 -0
  241. package/app/pages/account/addresses.jsx +382 -0
  242. package/app/pages/account/addresses.test.js +120 -0
  243. package/app/pages/account/constant.js +57 -0
  244. package/app/pages/account/index.jsx +237 -0
  245. package/app/pages/account/index.test.js +188 -0
  246. package/app/pages/account/order-detail.jsx +397 -0
  247. package/app/pages/account/order-history.jsx +264 -0
  248. package/app/pages/account/orders.jsx +30 -0
  249. package/app/pages/account/orders.test.js +95 -0
  250. package/app/pages/account/profile.jsx +357 -0
  251. package/app/pages/account/wishlist/index.jsx +195 -0
  252. package/app/pages/account/wishlist/index.mock.js +1481 -0
  253. package/app/pages/account/wishlist/index.test.js +56 -0
  254. package/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +170 -0
  255. package/app/pages/account/wishlist/partials/wishlist-primary-action.mock.js +1623 -0
  256. package/app/pages/account/wishlist/partials/wishlist-primary-action.test.js +99 -0
  257. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.jsx +120 -0
  258. package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.test.js +391 -0
  259. package/app/pages/cart/index.jsx +476 -0
  260. package/app/pages/cart/index.test.js +481 -0
  261. package/app/pages/cart/partials/cart-cta.jsx +46 -0
  262. package/app/pages/cart/partials/cart-secondary-button-group.jsx +135 -0
  263. package/app/pages/cart/partials/cart-secondary-button-group.test.js +103 -0
  264. package/app/pages/cart/partials/cart-skeleton.jsx +93 -0
  265. package/app/pages/cart/partials/cart-title.jsx +27 -0
  266. package/app/pages/cart/partials/empty-cart.jsx +86 -0
  267. package/app/pages/checkout/confirmation.jsx +541 -0
  268. package/app/pages/checkout/confirmation.mock.js +450 -0
  269. package/app/pages/checkout/confirmation.test.js +114 -0
  270. package/app/pages/checkout/index.jsx +169 -0
  271. package/app/pages/checkout/index.test.js +582 -0
  272. package/app/pages/checkout/partials/cc-radio-group.jsx +122 -0
  273. package/app/pages/checkout/partials/checkout-footer.jsx +140 -0
  274. package/app/pages/checkout/partials/checkout-footer.test.js +16 -0
  275. package/app/pages/checkout/partials/checkout-header.jsx +54 -0
  276. package/app/pages/checkout/partials/checkout-header.test.js +16 -0
  277. package/app/pages/checkout/partials/checkout-skeleton.jsx +52 -0
  278. package/app/pages/checkout/partials/contact-info.jsx +251 -0
  279. package/app/pages/checkout/partials/contact-info.test.js +43 -0
  280. package/app/pages/checkout/partials/payment-form.jsx +97 -0
  281. package/app/pages/checkout/partials/payment.jsx +276 -0
  282. package/app/pages/checkout/partials/shipping-address-selection.jsx +377 -0
  283. package/app/pages/checkout/partials/shipping-address.jsx +132 -0
  284. package/app/pages/checkout/partials/shipping-options.jsx +232 -0
  285. package/app/pages/checkout/util/checkout-context.js +94 -0
  286. package/app/pages/home/data.js +134 -0
  287. package/app/pages/home/index.jsx +301 -0
  288. package/app/pages/home/index.test.js +23 -0
  289. package/app/pages/login/index.jsx +123 -0
  290. package/app/pages/login/index.test.js +229 -0
  291. package/app/pages/login-redirect/index.jsx +23 -0
  292. package/app/pages/login-redirect/index.test.js +16 -0
  293. package/app/pages/page-not-found/index.jsx +90 -0
  294. package/app/pages/page-not-found/index.test.js +31 -0
  295. package/app/pages/product-detail/index.jsx +394 -0
  296. package/app/pages/product-detail/index.mock.js +197 -0
  297. package/app/pages/product-detail/index.test.js +162 -0
  298. package/app/pages/product-detail/partials/information-accordion.jsx +121 -0
  299. package/app/pages/product-list/index.jsx +735 -0
  300. package/app/pages/product-list/index.test.js +180 -0
  301. package/app/pages/product-list/partials/above-page-header.jsx +10 -0
  302. package/app/pages/product-list/partials/checkbox-refinements.jsx +41 -0
  303. package/app/pages/product-list/partials/checkbox-refinements.test.js +53 -0
  304. package/app/pages/product-list/partials/color-refinements.jsx +88 -0
  305. package/app/pages/product-list/partials/empty-results.jsx +118 -0
  306. package/app/pages/product-list/partials/link-refinements.jsx +38 -0
  307. package/app/pages/product-list/partials/page-header.jsx +42 -0
  308. package/app/pages/product-list/partials/radio-refinements.jsx +60 -0
  309. package/app/pages/product-list/partials/refinements.jsx +144 -0
  310. package/app/pages/product-list/partials/selected-refinements.jsx +100 -0
  311. package/app/pages/product-list/partials/size-refinements.jsx +55 -0
  312. package/app/pages/registration/index.jsx +87 -0
  313. package/app/pages/registration/index.test.jsx +132 -0
  314. package/app/pages/reset-password/index.jsx +112 -0
  315. package/app/pages/reset-password/index.test.jsx +141 -0
  316. package/app/request-processor.js +118 -0
  317. package/app/request-processor.test.js +23 -0
  318. package/app/routes.jsx +111 -0
  319. package/app/routes.test.js +13 -0
  320. package/app/ssr.js +70 -0
  321. package/app/static/ico/favicon.ico +0 -0
  322. package/app/static/img/global/app-icon-192.png +0 -0
  323. package/app/static/img/global/app-icon-512.png +0 -0
  324. package/app/static/img/global/apple-touch-icon.png +0 -0
  325. package/app/static/img/hero.png +0 -0
  326. package/app/static/manifest.json +19 -0
  327. package/app/static/robots.txt +2 -0
  328. package/app/theme/components/base/accordion.js +21 -0
  329. package/app/theme/components/base/alert.js +17 -0
  330. package/app/theme/components/base/badge.js +25 -0
  331. package/app/theme/components/base/button.js +77 -0
  332. package/app/theme/components/base/checkbox.js +30 -0
  333. package/app/theme/components/base/container.js +17 -0
  334. package/app/theme/components/base/drawer.js +26 -0
  335. package/app/theme/components/base/formLabel.js +13 -0
  336. package/app/theme/components/base/icon.js +13 -0
  337. package/app/theme/components/base/input.js +44 -0
  338. package/app/theme/components/base/modal.js +11 -0
  339. package/app/theme/components/base/popover.js +61 -0
  340. package/app/theme/components/base/radio.js +33 -0
  341. package/app/theme/components/base/select.js +15 -0
  342. package/app/theme/components/base/skeleton.js +12 -0
  343. package/app/theme/components/base/tooltip.js +19 -0
  344. package/app/theme/components/project/_app.js +25 -0
  345. package/app/theme/components/project/breadcrumb.js +25 -0
  346. package/app/theme/components/project/checkout-footer.js +35 -0
  347. package/app/theme/components/project/drawer-menu.js +66 -0
  348. package/app/theme/components/project/footer.js +84 -0
  349. package/app/theme/components/project/header.js +84 -0
  350. package/app/theme/components/project/image-gallery.js +59 -0
  351. package/app/theme/components/project/links-list.js +43 -0
  352. package/app/theme/components/project/list-menu.js +91 -0
  353. package/app/theme/components/project/locale-selector.js +42 -0
  354. package/app/theme/components/project/nested-accordion.js +26 -0
  355. package/app/theme/components/project/offline-banner.js +25 -0
  356. package/app/theme/components/project/pagination.js +22 -0
  357. package/app/theme/components/project/product-tile.js +32 -0
  358. package/app/theme/components/project/social-icons.js +52 -0
  359. package/app/theme/components/project/swatch-group.js +115 -0
  360. package/app/theme/foundations/colors.js +170 -0
  361. package/app/theme/foundations/gradients.js +9 -0
  362. package/app/theme/foundations/layerStyles.js +41 -0
  363. package/app/theme/foundations/shadows.js +9 -0
  364. package/app/theme/foundations/sizes.js +18 -0
  365. package/app/theme/foundations/space.js +9 -0
  366. package/app/theme/foundations/styles.js +21 -0
  367. package/app/theme/index.js +104 -0
  368. package/app/utils/cc-utils.js +112 -0
  369. package/app/utils/cc-utils.test.js +41 -0
  370. package/app/utils/image-groups-utils.js +62 -0
  371. package/app/utils/image-groups-utils.test.js +65 -0
  372. package/app/utils/locale.js +78 -0
  373. package/app/utils/locale.test.js +112 -0
  374. package/app/utils/password-utils.js +21 -0
  375. package/app/utils/phone-utils.js +22 -0
  376. package/app/utils/phone-utils.test.js +15 -0
  377. package/app/utils/product-utils.js +35 -0
  378. package/app/utils/product-utils.test.js +51 -0
  379. package/app/utils/responsive-image.js +198 -0
  380. package/app/utils/responsive-image.test.js +170 -0
  381. package/app/utils/routes-utils.js +111 -0
  382. package/app/utils/routes-utils.test.js +291 -0
  383. package/app/utils/site-utils.js +222 -0
  384. package/app/utils/site-utils.test.js +376 -0
  385. package/app/utils/test-utils.js +257 -0
  386. package/app/utils/url.js +291 -0
  387. package/app/utils/url.test.js +421 -0
  388. package/app/utils/utils.js +201 -0
  389. package/app/utils/utils.test.js +182 -0
  390. package/babel.config.js +7 -0
  391. package/cache-hash-config.json +8 -0
  392. package/config/default.js +64 -0
  393. package/config/mocks/default.js +131 -0
  394. package/config/sites.js +78 -0
  395. package/jest-setup.js +191 -0
  396. package/jest.config.js +50 -0
  397. package/jsconfig.json +13 -0
  398. package/package.json +105 -0
  399. package/scripts/extract-default-messages.js +92 -0
  400. package/tests/lighthouserc.js +37 -0
  401. package/translations/README.md +127 -0
  402. package/translations/compiled/de-DE.json +3212 -0
  403. package/translations/compiled/en-GB.json +3212 -0
  404. package/translations/compiled/en-US.json +3212 -0
  405. package/translations/compiled/en-XA.json +6948 -0
  406. package/translations/compiled/es-MX.json +3216 -0
  407. package/translations/compiled/fr-FR.json +3216 -0
  408. package/translations/compiled/it-IT.json +3188 -0
  409. package/translations/compiled/ja-JP.json +3200 -0
  410. package/translations/compiled/ko-KR.json +3180 -0
  411. package/translations/compiled/pt-BR.json +3220 -0
  412. package/translations/compiled/zh-CN.json +3212 -0
  413. package/translations/compiled/zh-TW.json +3208 -0
  414. package/translations/de-DE.json +1417 -0
  415. package/translations/en-GB.json +1417 -0
  416. package/translations/en-US.json +1417 -0
  417. package/translations/es-MX.json +1417 -0
  418. package/translations/fr-FR.json +1417 -0
  419. package/translations/it-IT.json +1417 -0
  420. package/translations/ja-JP.json +1417 -0
  421. package/translations/ko-KR.json +1417 -0
  422. package/translations/pt-BR.json +1417 -0
  423. package/translations/zh-CN.json +1417 -0
  424. package/translations/zh-TW.json +1417 -0
  425. package/worker/main.js +36 -0
@@ -0,0 +1,476 @@
1
+ /*
2
+ * Copyright (c) 2023, Salesforce, Inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+ import React, {useState} from 'react'
8
+ import {FormattedMessage, useIntl} from 'react-intl'
9
+
10
+ // Chakra Components
11
+ import {Box, Stack, Grid, GridItem, Container, useDisclosure, Button} from '@chakra-ui/react'
12
+
13
+ // Project Components
14
+ import CartCta from '@salesforce/retail-react-app/app/pages/cart/partials/cart-cta'
15
+ import CartSecondaryButtonGroup from '@salesforce/retail-react-app/app/pages/cart/partials/cart-secondary-button-group'
16
+ import CartSkeleton from '@salesforce/retail-react-app/app/pages/cart/partials/cart-skeleton'
17
+ import CartTitle from '@salesforce/retail-react-app/app/pages/cart/partials/cart-title'
18
+ import ConfirmationModal from '@salesforce/retail-react-app/app/components/confirmation-modal'
19
+ import EmptyCart from '@salesforce/retail-react-app/app/pages/cart/partials/empty-cart'
20
+ import OrderSummary from '@salesforce/retail-react-app/app/components/order-summary'
21
+ import ProductItem from '@salesforce/retail-react-app/app/components/product-item/index'
22
+ import ProductViewModal from '@salesforce/retail-react-app/app/components/product-view-modal'
23
+ import RecommendedProducts from '@salesforce/retail-react-app/app/components/recommended-products'
24
+
25
+ // Hooks
26
+ import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
27
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
28
+ import {useWishList} from '@salesforce/retail-react-app/app/hooks/use-wish-list'
29
+
30
+ // Constants
31
+ import {
32
+ API_ERROR_MESSAGE,
33
+ EINSTEIN_RECOMMENDERS,
34
+ TOAST_ACTION_VIEW_WISHLIST,
35
+ TOAST_MESSAGE_ADDED_TO_WISHLIST,
36
+ TOAST_MESSAGE_REMOVED_ITEM_FROM_CART
37
+ } from '@salesforce/retail-react-app/app/constants'
38
+ import {REMOVE_CART_ITEM_CONFIRMATION_DIALOG_CONFIG} from '@salesforce/retail-react-app/app/pages/cart/partials/cart-secondary-button-group'
39
+
40
+ // Utilities
41
+ import debounce from 'lodash/debounce'
42
+ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
43
+ import {
44
+ useShopperBasketsMutation,
45
+ useShippingMethodsForShipment,
46
+ useProducts,
47
+ useShopperCustomersMutation
48
+ } from '@salesforce/commerce-sdk-react'
49
+ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
50
+
51
+ const Cart = () => {
52
+ const {data: basket, isLoading} = useCurrentBasket()
53
+
54
+ const productIds = basket?.productItems?.map(({productId}) => productId).join(',') ?? ''
55
+ const {data: products} = useProducts(
56
+ {
57
+ parameters: {
58
+ ids: productIds,
59
+ allImages: true
60
+ }
61
+ },
62
+ {
63
+ enabled: Boolean(productIds),
64
+ select: (result) => {
65
+ // Convert array into key/value object with key is the product id
66
+ return result?.data?.reduce((result, item) => {
67
+ const key = item.id
68
+ result[key] = item
69
+ return result
70
+ }, {})
71
+ }
72
+ }
73
+ )
74
+ const {data: customer} = useCurrentCustomer()
75
+ const {customerId, isRegistered} = customer
76
+
77
+ /*****************Basket Mutation************************/
78
+ const updateItemInBasketMutation = useShopperBasketsMutation('updateItemInBasket')
79
+ const removeItemFromBasketMutation = useShopperBasketsMutation('removeItemFromBasket')
80
+ const updateShippingMethodForShipmentsMutation = useShopperBasketsMutation(
81
+ 'updateShippingMethodForShipment'
82
+ )
83
+ /*****************Basket Mutation************************/
84
+
85
+ const [selectedItem, setSelectedItem] = useState(undefined)
86
+ const [localQuantity, setLocalQuantity] = useState({})
87
+ const [isCartItemLoading, setCartItemLoading] = useState(false)
88
+
89
+ const {isOpen, onOpen, onClose} = useDisclosure()
90
+ const {formatMessage} = useIntl()
91
+ const toast = useToast()
92
+ const navigate = useNavigation()
93
+ const modalProps = useDisclosure()
94
+
95
+ /******************* Shipping Methods for basket shipment *******************/
96
+ // do this action only if the basket shipping method is not defined
97
+ // we need to fetch the shippment methods to get the default value before we can add it to the basket
98
+ useShippingMethodsForShipment(
99
+ {
100
+ parameters: {
101
+ basketId: basket?.basketId,
102
+ shipmentId: 'me'
103
+ }
104
+ },
105
+ {
106
+ // only fetch if basket is has no shipping method in the first shipment
107
+ enabled:
108
+ !!basket?.basketId &&
109
+ basket.shipments.length > 0 &&
110
+ !basket.shipments[0].shippingMethod,
111
+ onSuccess: (data) => {
112
+ updateShippingMethodForShipmentsMutation.mutate({
113
+ parameters: {
114
+ basketId: basket?.basketId,
115
+ shipmentId: 'me'
116
+ },
117
+ body: {
118
+ id: data.defaultShippingMethodId
119
+ }
120
+ })
121
+ }
122
+ }
123
+ )
124
+
125
+ /************************* Error handling ***********************/
126
+ const showError = () => {
127
+ toast({
128
+ title: formatMessage(API_ERROR_MESSAGE),
129
+ status: 'error'
130
+ })
131
+ }
132
+ /************************* Error handling ***********************/
133
+
134
+ /**************** Wishlist ****************/
135
+ const {data: wishlist} = useWishList()
136
+
137
+ const createCustomerProductListItem = useShopperCustomersMutation(
138
+ 'createCustomerProductListItem'
139
+ )
140
+ const handleAddToWishlist = async (product) => {
141
+ try {
142
+ if (!customerId || !wishlist) {
143
+ return
144
+ }
145
+ await createCustomerProductListItem.mutateAsync({
146
+ parameters: {
147
+ listId: wishlist.id,
148
+ customerId
149
+ },
150
+ body: {
151
+ // NOTE: APi does not respect quantity, it always adds 1
152
+ quantity: product.quantity,
153
+ productId: product.productId,
154
+ public: false,
155
+ priority: 1,
156
+ type: 'product'
157
+ }
158
+ })
159
+ toast({
160
+ title: formatMessage(TOAST_MESSAGE_ADDED_TO_WISHLIST, {quantity: 1}),
161
+ status: 'success',
162
+ action: (
163
+ // it would be better if we could use <Button as={Link}>
164
+ // but unfortunately the Link component is not compatible
165
+ // with Chakra Toast, since the ToastManager is rendered via portal
166
+ // and the toast doesn't have access to intl provider, which is a
167
+ // requirement of the Link component.
168
+ <Button variant="link" onClick={() => navigate('/account/wishlist')}>
169
+ {formatMessage(TOAST_ACTION_VIEW_WISHLIST)}
170
+ </Button>
171
+ )
172
+ })
173
+ } catch {
174
+ showError()
175
+ }
176
+ }
177
+ /**************** Wishlist ****************/
178
+
179
+ /***************************** Update Cart **************************/
180
+ const handleUpdateCart = async (variant, quantity) => {
181
+ // close the modal before handle the change
182
+ onClose()
183
+ // using try-catch is better than using onError callback since we have many mutation calls logic here
184
+ try {
185
+ setCartItemLoading(true)
186
+ const productIds = basket.productItems.map(({productId}) => productId)
187
+
188
+ // The user is selecting different variant, and it has not existed in basket
189
+ if (selectedItem.id !== variant.productId && !productIds.includes(variant.productId)) {
190
+ const item = {
191
+ productId: variant.productId,
192
+ quantity,
193
+ price: variant.price
194
+ }
195
+ return await updateItemInBasketMutation.mutateAsync({
196
+ parameters: {
197
+ basketId: basket.basketId,
198
+ itemId: selectedItem.itemId
199
+ },
200
+ body: item
201
+ })
202
+ }
203
+
204
+ // The user is selecting different variant, and it has existed in basket
205
+ // remove this item in the basket, change the quantity for the new selected variant in the basket
206
+ if (selectedItem.id !== variant.productId && productIds.includes(variant.productId)) {
207
+ await removeItemFromBasketMutation.mutateAsync({
208
+ parameters: {
209
+ basketId: basket.basketId,
210
+ itemId: selectedItem.itemId
211
+ }
212
+ })
213
+ const basketItem = basket.productItems.find(
214
+ ({productId}) => productId === variant.productId
215
+ )
216
+ const newQuantity = quantity + basketItem.quantity
217
+ return await changeItemQuantity(newQuantity, basketItem)
218
+ }
219
+
220
+ // the user only changes quantity of the same variant
221
+ if (selectedItem.quantity !== quantity) {
222
+ return await changeItemQuantity(quantity, selectedItem)
223
+ }
224
+ } catch {
225
+ showError()
226
+ } finally {
227
+ setCartItemLoading(false)
228
+ setSelectedItem(undefined)
229
+ }
230
+ }
231
+ /***************************** Update Cart **************************/
232
+
233
+ /***************************** Update quantity **************************/
234
+ const changeItemQuantity = debounce(async (quantity, product) => {
235
+ // This local state allows the dropdown to show the desired quantity
236
+ // while the API call to update it is happening.
237
+ const previousQuantity = localQuantity[product.itemId]
238
+ setLocalQuantity({...localQuantity, [product.itemId]: quantity})
239
+ setCartItemLoading(true)
240
+ setSelectedItem(product)
241
+
242
+ await updateItemInBasketMutation.mutateAsync(
243
+ {
244
+ parameters: {basketId: basket?.basketId, itemId: product.itemId},
245
+ body: {
246
+ productId: product.id,
247
+ quantity: parseInt(quantity)
248
+ }
249
+ },
250
+ {
251
+ onSettled: () => {
252
+ // reset the state
253
+ setCartItemLoading(false)
254
+ setSelectedItem(undefined)
255
+ },
256
+ onSuccess: () => {
257
+ setLocalQuantity({...localQuantity, [product.itemId]: undefined})
258
+ },
259
+ onError: () => {
260
+ // reset the quantity to the previous value
261
+ setLocalQuantity({...localQuantity, [product.itemId]: previousQuantity})
262
+ showError()
263
+ }
264
+ }
265
+ )
266
+ }, 750)
267
+
268
+ const handleChangeItemQuantity = async (product, value) => {
269
+ const {stockLevel} = products[product.productId].inventory
270
+
271
+ // Handle removing of the items when 0 is selected.
272
+ if (value === 0) {
273
+ // Flush last call to keep ui in sync with data.
274
+ changeItemQuantity.flush()
275
+
276
+ // Set the selected item to the current product to the modal acts on it.
277
+ setSelectedItem(product)
278
+
279
+ // Show the modal.
280
+ modalProps.onOpen()
281
+
282
+ // Return false as 0 isn't valid section.
283
+ return false
284
+ }
285
+
286
+ // Cancel any pending handlers.
287
+ changeItemQuantity.cancel()
288
+
289
+ // Allow use to selected values above the inventory.
290
+ if (value > stockLevel || value === product.quantity) {
291
+ return true
292
+ }
293
+
294
+ // Take action.
295
+ changeItemQuantity(value, product)
296
+
297
+ return true
298
+ }
299
+ /***************************** Update quantity **************************/
300
+
301
+ /***************************** Remove Item from basket **************************/
302
+ const handleRemoveItem = async (product) => {
303
+ setSelectedItem(product)
304
+ setCartItemLoading(true)
305
+ await removeItemFromBasketMutation.mutateAsync(
306
+ {
307
+ parameters: {basketId: basket.basketId, itemId: product.itemId}
308
+ },
309
+ {
310
+ onSettled: () => {
311
+ // reset the state
312
+ setCartItemLoading(false)
313
+ setSelectedItem(undefined)
314
+ },
315
+ onSuccess: () => {
316
+ toast({
317
+ title: formatMessage(TOAST_MESSAGE_REMOVED_ITEM_FROM_CART, {quantity: 1}),
318
+ status: 'success'
319
+ })
320
+ },
321
+ onError: () => {
322
+ showError()
323
+ }
324
+ }
325
+ )
326
+ }
327
+
328
+ /********* Rendering UI **********/
329
+ if (isLoading) {
330
+ return <CartSkeleton />
331
+ }
332
+
333
+ if (!isLoading && !basket?.productItems?.length) {
334
+ return <EmptyCart isRegistered={isRegistered} />
335
+ }
336
+ return (
337
+ <Box background="gray.50" flex="1" data-testid="sf-cart-container">
338
+ <Container
339
+ maxWidth="container.xl"
340
+ px={[4, 6, 6, 4]}
341
+ paddingTop={{base: 8, lg: 8}}
342
+ paddingBottom={{base: 8, lg: 14}}
343
+ >
344
+ <Stack spacing={24}>
345
+ <Stack spacing={4}>
346
+ <CartTitle />
347
+
348
+ <Grid
349
+ templateColumns={{base: '1fr', lg: '66% 1fr'}}
350
+ gap={{base: 10, xl: 20}}
351
+ >
352
+ <GridItem>
353
+ <Stack spacing={4}>
354
+ {basket.productItems?.map((productItem, idx) => {
355
+ return (
356
+ <ProductItem
357
+ key={productItem.productId}
358
+ index={idx}
359
+ secondaryActions={
360
+ <CartSecondaryButtonGroup
361
+ onAddToWishlistClick={handleAddToWishlist}
362
+ onEditClick={(product) => {
363
+ setSelectedItem(product)
364
+ onOpen()
365
+ }}
366
+ onRemoveItemClick={handleRemoveItem}
367
+ />
368
+ }
369
+ product={{
370
+ ...productItem,
371
+ ...(products &&
372
+ products[productItem.productId]),
373
+ price: productItem.price,
374
+ quantity: localQuantity[productItem.itemId]
375
+ ? localQuantity[productItem.itemId]
376
+ : productItem.quantity
377
+ }}
378
+ onItemQuantityChange={handleChangeItemQuantity.bind(
379
+ this,
380
+ productItem
381
+ )}
382
+ showLoading={
383
+ isCartItemLoading &&
384
+ selectedItem?.itemId === productItem.itemId
385
+ }
386
+ handleRemoveItem={handleRemoveItem}
387
+ />
388
+ )
389
+ })}
390
+ </Stack>
391
+ <Box>
392
+ {isOpen && (
393
+ <ProductViewModal
394
+ isOpen={isOpen}
395
+ onOpen={onOpen}
396
+ onClose={onClose}
397
+ product={selectedItem}
398
+ updateCart={(variant, quantity) =>
399
+ handleUpdateCart(variant, quantity)
400
+ }
401
+ />
402
+ )}
403
+ </Box>
404
+ </GridItem>
405
+ <GridItem>
406
+ <Stack spacing={4}>
407
+ <OrderSummary
408
+ showPromoCodeForm={true}
409
+ isEstimate={true}
410
+ basket={basket}
411
+ />
412
+ <Box display={{base: 'none', lg: 'block'}}>
413
+ <CartCta />
414
+ </Box>
415
+ </Stack>
416
+ </GridItem>
417
+ </Grid>
418
+
419
+ {/* Product Recommendations */}
420
+ <Stack spacing={16}>
421
+ <RecommendedProducts
422
+ title={
423
+ <FormattedMessage
424
+ defaultMessage="Recently Viewed"
425
+ id="cart.recommended_products.title.recently_viewed"
426
+ />
427
+ }
428
+ recommender={EINSTEIN_RECOMMENDERS.CART_RECENTLY_VIEWED}
429
+ mx={{base: -4, sm: -6, lg: 0}}
430
+ />
431
+
432
+ <RecommendedProducts
433
+ title={
434
+ <FormattedMessage
435
+ defaultMessage="You May Also Like"
436
+ id="cart.recommended_products.title.may_also_like"
437
+ />
438
+ }
439
+ recommender={EINSTEIN_RECOMMENDERS.CART_MAY_ALSO_LIKE}
440
+ products={basket?.productItems}
441
+ shouldFetch={() =>
442
+ basket?.basketId && basket.productItems?.length > 0
443
+ }
444
+ mx={{base: -4, sm: -6, lg: 0}}
445
+ />
446
+ </Stack>
447
+ </Stack>
448
+ </Stack>
449
+ </Container>
450
+
451
+ <Box
452
+ h="130px"
453
+ position="sticky"
454
+ bottom={0}
455
+ bg="white"
456
+ display={{base: 'block', lg: 'none'}}
457
+ align="center"
458
+ >
459
+ <CartCta />
460
+ </Box>
461
+
462
+ <ConfirmationModal
463
+ {...REMOVE_CART_ITEM_CONFIRMATION_DIALOG_CONFIG}
464
+ onPrimaryAction={() => {
465
+ handleRemoveItem(selectedItem)
466
+ }}
467
+ onAlternateAction={() => {}}
468
+ {...modalProps}
469
+ />
470
+ </Box>
471
+ )
472
+ }
473
+
474
+ Cart.getTemplateName = () => 'cart'
475
+
476
+ export default Cart