@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,30 @@
1
+ /*
2
+ * Copyright (c) 2023, 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 {Route, Switch, useRouteMatch} from 'react-router'
10
+ import OrderHistory from '@salesforce/retail-react-app/app/pages/account/order-history'
11
+ import OrderDetail from '@salesforce/retail-react-app/app/pages/account/order-detail'
12
+
13
+ const AccountOrders = () => {
14
+ const {path} = useRouteMatch()
15
+
16
+ return (
17
+ <Switch>
18
+ <Route exact path={path}>
19
+ <OrderHistory />
20
+ </Route>
21
+ <Route exact path={`${path}/:orderNo`}>
22
+ <OrderDetail />
23
+ </Route>
24
+ </Switch>
25
+ )
26
+ }
27
+
28
+ AccountOrders.getTemplateName = () => 'account-order-history'
29
+
30
+ export default AccountOrders
@@ -0,0 +1,95 @@
1
+ /*
2
+ * Copyright (c) 2023, 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 {Route, Switch} from 'react-router-dom'
9
+ import {screen} from '@testing-library/react'
10
+ import {rest} from 'msw'
11
+ import {
12
+ renderWithProviders,
13
+ createPathWithDefaults
14
+ } from '@salesforce/retail-react-app/app/utils/test-utils'
15
+ import {
16
+ mockCustomerBaskets,
17
+ mockOrderHistory,
18
+ mockOrderProducts
19
+ } from '@salesforce/retail-react-app/app/mocks/mock-data'
20
+ import Orders from '@salesforce/retail-react-app/app/pages/account/orders'
21
+ import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
22
+
23
+ const MockedComponent = () => {
24
+ return (
25
+ <Switch>
26
+ <Route path={createPathWithDefaults('/account/orders')}>
27
+ <Orders />
28
+ </Route>
29
+ </Switch>
30
+ )
31
+ }
32
+
33
+ // Set up and clean up
34
+ beforeEach(() => {
35
+ global.server.use(
36
+ rest.get('*/customers/:customerId/baskets', (req, res, ctx) =>
37
+ res(ctx.delay(0), ctx.json(mockCustomerBaskets))
38
+ )
39
+ )
40
+
41
+ window.history.pushState({}, 'Account', createPathWithDefaults('/account/orders'))
42
+ })
43
+ afterEach(() => {
44
+ jest.resetModules()
45
+ localStorage.clear()
46
+ })
47
+
48
+ test('Renders order history and details', async () => {
49
+ global.server.use(
50
+ rest.get('*/orders/:orderNo', (req, res, ctx) => {
51
+ return res(ctx.delay(0), ctx.json(mockOrderHistory.data[0]))
52
+ }),
53
+ rest.get('*/customers/:customerId/orders', (req, res, ctx) => {
54
+ return res(ctx.delay(0), ctx.json(mockOrderHistory))
55
+ }),
56
+ rest.get('*/products', (req, res, ctx) => {
57
+ return res(ctx.delay(0), ctx.json(mockOrderProducts))
58
+ })
59
+ )
60
+ const {user} = renderWithProviders(<MockedComponent history={history} />, {
61
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
62
+ })
63
+ expect(await screen.findByTestId('account-order-history-page')).toBeInTheDocument()
64
+ expect(await screen.findAllByText(/Ordered: /i)).toHaveLength(3)
65
+ expect(
66
+ await screen.findAllByAltText(
67
+ 'Pleated Bib Long Sleeve Shirt, Silver Grey, small',
68
+ {},
69
+ {timeout: 15000}
70
+ )
71
+ ).toHaveLength(3)
72
+
73
+ await user.click((await screen.findAllByText(/view details/i))[0])
74
+ expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
75
+ expect(await screen.findByText(/order number: 00028011/i)).toBeInTheDocument()
76
+ expect(
77
+ await screen.findByAltText(/Pleated Bib Long Sleeve Shirt, Silver Grey, small/i)
78
+ ).toBeInTheDocument()
79
+ expect(
80
+ await screen.findByAltText(/Long Sleeve Crew Neck, Fire Red, small/i)
81
+ ).toBeInTheDocument()
82
+ })
83
+
84
+ test('Renders order history place holder when no orders', async () => {
85
+ global.server.use(
86
+ rest.get('*/customers/:customerId/orders', (req, res, ctx) => {
87
+ return res(ctx.delay(0), ctx.json({limit: 0, offset: 0, total: 0}))
88
+ })
89
+ )
90
+ await renderWithProviders(<MockedComponent history={history} />, {
91
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
92
+ })
93
+
94
+ expect(await screen.findByTestId('account-order-history-place-holder')).toBeInTheDocument()
95
+ })
@@ -0,0 +1,357 @@
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, {useEffect, useState} from 'react'
9
+ import {FormattedMessage, useIntl} from 'react-intl'
10
+ import {
11
+ Alert,
12
+ Box,
13
+ Container,
14
+ Heading,
15
+ SimpleGrid,
16
+ Skeleton as ChakraSkeleton,
17
+ Stack,
18
+ Text,
19
+ useToast
20
+ } from '@chakra-ui/react'
21
+ import {useForm} from 'react-hook-form'
22
+ import {AlertIcon} from '@salesforce/retail-react-app/app/components/icons'
23
+ import {
24
+ ToggleCard,
25
+ ToggleCardEdit,
26
+ ToggleCardSummary
27
+ } from '@salesforce/retail-react-app/app/components/toggle-card'
28
+ import ProfileFields from '@salesforce/retail-react-app/app/components/forms/profile-fields'
29
+ import UpdatePasswordFields from '@salesforce/retail-react-app/app/components/forms/update-password-fields'
30
+ import FormActionButtons from '@salesforce/retail-react-app/app/components/forms/form-action-buttons'
31
+ import {
32
+ useShopperCustomersMutation,
33
+ useAuthHelper,
34
+ AuthHelpers
35
+ } from '@salesforce/commerce-sdk-react'
36
+ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
37
+
38
+ /**
39
+ * This is a specialized Skeleton component that which uses the customers authtype as the
40
+ * `isLoaded` state. It also will revert it's provided size (height, width) when the loaded
41
+ * state changes. This allows you to have skeletons of a specific size, but onece loaded
42
+ * the bounding element will affect the contents size.
43
+ */
44
+ // eslint-disable-next-line react/prop-types
45
+ const Skeleton = ({children, height, width, ...rest}) => {
46
+ const {data: customer} = useCurrentCustomer()
47
+ const {isRegistered} = customer
48
+ const size = !isRegistered
49
+ ? {
50
+ height,
51
+ width
52
+ }
53
+ : {}
54
+ return (
55
+ <ChakraSkeleton isLoaded={!customer.isLoading} {...rest} {...size}>
56
+ {children}
57
+ </ChakraSkeleton>
58
+ )
59
+ }
60
+
61
+ const ProfileCard = () => {
62
+ const {formatMessage} = useIntl()
63
+
64
+ const {data: customer} = useCurrentCustomer()
65
+ const {isRegistered, customerId} = customer
66
+
67
+ const updateCustomerMutation = useShopperCustomersMutation('updateCustomer')
68
+
69
+ const toast = useToast()
70
+ const [isEditing, setIsEditing] = useState(false)
71
+
72
+ const form = useForm({
73
+ defaultValues: {
74
+ firstName: customer?.firstName,
75
+ lastName: customer?.lastName,
76
+ email: customer?.email,
77
+ phone: customer?.phoneHome
78
+ }
79
+ })
80
+
81
+ useEffect(() => {
82
+ form.reset({
83
+ firstName: customer.firstName,
84
+ lastName: customer.lastName,
85
+ email: customer.email,
86
+ phone: customer.phoneHome
87
+ })
88
+ }, [customer?.firstName, customer?.lastName, customer?.email, customer?.phoneHome])
89
+
90
+ const submit = async (values) => {
91
+ try {
92
+ form.clearErrors()
93
+ updateCustomerMutation.mutate(
94
+ {
95
+ parameters: {customerId},
96
+ body: {
97
+ firstName: values.firstName,
98
+ lastName: values.lastName,
99
+ phoneHome: values.phone,
100
+ // NOTE/ISSUE
101
+ // The sdk is allowing you to change your email to an already-existing email.
102
+ // I would expect an error. We also want to keep the email and login the same
103
+ // for the customer, but the sdk isn't changing the login when we submit an
104
+ // updated email. This will lead to issues where you change your email but end
105
+ // up not being able to login since 'login' will no longer match the email.
106
+ email: values.email,
107
+ login: values.email
108
+ }
109
+ },
110
+ {
111
+ onSuccess: () => {
112
+ setIsEditing(false)
113
+ toast({
114
+ title: formatMessage({
115
+ defaultMessage: 'Profile updated',
116
+ id: 'profile_card.info.profile_updated'
117
+ }),
118
+ status: 'success',
119
+ isClosable: true
120
+ })
121
+ }
122
+ }
123
+ )
124
+ } catch (error) {
125
+ form.setError('global', {type: 'manual', message: error.message})
126
+ }
127
+ }
128
+
129
+ return (
130
+ <ToggleCard
131
+ id="my-profile"
132
+ title={
133
+ <Skeleton height="30px" width="120px">
134
+ {formatMessage({
135
+ defaultMessage: 'My Profile',
136
+ id: 'profile_card.title.my_profile'
137
+ })}
138
+ </Skeleton>
139
+ }
140
+ editing={isEditing}
141
+ isLoading={form.formState.isSubmitting}
142
+ onEdit={isRegistered ? () => setIsEditing(true) : undefined}
143
+ layerStyle="cardBordered"
144
+ >
145
+ <ToggleCardEdit>
146
+ <Container variant="form">
147
+ <form onSubmit={form.handleSubmit(submit)}>
148
+ <Stack spacing={6}>
149
+ {form.formState.errors?.global && (
150
+ <Alert status="error">
151
+ <AlertIcon color="red.500" boxSize={4} />
152
+ <Text fontSize="sm" ml={3}>
153
+ {form.formState.errors.global.message}
154
+ </Text>
155
+ </Alert>
156
+ )}
157
+ <ProfileFields form={form} />
158
+ <FormActionButtons onCancel={() => setIsEditing(false)} />
159
+ </Stack>
160
+ </form>
161
+ </Container>
162
+ </ToggleCardEdit>
163
+ <ToggleCardSummary>
164
+ <SimpleGrid columns={{base: 1, lg: 3}} spacing={4}>
165
+ <Box>
166
+ <Skeleton height="21px" width="84px" marginBottom={2}>
167
+ <Text fontSize="sm" fontWeight="bold">
168
+ <FormattedMessage
169
+ defaultMessage="Full Name"
170
+ id="profile_card.label.full_name"
171
+ />
172
+ </Text>
173
+ </Skeleton>
174
+
175
+ <Skeleton height="21px" width="140px">
176
+ <Text fontSize="sm">
177
+ {customer?.firstName} {customer?.lastName}
178
+ </Text>
179
+ </Skeleton>
180
+ </Box>
181
+ <Box>
182
+ <Skeleton height="21px" width="120px" marginBottom={2}>
183
+ <Text fontSize="sm" fontWeight="bold">
184
+ <FormattedMessage
185
+ defaultMessage="Email"
186
+ id="profile_card.label.email"
187
+ />
188
+ </Text>
189
+ </Skeleton>
190
+
191
+ <Skeleton height="21px" width="64px">
192
+ <Text fontSize="sm">{customer?.email}</Text>
193
+ </Skeleton>
194
+ </Box>
195
+ <Box>
196
+ <Skeleton height="21px" width="80px" marginBottom={2}>
197
+ <Text fontSize="sm" fontWeight="bold">
198
+ <FormattedMessage
199
+ defaultMessage="Phone Number"
200
+ id="profile_card.label.phone"
201
+ />
202
+ </Text>
203
+ </Skeleton>
204
+
205
+ <Skeleton height="21px" width="120px">
206
+ <Text fontSize="sm">
207
+ {customer?.phoneHome || (
208
+ <FormattedMessage
209
+ defaultMessage="Not provided"
210
+ id="profile_card.message.not_provided"
211
+ />
212
+ )}
213
+ </Text>
214
+ </Skeleton>
215
+ </Box>
216
+ </SimpleGrid>
217
+ </ToggleCardSummary>
218
+ </ToggleCard>
219
+ )
220
+ }
221
+
222
+ const PasswordCard = () => {
223
+ const {formatMessage} = useIntl()
224
+
225
+ const {data: customer} = useCurrentCustomer()
226
+ const {isRegistered, customerId} = customer
227
+
228
+ const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C)
229
+
230
+ const updateCustomerPassword = useShopperCustomersMutation('updateCustomerPassword')
231
+ const toast = useToast()
232
+ const [isEditing, setIsEditing] = useState(false)
233
+
234
+ const form = useForm()
235
+
236
+ const submit = async (values) => {
237
+ try {
238
+ form.clearErrors()
239
+ updateCustomerPassword.mutate(
240
+ {
241
+ parameters: {customerId},
242
+ body: {
243
+ password: values.password,
244
+ currentPassword: values.currentPassword
245
+ }
246
+ },
247
+ {
248
+ onSuccess: () => {
249
+ setIsEditing(false)
250
+ toast({
251
+ title: formatMessage({
252
+ defaultMessage: 'Password updated',
253
+ id: 'password_card.info.password_updated'
254
+ }),
255
+ status: 'success',
256
+ isClosable: true
257
+ })
258
+ login.mutate({
259
+ email: values.email,
260
+ password: values.password
261
+ })
262
+ }
263
+ }
264
+ )
265
+ setIsEditing(false)
266
+ toast({
267
+ title: formatMessage({
268
+ defaultMessage: 'Password updated',
269
+ id: 'password_card.info.password_updated'
270
+ }),
271
+ status: 'success',
272
+ isClosable: true
273
+ })
274
+ } catch (error) {
275
+ form.setError('global', {type: 'manual', message: error.message})
276
+ }
277
+ }
278
+
279
+ return (
280
+ <ToggleCard
281
+ id="password"
282
+ title={
283
+ <Skeleton height="30px" width="120px">
284
+ {formatMessage({
285
+ defaultMessage: 'Password',
286
+ id: 'password_card.title.password'
287
+ })}
288
+ </Skeleton>
289
+ }
290
+ editing={isEditing}
291
+ isLoading={form.formState.isSubmitting}
292
+ onEdit={isRegistered ? () => setIsEditing(true) : undefined}
293
+ layerStyle="cardBordered"
294
+ >
295
+ <ToggleCardEdit>
296
+ <Container variant="form">
297
+ <form onSubmit={form.handleSubmit(submit)}>
298
+ <Stack spacing={6}>
299
+ {form.formState.errors?.global && (
300
+ <Alert status="error">
301
+ <AlertIcon color="red.500" boxSize={4} />
302
+ <Text fontSize="sm" ml={3}>
303
+ {form.formState.errors.global.message}
304
+ </Text>
305
+ </Alert>
306
+ )}
307
+ <UpdatePasswordFields form={form} />
308
+ <FormActionButtons onCancel={() => setIsEditing(false)} />
309
+ </Stack>
310
+ </form>
311
+ </Container>
312
+ </ToggleCardEdit>
313
+ <ToggleCardSummary>
314
+ <SimpleGrid columns={{base: 1, lg: 3}} spacing={4}>
315
+ <Box>
316
+ <Skeleton height="21px" width="84px" marginBottom={2}>
317
+ <Text fontSize="sm" fontWeight="bold">
318
+ <FormattedMessage
319
+ defaultMessage="Password"
320
+ id="password_card.label.password"
321
+ />
322
+ </Text>
323
+ </Skeleton>
324
+
325
+ <Skeleton height="21px" width="140px">
326
+ <Text fontSize="sm">
327
+ &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;
328
+ </Text>
329
+ </Skeleton>
330
+ </Box>
331
+ </SimpleGrid>
332
+ </ToggleCardSummary>
333
+ </ToggleCard>
334
+ )
335
+ }
336
+
337
+ const AccountDetail = () => {
338
+ return (
339
+ <Stack data-testid="account-detail-page" spacing={6}>
340
+ <Heading as="h1" fontSize="24px">
341
+ <FormattedMessage
342
+ defaultMessage="Account Details"
343
+ id="account_detail.title.account_details"
344
+ />
345
+ </Heading>
346
+
347
+ <Stack spacing={4}>
348
+ <ProfileCard />
349
+ <PasswordCard />
350
+ </Stack>
351
+ </Stack>
352
+ )
353
+ }
354
+
355
+ AccountDetail.getTemplateName = () => 'account-detail'
356
+
357
+ export default AccountDetail
@@ -0,0 +1,195 @@
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, {useState} from 'react'
8
+ import {Stack, Heading} from '@chakra-ui/layout'
9
+ import {FormattedMessage, useIntl} from 'react-intl'
10
+ import {Box, Flex, Skeleton} from '@chakra-ui/react'
11
+ import {useProducts, useShopperCustomersMutation} from '@salesforce/commerce-sdk-react'
12
+
13
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
14
+ import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
15
+ import {useWishList} from '@salesforce/retail-react-app/app/hooks/use-wish-list'
16
+
17
+ import PageActionPlaceHolder from '@salesforce/retail-react-app/app/components/page-action-placeholder'
18
+ import {HeartIcon} from '@salesforce/retail-react-app/app/components/icons'
19
+ import ProductItem from '@salesforce/retail-react-app/app/components/product-item/index'
20
+ import WishlistPrimaryAction from '@salesforce/retail-react-app/app/pages/account/wishlist/partials/wishlist-primary-action'
21
+ import WishlistSecondaryButtonGroup from '@salesforce/retail-react-app/app/pages/account/wishlist/partials/wishlist-secondary-button-group'
22
+
23
+ import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
24
+ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
25
+
26
+ const numberOfSkeletonItems = 3
27
+
28
+ const AccountWishlist = () => {
29
+ const navigate = useNavigation()
30
+ const {formatMessage} = useIntl()
31
+ const toast = useToast()
32
+
33
+ const [selectedItem, setSelectedItem] = useState(undefined)
34
+ const [isWishlistItemLoading, setWishlistItemLoading] = useState(false)
35
+
36
+ const {data: wishListData, isLoading: isWishListLoading} = useWishList()
37
+ const productIds = wishListData?.customerProductListItems?.map((item) => item.productId)
38
+
39
+ const {data: productsData, isLoading: isProductsLoading} = useProducts(
40
+ {parameters: {ids: productIds?.join(','), allImages: true}},
41
+ {enabled: productIds?.length > 0}
42
+ )
43
+
44
+ const wishListItems = wishListData?.customerProductListItems?.map((item, i) => {
45
+ return {
46
+ ...item,
47
+ product: productsData?.data?.[i]
48
+ }
49
+ })
50
+
51
+ const updateCustomerProductListItem = useShopperCustomersMutation(
52
+ 'updateCustomerProductListItem'
53
+ )
54
+ const deleteCustomerProductListItem = useShopperCustomersMutation(
55
+ 'deleteCustomerProductListItem'
56
+ )
57
+ const {data: customer} = useCurrentCustomer()
58
+
59
+ const handleSecondaryAction = async (itemId, promise) => {
60
+ setWishlistItemLoading(true)
61
+ setSelectedItem(itemId)
62
+
63
+ try {
64
+ await promise
65
+ // No need to handle error here, as the inner component will take care of it
66
+ } finally {
67
+ setWishlistItemLoading(false)
68
+ setSelectedItem(undefined)
69
+ }
70
+ }
71
+
72
+ const handleItemQuantityChanged = async (quantity, item) => {
73
+ let isValidChange = false
74
+ setSelectedItem(item.productId)
75
+
76
+ const body = {
77
+ ...item,
78
+ quantity: parseInt(quantity)
79
+ }
80
+ // To meet expected schema, remove the custom `product` we added
81
+ delete body.product
82
+
83
+ const parameters = {
84
+ customerId: customer.customerId,
85
+ itemId: item.id,
86
+ listId: wishListData?.id
87
+ }
88
+
89
+ const mutation =
90
+ parseInt(quantity) > 0
91
+ ? updateCustomerProductListItem.mutateAsync({body, parameters})
92
+ : deleteCustomerProductListItem.mutateAsync({parameters})
93
+
94
+ try {
95
+ await mutation
96
+ isValidChange = true
97
+ setSelectedItem(undefined)
98
+ } catch (err) {
99
+ toast({
100
+ title: formatMessage(API_ERROR_MESSAGE),
101
+ status: 'error'
102
+ })
103
+ }
104
+
105
+ // If true, the quantity picker would immediately update its number
106
+ // without waiting for the invalidated lists data to finish refetching
107
+ return isValidChange
108
+ }
109
+
110
+ const isPageLoading = wishListItems ? isProductsLoading : isWishListLoading
111
+
112
+ return (
113
+ <Stack spacing={4} data-testid="account-wishlist-page">
114
+ <Heading as="h1" fontSize="2xl">
115
+ <FormattedMessage defaultMessage="Wishlist" id="account_wishlist.title.wishlist" />
116
+ </Heading>
117
+
118
+ {isPageLoading && (
119
+ <Box data-testid="sf-wishlist-skeleton">
120
+ {new Array(numberOfSkeletonItems).fill(0).map((i, idx) => (
121
+ <Box
122
+ key={idx}
123
+ p={[4, 6]}
124
+ my={4}
125
+ border="1px solid"
126
+ borderColor="gray.100"
127
+ borderRadius="base"
128
+ >
129
+ <Flex width="full" align="flex-start">
130
+ <Skeleton boxSize={['88px', 36]} mr={4} />
131
+
132
+ <Stack spacing={2}>
133
+ <Skeleton h="20px" w="112px" />
134
+ <Skeleton h="20px" w="84px" />
135
+ <Skeleton h="20px" w="140px" />
136
+ </Stack>
137
+ </Flex>
138
+ </Box>
139
+ ))}
140
+ </Box>
141
+ )}
142
+
143
+ {!isPageLoading && !wishListItems && (
144
+ <PageActionPlaceHolder
145
+ data-testid="empty-wishlist"
146
+ icon={<HeartIcon boxSize={8} />}
147
+ heading={formatMessage({
148
+ defaultMessage: 'No Wishlist Items',
149
+ id: 'account_wishlist.heading.no_wishlist'
150
+ })}
151
+ text={formatMessage({
152
+ defaultMessage: 'Continue shopping and add items to your wishlist.',
153
+ id: 'account_wishlist.description.continue_shopping'
154
+ })}
155
+ buttonText={formatMessage({
156
+ defaultMessage: 'Continue Shopping',
157
+ id: 'account_wishlist.button.continue_shopping'
158
+ })}
159
+ buttonProps={{leftIcon: undefined}}
160
+ onButtonClick={() => navigate('/')}
161
+ />
162
+ )}
163
+
164
+ {!isPageLoading &&
165
+ wishListItems &&
166
+ wishListItems.map((item) => (
167
+ <ProductItem
168
+ key={item.id}
169
+ product={{
170
+ ...item.product,
171
+ quantity: item.quantity
172
+ }}
173
+ showLoading={
174
+ (updateCustomerProductListItem.isLoading ||
175
+ deleteCustomerProductListItem.isLoading ||
176
+ isWishlistItemLoading) &&
177
+ selectedItem === item.productId
178
+ }
179
+ primaryAction={<WishlistPrimaryAction />}
180
+ onItemQuantityChange={(quantity) =>
181
+ handleItemQuantityChanged(quantity, item)
182
+ }
183
+ secondaryActions={
184
+ <WishlistSecondaryButtonGroup
185
+ productListItemId={item.id}
186
+ onClick={handleSecondaryAction}
187
+ />
188
+ }
189
+ />
190
+ ))}
191
+ </Stack>
192
+ )
193
+ }
194
+
195
+ export default AccountWishlist