@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,401 @@
|
|
|
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, {useState, useEffect} from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {useHistory, useLocation} from 'react-router-dom'
|
|
11
|
+
import {getAssetUrl} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
12
|
+
import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url'
|
|
13
|
+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
14
|
+
import {useQuery, useQueries} from '@tanstack/react-query'
|
|
15
|
+
import {
|
|
16
|
+
useAccessToken,
|
|
17
|
+
useCategory,
|
|
18
|
+
useCommerceApi,
|
|
19
|
+
useCustomerType,
|
|
20
|
+
useCustomerBaskets,
|
|
21
|
+
useShopperBasketsMutation
|
|
22
|
+
} from '@salesforce/commerce-sdk-react'
|
|
23
|
+
import * as queryKeyHelpers from '@salesforce/commerce-sdk-react/hooks/ShopperProducts/queryKeyHelpers'
|
|
24
|
+
// Chakra
|
|
25
|
+
import {Box, useDisclosure, useStyleConfig} from '@chakra-ui/react'
|
|
26
|
+
import {SkipNavLink, SkipNavContent} from '@chakra-ui/skip-nav'
|
|
27
|
+
|
|
28
|
+
// Contexts
|
|
29
|
+
import {CurrencyProvider} from '@salesforce/retail-react-app/app/contexts'
|
|
30
|
+
|
|
31
|
+
// Local Project Components
|
|
32
|
+
import Header from '@salesforce/retail-react-app/app/components/header'
|
|
33
|
+
import OfflineBanner from '@salesforce/retail-react-app/app/components/offline-banner'
|
|
34
|
+
import OfflineBoundary from '@salesforce/retail-react-app/app/components/offline-boundary'
|
|
35
|
+
import ScrollToTop from '@salesforce/retail-react-app/app/components/scroll-to-top'
|
|
36
|
+
import Footer from '@salesforce/retail-react-app/app/components/footer'
|
|
37
|
+
import CheckoutHeader from '@salesforce/retail-react-app/app/pages/checkout/partials/checkout-header'
|
|
38
|
+
import CheckoutFooter from '@salesforce/retail-react-app/app/pages/checkout/partials/checkout-footer'
|
|
39
|
+
import DrawerMenu from '@salesforce/retail-react-app/app/components/drawer-menu'
|
|
40
|
+
import ListMenu from '@salesforce/retail-react-app/app/components/list-menu'
|
|
41
|
+
import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
|
|
42
|
+
import AboveHeader from '@salesforce/retail-react-app/app/components/_app/partials/above-header'
|
|
43
|
+
|
|
44
|
+
// Hooks
|
|
45
|
+
import {AuthModal, useAuthModal} from '@salesforce/retail-react-app/app/hooks/use-auth-modal'
|
|
46
|
+
import {AddToCartModalProvider} from '@salesforce/retail-react-app/app/hooks/use-add-to-cart-modal'
|
|
47
|
+
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
48
|
+
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
49
|
+
|
|
50
|
+
// Localization
|
|
51
|
+
import {IntlProvider} from 'react-intl'
|
|
52
|
+
|
|
53
|
+
// Others
|
|
54
|
+
import {
|
|
55
|
+
watchOnlineStatus,
|
|
56
|
+
flatten,
|
|
57
|
+
mergeMatchedItems,
|
|
58
|
+
isServer
|
|
59
|
+
} from '@salesforce/retail-react-app/app/utils/utils'
|
|
60
|
+
import {getTargetLocale, fetchTranslations} from '@salesforce/retail-react-app/app/utils/locale'
|
|
61
|
+
import {
|
|
62
|
+
DEFAULT_SITE_TITLE,
|
|
63
|
+
HOME_HREF,
|
|
64
|
+
THEME_COLOR,
|
|
65
|
+
CAT_MENU_DEFAULT_NAV_SSR_DEPTH,
|
|
66
|
+
CAT_MENU_DEFAULT_ROOT_CATEGORY,
|
|
67
|
+
DEFAULT_LOCALE
|
|
68
|
+
} from '@salesforce/retail-react-app/app/constants'
|
|
69
|
+
|
|
70
|
+
import Seo from '@salesforce/retail-react-app/app/components/seo'
|
|
71
|
+
|
|
72
|
+
const onClient = typeof window !== 'undefined'
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
The categories tree can be really large! For performance reasons,
|
|
76
|
+
we only load the level 0 categories on server side, and load the rest
|
|
77
|
+
on client side to reduce SSR page size.
|
|
78
|
+
*/
|
|
79
|
+
const useLazyLoadCategories = () => {
|
|
80
|
+
const itemsKey = 'categories'
|
|
81
|
+
|
|
82
|
+
const levelZeroCategoriesQuery = useCategory({
|
|
83
|
+
parameters: {id: CAT_MENU_DEFAULT_ROOT_CATEGORY, levels: CAT_MENU_DEFAULT_NAV_SSR_DEPTH}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const ids = levelZeroCategoriesQuery.data?.[itemsKey].map((category) => category.id)
|
|
87
|
+
const queries = useCategoryBulk(ids, {
|
|
88
|
+
enabled: onClient && ids?.length > 0
|
|
89
|
+
})
|
|
90
|
+
const dataArray = queries.map((query) => query.data).filter(Boolean)
|
|
91
|
+
const isLoading = queries.some((query) => query.isLoading)
|
|
92
|
+
const isError = queries.some((query) => query.isError)
|
|
93
|
+
return {
|
|
94
|
+
isLoading,
|
|
95
|
+
isError,
|
|
96
|
+
data: {
|
|
97
|
+
...levelZeroCategoriesQuery.data,
|
|
98
|
+
[itemsKey]: mergeMatchedItems(
|
|
99
|
+
levelZeroCategoriesQuery.data?.categories || [],
|
|
100
|
+
dataArray
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const App = (props) => {
|
|
107
|
+
const {children} = props
|
|
108
|
+
const {data: categoriesTree} = useLazyLoadCategories()
|
|
109
|
+
const categories = flatten(categoriesTree || {}, 'categories')
|
|
110
|
+
|
|
111
|
+
const appOrigin = getAppOrigin()
|
|
112
|
+
|
|
113
|
+
const history = useHistory()
|
|
114
|
+
const location = useLocation()
|
|
115
|
+
const authModal = useAuthModal()
|
|
116
|
+
const {isRegistered} = useCustomerType()
|
|
117
|
+
const {site, locale, buildUrl} = useMultiSite()
|
|
118
|
+
|
|
119
|
+
const [isOnline, setIsOnline] = useState(true)
|
|
120
|
+
const styles = useStyleConfig('App')
|
|
121
|
+
|
|
122
|
+
const {isOpen, onOpen, onClose} = useDisclosure()
|
|
123
|
+
|
|
124
|
+
const targetLocale = getTargetLocale({
|
|
125
|
+
getUserPreferredLocales: () => {
|
|
126
|
+
// CONFIG: This function should return an array of preferred locales. They can be
|
|
127
|
+
// derived from various sources. Below are some examples of those:
|
|
128
|
+
//
|
|
129
|
+
// - client side: window.navigator.languages
|
|
130
|
+
// - the page URL they're on (example.com/en-GB/home)
|
|
131
|
+
// - cookie (if their previous preference is saved there)
|
|
132
|
+
//
|
|
133
|
+
// If this function returns an empty array (e.g. there isn't locale in the page url),
|
|
134
|
+
// then the app would use the default locale as the fallback.
|
|
135
|
+
|
|
136
|
+
// NOTE: Your implementation may differ, this is just what we did.
|
|
137
|
+
return [locale?.id || DEFAULT_LOCALE]
|
|
138
|
+
},
|
|
139
|
+
l10nConfig: site.l10n
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Fetch the translation message data using the target locale.
|
|
143
|
+
const {data: messages} = useQuery({
|
|
144
|
+
queryKey: ['app', 'translationas', 'messages', targetLocale],
|
|
145
|
+
queryFn: () => fetchTranslations(targetLocale),
|
|
146
|
+
enabled: isServer
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Used to conditionally render header/footer for checkout page
|
|
150
|
+
const isCheckout = /\/checkout$/.test(location?.pathname)
|
|
151
|
+
|
|
152
|
+
const {l10n} = site
|
|
153
|
+
// Get the current currency to be used through out the app
|
|
154
|
+
const currency = locale.preferredCurrency || l10n.defaultCurrency
|
|
155
|
+
|
|
156
|
+
// Handle creating a new basket if there isn't one already assigned to the current
|
|
157
|
+
// customer.
|
|
158
|
+
const {data: customer} = useCurrentCustomer()
|
|
159
|
+
const {data: baskets} = useCustomerBaskets(
|
|
160
|
+
{parameters: {customerId: customer.customerId}},
|
|
161
|
+
{enabled: !!customer.customerId && !isServer}
|
|
162
|
+
)
|
|
163
|
+
const createBasket = useShopperBasketsMutation('createBasket')
|
|
164
|
+
const updateBasket = useShopperBasketsMutation('updateBasket')
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
// Create a new basket if the current customer doesn't have one.
|
|
168
|
+
if (baskets?.total === 0) {
|
|
169
|
+
createBasket.mutate({
|
|
170
|
+
body: {}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
// update the basket currency if it doesn't match the current locale currency
|
|
174
|
+
if (baskets?.baskets?.[0]?.currency && baskets.baskets[0].currency !== currency) {
|
|
175
|
+
updateBasket.mutate({
|
|
176
|
+
parameters: {basketId: baskets.baskets[0].basketId},
|
|
177
|
+
body: {currency}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}, [baskets])
|
|
181
|
+
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
// Listen for online status changes.
|
|
184
|
+
watchOnlineStatus((isOnline) => {
|
|
185
|
+
setIsOnline(isOnline)
|
|
186
|
+
})
|
|
187
|
+
}, [])
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
// Lets automatically close the mobile navigation when the
|
|
191
|
+
// location path is changed.
|
|
192
|
+
onClose()
|
|
193
|
+
}, [location])
|
|
194
|
+
|
|
195
|
+
const onLogoClick = () => {
|
|
196
|
+
// Goto the home page.
|
|
197
|
+
const path = buildUrl(HOME_HREF)
|
|
198
|
+
|
|
199
|
+
history.push(path)
|
|
200
|
+
|
|
201
|
+
// Close the drawer.
|
|
202
|
+
onClose()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const onCartClick = () => {
|
|
206
|
+
const path = buildUrl('/cart')
|
|
207
|
+
history.push(path)
|
|
208
|
+
|
|
209
|
+
// Close the drawer.
|
|
210
|
+
onClose()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const onAccountClick = () => {
|
|
214
|
+
// Link to account page for registered customer, open auth modal otherwise
|
|
215
|
+
if (isRegistered) {
|
|
216
|
+
const path = buildUrl('/account')
|
|
217
|
+
history.push(path)
|
|
218
|
+
} else {
|
|
219
|
+
// if they already are at the login page, do not show login modal
|
|
220
|
+
if (new RegExp(`^/login$`).test(location.pathname)) return
|
|
221
|
+
authModal.onOpen()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const onWishlistClick = () => {
|
|
226
|
+
const path = buildUrl('/account/wishlist')
|
|
227
|
+
history.push(path)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Box className="sf-app" {...styles.container}>
|
|
232
|
+
<IntlProvider
|
|
233
|
+
onError={(err) => {
|
|
234
|
+
if (!messages) {
|
|
235
|
+
// During the ssr prepass phase the messages object has not loaded, so we can suppress
|
|
236
|
+
// errors during this time.
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
if (err.code === 'MISSING_TRANSLATION') {
|
|
240
|
+
// NOTE: Remove the console error for missing translations during development,
|
|
241
|
+
// as we knew translations would be added later.
|
|
242
|
+
console.warn('Missing translation', err.message)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
throw err
|
|
246
|
+
}}
|
|
247
|
+
locale={targetLocale}
|
|
248
|
+
messages={messages}
|
|
249
|
+
// For react-intl, the _default locale_ refers to the locale that the inline `defaultMessage`s are written for.
|
|
250
|
+
// NOTE: if you update this value, please also update the following npm scripts in `template-retail-react-app/package.json`:
|
|
251
|
+
// - "extract-default-translations"
|
|
252
|
+
// - "compile-translations:pseudo"
|
|
253
|
+
defaultLocale={DEFAULT_LOCALE}
|
|
254
|
+
>
|
|
255
|
+
<CurrencyProvider currency={currency}>
|
|
256
|
+
<Seo>
|
|
257
|
+
<meta name="theme-color" content={THEME_COLOR} />
|
|
258
|
+
<meta name="apple-mobile-web-app-title" content={DEFAULT_SITE_TITLE} />
|
|
259
|
+
<link
|
|
260
|
+
rel="apple-touch-icon"
|
|
261
|
+
href={getAssetUrl('static/img/global/apple-touch-icon.png')}
|
|
262
|
+
/>
|
|
263
|
+
<link rel="manifest" href={getAssetUrl('static/manifest.json')} />
|
|
264
|
+
|
|
265
|
+
{/* Urls for all localized versions of this page (including current page)
|
|
266
|
+
For more details on hrefLang, see https://developers.google.com/search/docs/advanced/crawling/localized-versions */}
|
|
267
|
+
{site.l10n?.supportedLocales.map((locale) => (
|
|
268
|
+
<link
|
|
269
|
+
rel="alternate"
|
|
270
|
+
hrefLang={locale.id.toLowerCase()}
|
|
271
|
+
href={`${appOrigin}${buildUrl(location.pathname)}`}
|
|
272
|
+
key={locale.id}
|
|
273
|
+
/>
|
|
274
|
+
))}
|
|
275
|
+
{/* A general locale as fallback. For example: "en" if default locale is "en-GB" */}
|
|
276
|
+
<link
|
|
277
|
+
rel="alternate"
|
|
278
|
+
hrefLang={site.l10n.defaultLocale.slice(0, 2)}
|
|
279
|
+
href={`${appOrigin}${buildUrl(location.pathname)}`}
|
|
280
|
+
/>
|
|
281
|
+
{/* A wider fallback for user locales that the app does not support */}
|
|
282
|
+
<link rel="alternate" hrefLang="x-default" href={`${appOrigin}/`} />
|
|
283
|
+
</Seo>
|
|
284
|
+
|
|
285
|
+
<ScrollToTop />
|
|
286
|
+
|
|
287
|
+
<Box id="app" display="flex" flexDirection="column" flex={1}>
|
|
288
|
+
<SkipNavLink zIndex="skipLink">Skip to Content</SkipNavLink>
|
|
289
|
+
|
|
290
|
+
<Box {...styles.headerWrapper}>
|
|
291
|
+
{!isCheckout ? (
|
|
292
|
+
<>
|
|
293
|
+
<AboveHeader />
|
|
294
|
+
<Header
|
|
295
|
+
onMenuClick={onOpen}
|
|
296
|
+
onLogoClick={onLogoClick}
|
|
297
|
+
onMyCartClick={onCartClick}
|
|
298
|
+
onMyAccountClick={onAccountClick}
|
|
299
|
+
onWishlistClick={onWishlistClick}
|
|
300
|
+
>
|
|
301
|
+
<HideOnDesktop>
|
|
302
|
+
<DrawerMenu
|
|
303
|
+
isOpen={isOpen}
|
|
304
|
+
onClose={onClose}
|
|
305
|
+
onLogoClick={onLogoClick}
|
|
306
|
+
root={categories?.[CAT_MENU_DEFAULT_ROOT_CATEGORY]}
|
|
307
|
+
/>
|
|
308
|
+
</HideOnDesktop>
|
|
309
|
+
|
|
310
|
+
<HideOnMobile>
|
|
311
|
+
<ListMenu
|
|
312
|
+
root={categories?.[CAT_MENU_DEFAULT_ROOT_CATEGORY]}
|
|
313
|
+
/>
|
|
314
|
+
</HideOnMobile>
|
|
315
|
+
</Header>
|
|
316
|
+
</>
|
|
317
|
+
) : (
|
|
318
|
+
<CheckoutHeader />
|
|
319
|
+
)}
|
|
320
|
+
</Box>
|
|
321
|
+
{!isOnline && <OfflineBanner />}
|
|
322
|
+
<AddToCartModalProvider>
|
|
323
|
+
<SkipNavContent
|
|
324
|
+
style={{
|
|
325
|
+
display: 'flex',
|
|
326
|
+
flexDirection: 'column',
|
|
327
|
+
flex: 1,
|
|
328
|
+
outline: 0
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
<Box
|
|
332
|
+
as="main"
|
|
333
|
+
id="app-main"
|
|
334
|
+
role="main"
|
|
335
|
+
display="flex"
|
|
336
|
+
flexDirection="column"
|
|
337
|
+
flex="1"
|
|
338
|
+
>
|
|
339
|
+
<OfflineBoundary isOnline={false}>{children}</OfflineBoundary>
|
|
340
|
+
</Box>
|
|
341
|
+
</SkipNavContent>
|
|
342
|
+
|
|
343
|
+
{!isCheckout ? <Footer /> : <CheckoutFooter />}
|
|
344
|
+
|
|
345
|
+
<AuthModal {...authModal} />
|
|
346
|
+
</AddToCartModalProvider>
|
|
347
|
+
</Box>
|
|
348
|
+
</CurrencyProvider>
|
|
349
|
+
</IntlProvider>
|
|
350
|
+
</Box>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
App.propTypes = {
|
|
355
|
+
children: PropTypes.node
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* a hook that parallelly and individually fetches category based on the given ids
|
|
360
|
+
* @param ids - list of categories ids to fetch
|
|
361
|
+
* @param queryOptions - react query options
|
|
362
|
+
* @return list of react query results
|
|
363
|
+
*/
|
|
364
|
+
export const useCategoryBulk = (ids = [], queryOptions) => {
|
|
365
|
+
const api = useCommerceApi()
|
|
366
|
+
const {getTokenWhenReady} = useAccessToken()
|
|
367
|
+
const {
|
|
368
|
+
app: {commerceAPI}
|
|
369
|
+
} = getConfig()
|
|
370
|
+
const {
|
|
371
|
+
parameters: {organizationId}
|
|
372
|
+
} = commerceAPI
|
|
373
|
+
const {site} = useMultiSite()
|
|
374
|
+
|
|
375
|
+
const queries = ids.map((id) => {
|
|
376
|
+
return {
|
|
377
|
+
queryKey: queryKeyHelpers.getCategory.queryKey({
|
|
378
|
+
id,
|
|
379
|
+
levels: 2,
|
|
380
|
+
organizationId,
|
|
381
|
+
siteId: site.id
|
|
382
|
+
}),
|
|
383
|
+
queryFn: async () => {
|
|
384
|
+
const token = await getTokenWhenReady()
|
|
385
|
+
const res = await api.shopperProducts.getCategory({
|
|
386
|
+
parameters: {id, levels: 2},
|
|
387
|
+
headers: {
|
|
388
|
+
authorization: `Bearer ${token}`
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
return res
|
|
392
|
+
},
|
|
393
|
+
...queryOptions,
|
|
394
|
+
enabled: queryOptions.enabled !== false && Boolean(id)
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
const res = useQueries({queries})
|
|
398
|
+
return res
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default App
|
|
@@ -0,0 +1,85 @@
|
|
|
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 {Helmet} from 'react-helmet'
|
|
10
|
+
|
|
11
|
+
import App from '@salesforce/retail-react-app/app/components/_app/index.jsx'
|
|
12
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
13
|
+
import {DEFAULT_LOCALE} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
14
|
+
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
15
|
+
import messages from '@salesforce/retail-react-app/translations/compiled/en-GB.json'
|
|
16
|
+
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
|
|
17
|
+
jest.mock('../../hooks/use-multi-site', () => jest.fn())
|
|
18
|
+
let windowSpy
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
jest.spyOn(console, 'log').mockImplementation(jest.fn())
|
|
21
|
+
jest.spyOn(console, 'groupCollapsed').mockImplementation(jest.fn())
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterAll(() => {
|
|
25
|
+
console.log.mockRestore()
|
|
26
|
+
console.groupCollapsed.mockRestore()
|
|
27
|
+
})
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
windowSpy = jest.spyOn(window, 'window', 'get')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
console.log.mockClear()
|
|
34
|
+
console.groupCollapsed.mockClear()
|
|
35
|
+
windowSpy.mockRestore()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('App', () => {
|
|
39
|
+
const site = {
|
|
40
|
+
...mockConfig.app.sites[0],
|
|
41
|
+
alias: 'uk'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const locale = DEFAULT_LOCALE
|
|
45
|
+
|
|
46
|
+
const buildUrl = jest.fn().mockImplementation((href, site, locale) => {
|
|
47
|
+
return `${site ? `/${site}` : ''}${locale ? `/${locale}` : ''}${href}`
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const resultUseMultiSite = {
|
|
51
|
+
site,
|
|
52
|
+
locale,
|
|
53
|
+
buildUrl
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('App component is rendered appropriately', () => {
|
|
57
|
+
useMultiSite.mockImplementation(() => resultUseMultiSite)
|
|
58
|
+
renderWithProviders(
|
|
59
|
+
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages}>
|
|
60
|
+
<p>Any children here</p>
|
|
61
|
+
</App>
|
|
62
|
+
)
|
|
63
|
+
screen.debug()
|
|
64
|
+
expect(screen.getByRole('main')).toBeInTheDocument()
|
|
65
|
+
expect(screen.getByText('Any children here')).toBeInTheDocument()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('The localized hreflang links exist in the html head', () => {
|
|
69
|
+
useMultiSite.mockImplementation(() => resultUseMultiSite)
|
|
70
|
+
renderWithProviders(
|
|
71
|
+
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages} />
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const helmet = Helmet.peek()
|
|
75
|
+
const hreflangLinks = helmet.linkTags.filter((link) => link.rel === 'alternate')
|
|
76
|
+
|
|
77
|
+
const hasGeneralLocale = ({hrefLang}) => hrefLang === DEFAULT_LOCALE.slice(0, 2)
|
|
78
|
+
|
|
79
|
+
// `length + 2` because one for a general locale and the other with x-default value
|
|
80
|
+
expect(hreflangLinks).toHaveLength(resultUseMultiSite.site.l10n.supportedLocales.length + 2)
|
|
81
|
+
|
|
82
|
+
expect(hreflangLinks.some((link) => hasGeneralLocale(link))).toBe(true)
|
|
83
|
+
expect(hreflangLinks.some((link) => link.hrefLang === 'x-default')).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const AboveHeader = () => null
|
|
9
|
+
|
|
10
|
+
export default AboveHeader
|
|
@@ -0,0 +1,125 @@
|
|
|
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 {ChakraProvider} from '@chakra-ui/react'
|
|
10
|
+
|
|
11
|
+
// Removes focus for non-keyboard interactions for the whole application
|
|
12
|
+
import 'focus-visible/dist/focus-visible'
|
|
13
|
+
|
|
14
|
+
import theme from '@salesforce/retail-react-app/app/theme'
|
|
15
|
+
import {MultiSiteProvider} from '@salesforce/retail-react-app/app/contexts'
|
|
16
|
+
import {
|
|
17
|
+
resolveSiteFromUrl,
|
|
18
|
+
resolveLocaleFromUrl
|
|
19
|
+
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
20
|
+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
21
|
+
import {createUrlTemplate} from '@salesforce/retail-react-app/app/utils/url'
|
|
22
|
+
|
|
23
|
+
import {CommerceApiProvider} from '@salesforce/commerce-sdk-react'
|
|
24
|
+
import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/components/with-react-query'
|
|
25
|
+
import {useCorrelationId} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
|
|
26
|
+
import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url'
|
|
27
|
+
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Use the AppConfig component to inject extra arguments into the getProps
|
|
31
|
+
* methods for all Route Components in the app – typically you'd want to do this
|
|
32
|
+
* to inject a connector instance that can be used in all Pages.
|
|
33
|
+
*
|
|
34
|
+
* You can also use the AppConfig to configure a state-management library such
|
|
35
|
+
* as Redux, or Mobx, if you like.
|
|
36
|
+
*/
|
|
37
|
+
const AppConfig = ({children, locals = {}}) => {
|
|
38
|
+
const {correlationId} = useCorrelationId()
|
|
39
|
+
const headers = {
|
|
40
|
+
'correlation-id': correlationId
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const commerceApiConfig = locals.appConfig.commerceAPI
|
|
44
|
+
|
|
45
|
+
const appOrigin = getAppOrigin()
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<CommerceApiProvider
|
|
49
|
+
shortCode={commerceApiConfig.parameters.shortCode}
|
|
50
|
+
clientId={commerceApiConfig.parameters.clientId}
|
|
51
|
+
organizationId={commerceApiConfig.parameters.organizationId}
|
|
52
|
+
siteId={locals.site?.id}
|
|
53
|
+
locale={locals.locale?.id}
|
|
54
|
+
currency={locals.locale?.preferredCurrency}
|
|
55
|
+
redirectURI={`${appOrigin}/callback`}
|
|
56
|
+
proxy={`${appOrigin}${commerceApiConfig.proxyPath}`}
|
|
57
|
+
headers={headers}
|
|
58
|
+
OCAPISessionsURL={`${appOrigin}/mobify/proxy/ocapi/s/${locals.site?.id}/dw/shop/v22_8/sessions`}
|
|
59
|
+
>
|
|
60
|
+
<MultiSiteProvider site={locals.site} locale={locals.locale} buildUrl={locals.buildUrl}>
|
|
61
|
+
<ChakraProvider theme={theme}>{children}</ChakraProvider>
|
|
62
|
+
</MultiSiteProvider>
|
|
63
|
+
<ReactQueryDevtools />
|
|
64
|
+
</CommerceApiProvider>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
AppConfig.restore = (locals = {}) => {
|
|
69
|
+
const path =
|
|
70
|
+
typeof window === 'undefined'
|
|
71
|
+
? locals.originalUrl
|
|
72
|
+
: `${window.location.pathname}${window.location.search}`
|
|
73
|
+
const site = resolveSiteFromUrl(path)
|
|
74
|
+
const locale = resolveLocaleFromUrl(path)
|
|
75
|
+
|
|
76
|
+
const {app: appConfig} = getConfig()
|
|
77
|
+
const apiConfig = {
|
|
78
|
+
...appConfig.commerceAPI,
|
|
79
|
+
einsteinConfig: appConfig.einsteinAPI
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
apiConfig.parameters.siteId = site.id
|
|
83
|
+
|
|
84
|
+
locals.buildUrl = createUrlTemplate(appConfig, site.alias || site.id, locale.id)
|
|
85
|
+
locals.site = site
|
|
86
|
+
locals.locale = locale
|
|
87
|
+
locals.appConfig = appConfig
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
AppConfig.freeze = () => undefined
|
|
91
|
+
|
|
92
|
+
AppConfig.extraGetPropsArgs = (locals = {}) => {
|
|
93
|
+
return {
|
|
94
|
+
buildUrl: locals.buildUrl,
|
|
95
|
+
site: locals.site,
|
|
96
|
+
locale: locals.locale
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
AppConfig.propTypes = {
|
|
101
|
+
children: PropTypes.node,
|
|
102
|
+
locals: PropTypes.object
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isServerSide = typeof window === 'undefined'
|
|
106
|
+
|
|
107
|
+
// Recommended settings for PWA-Kit usages.
|
|
108
|
+
// NOTE: they will be applied on both server and client side.
|
|
109
|
+
const options = {
|
|
110
|
+
queryClientConfig: {
|
|
111
|
+
defaultOptions: {
|
|
112
|
+
queries: {
|
|
113
|
+
retry: false,
|
|
114
|
+
refetchOnWindowFocus: false,
|
|
115
|
+
staleTime: 10 * 1000,
|
|
116
|
+
...(isServerSide ? {retryOnMount: false} : {})
|
|
117
|
+
},
|
|
118
|
+
mutations: {
|
|
119
|
+
retry: false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default withReactQuery(AppConfig, options)
|