@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.
- package/.eslintignore +7 -0
- package/.eslintrc.js +25 -0
- package/.prettierignore +4 -0
- package/.prettierrc.yaml +7 -0
- package/CHANGELOG.md +173 -0
- package/LICENSE +14 -0
- package/README.md +48 -0
- package/app/assets/svg/account.svg +3 -0
- package/app/assets/svg/alert.svg +3 -0
- package/app/assets/svg/basket.svg +3 -0
- package/app/assets/svg/brand-logo.svg +10 -0
- package/app/assets/svg/cc-amex.svg +7 -0
- package/app/assets/svg/cc-cvv.svg +8 -0
- package/app/assets/svg/cc-discover.svg +14 -0
- package/app/assets/svg/cc-mastercard.svg +8 -0
- package/app/assets/svg/cc-visa.svg +11 -0
- package/app/assets/svg/check-circle.svg +3 -0
- package/app/assets/svg/check.svg +3 -0
- package/app/assets/svg/chevron-down.svg +3 -0
- package/app/assets/svg/chevron-left.svg +3 -0
- package/app/assets/svg/chevron-right.svg +3 -0
- package/app/assets/svg/chevron-up.svg +3 -0
- package/app/assets/svg/close.svg +3 -0
- package/app/assets/svg/dashboard.svg +4 -0
- package/app/assets/svg/figma-logo.svg +14 -0
- package/app/assets/svg/file.svg +3 -0
- package/app/assets/svg/filter.svg +3 -0
- package/app/assets/svg/flag-ca.svg +5 -0
- package/app/assets/svg/flag-cn.svg +19 -0
- package/app/assets/svg/flag-fr.svg +19 -0
- package/app/assets/svg/flag-gb.svg +16 -0
- package/app/assets/svg/flag-it.svg +29 -0
- package/app/assets/svg/flag-jp.svg +10 -0
- package/app/assets/svg/flag-us.svg +7 -0
- package/app/assets/svg/github-logo.svg +40 -0
- package/app/assets/svg/hamburger.svg +8 -0
- package/app/assets/svg/heart-solid.svg +7 -0
- package/app/assets/svg/heart.svg +3 -0
- package/app/assets/svg/info.svg +3 -0
- package/app/assets/svg/like.svg +4 -0
- package/app/assets/svg/location.svg +3 -0
- package/app/assets/svg/lock.svg +3 -0
- package/app/assets/svg/paypal.svg +19 -0
- package/app/assets/svg/plug.svg +3 -0
- package/app/assets/svg/plus.svg +3 -0
- package/app/assets/svg/receipt.svg +3 -0
- package/app/assets/svg/search.svg +8 -0
- package/app/assets/svg/signout.svg +3 -0
- package/app/assets/svg/social-facebook.svg +3 -0
- package/app/assets/svg/social-instagram.svg +3 -0
- package/app/assets/svg/social-pinterest.svg +4 -0
- package/app/assets/svg/social-twitter.svg +3 -0
- package/app/assets/svg/social-youtube.svg +3 -0
- package/app/assets/svg/user.svg +3 -0
- package/app/assets/svg/visibility-off.svg +5 -0
- package/app/assets/svg/visibility.svg +3 -0
- package/app/components/_app/index.jsx +401 -0
- package/app/components/_app/index.test.js +85 -0
- package/app/components/_app/partials/above-header.jsx +10 -0
- package/app/components/_app-config/index.jsx +125 -0
- package/app/components/_app-config/index.test.js +77 -0
- package/app/components/_error/index.jsx +142 -0
- package/app/components/_error/index.test.js +25 -0
- package/app/components/action-card/index.jsx +75 -0
- package/app/components/address-display/index.jsx +30 -0
- package/app/components/basic-tile/index.jsx +65 -0
- package/app/components/basic-tile/index.test.js +23 -0
- package/app/components/breadcrumb/index.jsx +67 -0
- package/app/components/breadcrumb/index.test.js +30 -0
- package/app/components/confirmation-modal/index.jsx +111 -0
- package/app/components/confirmation-modal/index.test.js +98 -0
- package/app/components/drawer-menu/index.jsx +405 -0
- package/app/components/drawer-menu/index.test.js +33 -0
- package/app/components/dynamic-image/index.jsx +56 -0
- package/app/components/field/index.jsx +161 -0
- package/app/components/footer/index.jsx +269 -0
- package/app/components/footer/index.test.js +22 -0
- package/app/components/forms/address-fields.jsx +49 -0
- package/app/components/forms/credit-card-fields.jsx +149 -0
- package/app/components/forms/form-action-buttons.jsx +55 -0
- package/app/components/forms/login-fields.jsx +31 -0
- package/app/components/forms/password-requirements.jsx +99 -0
- package/app/components/forms/post-checkout-registration-fields.jsx +43 -0
- package/app/components/forms/profile-fields.jsx +36 -0
- package/app/components/forms/promo-code-fields.jsx +43 -0
- package/app/components/forms/registration-fields.jsx +42 -0
- package/app/components/forms/reset-password-fields.jsx +31 -0
- package/app/components/forms/state-province-options.jsx +75 -0
- package/app/components/forms/update-password-fields.jsx +49 -0
- package/app/components/forms/useAddressFields.jsx +196 -0
- package/app/components/forms/useCreditCardFields.jsx +146 -0
- package/app/components/forms/useLoginFields.jsx +52 -0
- package/app/components/forms/useProfileFields.jsx +95 -0
- package/app/components/forms/usePromoCodeFields.jsx +39 -0
- package/app/components/forms/useRegistrationFields.jsx +136 -0
- package/app/components/forms/useResetPasswordFields.jsx +40 -0
- package/app/components/forms/useUpdatePasswordFields.jsx +89 -0
- package/app/components/header/index.jsx +290 -0
- package/app/components/header/index.test.js +217 -0
- package/app/components/hero/index.jsx +84 -0
- package/app/components/hero/index.test.js +40 -0
- package/app/components/icons/index.jsx +158 -0
- package/app/components/icons/index.test.js +20 -0
- package/app/components/image-gallery/index.jsx +176 -0
- package/app/components/image-gallery/index.test.js +485 -0
- package/app/components/item-variant/index.jsx +33 -0
- package/app/components/item-variant/item-attributes.jsx +107 -0
- package/app/components/item-variant/item-image.jsx +73 -0
- package/app/components/item-variant/item-name.jsx +28 -0
- package/app/components/item-variant/item-price.jsx +117 -0
- package/app/components/link/index.jsx +32 -0
- package/app/components/link/index.test.js +72 -0
- package/app/components/links-list/index.jsx +89 -0
- package/app/components/links-list/index.test.js +62 -0
- package/app/components/list-menu/index.jsx +280 -0
- package/app/components/list-menu/index.test.js +44 -0
- package/app/components/loading-spinner/index.jsx +46 -0
- package/app/components/locale-selector/index.jsx +124 -0
- package/app/components/locale-selector/index.test.js +37 -0
- package/app/components/locale-text/index.jsx +97 -0
- package/app/components/locale-text/index.test.js +36 -0
- package/app/components/login/index.jsx +96 -0
- package/app/components/nested-accordion/index.jsx +185 -0
- package/app/components/nested-accordion/index.test.js +98 -0
- package/app/components/offline-banner/index.jsx +40 -0
- package/app/components/offline-banner/index.test.js +15 -0
- package/app/components/offline-boundary/index.jsx +104 -0
- package/app/components/offline-boundary/index.test.js +123 -0
- package/app/components/order-summary/index.jsx +331 -0
- package/app/components/page-action-placeholder/index.jsx +50 -0
- package/app/components/pagination/index.jsx +134 -0
- package/app/components/pagination/index.test.js +25 -0
- package/app/components/product-item/index.jsx +146 -0
- package/app/components/product-item/index.test.js +38 -0
- package/app/components/product-scroller/index.jsx +172 -0
- package/app/components/product-scroller/index.test.js +98 -0
- package/app/components/product-tile/index.jsx +195 -0
- package/app/components/product-tile/index.test.js +96 -0
- package/app/components/product-view/index.jsx +538 -0
- package/app/components/product-view/index.test.js +224 -0
- package/app/components/product-view-modal/index.jsx +48 -0
- package/app/components/product-view-modal/index.test.js +72 -0
- package/app/components/promo-code/index.jsx +162 -0
- package/app/components/promo-popover/index.jsx +83 -0
- package/app/components/quantity-picker/index.jsx +58 -0
- package/app/components/radio-card/index.jsx +75 -0
- package/app/components/recommended-products/index.jsx +227 -0
- package/app/components/register/index.jsx +114 -0
- package/app/components/reset-password/index.jsx +87 -0
- package/app/components/responsive/index.jsx +29 -0
- package/app/components/scroll-to-top/index.jsx +24 -0
- package/app/components/scroll-to-top/index.test.js +46 -0
- package/app/components/search/index.jsx +279 -0
- package/app/components/search/index.test.js +127 -0
- package/app/components/search/partials/recent-searches.jsx +76 -0
- package/app/components/search/partials/search-suggestions.jsx +45 -0
- package/app/components/search/partials/suggestions.jsx +43 -0
- package/app/components/section/index.jsx +68 -0
- package/app/components/seo/index.jsx +33 -0
- package/app/components/social-icons/index.jsx +101 -0
- package/app/components/social-icons/index.test.js +30 -0
- package/app/components/swatch-group/index.jsx +77 -0
- package/app/components/swatch-group/index.test.js +136 -0
- package/app/components/swatch-group/swatch.jsx +94 -0
- package/app/components/toggle-card/index.jsx +97 -0
- package/app/components/with-registration/index.jsx +58 -0
- package/app/components/with-registration/index.test.js +85 -0
- package/app/constants.js +109 -0
- package/app/contexts/index.js +92 -0
- package/app/hooks/einstein-mock-data.js +916 -0
- package/app/hooks/index.js +17 -0
- package/app/hooks/use-add-to-cart-modal.js +344 -0
- package/app/hooks/use-add-to-cart-modal.test.js +625 -0
- package/app/hooks/use-auth-modal.js +337 -0
- package/app/hooks/use-auth-modal.test.js +365 -0
- package/app/hooks/use-currency.js +20 -0
- package/app/hooks/use-currency.test.js +41 -0
- package/app/hooks/use-current-basket.js +39 -0
- package/app/hooks/use-current-customer.js +29 -0
- package/app/hooks/use-derived-product.js +77 -0
- package/app/hooks/use-derived-product.test.js +69 -0
- package/app/hooks/use-einstein.js +512 -0
- package/app/hooks/use-einstein.test.js +224 -0
- package/app/hooks/use-intersection-observer.js +64 -0
- package/app/hooks/use-limit-urls.js +31 -0
- package/app/hooks/use-limit-urls.test.js +40 -0
- package/app/hooks/use-multi-site.js +36 -0
- package/app/hooks/use-multi-site.test.js +53 -0
- package/app/hooks/use-navigation.js +37 -0
- package/app/hooks/use-navigation.test.js +109 -0
- package/app/hooks/use-page-urls.js +35 -0
- package/app/hooks/use-page-urls.test.js +39 -0
- package/app/hooks/use-pdp-search-params.js +16 -0
- package/app/hooks/use-pdp-search-params.test.js +52 -0
- package/app/hooks/use-previous.js +17 -0
- package/app/hooks/use-product-view-modal.js +93 -0
- package/app/hooks/use-product-view-modal.test.js +172 -0
- package/app/hooks/use-search-params.js +96 -0
- package/app/hooks/use-search-params.test.js +91 -0
- package/app/hooks/use-sort-urls.js +33 -0
- package/app/hooks/use-sort-urls.test.js +42 -0
- package/app/hooks/use-toast.js +68 -0
- package/app/hooks/use-toast.test.js +58 -0
- package/app/hooks/use-variant.js +32 -0
- package/app/hooks/use-variant.test.js +81 -0
- package/app/hooks/use-variation-attributes.js +138 -0
- package/app/hooks/use-variation-attributes.test.js +119 -0
- package/app/hooks/use-variation-params.js +31 -0
- package/app/hooks/use-variation-params.test.js +73 -0
- package/app/hooks/use-wish-list.js +42 -0
- package/app/main.jsx +14 -0
- package/app/mocks/basket-with-suit.js +146 -0
- package/app/mocks/empty-basket.js +39 -0
- package/app/mocks/mock-data.js +5632 -0
- package/app/mocks/product-set-winter-lookM.js +1224 -0
- package/app/mocks/searchResults.js +144 -0
- package/app/mocks/variant-750518699578M.js +434 -0
- package/app/page-designer/README.md +102 -0
- package/app/page-designer/assets/image-tile/index.jsx +51 -0
- package/app/page-designer/assets/image-tile/index.test.js +30 -0
- package/app/page-designer/assets/image-with-text/index.jsx +140 -0
- package/app/page-designer/assets/image-with-text/index.test.js +38 -0
- package/app/page-designer/assets/index.js +9 -0
- package/app/page-designer/index.js +10 -0
- package/app/page-designer/layouts/carousel/index.jsx +222 -0
- package/app/page-designer/layouts/carousel/index.test.js +43 -0
- package/app/page-designer/layouts/index.js +14 -0
- package/app/page-designer/layouts/mobileGrid1r1c/index.jsx +36 -0
- package/app/page-designer/layouts/mobileGrid1r1c/index.test.js +35 -0
- package/app/page-designer/layouts/mobileGrid2r1c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r1c/index.test.js +47 -0
- package/app/page-designer/layouts/mobileGrid2r2c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r2c/index.test.js +71 -0
- package/app/page-designer/layouts/mobileGrid2r3c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid2r3c/index.test.js +95 -0
- package/app/page-designer/layouts/mobileGrid3r1c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid3r1c/index.test.js +59 -0
- package/app/page-designer/layouts/mobileGrid3r2c/index.jsx +37 -0
- package/app/page-designer/layouts/mobileGrid3r2c/index.test.js +95 -0
- package/app/page-designer/utils.js +14 -0
- package/app/pages/account/addresses.jsx +382 -0
- package/app/pages/account/addresses.test.js +120 -0
- package/app/pages/account/constant.js +57 -0
- package/app/pages/account/index.jsx +237 -0
- package/app/pages/account/index.test.js +188 -0
- package/app/pages/account/order-detail.jsx +397 -0
- package/app/pages/account/order-history.jsx +264 -0
- package/app/pages/account/orders.jsx +30 -0
- package/app/pages/account/orders.test.js +95 -0
- package/app/pages/account/profile.jsx +357 -0
- package/app/pages/account/wishlist/index.jsx +195 -0
- package/app/pages/account/wishlist/index.mock.js +1481 -0
- package/app/pages/account/wishlist/index.test.js +56 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +170 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.mock.js +1623 -0
- package/app/pages/account/wishlist/partials/wishlist-primary-action.test.js +99 -0
- package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.jsx +120 -0
- package/app/pages/account/wishlist/partials/wishlist-secondary-button-group.test.js +391 -0
- package/app/pages/cart/index.jsx +476 -0
- package/app/pages/cart/index.test.js +481 -0
- package/app/pages/cart/partials/cart-cta.jsx +46 -0
- package/app/pages/cart/partials/cart-secondary-button-group.jsx +135 -0
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +103 -0
- package/app/pages/cart/partials/cart-skeleton.jsx +93 -0
- package/app/pages/cart/partials/cart-title.jsx +27 -0
- package/app/pages/cart/partials/empty-cart.jsx +86 -0
- package/app/pages/checkout/confirmation.jsx +541 -0
- package/app/pages/checkout/confirmation.mock.js +450 -0
- package/app/pages/checkout/confirmation.test.js +114 -0
- package/app/pages/checkout/index.jsx +169 -0
- package/app/pages/checkout/index.test.js +582 -0
- package/app/pages/checkout/partials/cc-radio-group.jsx +122 -0
- package/app/pages/checkout/partials/checkout-footer.jsx +140 -0
- package/app/pages/checkout/partials/checkout-footer.test.js +16 -0
- package/app/pages/checkout/partials/checkout-header.jsx +54 -0
- package/app/pages/checkout/partials/checkout-header.test.js +16 -0
- package/app/pages/checkout/partials/checkout-skeleton.jsx +52 -0
- package/app/pages/checkout/partials/contact-info.jsx +251 -0
- package/app/pages/checkout/partials/contact-info.test.js +43 -0
- package/app/pages/checkout/partials/payment-form.jsx +97 -0
- package/app/pages/checkout/partials/payment.jsx +276 -0
- package/app/pages/checkout/partials/shipping-address-selection.jsx +377 -0
- package/app/pages/checkout/partials/shipping-address.jsx +132 -0
- package/app/pages/checkout/partials/shipping-options.jsx +232 -0
- package/app/pages/checkout/util/checkout-context.js +94 -0
- package/app/pages/home/data.js +134 -0
- package/app/pages/home/index.jsx +301 -0
- package/app/pages/home/index.test.js +23 -0
- package/app/pages/login/index.jsx +123 -0
- package/app/pages/login/index.test.js +229 -0
- package/app/pages/login-redirect/index.jsx +23 -0
- package/app/pages/login-redirect/index.test.js +16 -0
- package/app/pages/page-not-found/index.jsx +90 -0
- package/app/pages/page-not-found/index.test.js +31 -0
- package/app/pages/product-detail/index.jsx +394 -0
- package/app/pages/product-detail/index.mock.js +197 -0
- package/app/pages/product-detail/index.test.js +162 -0
- package/app/pages/product-detail/partials/information-accordion.jsx +121 -0
- package/app/pages/product-list/index.jsx +735 -0
- package/app/pages/product-list/index.test.js +180 -0
- package/app/pages/product-list/partials/above-page-header.jsx +10 -0
- package/app/pages/product-list/partials/checkbox-refinements.jsx +41 -0
- package/app/pages/product-list/partials/checkbox-refinements.test.js +53 -0
- package/app/pages/product-list/partials/color-refinements.jsx +88 -0
- package/app/pages/product-list/partials/empty-results.jsx +118 -0
- package/app/pages/product-list/partials/link-refinements.jsx +38 -0
- package/app/pages/product-list/partials/page-header.jsx +42 -0
- package/app/pages/product-list/partials/radio-refinements.jsx +60 -0
- package/app/pages/product-list/partials/refinements.jsx +144 -0
- package/app/pages/product-list/partials/selected-refinements.jsx +100 -0
- package/app/pages/product-list/partials/size-refinements.jsx +55 -0
- package/app/pages/registration/index.jsx +87 -0
- package/app/pages/registration/index.test.jsx +132 -0
- package/app/pages/reset-password/index.jsx +112 -0
- package/app/pages/reset-password/index.test.jsx +141 -0
- package/app/request-processor.js +118 -0
- package/app/request-processor.test.js +23 -0
- package/app/routes.jsx +111 -0
- package/app/routes.test.js +13 -0
- package/app/ssr.js +70 -0
- package/app/static/ico/favicon.ico +0 -0
- package/app/static/img/global/app-icon-192.png +0 -0
- package/app/static/img/global/app-icon-512.png +0 -0
- package/app/static/img/global/apple-touch-icon.png +0 -0
- package/app/static/img/hero.png +0 -0
- package/app/static/manifest.json +19 -0
- package/app/static/robots.txt +2 -0
- package/app/theme/components/base/accordion.js +21 -0
- package/app/theme/components/base/alert.js +17 -0
- package/app/theme/components/base/badge.js +25 -0
- package/app/theme/components/base/button.js +77 -0
- package/app/theme/components/base/checkbox.js +30 -0
- package/app/theme/components/base/container.js +17 -0
- package/app/theme/components/base/drawer.js +26 -0
- package/app/theme/components/base/formLabel.js +13 -0
- package/app/theme/components/base/icon.js +13 -0
- package/app/theme/components/base/input.js +44 -0
- package/app/theme/components/base/modal.js +11 -0
- package/app/theme/components/base/popover.js +61 -0
- package/app/theme/components/base/radio.js +33 -0
- package/app/theme/components/base/select.js +15 -0
- package/app/theme/components/base/skeleton.js +12 -0
- package/app/theme/components/base/tooltip.js +19 -0
- package/app/theme/components/project/_app.js +25 -0
- package/app/theme/components/project/breadcrumb.js +25 -0
- package/app/theme/components/project/checkout-footer.js +35 -0
- package/app/theme/components/project/drawer-menu.js +66 -0
- package/app/theme/components/project/footer.js +84 -0
- package/app/theme/components/project/header.js +84 -0
- package/app/theme/components/project/image-gallery.js +59 -0
- package/app/theme/components/project/links-list.js +43 -0
- package/app/theme/components/project/list-menu.js +91 -0
- package/app/theme/components/project/locale-selector.js +42 -0
- package/app/theme/components/project/nested-accordion.js +26 -0
- package/app/theme/components/project/offline-banner.js +25 -0
- package/app/theme/components/project/pagination.js +22 -0
- package/app/theme/components/project/product-tile.js +32 -0
- package/app/theme/components/project/social-icons.js +52 -0
- package/app/theme/components/project/swatch-group.js +115 -0
- package/app/theme/foundations/colors.js +170 -0
- package/app/theme/foundations/gradients.js +9 -0
- package/app/theme/foundations/layerStyles.js +41 -0
- package/app/theme/foundations/shadows.js +9 -0
- package/app/theme/foundations/sizes.js +18 -0
- package/app/theme/foundations/space.js +9 -0
- package/app/theme/foundations/styles.js +21 -0
- package/app/theme/index.js +104 -0
- package/app/utils/cc-utils.js +112 -0
- package/app/utils/cc-utils.test.js +41 -0
- package/app/utils/image-groups-utils.js +62 -0
- package/app/utils/image-groups-utils.test.js +65 -0
- package/app/utils/locale.js +78 -0
- package/app/utils/locale.test.js +112 -0
- package/app/utils/password-utils.js +21 -0
- package/app/utils/phone-utils.js +22 -0
- package/app/utils/phone-utils.test.js +15 -0
- package/app/utils/product-utils.js +35 -0
- package/app/utils/product-utils.test.js +51 -0
- package/app/utils/responsive-image.js +198 -0
- package/app/utils/responsive-image.test.js +170 -0
- package/app/utils/routes-utils.js +111 -0
- package/app/utils/routes-utils.test.js +291 -0
- package/app/utils/site-utils.js +222 -0
- package/app/utils/site-utils.test.js +376 -0
- package/app/utils/test-utils.js +257 -0
- package/app/utils/url.js +291 -0
- package/app/utils/url.test.js +421 -0
- package/app/utils/utils.js +201 -0
- package/app/utils/utils.test.js +182 -0
- package/babel.config.js +7 -0
- package/cache-hash-config.json +8 -0
- package/config/default.js +64 -0
- package/config/mocks/default.js +131 -0
- package/config/sites.js +78 -0
- package/jest-setup.js +191 -0
- package/jest.config.js +50 -0
- package/jsconfig.json +13 -0
- package/package.json +105 -0
- package/scripts/extract-default-messages.js +92 -0
- package/tests/lighthouserc.js +37 -0
- package/translations/README.md +127 -0
- package/translations/compiled/de-DE.json +3212 -0
- package/translations/compiled/en-GB.json +3212 -0
- package/translations/compiled/en-US.json +3212 -0
- package/translations/compiled/en-XA.json +6948 -0
- package/translations/compiled/es-MX.json +3216 -0
- package/translations/compiled/fr-FR.json +3216 -0
- package/translations/compiled/it-IT.json +3188 -0
- package/translations/compiled/ja-JP.json +3200 -0
- package/translations/compiled/ko-KR.json +3180 -0
- package/translations/compiled/pt-BR.json +3220 -0
- package/translations/compiled/zh-CN.json +3212 -0
- package/translations/compiled/zh-TW.json +3208 -0
- package/translations/de-DE.json +1417 -0
- package/translations/en-GB.json +1417 -0
- package/translations/en-US.json +1417 -0
- package/translations/es-MX.json +1417 -0
- package/translations/fr-FR.json +1417 -0
- package/translations/it-IT.json +1417 -0
- package/translations/ja-JP.json +1417 -0
- package/translations/ko-KR.json +1417 -0
- package/translations/pt-BR.json +1417 -0
- package/translations/zh-CN.json +1417 -0
- package/translations/zh-TW.json +1417 -0
- package/worker/main.js +36 -0
|
@@ -0,0 +1,96 @@
|
|
|
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, {Fragment} from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {FormattedMessage} from 'react-intl'
|
|
11
|
+
import {Alert, Box, Button, Stack, Text} from '@chakra-ui/react'
|
|
12
|
+
import {AlertIcon, BrandLogo} from '@salesforce/retail-react-app/app/components/icons'
|
|
13
|
+
import LoginFields from '@salesforce/retail-react-app/app/components/forms/login-fields'
|
|
14
|
+
import {noop} from '@salesforce/retail-react-app/app/utils/utils'
|
|
15
|
+
|
|
16
|
+
const LoginForm = ({submitForm, clickForgotPassword = noop, clickCreateAccount = noop, form}) => {
|
|
17
|
+
return (
|
|
18
|
+
<Fragment>
|
|
19
|
+
<Stack justify="center" align="center" spacing={8} marginBottom={8}>
|
|
20
|
+
<BrandLogo width="60px" height="auto" />
|
|
21
|
+
<Text align="center" fontSize="xl" fontWeight="semibold">
|
|
22
|
+
<FormattedMessage
|
|
23
|
+
defaultMessage="Welcome Back"
|
|
24
|
+
id="login_form.message.welcome_back"
|
|
25
|
+
/>
|
|
26
|
+
</Text>
|
|
27
|
+
</Stack>
|
|
28
|
+
<form
|
|
29
|
+
id="login-form"
|
|
30
|
+
onSubmit={form.handleSubmit(submitForm)}
|
|
31
|
+
data-testid="sf-auth-modal-form"
|
|
32
|
+
>
|
|
33
|
+
<Stack spacing={8} paddingLeft={4} paddingRight={4}>
|
|
34
|
+
{form.formState.errors?.global && (
|
|
35
|
+
<Alert status="error">
|
|
36
|
+
<AlertIcon color="red.500" boxSize={4} />
|
|
37
|
+
<Text fontSize="sm" ml={3}>
|
|
38
|
+
{form.formState.errors.global.message}
|
|
39
|
+
</Text>
|
|
40
|
+
</Alert>
|
|
41
|
+
)}
|
|
42
|
+
<Stack>
|
|
43
|
+
<LoginFields form={form} />
|
|
44
|
+
|
|
45
|
+
<Box>
|
|
46
|
+
<Button variant="link" size="sm" onClick={clickForgotPassword}>
|
|
47
|
+
<FormattedMessage
|
|
48
|
+
defaultMessage="Forgot password?"
|
|
49
|
+
id="login_form.link.forgot_password"
|
|
50
|
+
/>
|
|
51
|
+
</Button>
|
|
52
|
+
</Box>
|
|
53
|
+
</Stack>
|
|
54
|
+
<Stack spacing={6}>
|
|
55
|
+
<Button
|
|
56
|
+
type="submit"
|
|
57
|
+
onClick={() => {
|
|
58
|
+
form.clearErrors('global')
|
|
59
|
+
}}
|
|
60
|
+
isLoading={form.formState.isSubmitting}
|
|
61
|
+
>
|
|
62
|
+
<FormattedMessage
|
|
63
|
+
defaultMessage="Sign In"
|
|
64
|
+
id="login_form.button.sign_in"
|
|
65
|
+
/>
|
|
66
|
+
</Button>
|
|
67
|
+
|
|
68
|
+
<Stack direction="row" spacing={1} justify="center">
|
|
69
|
+
<Text fontSize="sm">
|
|
70
|
+
<FormattedMessage
|
|
71
|
+
defaultMessage="Don't have an account?"
|
|
72
|
+
id="login_form.message.dont_have_account"
|
|
73
|
+
/>
|
|
74
|
+
</Text>
|
|
75
|
+
<Button variant="link" size="sm" onClick={clickCreateAccount}>
|
|
76
|
+
<FormattedMessage
|
|
77
|
+
defaultMessage="Create account"
|
|
78
|
+
id="login_form.action.create_account"
|
|
79
|
+
/>
|
|
80
|
+
</Button>
|
|
81
|
+
</Stack>
|
|
82
|
+
</Stack>
|
|
83
|
+
</Stack>
|
|
84
|
+
</form>
|
|
85
|
+
</Fragment>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
LoginForm.propTypes = {
|
|
90
|
+
submitForm: PropTypes.func,
|
|
91
|
+
clickForgotPassword: PropTypes.func,
|
|
92
|
+
clickCreateAccount: PropTypes.func,
|
|
93
|
+
form: PropTypes.object
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default LoginForm
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
|
|
11
|
+
// Components
|
|
12
|
+
import {
|
|
13
|
+
Accordion,
|
|
14
|
+
AccordionItem,
|
|
15
|
+
AccordionButton,
|
|
16
|
+
AccordionPanel,
|
|
17
|
+
Text,
|
|
18
|
+
|
|
19
|
+
// Hooks
|
|
20
|
+
useStyleConfig
|
|
21
|
+
} from '@chakra-ui/react'
|
|
22
|
+
import Link from '@salesforce/retail-react-app/app/components/link'
|
|
23
|
+
// Icons
|
|
24
|
+
import {ChevronDownIcon, ChevronRightIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The nested accordion allows you to create, as the name suggest, a nests
|
|
28
|
+
* accordion given a hierarchical data structure. Each nests accordion will
|
|
29
|
+
* be indented to further enhance the hierary view.
|
|
30
|
+
*/
|
|
31
|
+
const NestedAccordion = (props) => {
|
|
32
|
+
const styles = useStyleConfig('NestedAccordion')
|
|
33
|
+
const {
|
|
34
|
+
item,
|
|
35
|
+
initialDepth = 0,
|
|
36
|
+
itemsFilter = () => true,
|
|
37
|
+
itemsAfter,
|
|
38
|
+
itemsBefore,
|
|
39
|
+
itemsKey = 'items',
|
|
40
|
+
fontWeights = [],
|
|
41
|
+
fontSizes = [],
|
|
42
|
+
urlBuilder = (item) => `/${item.id}`,
|
|
43
|
+
...rest
|
|
44
|
+
} = props
|
|
45
|
+
|
|
46
|
+
const depth = initialDepth
|
|
47
|
+
const items = item[itemsKey] || []
|
|
48
|
+
|
|
49
|
+
// Handle filters in the folr of a function or a object key string.
|
|
50
|
+
const filter = (item) =>
|
|
51
|
+
typeof itemsFilter === 'function' ? itemsFilter(item) : !!item[itemsFilter]
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Accordion className="sf-nested-accordion" {...rest}>
|
|
55
|
+
{/* Optional accordion items before others in items list. */}
|
|
56
|
+
{typeof itemsBefore === 'function' ? itemsBefore({item, depth}) : itemsBefore}
|
|
57
|
+
|
|
58
|
+
{items.filter(filter).map((item) => {
|
|
59
|
+
const {id, name} = item
|
|
60
|
+
const items = item[itemsKey]
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<AccordionItem key={id} border="none">
|
|
64
|
+
{({isExpanded}) => (
|
|
65
|
+
<>
|
|
66
|
+
{/* Heading */}
|
|
67
|
+
<h2>
|
|
68
|
+
{/* Show item as a leaf node if it has no visible child items. */}
|
|
69
|
+
{items && items.filter(filter).length > 0 ? (
|
|
70
|
+
<AccordionButton {...styles.internalButton}>
|
|
71
|
+
{/* Replace default expanded/collapsed icons. */}
|
|
72
|
+
{isExpanded ? (
|
|
73
|
+
<ChevronDownIcon {...styles.internalButtonIcon} />
|
|
74
|
+
) : (
|
|
75
|
+
<ChevronRightIcon {...styles.internalButtonIcon} />
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
<Text
|
|
79
|
+
fontSize={fontSizes[depth]}
|
|
80
|
+
fontWeight={fontWeights[depth]}
|
|
81
|
+
>
|
|
82
|
+
{name}
|
|
83
|
+
</Text>
|
|
84
|
+
</AccordionButton>
|
|
85
|
+
) : (
|
|
86
|
+
<AccordionButton
|
|
87
|
+
{...styles.leafButton}
|
|
88
|
+
as={Link}
|
|
89
|
+
to={urlBuilder(item)}
|
|
90
|
+
>
|
|
91
|
+
<Text
|
|
92
|
+
fontSize={fontSizes[depth]}
|
|
93
|
+
fontWeight={fontWeights[depth]}
|
|
94
|
+
>
|
|
95
|
+
{name}
|
|
96
|
+
</Text>
|
|
97
|
+
</AccordionButton>
|
|
98
|
+
)}
|
|
99
|
+
</h2>
|
|
100
|
+
|
|
101
|
+
{/* Child Items */}
|
|
102
|
+
{items && (
|
|
103
|
+
<AccordionPanel {...styles.panel}>
|
|
104
|
+
<NestedAccordion
|
|
105
|
+
{...styles.nestedAccordion}
|
|
106
|
+
{...props}
|
|
107
|
+
item={item}
|
|
108
|
+
initialDepth={depth + 1}
|
|
109
|
+
/>
|
|
110
|
+
</AccordionPanel>
|
|
111
|
+
)}
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
</AccordionItem>
|
|
115
|
+
)
|
|
116
|
+
})}
|
|
117
|
+
|
|
118
|
+
{/* Optional accordion items after others in items list. */}
|
|
119
|
+
{typeof itemsAfter === 'function' ? itemsAfter({item, depth}) : itemsAfter}
|
|
120
|
+
</Accordion>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
NestedAccordion.displayName = 'NestedAccordion'
|
|
125
|
+
|
|
126
|
+
NestedAccordion.propTypes = {
|
|
127
|
+
/**
|
|
128
|
+
* A POJO consistening of an id, name, and items array of object with
|
|
129
|
+
* similarly specified properties.
|
|
130
|
+
*/
|
|
131
|
+
item: PropTypes.object.isRequired,
|
|
132
|
+
/**
|
|
133
|
+
* An array of `AccordionItem` components that will be displayed after all
|
|
134
|
+
* of the child items. Alternatively you can pass a function that will recieve
|
|
135
|
+
* the current item and it's depth, your should return an `AccordionItem` or
|
|
136
|
+
* array of `AccordionItem`'s.
|
|
137
|
+
*/
|
|
138
|
+
itemsAfter: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
|
|
139
|
+
/**
|
|
140
|
+
* An array of `AccordionItem` components that will be displayed before all
|
|
141
|
+
* of the child items. Alternatively you can pass a function that will recieve
|
|
142
|
+
* the current item and it's depth, your should return an `AccordionItem` or
|
|
143
|
+
* array of `AccordionItem`'s.
|
|
144
|
+
*/
|
|
145
|
+
itemsBefore: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
|
|
146
|
+
/**
|
|
147
|
+
* This is an internally used property used to pass the updated depth of the
|
|
148
|
+
* child accordion. This is used to access specfic styl data for accodions
|
|
149
|
+
* based on their depth.
|
|
150
|
+
*/
|
|
151
|
+
initialDepth: PropTypes.number,
|
|
152
|
+
/**
|
|
153
|
+
* By default child items are keyed at `items` but if your data differs you
|
|
154
|
+
* can specify a custom key name for chile items. (e.g. children)
|
|
155
|
+
*/
|
|
156
|
+
itemsKey: PropTypes.string,
|
|
157
|
+
/**
|
|
158
|
+
* Programatically filter out items that you do not want to show. You can do this by
|
|
159
|
+
* supplying a string that will be used to access an items value, the the value is truthy
|
|
160
|
+
* the item will be displayed. Otherwise you can pass a function, this function will be passed
|
|
161
|
+
* the item to be filtered, its return is expected to be true or false.
|
|
162
|
+
*/
|
|
163
|
+
itemsFilter: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
|
164
|
+
/**
|
|
165
|
+
* An array of font size strings, the position in the array will
|
|
166
|
+
* indicate at what depth that size is applied.
|
|
167
|
+
* TODO: Think about making this optionally a string value to make it behave like
|
|
168
|
+
* chakra-ui responsive properties.
|
|
169
|
+
*/
|
|
170
|
+
fontSizes: PropTypes.array,
|
|
171
|
+
/**
|
|
172
|
+
* An array of font weight strings, the position in the array will
|
|
173
|
+
* indicate at what depth that weight is applied.
|
|
174
|
+
* TODO: Think about making this optionally a string value to make it behave like
|
|
175
|
+
* chakra-ui responsive properties.
|
|
176
|
+
*/
|
|
177
|
+
fontWeights: PropTypes.array,
|
|
178
|
+
/**
|
|
179
|
+
* This function builds the urls for leaf items. It accepts
|
|
180
|
+
* the current item, and returns a string.
|
|
181
|
+
*/
|
|
182
|
+
urlBuilder: PropTypes.func
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default NestedAccordion
|
|
@@ -0,0 +1,98 @@
|
|
|
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 NestedAccordion from '@salesforce/retail-react-app/app/components/nested-accordion/index'
|
|
9
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
10
|
+
|
|
11
|
+
const mockItem = {
|
|
12
|
+
id: 't1',
|
|
13
|
+
name: 'Test One',
|
|
14
|
+
items: [
|
|
15
|
+
{
|
|
16
|
+
id: 't1-1',
|
|
17
|
+
name: 'Test One One'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 't1-2',
|
|
21
|
+
name: 'Test One Two',
|
|
22
|
+
items: [
|
|
23
|
+
{
|
|
24
|
+
id: 't1-2-1',
|
|
25
|
+
name: 'Test One Two One'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 't1-2-2',
|
|
29
|
+
name: 'Test One Two Two'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
test('Renders NestedAccordion', () => {
|
|
36
|
+
renderWithProviders(<NestedAccordion item={mockItem} />)
|
|
37
|
+
|
|
38
|
+
const accordions = document.querySelectorAll('.sf-nested-accordion')
|
|
39
|
+
|
|
40
|
+
expect(accordions).toHaveLength(2)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('Renders NestedAccordion with items elements before and after', () => {
|
|
44
|
+
renderWithProviders(
|
|
45
|
+
<NestedAccordion
|
|
46
|
+
item={mockItem}
|
|
47
|
+
itemsBefore={[
|
|
48
|
+
<div className="itemsBefore" key="before">
|
|
49
|
+
before
|
|
50
|
+
</div>
|
|
51
|
+
]}
|
|
52
|
+
itemsAfter={[
|
|
53
|
+
<div className="itemsAfter" key="after">
|
|
54
|
+
before
|
|
55
|
+
</div>
|
|
56
|
+
]}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const itemBefore = document.querySelector('.itemsBefore')
|
|
61
|
+
const itemAfter = document.querySelector('.itemsAfter')
|
|
62
|
+
|
|
63
|
+
expect(itemBefore).toBeInTheDocument()
|
|
64
|
+
expect(itemAfter).toBeInTheDocument()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('Renders NestedAccordion with items functions before and after', () => {
|
|
68
|
+
const onItemsBefore = jest.fn(() => [
|
|
69
|
+
<div className="itemsBefore" key="before">
|
|
70
|
+
before
|
|
71
|
+
</div>
|
|
72
|
+
])
|
|
73
|
+
const onItemsAfter = jest.fn(() => [
|
|
74
|
+
<div className="itemsAfter" key="after">
|
|
75
|
+
after
|
|
76
|
+
</div>
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
renderWithProviders(
|
|
80
|
+
<NestedAccordion item={mockItem} itemsBefore={onItemsBefore} itemsAfter={onItemsAfter} />
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const itemBefore = document.querySelector('.itemsBefore')
|
|
84
|
+
const itemAfter = document.querySelector('.itemsAfter')
|
|
85
|
+
|
|
86
|
+
expect(onItemsBefore).toHaveBeenCalled()
|
|
87
|
+
expect(onItemsAfter).toHaveBeenCalled()
|
|
88
|
+
expect(itemBefore).toBeInTheDocument()
|
|
89
|
+
expect(itemAfter).toBeInTheDocument()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('Renders NestedAccordion with custom url builder', () => {
|
|
93
|
+
const mockPath = '/mock-path'
|
|
94
|
+
renderWithProviders(<NestedAccordion item={mockItem} urlBuilder={() => mockPath} />)
|
|
95
|
+
|
|
96
|
+
const firstLeafLink = document.querySelector('.sf-nested-accordion a')
|
|
97
|
+
expect(firstLeafLink.href.endsWith(mockPath)).toBe(true)
|
|
98
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
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 {useIntl} from 'react-intl'
|
|
10
|
+
|
|
11
|
+
// Components
|
|
12
|
+
import {
|
|
13
|
+
Alert,
|
|
14
|
+
Text,
|
|
15
|
+
|
|
16
|
+
// Hooks
|
|
17
|
+
useStyleConfig
|
|
18
|
+
} from '@chakra-ui/react'
|
|
19
|
+
|
|
20
|
+
// Icons
|
|
21
|
+
import {AlertIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
22
|
+
|
|
23
|
+
const OfflineBanner = () => {
|
|
24
|
+
const intl = useIntl()
|
|
25
|
+
const style = useStyleConfig('OfflineBanner')
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Alert status="info" {...style.container}>
|
|
29
|
+
<AlertIcon {...style.icon} />
|
|
30
|
+
<Text {...style.message}>
|
|
31
|
+
{intl.formatMessage({
|
|
32
|
+
id: 'offline_banner.description.browsing_offline_mode',
|
|
33
|
+
defaultMessage: "You're currently browsing in offline mode"
|
|
34
|
+
})}
|
|
35
|
+
</Text>
|
|
36
|
+
</Alert>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default OfflineBanner
|
|
@@ -0,0 +1,15 @@
|
|
|
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 {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
9
|
+
import OfflineBanner from '@salesforce/retail-react-app/app/components/offline-banner/index'
|
|
10
|
+
import {screen} from '@testing-library/react'
|
|
11
|
+
|
|
12
|
+
test('OfflineBanner component is rendered appropriately', () => {
|
|
13
|
+
renderWithProviders(<OfflineBanner />)
|
|
14
|
+
expect(screen.getByText("You're currently browsing in offline mode")).toBeInTheDocument()
|
|
15
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
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 {withRouter} from 'react-router-dom'
|
|
10
|
+
import {Button} from '@chakra-ui/react'
|
|
11
|
+
import {AlertIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
12
|
+
|
|
13
|
+
// import Button from '@salesforce/pwa-kit-react-sdk/components/button'
|
|
14
|
+
// import Icon from '@salesforce/pwa-kit-react-sdk/components/icon'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* OfflineBoundary is a React Error boundary that catches errors thrown when
|
|
18
|
+
* dynamically loading pages and renders a fallback.
|
|
19
|
+
*/
|
|
20
|
+
class OfflineBoundary extends React.Component {
|
|
21
|
+
constructor(props) {
|
|
22
|
+
super(props)
|
|
23
|
+
this.state = {
|
|
24
|
+
chunkLoadError: false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
componentDidCatch(e) {
|
|
29
|
+
// Only catch errors loading components with @loadable/components. Everything
|
|
30
|
+
// else should bubble up the component tree to the built-in root error boundary.
|
|
31
|
+
if (e.name !== 'ChunkLoadError') {
|
|
32
|
+
throw e
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static getDerivedStateFromError() {
|
|
37
|
+
return {chunkLoadError: true}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
componentDidUpdate(previousProps) {
|
|
41
|
+
const {location: previousLocation, isOnline: wasOnline} = previousProps
|
|
42
|
+
const {location, isOnline} = this.props
|
|
43
|
+
|
|
44
|
+
const cameOnline = !wasOnline && isOnline
|
|
45
|
+
|
|
46
|
+
const locationChanged = ['pathname', 'search'].some(
|
|
47
|
+
(k) => (previousLocation || {})[k] !== (location || {})[k]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const shouldClear = cameOnline || locationChanged
|
|
51
|
+
|
|
52
|
+
if (shouldClear) {
|
|
53
|
+
this.clearError()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
clearError() {
|
|
58
|
+
// Use an updater in order to only re-render if the state needs to change
|
|
59
|
+
this.setState((prevState) => {
|
|
60
|
+
return prevState.chunkLoadError ? {chunkLoadError: false} : null
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
const {children} = this.props
|
|
66
|
+
const {chunkLoadError} = this.state
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<React.Fragment>
|
|
70
|
+
{chunkLoadError ? (
|
|
71
|
+
<div className="c-offline-boundary u-direction-column u-text-align-center u-padding-top u-padding-bottom">
|
|
72
|
+
<AlertIcon />
|
|
73
|
+
|
|
74
|
+
<h1 className="u-margin-bottom-md u-text-family">
|
|
75
|
+
You are currently offline
|
|
76
|
+
</h1>
|
|
77
|
+
|
|
78
|
+
<p className="u-margin-bottom-lg">
|
|
79
|
+
{"We couldn't load the next page on this connection. Please try again."}
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<Button
|
|
83
|
+
className="u-width-block-full pw--primary qa-retry-button"
|
|
84
|
+
onClick={() => this.clearError()}
|
|
85
|
+
>
|
|
86
|
+
Retry Connection
|
|
87
|
+
</Button>
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
children
|
|
91
|
+
)}
|
|
92
|
+
</React.Fragment>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
OfflineBoundary.propTypes = {
|
|
98
|
+
isOnline: PropTypes.bool.isRequired,
|
|
99
|
+
location: PropTypes.object,
|
|
100
|
+
children: PropTypes.node
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {OfflineBoundary as UnwrappedOfflineBoundary}
|
|
104
|
+
export default withRouter(OfflineBoundary)
|
|
@@ -0,0 +1,123 @@
|
|
|
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 {screen} from '@testing-library/react'
|
|
9
|
+
// import userEvent from '@testing-library/user-event'
|
|
10
|
+
|
|
11
|
+
import OfflineBoundary from '@salesforce/retail-react-app/app/components/offline-boundary/index'
|
|
12
|
+
import {renderWithRouter} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
13
|
+
|
|
14
|
+
// class ChunkLoadError extends Error {
|
|
15
|
+
// constructor(...params) {
|
|
16
|
+
// // Pass remaining arguments (including vendor specific ones) to parent constructor
|
|
17
|
+
// super(...params)
|
|
18
|
+
// this.name = 'ChunkLoadError'
|
|
19
|
+
// }
|
|
20
|
+
// }
|
|
21
|
+
|
|
22
|
+
describe('The OfflineBoundary', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// React's logging is noisey even when an Error Boundary catches. Silence
|
|
25
|
+
// the distracting logs during tests, since they are expected in any event.
|
|
26
|
+
jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
console.error.mockRestore()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should render its children', () => {
|
|
34
|
+
renderWithRouter(
|
|
35
|
+
<OfflineBoundary isOnline={true}>
|
|
36
|
+
<div id="child">child</div>
|
|
37
|
+
</OfflineBoundary>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText(/child/i)).toBeInTheDocument()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// TODO: Fix flaky/broken test
|
|
44
|
+
// eslint-disable-next-line jest/no-commented-out-tests
|
|
45
|
+
// test('should render the error splash when a child throws a chunk load error', () => {
|
|
46
|
+
// const ThrowingComponent = () => {
|
|
47
|
+
// throw new ChunkLoadError()
|
|
48
|
+
// }
|
|
49
|
+
// renderWithRouter(
|
|
50
|
+
// <OfflineBoundary isOnline={true}>
|
|
51
|
+
// <div>
|
|
52
|
+
// <ThrowingComponent />
|
|
53
|
+
// <div id="child">child</div>
|
|
54
|
+
// </div>
|
|
55
|
+
// </OfflineBoundary>
|
|
56
|
+
// )
|
|
57
|
+
|
|
58
|
+
// expect(screen.getByRole('img', {name: /offline cloud/i})).toBeInTheDocument()
|
|
59
|
+
// expect(
|
|
60
|
+
// screen.getByRole('heading', {name: /you are currently offline/i})
|
|
61
|
+
// ).toBeInTheDocument()
|
|
62
|
+
// expect(screen.queryByText(/child/i)).not.toBeInTheDocument()
|
|
63
|
+
// })
|
|
64
|
+
|
|
65
|
+
// TODO: Fix flaky/broken test
|
|
66
|
+
// eslint-disable-next-line jest/no-commented-out-tests
|
|
67
|
+
// test('should re-throw errors that are not chunk load errors', () => {
|
|
68
|
+
// const ThrowingComponent = () => {
|
|
69
|
+
// throw new Error('Anything else')
|
|
70
|
+
// }
|
|
71
|
+
// expect(() => {
|
|
72
|
+
// renderWithRouter(
|
|
73
|
+
// <OfflineBoundary isOnline={true}>
|
|
74
|
+
// <div>
|
|
75
|
+
// <ThrowingComponent />
|
|
76
|
+
// <div id="child">child</div>
|
|
77
|
+
// </div>
|
|
78
|
+
// </OfflineBoundary>
|
|
79
|
+
// )
|
|
80
|
+
// }).toThrow()
|
|
81
|
+
// })
|
|
82
|
+
|
|
83
|
+
// TODO: Fix flaky/broken test
|
|
84
|
+
// eslint-disable-next-line jest/no-commented-out-tests
|
|
85
|
+
// test('should attempt to reload the page when the user clicks retry', () => {
|
|
86
|
+
// let firstRender = true
|
|
87
|
+
// const ThrowingOnceComponent = () => {
|
|
88
|
+
// if (firstRender) {
|
|
89
|
+
// firstRender = false
|
|
90
|
+
// throw new ChunkLoadError()
|
|
91
|
+
// } else {
|
|
92
|
+
// return <div id="child">child</div>
|
|
93
|
+
// }
|
|
94
|
+
// }
|
|
95
|
+
// renderWithRouter(
|
|
96
|
+
// <OfflineBoundary isOnline={true}>
|
|
97
|
+
// <ThrowingOnceComponent />
|
|
98
|
+
// </OfflineBoundary>
|
|
99
|
+
// )
|
|
100
|
+
|
|
101
|
+
// expect(screen.getByRole('img', {name: /offline cloud/i})).toBeInTheDocument()
|
|
102
|
+
// expect(
|
|
103
|
+
// screen.getByRole('heading', {name: /you are currently offline/i})
|
|
104
|
+
// ).toBeInTheDocument()
|
|
105
|
+
// expect(screen.queryByText(/child/i)).not.toBeInTheDocument()
|
|
106
|
+
|
|
107
|
+
// userEvent.click(screen.getByRole('button', {name: /retry connection/i}))
|
|
108
|
+
// expect(screen.getByText(/child/i)).toBeInTheDocument()
|
|
109
|
+
// expect(screen.queryByRole('img', {name: /offline cloud/i})).not.toBeInTheDocument()
|
|
110
|
+
// expect(
|
|
111
|
+
// screen.queryByRole('heading', {name: /you are currently offline/i})
|
|
112
|
+
// ).not.toBeInTheDocument()
|
|
113
|
+
// })
|
|
114
|
+
|
|
115
|
+
// TODO: Fix flaky/broken test
|
|
116
|
+
// eslint-disable-next-line jest/no-commented-out-tests
|
|
117
|
+
// test('should derive state from a chunk load error', () => {
|
|
118
|
+
// const derived = UnwrappedOfflineBoundary.getDerivedStateFromError(
|
|
119
|
+
// new ChunkLoadError('test')
|
|
120
|
+
// )
|
|
121
|
+
// expect(derived).toEqual({chunkLoadError: true})
|
|
122
|
+
// })
|
|
123
|
+
})
|