@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,23 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, 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
+
8
+ // This page is here along witht the `callback` route to handle the redirect
9
+ // after a user logs in using the SLAS Implementation
10
+
11
+ import React, {Fragment} from 'react'
12
+
13
+ const LoginRedirect = () => {
14
+ return (
15
+ <Fragment>
16
+ <h1 data-testid="login-redirect-page-heading">Login Redirect</h1>
17
+ </Fragment>
18
+ )
19
+ }
20
+
21
+ LoginRedirect.getTemplateName = () => 'login-redirect'
22
+
23
+ export default LoginRedirect
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, 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 from 'react'
8
+ import {renderWithReactIntl} from '@salesforce/retail-react-app/app/utils/test-utils'
9
+ import LoginRedirect from '@salesforce/retail-react-app/app/pages/login-redirect/index'
10
+
11
+ test('Login Redirect renders without errors', () => {
12
+ const {getByRole} = renderWithReactIntl(<LoginRedirect />)
13
+
14
+ expect(getByRole('heading', {name: /login redirect/i})).toBeInTheDocument()
15
+ expect(typeof LoginRedirect.getTemplateName()).toBe('string')
16
+ })
@@ -0,0 +1,90 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, 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
+
8
+ import React from 'react'
9
+ import {Box, Heading, Flex, Button, Stack, Text} from '@chakra-ui/react'
10
+ import {Helmet} from 'react-helmet'
11
+ import {useIntl} from 'react-intl'
12
+ import {useServerContext} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
13
+ import {SearchIcon} from '@salesforce/retail-react-app/app/components/icons'
14
+ import {useHistory} from 'react-router-dom'
15
+ import Link from '@salesforce/retail-react-app/app/components/link'
16
+
17
+ const PageNotFound = () => {
18
+ const intl = useIntl()
19
+ const history = useHistory()
20
+ const {res} = useServerContext()
21
+
22
+ if (res) {
23
+ res.status(404)
24
+ }
25
+
26
+ return (
27
+ <Box
28
+ layerStyle="page"
29
+ className="page-not-found"
30
+ height={'100%'}
31
+ padding={{lg: 8, md: 6, sm: 0, base: 0}}
32
+ >
33
+ <Helmet>
34
+ <title>
35
+ {intl.formatMessage({
36
+ defaultMessage: "The page you're looking for can't be found.",
37
+ id: 'page_not_found.title.page_cant_be_found'
38
+ })}
39
+ </title>
40
+ </Helmet>
41
+
42
+ <Flex
43
+ h="100%"
44
+ justify="center"
45
+ align="center"
46
+ flexDirection="column"
47
+ px={{base: 5, md: 12}}
48
+ py={{base: 48, md: 60}}
49
+ >
50
+ <SearchIcon boxSize={['30px', '32px']} mb={8} />
51
+ <Heading as="h2" fontSize={['xl', '2xl', '2xl', '3xl']} mb={2} align="center">
52
+ {intl.formatMessage({
53
+ defaultMessage: "The page you're looking for can't be found.",
54
+ id: 'page_not_found.title.page_cant_be_found'
55
+ })}
56
+ </Heading>
57
+ <Box mb={12}>
58
+ <Text textAlign="center">
59
+ {intl.formatMessage({
60
+ defaultMessage:
61
+ 'Please try retyping the address, going back to the previous page, or going to the home page.',
62
+ id: 'page_not_found.message.suggestion_to_try'
63
+ })}
64
+ </Text>
65
+ </Box>
66
+ <Stack direction={['column', 'row']} width={['100%', 'auto']}>
67
+ <Button
68
+ variant="outline"
69
+ bg="white"
70
+ onClick={() => history.goBack()}
71
+ borderColor={'gray.200'}
72
+ >
73
+ {intl.formatMessage({
74
+ defaultMessage: 'Back to previous page',
75
+ id: 'page_not_found.action.go_back'
76
+ })}
77
+ </Button>
78
+ <Button as={Link} to={'/'}>
79
+ {intl.formatMessage({
80
+ defaultMessage: 'Go to home page',
81
+ id: 'page_not_found.link.homepage'
82
+ })}
83
+ </Button>
84
+ </Stack>
85
+ </Flex>
86
+ </Box>
87
+ )
88
+ }
89
+
90
+ export default PageNotFound
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright (c) 2021, salesforce.com, 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 from 'react'
8
+ import PageNotFound from '@salesforce/retail-react-app/app/pages/page-not-found/index'
9
+ import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
10
+ import {screen} from '@testing-library/react'
11
+
12
+ // Set up and clean up
13
+ beforeEach(() => {
14
+ jest.resetModules()
15
+ })
16
+
17
+ const MockedComponent = () => {
18
+ return <PageNotFound />
19
+ }
20
+
21
+ test('renders product item name, attributes and price', () => {
22
+ renderWithProviders(<MockedComponent />)
23
+
24
+ expect(screen.getByText(/The page you're looking for can't be found/i)).toBeInTheDocument()
25
+ expect(
26
+ screen.getByText(
27
+ /Please try retyping the address, going back to the previous page, or going to the home page./i
28
+ )
29
+ ).toBeInTheDocument()
30
+ expect(screen.getByText(/Go to home page/i)).toBeInTheDocument()
31
+ })
@@ -0,0 +1,394 @@
1
+ /*
2
+ * Copyright (c) 2022, 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
+
8
+ import React, {Fragment, useCallback, useEffect, useState} from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import {Helmet} from 'react-helmet'
11
+ import {FormattedMessage, useIntl} from 'react-intl'
12
+
13
+ // Components
14
+ import {Box, Button, Stack} from '@chakra-ui/react'
15
+ import {
16
+ useProduct,
17
+ useCategory,
18
+ useShopperBasketsMutation,
19
+ useShopperCustomersMutation,
20
+ useCustomerId
21
+ } from '@salesforce/commerce-sdk-react'
22
+
23
+ // Hooks
24
+ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
25
+ import {useVariant} from '@salesforce/retail-react-app/app/hooks'
26
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
27
+ import useEinstein from '@salesforce/retail-react-app/app/hooks/use-einstein'
28
+ import {useServerContext} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
29
+ // Project Components
30
+ import RecommendedProducts from '@salesforce/retail-react-app/app/components/recommended-products'
31
+ import ProductView from '@salesforce/retail-react-app/app/components/product-view'
32
+ import InformationAccordion from '@salesforce/retail-react-app/app/pages/product-detail/partials/information-accordion'
33
+
34
+ // constant
35
+ import {
36
+ API_ERROR_MESSAGE,
37
+ EINSTEIN_RECOMMENDERS,
38
+ MAX_CACHE_AGE,
39
+ TOAST_ACTION_VIEW_WISHLIST,
40
+ TOAST_MESSAGE_ADDED_TO_WISHLIST
41
+ } from '@salesforce/retail-react-app/app/constants'
42
+ import {rebuildPathWithParams} from '@salesforce/retail-react-app/app/utils/url'
43
+ import {useHistory, useLocation, useParams} from 'react-router-dom'
44
+ import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
45
+ import {useWishList} from '@salesforce/retail-react-app/app/hooks/use-wish-list'
46
+
47
+ const ProductDetail = () => {
48
+ const {formatMessage} = useIntl()
49
+ const history = useHistory()
50
+ const location = useLocation()
51
+ const einstein = useEinstein()
52
+ const toast = useToast()
53
+ const navigate = useNavigation()
54
+ const [productSetSelection, setProductSetSelection] = useState({})
55
+ const childProductRefs = React.useRef({})
56
+ const customerId = useCustomerId()
57
+ /****************************** Basket *********************************/
58
+ const {data: basket} = useCurrentBasket()
59
+ const addItemToBasketMutation = useShopperBasketsMutation('addItemToBasket')
60
+ const {res} = useServerContext()
61
+ if (res) {
62
+ res.set('Cache-Control', `max-age=${MAX_CACHE_AGE}`)
63
+ }
64
+
65
+ /*************************** Product Detail and Category ********************/
66
+ const {productId} = useParams()
67
+ const urlParams = new URLSearchParams(location.search)
68
+ const {data: product, isLoading: isProductLoading} = useProduct(
69
+ {
70
+ parameters: {
71
+ id: urlParams.get('pid') || productId,
72
+ allImages: true
73
+ }
74
+ },
75
+ {
76
+ // When shoppers select a different variant (and the app fetches the new data),
77
+ // the old data is still rendered (and not the skeletons).
78
+ keepPreviousData: true
79
+ }
80
+ )
81
+ const isProductASet = product?.type.set
82
+ // Note: Since category needs id from product detail, it can't be server side rendered atm
83
+ // until we can do dependent query on server
84
+ const {data: category} = useCategory({
85
+ parameters: {
86
+ id: product?.primaryCategoryId,
87
+ level: 1
88
+ }
89
+ })
90
+ const [primaryCategory, setPrimaryCategory] = useState(category)
91
+ const variant = useVariant(product)
92
+ // This page uses the `primaryCategoryId` to retrieve the category data. This attribute
93
+ // is only available on `master` products. Since a variation will be loaded once all the
94
+ // attributes are selected (to get the correct inventory values), the category information
95
+ // is overridden. This will allow us to keep the initial category around until a different
96
+ // master product is loaded.
97
+ useEffect(() => {
98
+ if (category) {
99
+ setPrimaryCategory(category)
100
+ }
101
+ }, [category])
102
+
103
+ /**************** Product Variant ****************/
104
+ useEffect(() => {
105
+ if (!variant) {
106
+ return
107
+ }
108
+ // update the variation attributes parameter on
109
+ // the url accordingly as the variant changes
110
+ const updatedUrl = rebuildPathWithParams(`${location.pathname}${location.search}`, {
111
+ pid: variant?.productId
112
+ })
113
+ history.replace(updatedUrl)
114
+ }, [variant])
115
+
116
+ /**************** Wishlist ****************/
117
+ const {data: wishlist, isLoading: isWishlistLoading} = useWishList()
118
+ const createCustomerProductListItem = useShopperCustomersMutation(
119
+ 'createCustomerProductListItem'
120
+ )
121
+
122
+ const handleAddToWishlist = (product, variant, quantity) => {
123
+ createCustomerProductListItem.mutate(
124
+ {
125
+ parameters: {
126
+ listId: wishlist.id,
127
+ customerId
128
+ },
129
+ body: {
130
+ // NOTE: APi does not respect quantity, it always adds 1
131
+ quantity,
132
+ productId: variant?.productId || product?.id,
133
+ public: false,
134
+ priority: 1,
135
+ type: 'product'
136
+ }
137
+ },
138
+ {
139
+ onSuccess: () => {
140
+ toast({
141
+ title: formatMessage(TOAST_MESSAGE_ADDED_TO_WISHLIST, {quantity: 1}),
142
+ status: 'success',
143
+ action: (
144
+ // it would be better if we could use <Button as={Link}>
145
+ // but unfortunately the Link component is not compatible
146
+ // with Chakra Toast, since the ToastManager is rendered via portal
147
+ // and the toast doesn't have access to intl provider, which is a
148
+ // requirement of the Link component.
149
+ <Button variant="link" onClick={() => navigate('/account/wishlist')}>
150
+ {formatMessage(TOAST_ACTION_VIEW_WISHLIST)}
151
+ </Button>
152
+ )
153
+ })
154
+ },
155
+ onError: () => {
156
+ showError()
157
+ }
158
+ }
159
+ )
160
+ }
161
+
162
+ /**************** Add To Cart ****************/
163
+ const showToast = useToast()
164
+ const showError = () => {
165
+ showToast({
166
+ title: formatMessage(API_ERROR_MESSAGE),
167
+ status: 'error'
168
+ })
169
+ }
170
+
171
+ const handleAddToCart = async (productSelectionValues) => {
172
+ try {
173
+ const productItems = productSelectionValues.map(({variant, quantity}) => ({
174
+ productId: variant.productId,
175
+ price: variant.price,
176
+ quantity
177
+ }))
178
+
179
+ await addItemToBasketMutation.mutateAsync({
180
+ parameters: {basketId: basket.basketId},
181
+ body: productItems
182
+ })
183
+
184
+ einstein.sendAddToCart(productItems)
185
+
186
+ // If the items were successfully added, set the return value to be used
187
+ // by the add to cart modal.
188
+ return productSelectionValues
189
+ } catch (error) {
190
+ showError(error)
191
+ }
192
+ }
193
+
194
+ /**************** Product Set Handlers ****************/
195
+ const handleProductSetValidation = useCallback(() => {
196
+ // Run validation for all child products. This will ensure the error
197
+ // messages are shown.
198
+ Object.values(childProductRefs.current).forEach(({validateOrderability}) => {
199
+ validateOrderability({scrollErrorIntoView: false})
200
+ })
201
+
202
+ // Using ot state for which child products are selected, scroll to the first
203
+ // one that isn't selected.
204
+ const selectedProductIds = Object.keys(productSetSelection)
205
+ const firstUnselectedProduct = product.setProducts.find(
206
+ ({id}) => !selectedProductIds.includes(id)
207
+ )
208
+
209
+ if (firstUnselectedProduct) {
210
+ // Get the reference to the product view and scroll to it.
211
+ const {ref} = childProductRefs.current[firstUnselectedProduct.id]
212
+
213
+ if (ref.scrollIntoView) {
214
+ ref.scrollIntoView({
215
+ behavior: 'smooth',
216
+ block: 'end'
217
+ })
218
+ }
219
+
220
+ return false
221
+ }
222
+
223
+ return true
224
+ }, [product, productSetSelection])
225
+
226
+ const handleProductSetAddToCart = () => {
227
+ // Get all the selected products, and pass them to the addToCart handler which
228
+ // accepts an array.
229
+ const productSelectionValues = Object.values(productSetSelection)
230
+ return handleAddToCart(productSelectionValues)
231
+ }
232
+
233
+ /**************** Einstein ****************/
234
+ useEffect(() => {
235
+ if (product && product.type.set) {
236
+ einstein.sendViewProduct(product)
237
+ const childrenProducts = product.setProducts
238
+ childrenProducts.map((child) => {
239
+ einstein.sendViewProduct(child)
240
+ })
241
+ } else if (product) {
242
+ einstein.sendViewProduct(product)
243
+ }
244
+ }, [product])
245
+
246
+ return (
247
+ <Box
248
+ className="sf-product-detail-page"
249
+ layerStyle="page"
250
+ data-testid="product-details-page"
251
+ >
252
+ <Helmet>
253
+ <title>{product?.pageTitle}</title>
254
+ <meta name="description" content={product?.pageDescription} />
255
+ </Helmet>
256
+
257
+ <Stack spacing={16}>
258
+ {isProductASet ? (
259
+ <Fragment>
260
+ {/* Product Set: parent product */}
261
+ <ProductView
262
+ product={product}
263
+ category={primaryCategory?.parentCategoryTree || []}
264
+ addToCart={handleProductSetAddToCart}
265
+ addToWishlist={handleAddToWishlist}
266
+ isProductLoading={isProductLoading}
267
+ isWishlistLoading={isWishlistLoading}
268
+ validateOrderability={handleProductSetValidation}
269
+ />
270
+
271
+ <hr />
272
+
273
+ {/* TODO: consider `childProduct.belongsToSet` */}
274
+ {
275
+ // Product Set: render the child products
276
+ product.setProducts.map((childProduct) => (
277
+ <Box key={childProduct.id} data-testid="child-product">
278
+ <ProductView
279
+ // Do no use an arrow function as we are manipulating the functions scope.
280
+ ref={function (ref) {
281
+ // Assign the "set" scope of the ref, this is how we access the internal
282
+ // validation.
283
+ childProductRefs.current[childProduct.id] = {
284
+ ref,
285
+ validateOrderability: this.validateOrderability
286
+ }
287
+ }}
288
+ product={childProduct}
289
+ isProductPartOfSet={true}
290
+ addToCart={(variant, quantity) =>
291
+ handleAddToCart([
292
+ {product: childProduct, variant, quantity}
293
+ ])
294
+ }
295
+ addToWishlist={handleAddToWishlist}
296
+ onVariantSelected={(product, variant, quantity) => {
297
+ if (quantity) {
298
+ setProductSetSelection((previousState) => ({
299
+ ...previousState,
300
+ [product.id]: {
301
+ product,
302
+ variant,
303
+ quantity
304
+ }
305
+ }))
306
+ } else {
307
+ const selections = {...productSetSelection}
308
+ delete selections[product.id]
309
+ setProductSetSelection(selections)
310
+ }
311
+ }}
312
+ isProductLoading={isProductLoading}
313
+ isWishlistLoading={isWishlistLoading}
314
+ />
315
+ <InformationAccordion product={childProduct} />
316
+
317
+ <Box display={['none', 'none', 'none', 'block']}>
318
+ <hr />
319
+ </Box>
320
+ </Box>
321
+ ))
322
+ }
323
+ </Fragment>
324
+ ) : (
325
+ <Fragment>
326
+ <ProductView
327
+ product={product}
328
+ category={primaryCategory?.parentCategoryTree || []}
329
+ addToCart={(variant, quantity) =>
330
+ handleAddToCart([{product, variant, quantity}])
331
+ }
332
+ addToWishlist={handleAddToWishlist}
333
+ isProductLoading={isProductLoading}
334
+ isWishlistLoading={isWishlistLoading}
335
+ />
336
+ <InformationAccordion product={product} />
337
+ </Fragment>
338
+ )}
339
+
340
+ {/* Product Recommendations */}
341
+ <Stack spacing={16}>
342
+ {!isProductASet && (
343
+ <RecommendedProducts
344
+ title={
345
+ <FormattedMessage
346
+ defaultMessage="Complete the Set"
347
+ id="product_detail.recommended_products.title.complete_set"
348
+ />
349
+ }
350
+ recommender={EINSTEIN_RECOMMENDERS.PDP_COMPLETE_SET}
351
+ products={[product]}
352
+ mx={{base: -4, md: -8, lg: 0}}
353
+ shouldFetch={() => product?.id}
354
+ />
355
+ )}
356
+ <RecommendedProducts
357
+ title={
358
+ <FormattedMessage
359
+ defaultMessage="You might also like"
360
+ id="product_detail.recommended_products.title.might_also_like"
361
+ />
362
+ }
363
+ recommender={EINSTEIN_RECOMMENDERS.PDP_MIGHT_ALSO_LIKE}
364
+ products={[product]}
365
+ mx={{base: -4, md: -8, lg: 0}}
366
+ shouldFetch={() => product?.id}
367
+ />
368
+
369
+ <RecommendedProducts
370
+ title={
371
+ <FormattedMessage
372
+ defaultMessage="Recently Viewed"
373
+ id="product_detail.recommended_products.title.recently_viewed"
374
+ />
375
+ }
376
+ recommender={EINSTEIN_RECOMMENDERS.PDP_RECENTLY_VIEWED}
377
+ mx={{base: -4, md: -8, lg: 0}}
378
+ />
379
+ </Stack>
380
+ </Stack>
381
+ </Box>
382
+ )
383
+ }
384
+
385
+ ProductDetail.getTemplateName = () => 'product-detail'
386
+
387
+ ProductDetail.propTypes = {
388
+ /**
389
+ * The current react router match object. (Provided internally)
390
+ */
391
+ match: PropTypes.object
392
+ }
393
+
394
+ export default ProductDetail