@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,279 @@
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, {useEffect, useMemo, useRef, useState} from 'react'
8
+ import {useSearchSuggestions} from '@salesforce/commerce-sdk-react'
9
+ import {
10
+ Input,
11
+ InputGroup,
12
+ InputLeftElement,
13
+ Popover,
14
+ PopoverTrigger,
15
+ PopoverContent,
16
+ Button,
17
+ Box,
18
+ Flex,
19
+ HStack,
20
+ Spinner
21
+ } from '@chakra-ui/react'
22
+ import SearchSuggestions from '@salesforce/retail-react-app/app/components/search/partials/search-suggestions'
23
+ import {SearchIcon} from '@salesforce/retail-react-app/app/components/icons'
24
+ import {
25
+ capitalize,
26
+ boldString,
27
+ getSessionJSONItem,
28
+ setSessionJSONItem
29
+ } from '@salesforce/retail-react-app/app/utils/utils'
30
+ import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
31
+ import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
32
+ import {FormattedMessage} from 'react-intl'
33
+ import debounce from 'lodash/debounce'
34
+ import {
35
+ RECENT_SEARCH_KEY,
36
+ RECENT_SEARCH_LIMIT,
37
+ RECENT_SEARCH_MIN_LENGTH
38
+ } from '@salesforce/retail-react-app/app/constants'
39
+ import {
40
+ productUrlBuilder,
41
+ searchUrlBuilder,
42
+ categoryUrlBuilder
43
+ } from '@salesforce/retail-react-app/app/utils/url'
44
+
45
+ const formatSuggestions = (searchSuggestions, input) => {
46
+ return {
47
+ categorySuggestions: searchSuggestions?.categorySuggestions?.categories?.map(
48
+ (suggestion) => {
49
+ return {
50
+ type: 'category',
51
+ id: suggestion.id,
52
+ link: categoryUrlBuilder({id: suggestion.id}),
53
+ name: boldString(suggestion.name, capitalize(input))
54
+ }
55
+ }
56
+ ),
57
+ productSuggestions: searchSuggestions?.productSuggestions?.products?.map((product) => {
58
+ return {
59
+ type: 'product',
60
+ currency: product.currency,
61
+ price: product.price,
62
+ productId: product.productId,
63
+ name: boldString(product.productName, capitalize(input)),
64
+ link: productUrlBuilder({id: product.productId})
65
+ }
66
+ }),
67
+ phraseSuggestions: searchSuggestions?.categorySuggestions?.suggestedPhrases?.map(
68
+ (phrase) => {
69
+ return {
70
+ type: 'phrase',
71
+ name: boldString(phrase.phrase, capitalize(input)),
72
+ link: searchUrlBuilder(phrase.phrase)
73
+ }
74
+ }
75
+ )
76
+ }
77
+ }
78
+
79
+ /**
80
+ * The SearchInput component is a stylized
81
+ * text input made specifically for use in
82
+ * the application header.
83
+ * @param {object} props
84
+ * @param {object} ref reference to the input element
85
+ * @return {React.ReactElement} - SearchInput component
86
+ */
87
+ const Search = (props) => {
88
+ const [isOpen, setIsOpen] = useState(false)
89
+ const [searchQuery, setSearchQuery] = useState('')
90
+ const navigate = useNavigation()
91
+ const searchSuggestion = useSearchSuggestions(
92
+ {
93
+ parameters: {
94
+ q: searchQuery
95
+ }
96
+ },
97
+ {
98
+ enabled: searchQuery?.length >= RECENT_SEARCH_MIN_LENGTH
99
+ }
100
+ )
101
+ const searchInputRef = useRef()
102
+ const recentSearches = getSessionJSONItem(RECENT_SEARCH_KEY)
103
+ const searchSuggestions = useMemo(
104
+ () => formatSuggestions(searchSuggestion.data, searchInputRef?.current?.value),
105
+ [searchSuggestion]
106
+ )
107
+
108
+ // check if popover should open if we have suggestions
109
+ useEffect(() => {
110
+ shouldOpenPopover()
111
+ }, [searchQuery, searchSuggestion.data])
112
+
113
+ const searchSuggestionsAvailable =
114
+ searchSuggestions &&
115
+ (searchSuggestions?.categorySuggestions?.length ||
116
+ searchSuggestions?.phraseSuggestions?.length)
117
+
118
+ const saveRecentSearch = (searchText) => {
119
+ // Get recent searches or an empty array if undefined.
120
+ let searches = getSessionJSONItem(RECENT_SEARCH_KEY) || []
121
+
122
+ // Check if term is already in the saved searches
123
+ searches = searches.filter((savedSearchTerm) => {
124
+ return searchText.toLowerCase() !== savedSearchTerm.toLowerCase()
125
+ })
126
+
127
+ // Create a new array consisting of the search text and up to 4 other resent searches.
128
+ // I'm assuming the order is newest to oldest.
129
+ searches = [searchText, ...searches].slice(0, RECENT_SEARCH_LIMIT)
130
+
131
+ // Replace the save resent search with the updated value.
132
+ setSessionJSONItem(RECENT_SEARCH_KEY, searches)
133
+ }
134
+
135
+ const debouncedSearch = debounce((input) => {
136
+ debouncedSearch.cancel()
137
+ setSearchQuery(input)
138
+ }, 300)
139
+
140
+ const onSearchChange = async (e) => {
141
+ const input = e.target.value
142
+ if (input.length >= RECENT_SEARCH_MIN_LENGTH) {
143
+ debouncedSearch(input)
144
+ } else {
145
+ setSearchQuery('')
146
+ }
147
+ }
148
+
149
+ const clearInput = () => {
150
+ searchInputRef.current.blur()
151
+ setIsOpen(false)
152
+ }
153
+
154
+ const onSubmitSearch = (e) => {
155
+ e.preventDefault()
156
+ // Avoid blank spaces to be searched
157
+ let searchText = searchInputRef.current.value.trim()
158
+ // Avoid empty string searches
159
+ if (searchText.length < 1) {
160
+ return
161
+ }
162
+ saveRecentSearch(searchText)
163
+ clearInput()
164
+ navigate(searchUrlBuilder(searchText))
165
+ }
166
+
167
+ const closeAndNavigate = (link) => {
168
+ if (!link) {
169
+ clearInput()
170
+ setIsOpen(false)
171
+ } else {
172
+ clearInput()
173
+ setIsOpen(false)
174
+ navigate(link)
175
+ }
176
+ }
177
+
178
+ const shouldOpenPopover = () => {
179
+ // As per design we only want to show the popover if the input is focused and we have recent searches saved
180
+ // or we have search suggestions available and have inputed some text (empty text in this scenario should show recent searches)
181
+ if (
182
+ (document.activeElement.id === 'search-input' && recentSearches?.length > 0) ||
183
+ (searchSuggestionsAvailable && searchInputRef.current.value.length > 0)
184
+ ) {
185
+ setIsOpen(true)
186
+ } else {
187
+ setIsOpen(false)
188
+ }
189
+ }
190
+
191
+ const onSearchInputChange = (e) => {
192
+ onSearchChange(e)
193
+ shouldOpenPopover()
194
+ }
195
+
196
+ return (
197
+ <Box>
198
+ <Popover isOpen={isOpen} isLazy initialFocusRef={searchInputRef}>
199
+ <PopoverTrigger>
200
+ <form onSubmit={onSubmitSearch}>
201
+ <HStack>
202
+ <InputGroup>
203
+ <InputLeftElement pointerEvents="none">
204
+ <SearchIcon />
205
+ </InputLeftElement>
206
+ <Input
207
+ autoComplete="off"
208
+ id="search-input"
209
+ onChange={(e) => onSearchInputChange(e)}
210
+ onFocus={() => shouldOpenPopover()}
211
+ onBlur={() => setIsOpen(false)}
212
+ type="search"
213
+ ref={searchInputRef}
214
+ {...props}
215
+ variant="filled"
216
+ />
217
+ </InputGroup>
218
+ <HideOnDesktop>
219
+ <Button
220
+ display={isOpen ? 'block' : 'none'}
221
+ variant="link"
222
+ size="sm"
223
+ onMouseDown={() => closeAndNavigate(false)}
224
+ >
225
+ <FormattedMessage
226
+ defaultMessage="Cancel"
227
+ id="search.action.cancel"
228
+ />
229
+ </Button>
230
+ </HideOnDesktop>
231
+ </HStack>
232
+ </form>
233
+ </PopoverTrigger>
234
+
235
+ <HideOnMobile>
236
+ <PopoverContent data-testid="sf-suggestion-popover">
237
+ <SearchSuggestions
238
+ closeAndNavigate={closeAndNavigate}
239
+ recentSearches={recentSearches}
240
+ searchSuggestions={searchSuggestions}
241
+ />
242
+ </PopoverContent>
243
+ </HideOnMobile>
244
+ </Popover>
245
+ <HideOnDesktop>
246
+ <Flex
247
+ display={isOpen || searchInputRef?.value?.length > 0 ? 'block' : 'none'}
248
+ postion="absolute"
249
+ background="white"
250
+ left={0}
251
+ right={0}
252
+ height="100vh"
253
+ >
254
+ {searchSuggestion.isLoading ? (
255
+ <Spinner
256
+ position="absolute"
257
+ top="50%"
258
+ left="50%"
259
+ opacity={0.85}
260
+ color="blue.600"
261
+ zIndex="9999"
262
+ margin="-25px 0 0 -25px"
263
+ />
264
+ ) : (
265
+ <SearchSuggestions
266
+ closeAndNavigate={closeAndNavigate}
267
+ recentSearches={recentSearches}
268
+ searchSuggestions={searchSuggestions}
269
+ />
270
+ )}
271
+ </Flex>
272
+ </HideOnDesktop>
273
+ </Box>
274
+ )
275
+ }
276
+
277
+ Search.displayName = 'SearchInput'
278
+
279
+ export default Search
@@ -0,0 +1,127 @@
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 {
9
+ renderWithProviders,
10
+ createPathWithDefaults
11
+ } from '@salesforce/retail-react-app/app/utils/test-utils'
12
+ import userEvent from '@testing-library/user-event'
13
+ import {screen, waitFor, within} from '@testing-library/react'
14
+ import SearchInput from '@salesforce/retail-react-app/app/components/search/index'
15
+ import Suggestions from '@salesforce/retail-react-app/app/components/search/partials/suggestions'
16
+ import {
17
+ clearSessionJSONItem,
18
+ getSessionJSONItem,
19
+ setSessionJSONItem,
20
+ noop
21
+ } from '@salesforce/retail-react-app/app/utils/utils'
22
+ import {RECENT_SEARCH_KEY, RECENT_SEARCH_LIMIT} from '@salesforce/retail-react-app/app/constants'
23
+ import mockSearchResults from '@salesforce/retail-react-app/app/mocks/searchResults'
24
+ import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
25
+ import {rest} from 'msw'
26
+ import {mockCustomerBaskets} from '@salesforce/retail-react-app/app/mocks/mock-data'
27
+
28
+ beforeEach(() => {
29
+ clearSessionJSONItem(RECENT_SEARCH_KEY)
30
+ jest.resetModules()
31
+ global.server.use(
32
+ rest.get('*/search-suggestions', (req, res, ctx) => {
33
+ return res(ctx.delay(0), ctx.status(200), ctx.json(mockSearchResults))
34
+ }),
35
+ rest.get('*/customers/:customerId/baskets', (req, res, ctx) => {
36
+ return res(ctx.delay(0), ctx.status(200), ctx.json(mockCustomerBaskets))
37
+ })
38
+ )
39
+ })
40
+
41
+ test('renders SearchInput', () => {
42
+ renderWithProviders(<SearchInput />)
43
+ const searchInput = document.querySelector('input[type="search"]')
44
+ expect(searchInput).toBeInTheDocument()
45
+ })
46
+
47
+ test('changes url when enter is pressed', async () => {
48
+ const user = userEvent.setup()
49
+
50
+ renderWithProviders(<SearchInput />, {
51
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
52
+ })
53
+ const searchInput = document.querySelector('input[type="search"]')
54
+ await user.type(searchInput, 'Dresses{enter}')
55
+ await waitFor(() => {
56
+ expect(window.location.pathname).toEqual(createPathWithDefaults('/search'))
57
+ expect(window.location.search).toBe('?q=Dresses')
58
+ const suggestionPopoverEl = screen.getByTestId('sf-suggestion-popover')
59
+ expect(suggestionPopoverEl).toBeInTheDocument()
60
+ })
61
+ })
62
+
63
+ test('shows previously searched items when focused', async () => {
64
+ const user = userEvent.setup()
65
+
66
+ setSessionJSONItem(RECENT_SEARCH_KEY, ['Dresses', 'Suits', 'Tops'])
67
+ renderWithProviders(<SearchInput />)
68
+ const searchInput = document.querySelector('input[type="search"]')
69
+ await user.clear(searchInput)
70
+ await searchInput.focus()
71
+ const suggestionPopoverEl = await screen.getByTestId('sf-suggestion-popover')
72
+ const recentSearchesEl = await within(suggestionPopoverEl).getByTestId('sf-suggestion-recent')
73
+ expect(recentSearchesEl).toBeInTheDocument()
74
+ expect(
75
+ document.querySelectorAll('[data-testid=sf-suggestion-popover] button[name=recent-search]')
76
+ ).toHaveLength(3)
77
+ })
78
+
79
+ test('saves recent searches on submit', async () => {
80
+ const user = userEvent.setup()
81
+ setSessionJSONItem(RECENT_SEARCH_KEY, ['Dresses', 'Suits', 'Tops'])
82
+ renderWithProviders(<SearchInput />)
83
+ const searchInput = document.querySelector('input[type="search"]')
84
+ await user.type(searchInput, 'Gloves{enter}')
85
+ expect(getSessionJSONItem(RECENT_SEARCH_KEY)).toHaveLength(4)
86
+ })
87
+
88
+ test('limits number of saved recent searches', async () => {
89
+ const user = userEvent.setup()
90
+
91
+ setSessionJSONItem(RECENT_SEARCH_KEY, ['Dresses', 'Suits', 'Tops', 'Gloves', 'Bracelets'])
92
+ renderWithProviders(<SearchInput />)
93
+ const searchInput = document.querySelector('input[type="search"]')
94
+ await user.type(searchInput, 'Ties{enter}')
95
+ expect(getSessionJSONItem(RECENT_SEARCH_KEY)).toHaveLength(RECENT_SEARCH_LIMIT)
96
+ })
97
+
98
+ test('suggestions render when there are some', async () => {
99
+ const user = userEvent.setup()
100
+ renderWithProviders(<SearchInput />)
101
+ const searchInput = document.querySelector('input[type="search"]')
102
+ await user.type(searchInput, 'Dress')
103
+ expect(searchInput.value).toBe('Dress')
104
+ const suggestionPopoverEl = await screen.getByTestId('sf-suggestion-popover')
105
+ await waitFor(() => {
106
+ const suggestionsEl = within(suggestionPopoverEl).getByTestId('sf-suggestion')
107
+ expect(suggestionsEl.querySelector('button').textContent).toBe('Dresses')
108
+ })
109
+ })
110
+
111
+ test('clicking clear searches clears recent searches', async () => {
112
+ const user = userEvent.setup()
113
+ setSessionJSONItem(RECENT_SEARCH_KEY, ['Dresses', 'Suits', 'Tops'])
114
+ renderWithProviders(<SearchInput />)
115
+ const searchInput = document.querySelector('input[type="search"]')
116
+ await searchInput.focus()
117
+ const clearSearch = document.getElementById('clear-search')
118
+ await user.click(clearSearch)
119
+ expect(getSessionJSONItem(RECENT_SEARCH_KEY)).toBeUndefined()
120
+ })
121
+
122
+ test('passing undefined to Suggestions returns undefined', async () => {
123
+ const suggestions = renderWithProviders(
124
+ <Suggestions suggestions={undefined} closeAndNavigate={noop} />
125
+ )
126
+ expect(suggestions.innerHTML).toBeUndefined()
127
+ })
@@ -0,0 +1,76 @@
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 PropTypes from 'prop-types'
9
+ import {Text, Button, Box} from '@chakra-ui/react'
10
+
11
+ import {clearSessionJSONItem} from '@salesforce/retail-react-app/app/utils/utils'
12
+ import {RECENT_SEARCH_KEY} from '@salesforce/retail-react-app/app/constants'
13
+
14
+ import {FormattedMessage} from 'react-intl'
15
+ import {searchUrlBuilder} from '@salesforce/retail-react-app/app/utils/url'
16
+
17
+ const RecentSearches = ({recentSearches, closeAndNavigate}) => {
18
+ const clearSearches = () => {
19
+ clearSessionJSONItem(RECENT_SEARCH_KEY)
20
+ closeAndNavigate(false)
21
+ }
22
+ return (
23
+ <Box>
24
+ {recentSearches?.length > 0 && (
25
+ <Box>
26
+ <Text fontWeight="700" fontSize={'md'} data-testid="sf-suggestion-recent">
27
+ <FormattedMessage
28
+ defaultMessage="Recent Searches"
29
+ id="recent_searches.heading.recent_searches"
30
+ />
31
+ </Text>
32
+ <Box mx={'-16px'}>
33
+ {recentSearches.map((recentSearch, idx) => (
34
+ <Button
35
+ width="full"
36
+ role="button"
37
+ name="recent-search"
38
+ fontSize={'md'}
39
+ key={idx}
40
+ onMouseDown={() => {
41
+ closeAndNavigate(searchUrlBuilder(recentSearch))
42
+ }}
43
+ variant="menu-link"
44
+ >
45
+ <Text fontWeight="400">{recentSearch}</Text>
46
+ </Button>
47
+ ))}
48
+ <Button
49
+ data-testid="sf-clear-search"
50
+ id="clear-search"
51
+ width="full"
52
+ onMouseDown={() => {
53
+ clearSearches()
54
+ }}
55
+ variant="menu-link"
56
+ >
57
+ <Text fontWeight="400" color="blue.600" fontSize={'md'}>
58
+ <FormattedMessage
59
+ defaultMessage="Clear recent searches"
60
+ id="recent_searches.action.clear_searches"
61
+ />
62
+ </Text>
63
+ </Button>
64
+ </Box>
65
+ </Box>
66
+ )}
67
+ </Box>
68
+ )
69
+ }
70
+
71
+ RecentSearches.propTypes = {
72
+ recentSearches: PropTypes.array,
73
+ closeAndNavigate: PropTypes.func
74
+ }
75
+
76
+ export default RecentSearches
@@ -0,0 +1,45 @@
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, {Fragment} from 'react'
8
+ import PropTypes from 'prop-types'
9
+ import {Stack} from '@chakra-ui/react'
10
+ import RecentSearches from '@salesforce/retail-react-app/app/components/search/partials/recent-searches'
11
+ import Suggestions from '@salesforce/retail-react-app/app/components/search/partials/suggestions'
12
+
13
+ const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}) => {
14
+ const useSuggestions = searchSuggestions && searchSuggestions?.categorySuggestions?.length
15
+ return (
16
+ <Stack padding={6} spacing={0}>
17
+ {useSuggestions ? (
18
+ <Fragment>
19
+ <Suggestions
20
+ closeAndNavigate={closeAndNavigate}
21
+ suggestions={searchSuggestions?.categorySuggestions}
22
+ />
23
+ {/* <Suggestions
24
+ closeAndNavigate={closeAndNavigate}
25
+ suggestions={searchSuggestions?.phraseSuggestions}
26
+ /> */}
27
+ {/* <Suggestions suggestions={searchSuggestions.productSuggestions} /> */}
28
+ </Fragment>
29
+ ) : (
30
+ <RecentSearches
31
+ recentSearches={recentSearches}
32
+ closeAndNavigate={closeAndNavigate}
33
+ />
34
+ )}
35
+ </Stack>
36
+ )
37
+ }
38
+
39
+ SearchSuggestions.propTypes = {
40
+ recentSearches: PropTypes.array,
41
+ searchSuggestions: PropTypes.object,
42
+ closeAndNavigate: PropTypes.func
43
+ }
44
+
45
+ export default SearchSuggestions
@@ -0,0 +1,43 @@
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 PropTypes from 'prop-types'
9
+ import {Text, Button, Stack, Box} from '@chakra-ui/react'
10
+
11
+ const Suggestions = ({suggestions, closeAndNavigate}) => {
12
+ if (!suggestions) {
13
+ return null
14
+ }
15
+ return (
16
+ <Stack spacing={0} data-testid="sf-suggestion">
17
+ <Box mx={'-16px'}>
18
+ {suggestions.map((suggestion, idx) => (
19
+ <Button
20
+ width="full"
21
+ onMouseDown={() => closeAndNavigate(suggestion.link)}
22
+ fontSize={'md'}
23
+ key={idx}
24
+ marginTop={0}
25
+ variant="menu-link"
26
+ >
27
+ <Text
28
+ fontWeight="400"
29
+ dangerouslySetInnerHTML={{__html: suggestion.name}}
30
+ />
31
+ </Button>
32
+ ))}
33
+ </Box>
34
+ </Stack>
35
+ )
36
+ }
37
+
38
+ Suggestions.propTypes = {
39
+ suggestions: PropTypes.array,
40
+ closeAndNavigate: PropTypes.func
41
+ }
42
+
43
+ export default Suggestions
@@ -0,0 +1,68 @@
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 PropTypes from 'prop-types'
10
+ import {Box, Heading, Stack, Text, Container} from '@chakra-ui/react'
11
+
12
+ /**
13
+ * Section component used on content pages like home page.
14
+ * This component helps with creating a consistent layout and
15
+ * consistent typography styles for section headings.
16
+ */
17
+ const Section = ({title, subtitle, actions, maxWidth, children, ...props}) => {
18
+ const sectionMaxWidth = maxWidth || '3xl'
19
+ return (
20
+ <Box as={'section'} paddingBottom="16" {...props}>
21
+ <Stack spacing={4} as={Container} maxW={sectionMaxWidth} textAlign={'center'}>
22
+ {title && (
23
+ <Heading as="h2" fontSize={40} textAlign="center">
24
+ {title}
25
+ </Heading>
26
+ )}
27
+ {subtitle && (
28
+ <Text color={'gray.700'} fontWeight={600}>
29
+ {subtitle}
30
+ </Text>
31
+ )}
32
+ {actions && (
33
+ <Box paddingTop="2" width={{base: 'full', md: 'auto'}}>
34
+ {actions}
35
+ </Box>
36
+ )}
37
+ </Stack>
38
+ {children}
39
+ </Box>
40
+ )
41
+ }
42
+
43
+ Section.displayName = 'Section'
44
+
45
+ Section.propTypes = {
46
+ /**
47
+ * Section component main title
48
+ */
49
+ title: PropTypes.string,
50
+ /**
51
+ * Section component subtitle
52
+ */
53
+ subtitle: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.node]),
54
+ /**
55
+ * Section children node(s)
56
+ */
57
+ children: PropTypes.node,
58
+ /**
59
+ * Call to action component(s)
60
+ */
61
+ actions: PropTypes.element,
62
+ /**
63
+ * Section maximum width
64
+ */
65
+ maxWidth: PropTypes.string
66
+ }
67
+
68
+ export default Section
@@ -0,0 +1,33 @@
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 PropTypes from 'prop-types'
9
+ import Helmet from 'react-helmet'
10
+
11
+ import {DEFAULT_SITE_TITLE} from '@salesforce/retail-react-app/app/constants'
12
+
13
+ const Seo = ({title, description, noIndex, children, ...props}) => {
14
+ const fullTitle = title ? `${title} | ${DEFAULT_SITE_TITLE}` : DEFAULT_SITE_TITLE
15
+
16
+ return (
17
+ <Helmet {...props}>
18
+ <title>{fullTitle}</title>
19
+ {description && <meta name="description" content={description} />}
20
+ {noIndex && <meta name="robots" content="noindex" />}
21
+ {children}
22
+ </Helmet>
23
+ )
24
+ }
25
+
26
+ Seo.propTypes = {
27
+ title: PropTypes.string,
28
+ description: PropTypes.string,
29
+ noIndex: PropTypes.bool,
30
+ children: PropTypes.node
31
+ }
32
+
33
+ export default Seo