@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,224 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {fireEvent, screen, waitFor} from '@testing-library/react'
|
|
11
|
+
import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
|
|
12
|
+
import mockProductSet from '@salesforce/retail-react-app/app/mocks/product-set-winter-lookM'
|
|
13
|
+
import ProductView from '@salesforce/retail-react-app/app/components/product-view'
|
|
14
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
15
|
+
import userEvent from '@testing-library/user-event'
|
|
16
|
+
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
17
|
+
|
|
18
|
+
const MockComponent = (props) => {
|
|
19
|
+
const {data: customer} = useCurrentCustomer()
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<div>customer: {customer?.authType}</div>
|
|
23
|
+
<ProductView {...props} />
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
MockComponent.propTypes = {
|
|
29
|
+
product: PropTypes.object,
|
|
30
|
+
addToCart: PropTypes.func,
|
|
31
|
+
addToWishlist: PropTypes.func,
|
|
32
|
+
updateWishlist: PropTypes.func
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Set up and clean up
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
// Since we're testing some navigation logic, we are using a simple Router
|
|
38
|
+
// around our component. We need to initialize the default route/path here.
|
|
39
|
+
window.history.pushState({}, 'Account', '/en/account')
|
|
40
|
+
})
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
jest.resetModules()
|
|
43
|
+
localStorage.clear()
|
|
44
|
+
sessionStorage.clear()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('ProductView Component renders properly', async () => {
|
|
48
|
+
const addToCart = jest.fn()
|
|
49
|
+
await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
|
|
50
|
+
|
|
51
|
+
expect(screen.getAllByText(/Black Single Pleat Athletic Fit Wool Suit/i)).toHaveLength(2)
|
|
52
|
+
expect(screen.getAllByText(/299.99/)).toHaveLength(2)
|
|
53
|
+
expect(screen.getAllByText(/Add to cart/i)).toHaveLength(2)
|
|
54
|
+
expect(screen.getAllByRole('radiogroup')).toHaveLength(3)
|
|
55
|
+
expect(screen.getAllByText(/add to cart/i)).toHaveLength(2)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('ProductView Component renders with addToCart event handler', async () => {
|
|
59
|
+
const addToCart = jest.fn()
|
|
60
|
+
await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
|
|
61
|
+
|
|
62
|
+
const addToCartButton = screen.getAllByText(/add to cart/i)[0]
|
|
63
|
+
fireEvent.click(addToCartButton)
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(addToCart).toHaveBeenCalledTimes(1)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('ProductView Component renders with addToWishList event handler', async () => {
|
|
71
|
+
const addToWishlist = jest.fn()
|
|
72
|
+
|
|
73
|
+
await renderWithProviders(
|
|
74
|
+
<MockComponent product={mockProductDetail} addToWishlist={addToWishlist} />
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(screen.getByText(/customer: registered/)).toBeInTheDocument()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
const addToWishListButton = screen.getAllByText(/Add to wishlist/i)[0]
|
|
83
|
+
|
|
84
|
+
fireEvent.click(addToWishListButton)
|
|
85
|
+
expect(addToWishlist).toHaveBeenCalledTimes(1)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('ProductView Component renders with updateWishlist event handler', async () => {
|
|
90
|
+
const updateWishlist = jest.fn()
|
|
91
|
+
|
|
92
|
+
await renderWithProviders(
|
|
93
|
+
<MockComponent product={mockProductDetail} updateWishlist={updateWishlist} />
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(screen.getByText(/customer: registered/)).toBeInTheDocument()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
const updateWishlistButton = screen.getAllByText(/Update/i)[0]
|
|
102
|
+
|
|
103
|
+
fireEvent.click(updateWishlistButton)
|
|
104
|
+
expect(updateWishlist).toHaveBeenCalledTimes(1)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('Product View can update quantity', async () => {
|
|
109
|
+
const user = userEvent.setup()
|
|
110
|
+
const addToCart = jest.fn()
|
|
111
|
+
await renderWithProviders(<MockComponent product={mockProductDetail} addToCart={addToCart} />)
|
|
112
|
+
|
|
113
|
+
let quantityBox
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
quantityBox = screen.getByRole('spinbutton')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await waitFor(() => {
|
|
119
|
+
expect(quantityBox).toHaveValue('1')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// update item quantity
|
|
123
|
+
await user.type(quantityBox, '{backspace}3')
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(quantityBox).toHaveValue('3')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('renders a product set properly - parent item', () => {
|
|
131
|
+
const parent = mockProductSet
|
|
132
|
+
renderWithProviders(
|
|
133
|
+
<MockComponent product={parent} addToCart={() => {}} addToWishlist={() => {}} />
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// NOTE: there can be duplicates of the same element, due to mobile and desktop views
|
|
137
|
+
// (they're hidden with display:none style)
|
|
138
|
+
|
|
139
|
+
const startingAtLabel = screen.getAllByText(/starting at/i)[0]
|
|
140
|
+
const addSetToCartButton = screen.getAllByRole('button', {name: /add set to cart/i})[0]
|
|
141
|
+
const addSetToWishlistButton = screen.getAllByRole('button', {name: /add set to wishlist/i})[0]
|
|
142
|
+
const variationAttributes = screen.queryAllByRole('radiogroup') // e.g. sizes, colors
|
|
143
|
+
const quantityPicker = screen.queryByRole('spinbutton', {name: /quantity:/i})
|
|
144
|
+
|
|
145
|
+
// What should exist:
|
|
146
|
+
expect(startingAtLabel).toBeInTheDocument()
|
|
147
|
+
expect(addSetToCartButton).toBeInTheDocument()
|
|
148
|
+
expect(addSetToWishlistButton).toBeInTheDocument()
|
|
149
|
+
|
|
150
|
+
// What should _not_ exist:
|
|
151
|
+
expect(variationAttributes).toHaveLength(0)
|
|
152
|
+
expect(quantityPicker).toBeNull()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('renders a product set properly - child item', () => {
|
|
156
|
+
const child = mockProductSet.setProducts[0]
|
|
157
|
+
renderWithProviders(
|
|
158
|
+
<MockComponent product={child} addToCart={() => {}} addToWishlist={() => {}} />
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
// NOTE: there can be duplicates of the same element, due to mobile and desktop views
|
|
162
|
+
// (they're hidden with display:none style)
|
|
163
|
+
|
|
164
|
+
const addToCartButton = screen.getAllByRole('button', {name: /add to cart/i})[0]
|
|
165
|
+
const addToWishlistButton = screen.getAllByRole('button', {name: /add to wishlist/i})[0]
|
|
166
|
+
const variationAttributes = screen.getAllByRole('radiogroup') // e.g. sizes, colors
|
|
167
|
+
const quantityPicker = screen.getByRole('spinbutton', {name: /quantity:/i})
|
|
168
|
+
const startingAtLabels = screen.queryAllByText(/starting at/i)
|
|
169
|
+
|
|
170
|
+
// What should exist:
|
|
171
|
+
expect(addToCartButton).toBeInTheDocument()
|
|
172
|
+
expect(addToWishlistButton).toBeInTheDocument()
|
|
173
|
+
expect(variationAttributes).toHaveLength(2)
|
|
174
|
+
expect(quantityPicker).toBeInTheDocument()
|
|
175
|
+
|
|
176
|
+
// What should _not_ exist:
|
|
177
|
+
expect(startingAtLabels).toHaveLength(0)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('validateOrderability callback is called when adding a set to cart', async () => {
|
|
181
|
+
const user = userEvent.setup()
|
|
182
|
+
|
|
183
|
+
const parent = mockProductSet
|
|
184
|
+
const validateOrderability = jest.fn()
|
|
185
|
+
|
|
186
|
+
renderWithProviders(
|
|
187
|
+
<MockComponent
|
|
188
|
+
product={parent}
|
|
189
|
+
validateOrderability={validateOrderability}
|
|
190
|
+
addToCart={() => {}}
|
|
191
|
+
addToWishlist={() => {}}
|
|
192
|
+
/>
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const button = screen.getByRole('button', {name: /add set to cart/i})
|
|
196
|
+
await user.click(button)
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(validateOrderability).toHaveBeenCalledTimes(1)
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('onVariantSelected callback is called after successfully selected a variant', async () => {
|
|
204
|
+
const user = userEvent.setup()
|
|
205
|
+
|
|
206
|
+
const onVariantSelected = jest.fn()
|
|
207
|
+
const child = mockProductSet.setProducts[0]
|
|
208
|
+
|
|
209
|
+
renderWithProviders(
|
|
210
|
+
<MockComponent
|
|
211
|
+
product={child}
|
|
212
|
+
onVariantSelected={onVariantSelected}
|
|
213
|
+
addToCart={() => {}}
|
|
214
|
+
addToWishlist={() => {}}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const size = screen.getByRole('radio', {name: /xl/i})
|
|
219
|
+
await user.click(size)
|
|
220
|
+
|
|
221
|
+
await waitFor(() => {
|
|
222
|
+
expect(onVariantSelected).toHaveBeenCalledTimes(1)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay} from '@chakra-ui/react'
|
|
11
|
+
import ProductView from '@salesforce/retail-react-app/app/components/product-view'
|
|
12
|
+
import {useProductViewModal} from '@salesforce/retail-react-app/app/hooks/use-product-view-modal'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A Modal that contains Product View
|
|
16
|
+
*/
|
|
17
|
+
const ProductViewModal = ({product, isOpen, onClose, ...props}) => {
|
|
18
|
+
const productViewModalData = useProductViewModal(product)
|
|
19
|
+
return (
|
|
20
|
+
<Modal size="4xl" isOpen={isOpen} onClose={onClose}>
|
|
21
|
+
<ModalOverlay />
|
|
22
|
+
<ModalContent containerProps={{'data-testid': 'product-view-modal'}}>
|
|
23
|
+
<ModalCloseButton />
|
|
24
|
+
<ModalBody pb={8} bg="white" paddingBottom={6} marginTop={6}>
|
|
25
|
+
<ProductView
|
|
26
|
+
showFullLink={true}
|
|
27
|
+
imageSize="sm"
|
|
28
|
+
product={productViewModalData.product}
|
|
29
|
+
isLoading={productViewModalData.isFetching}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
</ModalBody>
|
|
33
|
+
</ModalContent>
|
|
34
|
+
</Modal>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ProductViewModal.propTypes = {
|
|
39
|
+
isOpen: PropTypes.bool.isRequired,
|
|
40
|
+
onOpen: PropTypes.func.isRequired,
|
|
41
|
+
onClose: PropTypes.func.isRequired,
|
|
42
|
+
product: PropTypes.object,
|
|
43
|
+
isLoading: PropTypes.bool,
|
|
44
|
+
actionButtons: PropTypes.node,
|
|
45
|
+
onModalClose: PropTypes.func
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default ProductViewModal
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import ProductViewModal from '@salesforce/retail-react-app/app/components/product-view-modal/index'
|
|
11
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
12
|
+
import {fireEvent, screen} from '@testing-library/react'
|
|
13
|
+
import {useDisclosure} from '@chakra-ui/react'
|
|
14
|
+
import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-750518699578M'
|
|
15
|
+
import {prependHandlersToServer} from '@salesforce/retail-react-app/jest-setup'
|
|
16
|
+
|
|
17
|
+
const MockComponent = ({updateCart}) => {
|
|
18
|
+
const {isOpen, onOpen, onClose} = useDisclosure()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<button onClick={onOpen}>Open Modal</button>
|
|
23
|
+
<ProductViewModal
|
|
24
|
+
updateCart={updateCart}
|
|
25
|
+
onOpen={onOpen}
|
|
26
|
+
onClose={onClose}
|
|
27
|
+
isOpen={isOpen}
|
|
28
|
+
product={mockProductDetail}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
MockComponent.propTypes = {
|
|
35
|
+
updateCart: PropTypes.func
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
prependHandlersToServer([
|
|
40
|
+
{
|
|
41
|
+
path: '*/products/:productId',
|
|
42
|
+
res: () => {
|
|
43
|
+
return mockProductDetail
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('renders product view modal by default', () => {
|
|
50
|
+
renderWithProviders(<MockComponent />)
|
|
51
|
+
|
|
52
|
+
// open the modal
|
|
53
|
+
const trigger = screen.getByText(/open modal/i)
|
|
54
|
+
fireEvent.click(trigger)
|
|
55
|
+
|
|
56
|
+
expect(screen.getAllByText(/Black Single Pleat Athletic Fit Wool Suit/i)).toHaveLength(2)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('renders product view modal with handleUpdateCart handler', () => {
|
|
60
|
+
const handleUpdateCart = jest.fn()
|
|
61
|
+
renderWithProviders(<MockComponent updateCart={handleUpdateCart} />)
|
|
62
|
+
|
|
63
|
+
// open the modal
|
|
64
|
+
const trigger = screen.getByText(/open modal/i)
|
|
65
|
+
fireEvent.click(trigger)
|
|
66
|
+
|
|
67
|
+
// click on update
|
|
68
|
+
const updateButton = screen.getAllByText(/Update/)[0]
|
|
69
|
+
fireEvent.click(updateButton)
|
|
70
|
+
|
|
71
|
+
expect(handleUpdateCart).toHaveBeenCalledTimes(1)
|
|
72
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
import React, {useEffect, useState} from 'react'
|
|
8
|
+
import PropTypes from 'prop-types'
|
|
9
|
+
import {FormattedMessage, useIntl} from 'react-intl'
|
|
10
|
+
import {
|
|
11
|
+
Box,
|
|
12
|
+
Button,
|
|
13
|
+
Accordion,
|
|
14
|
+
AccordionButton,
|
|
15
|
+
AccordionItem,
|
|
16
|
+
AccordionPanel,
|
|
17
|
+
useToast
|
|
18
|
+
} from '@chakra-ui/react'
|
|
19
|
+
import {useForm} from 'react-hook-form'
|
|
20
|
+
import {ChevronDownIcon, ChevronUpIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
21
|
+
import PromoCodeFields from '@salesforce/retail-react-app/app/components/forms/promo-code-fields'
|
|
22
|
+
import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
|
|
23
|
+
import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react'
|
|
24
|
+
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
|
|
25
|
+
|
|
26
|
+
export const usePromoCode = () => {
|
|
27
|
+
const {formatMessage} = useIntl()
|
|
28
|
+
const {data: basket} = useCurrentBasket()
|
|
29
|
+
const form = useForm()
|
|
30
|
+
const toast = useToast()
|
|
31
|
+
|
|
32
|
+
const applyPromoCodeMutation = useShopperBasketsMutation('addCouponToBasket')
|
|
33
|
+
const removePromoCodeMutation = useShopperBasketsMutation('removeCouponFromBasket')
|
|
34
|
+
|
|
35
|
+
const submitPromoCode = async ({code}) => {
|
|
36
|
+
applyPromoCodeMutation.mutate(
|
|
37
|
+
{
|
|
38
|
+
parameters: {basketId: basket?.basketId},
|
|
39
|
+
body: {
|
|
40
|
+
code
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
onSuccess: () => {
|
|
45
|
+
form.reset({code: ''})
|
|
46
|
+
toast({
|
|
47
|
+
title: formatMessage({
|
|
48
|
+
defaultMessage: 'Promotion applied',
|
|
49
|
+
id: 'use_promocode.info.promo_applied'
|
|
50
|
+
}),
|
|
51
|
+
status: 'success',
|
|
52
|
+
position: 'top-right',
|
|
53
|
+
isClosable: true
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
onError: () => {
|
|
57
|
+
form.setError('code', {
|
|
58
|
+
type: 'manual',
|
|
59
|
+
message: formatMessage({
|
|
60
|
+
defaultMessage:
|
|
61
|
+
'Check the code and try again, it may already be applied or the promo has expired.',
|
|
62
|
+
id: 'use_promocode.error.check_the_code'
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const removePromoCode = async (couponItemId) => {
|
|
71
|
+
removePromoCodeMutation.mutate(
|
|
72
|
+
{
|
|
73
|
+
parameters: {basketId: basket?.basketId, couponItemId}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
onSuccess: () => {
|
|
77
|
+
toast({
|
|
78
|
+
title: formatMessage({
|
|
79
|
+
defaultMessage: 'Promotion removed',
|
|
80
|
+
id: 'use_promocode.info.promo_removed'
|
|
81
|
+
}),
|
|
82
|
+
status: 'success',
|
|
83
|
+
position: 'top-right',
|
|
84
|
+
isClosable: true
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
onError: () => {
|
|
88
|
+
toast({
|
|
89
|
+
title: formatMessage(API_ERROR_MESSAGE),
|
|
90
|
+
status: 'error',
|
|
91
|
+
position: 'top-right',
|
|
92
|
+
isClosable: true
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {form, submitPromoCode, removePromoCode}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const PromoCode = ({form, submitPromoCode, itemProps}) => {
|
|
103
|
+
const [isOpen, setOpen] = useState()
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (form.formState.isSubmitSuccessful) {
|
|
107
|
+
setOpen(false)
|
|
108
|
+
}
|
|
109
|
+
}, [form.formState.isSubmitSuccessful])
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Accordion allowToggle index={isOpen ? 0 : -1} onChange={() => setOpen(!isOpen)}>
|
|
113
|
+
<AccordionItem {...itemProps}>
|
|
114
|
+
{({isExpanded}) => (
|
|
115
|
+
<>
|
|
116
|
+
<AccordionButton
|
|
117
|
+
as={Button}
|
|
118
|
+
justifyContent="flex-start"
|
|
119
|
+
variant="link"
|
|
120
|
+
fontSize="sm"
|
|
121
|
+
rightIcon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
|
122
|
+
onClick={() => form.reset()}
|
|
123
|
+
>
|
|
124
|
+
<FormattedMessage
|
|
125
|
+
defaultMessage="Do you have a promo code?"
|
|
126
|
+
id="promocode.accordion.button.have_promocode"
|
|
127
|
+
/>
|
|
128
|
+
</AccordionButton>
|
|
129
|
+
|
|
130
|
+
<AccordionPanel px={0} mb={4}>
|
|
131
|
+
<Box
|
|
132
|
+
data-testid="promo-code-form"
|
|
133
|
+
as="form"
|
|
134
|
+
p={4}
|
|
135
|
+
background="white"
|
|
136
|
+
border="1px solid"
|
|
137
|
+
borderColor="gray.100"
|
|
138
|
+
borderRadius="base"
|
|
139
|
+
onSubmit={form.handleSubmit(submitPromoCode)}
|
|
140
|
+
>
|
|
141
|
+
<PromoCodeFields form={form} maxWidth="350px" />
|
|
142
|
+
</Box>
|
|
143
|
+
</AccordionPanel>
|
|
144
|
+
</>
|
|
145
|
+
)}
|
|
146
|
+
</AccordionItem>
|
|
147
|
+
</Accordion>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
PromoCode.propTypes = {
|
|
152
|
+
/** The form object returned from `usePromoCode` hook */
|
|
153
|
+
form: PropTypes.object.isRequired,
|
|
154
|
+
|
|
155
|
+
/** The submit callback returned from `usePromoCode` hook */
|
|
156
|
+
submitPromoCode: PropTypes.func.isRequired,
|
|
157
|
+
|
|
158
|
+
/** Props applied to inner AccordionItem. Useful for style overrides. */
|
|
159
|
+
itemProps: PropTypes.object
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default PromoCode
|
|
@@ -0,0 +1,83 @@
|
|
|
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 {
|
|
10
|
+
Box,
|
|
11
|
+
IconButton,
|
|
12
|
+
Popover,
|
|
13
|
+
PopoverArrow,
|
|
14
|
+
PopoverBody,
|
|
15
|
+
PopoverCloseButton,
|
|
16
|
+
PopoverContent,
|
|
17
|
+
PopoverHeader,
|
|
18
|
+
PopoverTrigger,
|
|
19
|
+
Portal,
|
|
20
|
+
Text
|
|
21
|
+
} from '@chakra-ui/react'
|
|
22
|
+
import {InfoIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
23
|
+
import {FormattedMessage} from 'react-intl'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This component renders a small info icon and displays a popover when hovered. It could be adapted
|
|
27
|
+
* to handle any kind of popover if needed, but for now its been set up to be used/shared for displaying
|
|
28
|
+
* promotions applied to products and/or orders on cart, checkout, order confirmation and order history.
|
|
29
|
+
*/
|
|
30
|
+
const PromoPopover = ({header, children, ...props}) => {
|
|
31
|
+
return (
|
|
32
|
+
<Box position="relative" {...props}>
|
|
33
|
+
<Popover isLazy placement="top" boundary="scrollParent" trigger="hover" variant="small">
|
|
34
|
+
<PopoverTrigger>
|
|
35
|
+
<IconButton
|
|
36
|
+
icon={
|
|
37
|
+
<InfoIcon
|
|
38
|
+
display="block"
|
|
39
|
+
boxSize="18px"
|
|
40
|
+
mt="-2px"
|
|
41
|
+
ml="-1px"
|
|
42
|
+
color="gray.600"
|
|
43
|
+
/>
|
|
44
|
+
}
|
|
45
|
+
display="block"
|
|
46
|
+
size="xs"
|
|
47
|
+
height="14px"
|
|
48
|
+
width="14px"
|
|
49
|
+
minWidth="auto"
|
|
50
|
+
position="relative"
|
|
51
|
+
variant="unstyled"
|
|
52
|
+
/>
|
|
53
|
+
</PopoverTrigger>
|
|
54
|
+
<Portal>
|
|
55
|
+
<PopoverContent border="none" borderRadius="base">
|
|
56
|
+
<Box boxShadow="lg" zIndex="-1">
|
|
57
|
+
<PopoverArrow />
|
|
58
|
+
<PopoverCloseButton />
|
|
59
|
+
<PopoverHeader borderBottom="none">
|
|
60
|
+
{header || (
|
|
61
|
+
<Text fontWeight="bold" fontSize="md">
|
|
62
|
+
<FormattedMessage
|
|
63
|
+
defaultMessage="Promotions Applied"
|
|
64
|
+
id="promo_popover.heading.promo_applied"
|
|
65
|
+
/>
|
|
66
|
+
</Text>
|
|
67
|
+
)}
|
|
68
|
+
</PopoverHeader>
|
|
69
|
+
<PopoverBody pt={0}>{children}</PopoverBody>
|
|
70
|
+
</Box>
|
|
71
|
+
</PopoverContent>
|
|
72
|
+
</Portal>
|
|
73
|
+
</Popover>
|
|
74
|
+
</Box>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
PromoPopover.propTypes = {
|
|
79
|
+
header: PropTypes.any,
|
|
80
|
+
children: PropTypes.any
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default PromoPopover
|
|
@@ -0,0 +1,58 @@
|
|
|
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 {Button, HStack, Input, useNumberInput} from '@chakra-ui/react'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This is the mobile implementation of the Chakra NumberInput. This simple component essentially
|
|
13
|
+
* is a helper so we don't have to reuse the hooks every time we need a number input since design dictates
|
|
14
|
+
* we use the moobile variation on all screens.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: We can optionally put global logic we see if in here, and various styling decisions in this single
|
|
17
|
+
* component.
|
|
18
|
+
*
|
|
19
|
+
* @param {*} props
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
const QuantityPicker = (props) => {
|
|
23
|
+
const {getInputProps, getIncrementButtonProps, getDecrementButtonProps} = useNumberInput({
|
|
24
|
+
...props,
|
|
25
|
+
// Defaults
|
|
26
|
+
focusInputOnChange: false,
|
|
27
|
+
onFocus: (e) => {
|
|
28
|
+
// eslint-disable-next-line react/prop-types
|
|
29
|
+
const {onFocus} = props
|
|
30
|
+
|
|
31
|
+
// This is useful for mobile devices, this allows the user to pop open the keyboard and set the
|
|
32
|
+
// new quantity with one click.
|
|
33
|
+
e.target.select()
|
|
34
|
+
|
|
35
|
+
// If there is a `onFocus` property define, call it with the event captured.
|
|
36
|
+
// eslint-disable-next-line react/prop-types
|
|
37
|
+
onFocus && onFocus.call(this, e)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const inc = getIncrementButtonProps({variant: 'outline'})
|
|
42
|
+
const dec = getDecrementButtonProps({variant: 'outline'})
|
|
43
|
+
const input = getInputProps({maxWidth: '44px', textAlign: 'center'})
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<HStack>
|
|
47
|
+
<Button data-testid="quantity-decrement" {...dec}>
|
|
48
|
+
-
|
|
49
|
+
</Button>
|
|
50
|
+
<Input {...input} />
|
|
51
|
+
<Button data-testid="quantity-increment" {...inc}>
|
|
52
|
+
+
|
|
53
|
+
</Button>
|
|
54
|
+
</HStack>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default QuantityPicker
|