{"version":3,"file":"main.d3fb814dd1690866.js","sources":["webpack://storefronts/./rspack/polyfills.js","webpack://storefronts/./src/State.tsx","webpack://storefronts/./src/appsource/components/HiddenComponentWrapper/HiddenComponentWrapper.tsx","webpack://storefronts/./src/appsource/components/RecommendationsRibbonWrapper/RecommendationsRibbonWrapper.tsx","webpack://storefronts/./src/appsource/components/billingFrequencyChoiceGroup/billingFrequencyChoiceGroup.tsx","webpack://storefronts/./src/appsource/components/billingTermChoiceGroup/billingTermChoiceGroup.tsx","webpack://storefronts/./src/appsource/components/saasPricingTable/saasPricingTable.tsx","webpack://storefronts/./src/appsource/components/orderLoading/orderLoading.tsx","webpack://storefronts/./src/appsource/components/redirectModal/redirectModal.tsx","webpack://storefronts/./src/appsource/components/busyLoader/busyLoader.tsx","webpack://storefronts/./src/appsource/components/MSuserErrorBanner/MSuserErrorBanner.tsx","webpack://storefronts/./src/appsource/components/thumbsUpDown/thumbsUpDown.tsx","webpack://storefronts/./src/appsource/containers/LegacyCheckoutHydration.tsx","webpack://storefronts/./src/appsource/components/UsefulInfo.tsx","webpack://storefronts/./src/shared/utils/popupDisplayManager.ts","webpack://storefronts/./src/shared/components/filtersDropDown.tsx","webpack://storefronts/./src/shared/actions/tileDataThunkActions.tsx","webpack://storefronts/./src/shared/handlers/recoverableHydrationError.ts","webpack://storefronts/./src/shared/utils/pltUtils.ts","webpack://storefronts/./src/shared/actions/tileDataActions.tsx","webpack://storefronts/./src/shared/actions/queryActions.tsx","webpack://storefronts/./src/shared/components/LoadingSpinner.tsx","webpack://storefronts/./src/shared/hooks/useClientRoutes.tsx","webpack://storefronts/./src/shared/components/longContent.tsx","webpack://storefronts/./src/shared/components/reviewQAContent.tsx","webpack://storefronts/./src/shared/components/appReviewItem/appReviewItem.tsx","webpack://storefronts/./src/shared/components/ratingPercentages.tsx","webpack://storefronts/./src/shared/components/ratingSummary.tsx","webpack://storefronts/./src/shared/components/reviewsDropDowns.tsx","webpack://storefronts/./src/shared/components/noReviewsToShow.tsx","webpack://storefronts/./src/shared/components/externalRatingSummary.tsx","webpack://storefronts/./src/shared/components/externalRatingSummaries.tsx","webpack://storefronts/./src/shared/containers/externalRatingSummaries.tsx","webpack://storefronts/./src/appsource/components/userTerms/userTerms.tsx","webpack://storefronts/./src/appsource/components/appReviewCollection.tsx","webpack://storefronts/./src/appsource/containers/appReviewCollection.tsx","webpack://storefronts/./src/shared/utils/capabilities.tsx","webpack://storefronts/./src/shared/utils/autoruns.tsx","webpack://storefronts/./src/appsource/components/overview.tsx","webpack://storefronts/./src/appsource/containers/overview.tsx","webpack://storefronts/./src/shared/components/userFavouriteTileDetailButton.tsx","webpack://storefronts/./src/shared/containers/userFavouriteTileDetailButton.tsx","webpack://storefronts/./src/shared/components/sortedTable/sortedTableColumn.tsx","webpack://storefronts/./src/shared/components/sortedTable/sortedTable.tsx","webpack://storefronts/./src/shared/components/sortedTable/sortedTH.tsx","webpack://storefronts/./src/shared/components/simplePriceCell.tsx","webpack://storefronts/./src/shared/components/pureSpzaComponent.tsx","webpack://storefronts/./src/shared/components/simplePlanPricing.tsx","webpack://storefronts/./src/shared/components/iconLink.tsx","webpack://storefronts/./src/shared/components/card.tsx","webpack://storefronts/./src/shared/components/stickyCard.tsx","webpack://storefronts/./src/shared/components/microsoftManagedIndicatorWrapper.tsx","webpack://storefronts/./src/shared/utils/localeUtils.ts","webpack://storefronts/./src/appsource/components/linkedin/linkedinProductGroupBar.tsx","webpack://storefronts/./src/appsource/components/linkedin/linkedinUpcomingEvent.tsx","webpack://storefronts/./src/appsource/components/linkedin/linkedinFeaturedCustomer.tsx","webpack://storefronts/./src/shared/components/modals/submittedModal.tsx","webpack://storefronts/./src/shared/components/featureFeedback.tsx","webpack://storefronts/./src/appsource/components/linkedin/linkedinProductGroup.tsx","webpack://storefronts/./src/appsource/components/Badge.tsx","webpack://storefronts/./src/appsource/components/appDetails.tsx","webpack://storefronts/./src/appsource/containers/appDetails.tsx","webpack://storefronts/./src/appsource/components/serviceDetails.tsx","webpack://storefronts/./src/appsource/utils/consultingServicesUtils.ts","webpack://storefronts/./src/appsource/containers/serviceDetails.tsx","webpack://storefronts/./src/appsource/pathsViewMap.ts","webpack://storefronts/./src/shared/utils/signin.tsx","webpack://storefronts/./src/shared/msal/loginRedirect.ts","webpack://storefronts/./src/shared/components/modals/signInModal.tsx","webpack://storefronts/./src/shared/components/modals/mediaModal.tsx","webpack://storefronts/./src/shared/components/modals/videoModal.tsx","webpack://storefronts/./src/shared/components/modals/driveModal.tsx","webpack://storefronts/./src/shared/utils/fieldHubUtils.ts","webpack://storefronts/./src/shared/components/modals/fieldHubModal.tsx","webpack://storefronts/./src/shared/containers/modals/fieldHubModal.tsx","webpack://storefronts/./src/shared/components/modals/errorModal.tsx","webpack://storefronts/./src/shared/containers/modals/errorModal.tsx","webpack://storefronts/./src/shared/components/modals/animationModal.tsx","webpack://storefronts/./src/shared/components/modals/disclaimerModal.tsx","webpack://storefronts/./src/shared/utils/npsUtils.tsx","webpack://storefronts/./src/shared/components/modals/npsModal.tsx","webpack://storefronts/./src/shared/components/modals/instructionsModalBody.tsx","webpack://storefronts/./src/shared/components/modals/instructionsModal.tsx","webpack://storefronts/./src/shared/components/modals/downloadSampleModal.tsx","webpack://storefronts/./src/shared/store/createStore.ts","webpack://storefronts/./src/shared/utils/hashMapUtils.tsx","webpack://storefronts/./src/shared/reducers/commonEntityDataReducer.tsx","webpack://storefronts/./src/embed/reducers/pbiAppDataReducer.tsx","webpack://storefronts/./src/embed/reducers/partnerAppDataReducer.tsx","webpack://storefronts/./src/shared/reducers/checkoutReducer.ts","webpack://storefronts/./src/shared/reducers/serviceDataReducer.tsx","webpack://storefronts/./src/shared/reducers/reducers.tsx","webpack://storefronts/./src/shared/reducers/searchReducer.tsx","webpack://storefronts/./src/shared/reducers/modalReducer.tsx","webpack://storefronts/./src/shared/reducers/appDataReducer.tsx","webpack://storefronts/./src/shared/reducers/cloudsIndustryDataReducer.tsx","webpack://storefronts/./src/shared/reducers/userDataReducer.tsx","webpack://storefronts/./src/shared/reducers/configReducer.tsx","webpack://storefronts/./src/shared/reducers/dynamicCampaignReducer.tsx","webpack://storefronts/./src/shared/reducers/userFavouriteReducer.tsx","webpack://storefronts/./src/shared/reducers/partnersReducer.ts","webpack://storefronts/./src/shared/utils/errorHandlerUtil.tsx","webpack://storefronts/./src/embed/actions/embedThunks.tsx","webpack://storefronts/./src/embed/embedMessaging.tsx","webpack://storefronts/./src/appsource/components/modals/userProfileModal/userProfileModal.tsx","webpack://storefronts/./src/appsource/containers/modals/userProfileModal/userProfileModal.tsx","webpack://storefronts/./src/appsource/components/modals/successConsentModal/successConsentModal.tsx","webpack://storefronts/./src/appsource/components/modals/failureConsentModal/failureConsentModal.tsx","webpack://storefronts/./src/appsource/components/modals/consentModal/consentModalContent.tsx","webpack://storefronts/./src/shared/utils/InputValidationUtils.tsx","webpack://storefronts/./src/appsource/components/modals/consentModal/consentModal.tsx","webpack://storefronts/./src/appsource/containers/modals/consentModal/consentModal.tsx","webpack://storefronts/./src/shared/components/modals/reviewActionModal.tsx","webpack://storefronts/./src/shared/components/modals/submitReviewResponseModal.tsx","webpack://storefronts/./src/shared/components/modals/ratingModal.tsx","webpack://storefronts/./src/shared/containers/modals/ratingModal.tsx","webpack://storefronts/./src/shared/components/modals/reportAbuseModal.tsx","webpack://storefronts/./src/shared/containers/modals/reportAbuseModal.tsx","webpack://storefronts/./src/shared/components/modals/reviewCommentModal/reviewCommentModal.tsx","webpack://storefronts/./src/shared/containers/modals/reviewCommentModal.tsx","webpack://storefronts/./src/shared/components/modals/reviewMarkAsHelpfulModal/reviewMarkAsHelpfulModalErrorDialog.tsx","webpack://storefronts/./src/shared/components/modals/reviewMarkAsHelpfulModal/reviewMarkAsHelpfulModal.tsx","webpack://storefronts/./src/shared/components/modals/modal.tsx","webpack://storefronts/./src/shared/handlers/reportAbuseModalHandler.tsx","webpack://storefronts/./src/shared/handlers/consentModalHandler.tsx","webpack://storefronts/./src/shared/handlers/ratingModalHandler.tsx","webpack://storefronts/./src/shared/containers/modals/modal.tsx","webpack://storefronts/./src/shared/components/footer.tsx","webpack://storefronts/./src/shared/containers/footer.tsx","webpack://storefronts/./src/shared/utils/appViewUtils.tsx","webpack://storefronts/./src/appsource/components/modals/npsPreviewModal.tsx","webpack://storefronts/./src/appsource/components/modals/npsSubmittedModal.tsx","webpack://storefronts/./src/appsource/components/modals/npsSurveyModal.tsx","webpack://storefronts/./src/appsource/components/modals/npsModal/npsModal.tsx","webpack://storefronts/./src/appsource/components/nps.tsx","webpack://storefronts/./src/shared/components/cookieBanner.tsx","webpack://storefronts/./src/shared/containers/cookieBanner.tsx","webpack://storefronts/./src/shared/components/loader.tsx","webpack://storefronts/./src/shared/msal/user.ts","webpack://storefronts/./src/shared/components/SilentClientLogin.tsx","webpack://storefronts/./src/shared/msal/logout.ts","webpack://storefronts/./src/shared/components/appView.tsx","webpack://storefronts/./src/shared/utils/cookieBannerUtils.ts","webpack://storefronts/./src/shared/handlers/appViewHandler.tsx","webpack://storefronts/./src/shared/containers/AppView.tsx","webpack://storefronts/./src/shared/actions/queryThunkActions.tsx","webpack://storefronts/./src/shared/utils/filterItemUtils.ts","webpack://storefronts/./src/shared/components/filterPillItem.tsx","webpack://storefronts/./src/shared/components/home.tsx","webpack://storefronts/./src/shared/containers/home.tsx","webpack://storefronts/./src/shared/components/curatedGallery.tsx","webpack://storefronts/./src/shared/containers/curatedGallery.tsx","webpack://storefronts/./src/shared/components/filterItem.tsx","webpack://storefronts/./src/shared/components/filterPane.tsx","webpack://storefronts/./src/appsource/components/filterLink.tsx","webpack://storefronts/./src/appsource/components/filterPane.tsx","webpack://storefronts/./src/shared/utils/filterUtils.tsx","webpack://storefronts/./src/shared/components/paginationControl.tsx","webpack://storefronts/./src/shared/components/noSearchResultsButtons.tsx","webpack://storefronts/./src/shared/components/noSearchResults.tsx","webpack://storefronts/./src/appsource/components/filteredGallery.tsx","webpack://storefronts/./src/shared/components/galleryHeader.tsx","webpack://storefronts/./src/shared/components/appPromotionPane.tsx","webpack://storefronts/./src/shared/components/popularApps.tsx","webpack://storefronts/./src/shared/components/GalleryContent.tsx","webpack://storefronts/./src/shared/components/galleryPage.tsx","webpack://storefronts/./src/shared/containers/galleryPage.tsx","webpack://storefronts/./src/shared/components/AuthRedirect.tsx","webpack://storefronts/./src/appsource/Router.tsx","webpack://storefronts/./src/shared/hooks/useLocationChange.tsx","webpack://storefronts/./src/shared/components/FederatedModules/RestClient.tsx","webpack://storefronts/./src/shared/handlers/rootHandler.tsx","webpack://storefronts/./src/shared/components/scrollToTopButton.tsx","webpack://storefronts/./src/shared/services/http/tokenRestClient.ts","webpack://storefronts/./src/appsource/refreshTokenHandler.tsx","webpack://storefronts/./src/shared/utils/locales.tsx","webpack://storefronts/./src/shared/utils/debug.ts","webpack://storefronts/./src/shared/i18n/client.ts","webpack://storefronts/./src/shared/handlers/onError.ts","webpack://storefronts/./src/shared/handlers/seo.ts","webpack://storefronts/./src/shared/msal/postRedirect.ts","webpack://storefronts/./src/appsource/App.tsx","webpack://storefronts/./src/appsource/mainClientHookup.tsx","webpack://storefronts/./src/shared/services/init/initBrowser.ts","webpack://storefronts/./src/appsource/utils/comscore.ts","webpack://storefronts/./src/appsource/utils/feedback.ts","webpack://storefronts/./src/appsource/utils/nps/nps.ts","webpack://storefronts/./src/embed/constants.tsx","webpack://storefronts/./src/embed/embedHostUtils.tsx","webpack://storefronts/./src/logger/index.ts","webpack://storefronts/./src/logger/logger.ts","webpack://storefronts/./rspack/paths.ts","webpack://storefronts/./src/server/api/ApiVersion.ts","webpack://storefronts/./src/server/Utils.ts","webpack://storefronts/./src/server/LogHelper.tsx","webpack://storefronts/./src/server/serverLogger/serverLogger.ts","webpack://storefronts/./src/shared/AzureEnvironmentSettings.tsx","webpack://storefronts/./src/shared/Models.tsx","webpack://storefronts/./src/shared/actions/actions.tsx","webpack://storefronts/./src/shared/actions/checkoutActions.ts","webpack://storefronts/./src/shared/actions/partnersActions.ts","webpack://storefronts/./src/shared/actions/privateOffersThunkActions.tsx","webpack://storefronts/./src/interfaces/IBilling.tsx","webpack://storefronts/./src/shared/utils/leadsUtils.tsx","webpack://storefronts/./src/shared/services/http/leadgenRestClient.ts","webpack://storefronts/./src/shared/services/http/reviewRestClient.ts","webpack://storefronts/./src/shared/services/http/fieldHubRestClient.ts","webpack://storefronts/./src/shared/services/http/dynamicCampaignRestClient.ts","webpack://storefronts/./src/shared/services/http/purchaseRestClient.ts","webpack://storefronts/./src/shared/services/http/commerceApiRestClient.ts","webpack://storefronts/./src/shared/services/http/reviewCommentsRestClient.ts","webpack://storefronts/./src/shared/services/http/reviewMarkAsHelpfulRestClient.ts","webpack://storefronts/./src/shared/services/http/reviewsCatalogRestClient.ts","webpack://storefronts/./src/shared/services/http/linkedInRestClient.ts","webpack://storefronts/./src/shared/services/http/graphRestClient.ts","webpack://storefronts/./src/shared/services/http/reportReviewRestClient.ts","webpack://storefronts/./src/shared/services/http/reviewAppReviewsRestClient.ts","webpack://storefronts/./src/shared/services/http/userReviewRestClient.ts","webpack://storefronts/./src/shared/services/http/authRestClient.ts","webpack://storefronts/./src/shared/services/product/product.ts","webpack://storefronts/./src/shared/actions/thunkActions.tsx","webpack://storefronts/./src/shared/services/http/catalogApiRestClient.ts","webpack://storefronts/./src/shared/utils/thunkActionUtils.tsx","webpack://storefronts/./src/shared/services/http/userRestClient.ts","webpack://storefronts/./src/shared/actions/userFavouriteActions.tsx","webpack://storefronts/./src/shared/services/http/userFavouriteRestClient.ts","webpack://storefronts/./src/shared/actions/userFavouriteThunkActions.tsx","webpack://storefronts/./src/shared/utils/userFavouriteThunkActionUtils.tsx","webpack://storefronts/./src/shared/components/ButtonLink.tsx","webpack://storefronts/./src/appsource/components/ShowingResultsFor/ShowingResultsFor.tsx","webpack://storefronts/./src/shared/components/FilteredGalleryHeader/FilteredGalleryHeader.tsx","webpack://storefronts/./src/shared/components/ItemsTooltip.tsx","webpack://storefronts/./src/shared/components/successStory.tsx","webpack://storefronts/./src/shared/components/Testimonials/Testimonials.data.ts","webpack://storefronts/./src/shared/components/Testimonials/Testimonials.tsx","webpack://storefronts/./src/shared/components/animation.tsx","webpack://storefronts/./src/shared/components/appDetailsHeaderRatingBar/appDetailsHeaderRatingBar.tsx","webpack://storefronts/./src/shared/components/appDetailsLinkedItems/appDetailsLinkedItems.tsx","webpack://storefronts/./src/shared/components/appReviewCommentCollection/appReviewItemCommentPagination/appReviewItemCommentPagination.tsx","webpack://storefronts/./src/shared/components/appReviewCommentCollection/appReviewItemCommentEmptyMessage/appReviewItemCommentEmptyMessage.tsx","webpack://storefronts/./src/shared/components/appReviewCommentCollection/appReviewItemCommentCollectionListSection/appReviewItemCommentCollectionListSection.tsx","webpack://storefronts/./src/shared/components/appReviewCommentCollection/appReviewCommentCollection.tsx","webpack://storefronts/./src/shared/components/appReviewCommentCollection/appReviewCommentCollectionPaginationButton/appReviewCommentCollectionPaginationButton.tsx","webpack://storefronts/./src/shared/components/appReviewCommentItem/appReviewCommentItem.tsx","webpack://storefronts/./src/shared/components/appReviewCommentItemToolbar/appReviewCommentItemToolbar.tsx","webpack://storefronts/./src/shared/components/appReviewItemCommentComposer.tsx","webpack://storefronts/./src/shared/components/appReviewItemToolbar/appReviewItemToolbar.tsx","webpack://storefronts/./src/shared/components/reviewIsvReply.tsx","webpack://storefronts/./src/shared/containers/appReviewCommentCollection.tsx","webpack://storefronts/./src/shared/components/appReviewSocialContent/appReviewSocialContent.tsx","webpack://storefronts/./src/shared/components/linkedAddins.tsx","webpack://storefronts/./src/shared/utils/linkedItems.ts","webpack://storefronts/./src/shared/components/appTile/appTile.tsx","webpack://storefronts/./src/shared/components/appTileLinkedItems/appTileLinkedItems.tsx","webpack://storefronts/./src/shared/components/bannerLinks.tsx","webpack://storefronts/./src/shared/components/banner.tsx","webpack://storefronts/./src/shared/components/baseCtaButton.tsx","webpack://storefronts/./src/shared/components/breadcrumbUrl.tsx","webpack://storefronts/./src/shared/components/commonTile.tsx","webpack://storefronts/./src/shared/components/contentStyles.ts","webpack://storefronts/./src/shared/components/ctaButtons/ctaButtons.tsx","webpack://storefronts/./src/shared/components/customMeterPricing/customMeterPricing.tsx","webpack://storefronts/./src/shared/components/externalLink.tsx","webpack://storefronts/./src/shared/utils/externalLinkUtils.tsx","webpack://storefronts/./src/shared/components/futurePriceTooltip.tsx","webpack://storefronts/./src/shared/components/futurePriceWarning.tsx","webpack://storefronts/./src/shared/components/badges/freeTrialBadge.tsx","webpack://storefronts/./src/shared/components/badges/privatePlanBadge.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasPlanNameCell.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasPricingCell.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasSubtotalCell/saasSubtotalCell.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasPricingTableColumns/saasPricingTableColumns.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasPricingRow/saasPricingRow.tsx","webpack://storefronts/./src/shared/components/saasPricingTableBase/saasPricingTableBase/saasPricingTableBase.tsx","webpack://storefronts/./src/shared/components/internalLink.tsx","webpack://storefronts/./src/shared/components/itemsContainer.tsx","webpack://storefronts/./src/shared/components/linkedItem/linkedItem.tsx","webpack://storefronts/./src/shared/components/msClarity/msClarity.tsx","webpack://storefronts/./src/shared/components/products.tsx","webpack://storefronts/./src/shared/components/ratings/ratingsInfo.tsx","webpack://storefronts/./src/shared/components/ribbon.tsx","webpack://storefronts/./src/shared/utils/richTextDropDownUtils.tsx","webpack://storefronts/./src/shared/components/richTextDropDown.tsx","webpack://storefronts/./src/shared/components/safeHtmlWrapper/safeHtmlWrapper.tsx","webpack://storefronts/./src/shared/components/sortingDropdown/sortingDropdown.tsx","webpack://storefronts/./src/shared/components/spzaComponent.tsx","webpack://storefronts/./src/shared/components/tabs.tsx","webpack://storefronts/./src/shared/components/telemetryImage.tsx","webpack://storefronts/./src/shared/components/tooltip.tsx","webpack://storefronts/./src/shared/containers/appTile.tsx","webpack://storefronts/./src/shared/components/userFavouriteTileButton.tsx","webpack://storefronts/./src/shared/containers/userFavouriteTileButton.tsx","webpack://storefronts/./src/shared/components/baseTile.tsx","webpack://storefronts/./src/shared/containers/baseTile.tsx","webpack://storefronts/./src/shared/components/dynamicCampaign.tsx","webpack://storefronts/./src/shared/containers/dynamicCampaign.tsx","webpack://storefronts/./src/shared/components/serviceTile.tsx","webpack://storefronts/./src/shared/containers/serviceTile.tsx","webpack://storefronts/./src/shared/hooks/useMsClarity.tsx","webpack://storefronts/./src/shared/hooks/useHydration.tsx","webpack://storefronts/./src/shared/hooks/useSpzaInstrumentService.tsx","webpack://storefronts/./src/shared/services/telemetry/1DSClient.tsx","webpack://storefronts/./src/shared/hooks/useTelemetry.tsx","webpack://storefronts/./src/shared/logger/client.ts","webpack://storefronts/./src/shared/logger/base.ts","webpack://storefronts/./src/shared/logger/system.ts","webpack://storefronts/./src/shared/logger/config.ts","webpack://storefronts/./src/shared/msal/client.ts","webpack://storefronts/./src/shared/msal/config.ts","webpack://storefronts/./src/shared/msal/scopes.ts","webpack://storefronts/./src/shared/msal/tokens.ts","webpack://storefronts/./src/shared/msal/windowUtils.ts","webpack://storefronts/./src/shared/oneTaxonomy/utils.ts","webpack://storefronts/./src/shared/utils/xssUtils.ts","webpack://storefronts/./src/shared/routerHistory.tsx","webpack://storefronts/./src/shared/services/feedback/index.ts","webpack://storefronts/./src/shared/services/http/armRestClient.ts","webpack://storefronts/./src/shared/services/http/entityRestClient.ts","webpack://storefronts/./src/shared/services/http/httpProtocol.ts","webpack://storefronts/./src/server/errors/httpError.tsx","webpack://storefronts/./src/shared/services/init/appConfig.ts","webpack://storefronts/./src/shared/services/telemetry/core/action.ts","webpack://storefronts/./src/shared/services/telemetry/core/buffer.ts","webpack://storefronts/./src/shared/services/telemetry/core/consumer.ts","webpack://storefronts/./src/shared/services/telemetry/core/instrument.ts","webpack://storefronts/./src/shared/services/telemetry/core/provider.ts","webpack://storefronts/./src/shared/services/telemetry/providers/staticProvider.ts","webpack://storefronts/./src/shared/services/telemetry/shared/sharedTelemetry.ts","webpack://storefronts/./src/shared/services/telemetry/shared/sharedInstrument.ts","webpack://storefronts/./src/shared/services/telemetry/spza/spzaTelemetry.ts","webpack://storefronts/./src/shared/services/telemetry/spza/spzaInstrument.ts","webpack://storefronts/./src/shared/services/window.tsx","webpack://storefronts/./src/shared/specifics.tsx","webpack://storefronts/./src/shared/utils/accessToken/accessToken.ts","webpack://storefronts/./src/shared/utils/countryCodeMap.ts","webpack://storefronts/./src/shared/utils/optimizeUrlHelper.ts","webpack://storefronts/./src/server/utils/func.ts","webpack://storefronts/./src/shared/services/http/urlUtils.ts","webpack://storefronts/./src/shared/utils/appUtils.tsx","webpack://storefronts/./src/shared/utils/cookieUtils.tsx","webpack://storefronts/./src/shared/utils/browserStorageUtils.tsx","webpack://storefronts/./src/shared/utils/businessConstants.ts","webpack://storefronts/./src/shared/utils/checkoutUtils.tsx","webpack://storefronts/./src/shared/utils/constants.tsx","webpack://storefronts/./src/shared/utils/dataMappingConstants.ts","webpack://storefronts/./src/shared/utils/consultingServicesUtils.ts","webpack://storefronts/./src/shared/utils/dataMapping.tsx","webpack://storefronts/./src/shared/utils/dataMappingUtils.tsx","webpack://storefronts/./src/shared/utils/datamappingHelpers.tsx","webpack://storefronts/./src/shared/utils/detailUtils.tsx","webpack://storefronts/./src/shared/utils/entityRegistration.tsx","webpack://storefronts/./src/shared/utils/errorUtils.ts","webpack://storefronts/./src/shared/utils/filterHelpers.tsx","webpack://storefronts/./src/shared/utils/filterModule.tsx","webpack://storefronts/./src/shared/utils/futurePricesUtils/futurePricesUtils.ts","webpack://storefronts/./src/shared/utils/httpClientUtil.tsx","webpack://storefronts/./src/shared/utils/localization.tsx","webpack://storefronts/./src/shared/utils/modals.tsx","webpack://storefronts/./src/shared/utils/objectUtils.tsx","webpack://storefronts/./src/shared/utils/onetimepaymentoffers/oneTimePaymentOffers.tsx","webpack://storefronts/./src/shared/utils/partnerManifestLoader.ts","webpack://storefronts/./src/shared/utils/pricing/pricing.tsx","webpack://storefronts/./src/shared/utils/privacyUtils.ts","webpack://storefronts/./src/shared/utils/reactUtils.tsx","webpack://storefronts/./src/shared/utils/reviewsUtils.tsx","webpack://storefronts/./src/shared/utils/routeUtils.ts","webpack://storefronts/./src/shared/utils/search.tsx","webpack://storefronts/./src/shared/utils/seoUtils.ts","webpack://storefronts/./src/shared/utils/serviceOffersSupportedStatesCodes.ts","webpack://storefronts/./src/shared/utils/silentLogInUtils.tsx","webpack://storefronts/./src/shared/utils/stringUtils.tsx","webpack://storefronts/./src/shared/utils/telemetryUtils.tsx","webpack://storefronts/./src/shared/utils/urlUtils.tsx","webpack://storefronts/./src/shared/utils/userFavouriteDedicatedUtils.tsx","webpack://storefronts/./src/shared/utils/userFavouriteUtils.tsx","webpack://storefronts/./src/shared/utils/userUtils.tsx"],"sourcesContent":["module.exports = () => {};\n","import {\n IAppSearchResult,\n IServiceSearchResult,\n IDataItem,\n IAppDataItem,\n IUserInfo,\n ICuratedSection,\n UserSegment,\n IUserLeadGenProfile,\n ICartItem,\n ICustomer,\n IPublisherOfferResponse,\n IURLQuery,\n IAppsPricingsPayload,\n IAppReview,\n IUserTenantsDetails,\n ICloudsIndustry,\n IPartnerSearchResult,\n ISetSeatsAction,\n ISetTermIdAction,\n ISetTermDurationAction,\n ISetRecurringBilling,\n IBillingAddress,\n CheckoutSource,\n IAccountsDetails,\n ICheckoutId,\n SendLeadInfoOptions,\n} from '@shared/Models';\n\nimport { IDataMap, IDataValues } from '@shared/utils/dataMapping';\nimport { Constants } from '@shared/utils/constants';\nimport { Service } from '@shared/serviceViewModel';\n\nimport { WCPconsent } from '@shared/components/cookieBanner';\nimport { IContactDetails, IFilterLocation } from '@shared/partnersProtocol/partnersHostInboundMessages';\nimport { IUserFavouriteState, IUserFavouriteApp, IUserFavouriteService } from '@shared/interfaces/userFavouriteModels';\n\n// eslint-disable-next-line import/named\nimport type { IReceiptView } from '@microsoft-commerce/purchase-blends-component-library';\nimport { enrichReviewCommentsApiVersion } from '@shared/specifics';\nimport { IAppReviewCommentsState } from '@shared/interfaces/reviews/comments';\nimport { IAppReviewMarkedAsHelpfulState } from '@shared/interfaces/reviews/markAsHelpful/stateModels';\nimport type { i18n } from 'i18next';\n\nexport interface IReviewsState {\n entityId: string;\n reviews: IAppReview[];\n isPurchased: boolean;\n userReview?: IAppReview;\n reviewComments?: { [reviewId: string]: IAppReviewCommentsState };\n markedAsHelpful: IAppReviewMarkedAsHelpfulState;\n userCommentedReviewIds?: string[];\n}\n\nexport interface ILinkedInCustomer {\n id: string;\n name: string;\n logoUrl: string;\n followersCount: number;\n businessCategory: string;\n detailsUrl: string;\n}\n\nexport interface ILinkedInEvent {\n id: string;\n name: string;\n logoUrl: string;\n eventType: string;\n startTime: number;\n endTime: number;\n attendeeCount: number;\n detailsUrl: string;\n}\n\nexport interface ILinkedInProductGroup {\n offerId: string;\n id: string;\n productGroupId: string;\n membersCount: number;\n featuredCustomers: ILinkedInCustomer[];\n upcomingEvents: ILinkedInEvent[];\n detailsUrl: string;\n eventsUrl: string;\n}\n\nconst initialAppReviewsDataState: IReviewsState = {\n entityId: '',\n reviews: [],\n isPurchased: false,\n reviewComments: {},\n markedAsHelpful: {\n userReviewIdsMarkedAsHelpful: [],\n userReviewIdsLoading: {},\n },\n};\n\n// add your feature flags here\n// for now the type should be string\n// we can explore other types in the future\n// DON'T FORGET to initialize it in the constructor\n// ALSO DON'T FORGET to initialize it in the initial state\n// your new feature flag value will be automatically update in\n// state.config.featureFlags.myNewFeatureFlag\nexport class IFeatureFlags {\n // i.e. myNewFeatureFlag: string;\n AppReviews: string;\n ProfileExp: string;\n AltAuth: string;\n SaasLinkingEnabled: string;\n RedesignEnabled: string;\n ShowPopularity: string;\n UseSecondarySearch: string;\n privateOffers: string;\n purchaseBlends: string;\n newCustomerFlow: string;\n PartnersEnabled: string;\n forceCompletePurchase: string;\n allowAdminUser: string;\n showConsent: string;\n ensureprewarm: string;\n useSearchV2: string;\n ReviewsMyCommentsFilter: string;\n sortByRecentlyUpdatedEnabled: string;\n sortByRatingEnabled: string;\n allNewSortsEnabled: string;\n I18nextEnabled: string;\n cloudMarketplaceModeOn: boolean;\n recommendations: boolean;\n openInNewWindowButton: boolean;\n logToConsole: boolean;\n expredirect: boolean;\n partnersTestMode: boolean;\n\n cloudMarketplaceModeFF = ['newHeader'];\n constructor() {\n // i.e. this.myNewFeatureFlag = 'false';\n this.AppReviews = 'false';\n this.ProfileExp = 'false';\n this.AltAuth = 'false';\n this.SaasLinkingEnabled = 'false';\n this.RedesignEnabled = 'false';\n this.ShowPopularity = 'false';\n this.UseSecondarySearch = 'false';\n this.privateOffers = 'false';\n this.purchaseBlends = 'false';\n this.newCustomerFlow = 'false';\n this.PartnersEnabled = 'false';\n this.forceCompletePurchase = 'false';\n this.allowAdminUser = 'false';\n this.showConsent = 'false';\n this.ensureprewarm = 'false';\n this.useSearchV2 = 'false';\n this.ReviewsMyCommentsFilter = 'true';\n this.sortByRecentlyUpdatedEnabled = 'false';\n this.sortByRatingEnabled = 'false';\n this.allNewSortsEnabled = 'false';\n this.I18nextEnabled = 'false';\n this.cloudMarketplaceModeOn = false;\n this.recommendations = true;\n this.openInNewWindowButton = false;\n this.logToConsole = false;\n this.expredirect = false;\n this.partnersTestMode = false;\n }\n\n activeCloudMarketplaceFeatures = () => {\n if (this.cloudMarketplaceModeOn) {\n this.cloudMarketplaceModeFF.forEach((ff) => {\n this[`${ff}`] = true;\n });\n }\n };\n}\nexport interface ISearchState {\n searchText: string;\n searchSortingOption: Constants.SearchSortingOption;\n appSearchResults: IAppSearchResult[];\n servicesSearchResults: IServiceSearchResult[];\n filteredAppSearchResults: IAppDataItem[];\n filteredServicesSearchResults: Service[];\n filteredCloudsIndustrySearchResults: IAppDataItem[];\n appsCount: number;\n servicesCount: number;\n cloudsIndustryCount: number;\n searchIdCurrentlyOngoing: number;\n partnerSearchResults: IPartnerSearchResult[];\n showingResultsFor?: string;\n previousSearchText?: string;\n didYouMean?: string;\n requestId: string;\n suggestionsRequestId?: string;\n}\n\nexport interface IModalState {\n entityId?: string;\n modalId: number;\n isSignedIn: boolean;\n showModal: boolean;\n payload: any;\n options?: any;\n entityType: Constants.EntityType;\n onModalDismiss?: () => void;\n disableDismissModal?: boolean;\n isOpenedFromPDP?: boolean;\n}\n\nexport interface IRelatedItems {\n suggestedItems: IDataItem[];\n saasLinkingItems?: IAppDataItem[];\n linkedAddIns?: IAppDataItem[];\n}\nexport interface ISuggestedItems {\n entityId: string;\n items: IRelatedItems;\n}\nexport type RecommendedWhatsNewApps = IDataItem[];\nexport type RecommendedTrendingApps = IDataItem[];\n\nexport type RecommendedWhatsNewService = Service[];\nexport type RecommendedTrendingService = Service[];\n\nexport type RecommendedMicrosoft365WhatsNewApps = IDataItem[];\nexport type RecommendedMicrosoft365TrendingApps = IDataItem[];\nexport interface IRecommendedApps {\n whatsNewApps: RecommendedWhatsNewApps;\n trendingApps: RecommendedTrendingApps;\n microsoft365WhatsNewApps: RecommendedMicrosoft365WhatsNewApps;\n microsoft365TrendingApps: RecommendedMicrosoft365TrendingApps;\n}\n\nexport interface IRecommendedServies {\n whatsNewService: RecommendedWhatsNewService;\n trendingService: RecommendedTrendingService;\n}\n\nexport interface IEntityDataState {\n idMap: { [id: string]: number };\n dataList: T[];\n dataMap: IDataMap;\n dataMapLoaded: boolean;\n entityIdFailed: string;\n entityIdLoading: string;\n activeFilters: IDataValues[];\n count: number;\n dataLoaded: { [id: string]: boolean };\n curatedData: { [id: string]: ICuratedSection[] };\n curatedDataLoaded: { [id: string]: boolean };\n subsetData: T[];\n subsetSearchQuery: string;\n tileDataRequestId: string;\n relatedAppsItems: ISuggestedItems;\n appReviewsData: IReviewsState;\n linkedInProductGroup: ILinkedInProductGroup;\n}\n\nexport interface IRecommendedSizesState {\n loading: boolean;\n entityId: string;\n planId: string;\n recommendedSizes: string[];\n}\n\nconst initialRecommendedSizesState: IRecommendedSizesState = {\n loading: false,\n entityId: '',\n planId: '',\n recommendedSizes: [],\n};\n\nexport interface IAppDataState extends IEntityDataState {\n recommendedApps: IRecommendedApps;\n pricingPayload: IAppsPricingsPayload;\n partnerAppDataLoaded?: boolean;\n partnerAppDataLoadedError?: boolean;\n recommendedSizesState: IRecommendedSizesState;\n}\n\nexport interface ICloudsIndustryDataState extends IEntityDataState {\n pricingPayload: IAppsPricingsPayload;\n}\n\nexport interface IAppCheckoutState {\n checkoutId?: string;\n items: ICartItem[];\n customers?: ICustomer[];\n activeCustomer?: ICustomer;\n retryAfter?: number;\n publisherToken?: IPublisherOfferResponse;\n error: boolean;\n currentStep: number;\n purchaseAttempt: number;\n receipt?: IReceiptView;\n billingAddress: IBillingAddress;\n productFetched?: boolean;\n source?: CheckoutSource;\n}\n\nexport interface IWizardProps extends IWizardDispatchProps {\n checkoutId: string;\n checkout: IAppCheckoutState;\n app: IAppDataItem;\n currentStep: number;\n users: IUserDataState;\n config: IConfigState;\n // While customers aren't fetched, the wizard is considered to be loading\n wizardLoading: boolean;\n featureFlags: IFeatureFlags;\n billingCountry: string;\n productFetched: boolean;\n adminFlow: boolean;\n isBagOffer: boolean;\n source: CheckoutSource;\n isProfessionalService?: boolean;\n}\n\nexport interface IWizardDispatchProps {\n setActiveStep?: (targetStepId: number) => void;\n setPurchaseAttemptNumber?: (purchaseAttemptNumber: number) => void;\n setBillingCountry?: (countryCode: string) => any;\n fetchPricing?: (entityId: string, isPrivate: boolean) => void;\n createCustomer?: () => void;\n setCartItem?: (cartItem: ICartItem) => void;\n createCheckoutId?: ({ checkoutId }: ICheckoutId) => void;\n setPurchaseReceipt?: (receipt: IReceiptView) => void;\n generatePublisherToken?: (subscriptionId: string) => void;\n setBillingTerm?: (termDurationAction: ISetTermDurationAction) => void;\n setRecurringBilling?: (recurringBillingActionProps: ISetRecurringBilling) => void;\n setSeatNumber?: (setSeatsProps: ISetSeatsAction) => void;\n setSoldToAddress: (soldToAddress: IBillingAddress) => void;\n fetchProduct?: (id: string, market: string) => void;\n setSelectedTerm?: (termIdAction: ISetTermIdAction) => void;\n sendLeadInfo: (options: SendLeadInfoOptions) => Promise;\n}\n\nexport interface ICookiesConsent {\n WCPConsent: WCPconsent;\n isManageModalOpen: boolean;\n isConsentRequired: boolean;\n}\nexport interface IAccessToken {\n arm: string;\n graph: string;\n spza: string;\n pifd: string;\n commerce: string;\n jarvis: string;\n marketplaceLeads: string;\n agreement: string;\n}\nexport interface IUserLoginStatus {\n signedIn?: boolean;\n refreshToken?: string;\n homeAccountId?: string;\n}\nexport interface IUserLoginFetchStatus {\n loading: boolean;\n}\n\nexport interface IUserDataState extends IUserInfo {\n id: string;\n loading: boolean;\n signedIn: boolean;\n homeAccountId: string;\n isLoadingUserProfile: boolean;\n group: string[];\n idToken: string;\n idTokenPayload?: string;\n accessToken: IAccessToken;\n refreshToken: string;\n graphApi?: string;\n hasReview?: boolean;\n profile?: IUserLeadGenProfile;\n purchases?: IUserPurchases;\n privateOffers?: IUserPrivateOffers;\n tenantsDetails: IUserTenantsDetails;\n /** List of AAD role definitions -> Global Admin / Billing Admin */\n roleDefinitions?: string[];\n /** String including all treatments and ids for specific user, i.e. \"search-acs:31428\" */\n assignmentContext: string;\n /** ClientId for the ab testing platform */\n abClientId: string;\n expTimestamp: number;\n accounts: IAccountsDetails;\n isMaccTenant: boolean;\n}\n\nexport interface IUserPurchases {\n hasPurchases?: boolean;\n loading?: boolean;\n}\nexport interface IUserPrivateOffers {\n dataList: IAppDataItem[];\n loading?: boolean;\n}\nexport interface IAppCheckoutStateIUserDataState extends IUserInfo {\n id: string;\n signedIn: boolean;\n isLoadingUserProfile: boolean;\n group: string[];\n idToken: string;\n idTokenPayload?: string;\n accessToken: IAccessToken;\n refreshToken: string;\n graphApi?: string;\n hasReview?: boolean;\n profile?: IUserLeadGenProfile;\n purchases?: IUserPurchases;\n privateOffers?: IUserPrivateOffers;\n tenantsDetails: IUserTenantsDetails;\n}\n\nexport interface IExperiment {\n // eslint-disable-next-line camelcase\n abTestingVersion_AMP: number;\n // eslint-disable-next-line camelcase\n abTestingVersion_newFilterAMP: number;\n}\n\nexport interface IConfigState {\n i18nInstance: i18n;\n showContextPanel: boolean;\n initLoading: boolean;\n isEmbedded: boolean;\n experiment: IExperiment;\n embedHost: string;\n locStrings: any;\n locale: string;\n user: string;\n flightCodes: string;\n correlationId: string;\n requestId: string;\n breadcrumbUrl: string;\n query: IURLQuery;\n requestFilteredLoading: boolean;\n requestFilteredEntityType: Constants.TileDataEntityType;\n requestFilteredQuery: IURLQuery;\n responseFilteredQuery: IURLQuery;\n cookiesConsent: ICookiesConsent;\n telemetryEndpoint: string;\n appVersion: string;\n region: string;\n\n // current rendered view, one of routes property `name` in routerHistory.tsx\n currentView: string;\n\n // very first view when user enters our sites, one of routes property `name` in routerHistory.tsx\n landingView: string;\n\n // determine current rendered gallery page is curated view or filtered view\n // it should be undefined when currentView is not a gallery page\n currentGalleryViewIsCurated: boolean;\n appViewTelemetryLoggedCount: number;\n nationalCloud: string;\n billingCountryCode: string;\n pricingDataLoaded: boolean;\n featureFlags: IFeatureFlags;\n marketplaceApiHost: string;\n saasRPHost: string;\n MPRPHost: string;\n armApiHost: string;\n isMobile: boolean;\n isAppSource?: boolean;\n pidlEnv?: string;\n partnersIframeUrl: string;\n partnersIframeUrlTest: string;\n telemetryInstrumentationKey: string;\n loggingInstrumentationKey: string;\n partnersApiHost: string;\n reviewsAPI: string;\n reviewsAPIVersion: string;\n linkedinAPI: string;\n linkedinAPIVersion: string;\n communityAPI: string;\n communityAPIVersion: string;\n newNpsEnabled: boolean;\n npsLogicVerified: boolean;\n siteEnvironment: string;\n commerceApiHost: string;\n isDisabledBilling: boolean;\n storefrontName: string;\n msClarityId: string;\n searchApiEndpoint: string;\n oneCatalogApiHost: string;\n expHeaderDisabled: boolean;\n marketplaceLeadsHost: string;\n marketplaceLeadsAudienceId: string;\n enableOneTaxonomy: boolean;\n aadApplicationId: string;\n aadLoginUrl: string;\n jarvisCM: string;\n mprpAadApplicationId: string;\n pifdEndpoint: string;\n commerceApi: string;\n graphApi: string;\n pcaInstanceInitialized: boolean;\n includeTestProducts: boolean;\n agreementAppId: string;\n isConverged: boolean;\n}\n\nexport interface ServiceCountries {\n [regionCode: string]: string;\n}\n\nexport interface IServiceDataState extends IEntityDataState {\n recommendedServices: IRecommendedServies;\n countriesList: ServiceCountries;\n entityType: Constants.EntityType.Service;\n selectedCountry: string;\n selectedRegion: string;\n}\n\nexport interface IDynamicCampaignState {\n html: string;\n}\n\nexport interface IPartnersState {\n filterLocation: IFilterLocation;\n contactDetails: IContactDetails;\n}\nexport interface IState {\n search: ISearchState;\n modal: IModalState;\n apps: IAppDataState;\n cloudsIndustry: ICloudsIndustryDataState;\n users: IUserDataState;\n config: IConfigState;\n services: IServiceDataState;\n dynamicCampaign?: IDynamicCampaignState;\n userFavourite: IUserFavouriteState;\n checkout?: IAppCheckoutState;\n partners?: IPartnersState;\n}\n\nexport const initialUserFavouriteState: IUserFavouriteState = {\n fetchDataStatus: Constants.UserFavourite.FetchDataStatus.Invalid,\n apps: [] as IUserFavouriteApp[],\n services: [] as IUserFavouriteService[],\n};\n\nexport const initialDynamicCampaignState: IDynamicCampaignState = {\n html: '',\n};\n\nexport const initialSearchState: ISearchState = {\n searchText: '',\n searchSortingOption: Constants.SearchSortingOption.BestMatch,\n appSearchResults: [] as IAppSearchResult[],\n servicesSearchResults: [] as IServiceSearchResult[],\n filteredAppSearchResults: [] as IAppDataItem[],\n filteredServicesSearchResults: [] as Service[],\n filteredCloudsIndustrySearchResults: [] as IAppDataItem[],\n appsCount: 0,\n servicesCount: 0,\n cloudsIndustryCount: 0,\n searchIdCurrentlyOngoing: 0,\n partnerSearchResults: [] as IPartnerSearchResult[],\n requestId: Constants.ReservedCorrelationIds.EmptyId,\n};\n\nexport const initialModalState = {\n appId: '',\n parnerId: '',\n modalId: 0,\n isSignedIn: false,\n showModal: false,\n payload: null as any,\n entityType: Constants.EntityType.App,\n isOpenedFromPDP: false,\n};\n\nexport const initialEntityDataState: IEntityDataState = {\n idMap: {},\n dataList: [] as IDataItem[],\n dataMap: {} as IDataMap,\n dataMapLoaded: false,\n entityIdFailed: '',\n entityIdLoading: '',\n activeFilters: [] as IDataValues[],\n count: 0,\n curatedData: {},\n dataLoaded: {},\n curatedDataLoaded: {},\n subsetData: [] as IAppDataItem[],\n subsetSearchQuery: '',\n tileDataRequestId: '',\n relatedAppsItems: {\n entityId: '',\n items: {\n suggestedItems: [],\n saasLinkingItems: [],\n linkedAddIns: [],\n },\n },\n appReviewsData: initialAppReviewsDataState,\n linkedInProductGroup: null,\n};\n\nexport const initialAppDataState: IAppDataState = {\n idMap: {},\n dataList: [] as IAppDataItem[],\n recommendedApps: {\n whatsNewApps: [],\n trendingApps: [],\n microsoft365WhatsNewApps: [],\n microsoft365TrendingApps: [],\n },\n dataMap: {} as IDataMap,\n dataMapLoaded: false,\n entityIdFailed: '',\n entityIdLoading: '',\n activeFilters: [] as IDataValues[],\n count: 0,\n curatedData: {},\n dataLoaded: {},\n pricingPayload: null,\n partnerAppDataLoaded: false,\n curatedDataLoaded: {},\n subsetData: [] as IAppDataItem[],\n subsetSearchQuery: '',\n tileDataRequestId: '',\n relatedAppsItems: {\n entityId: '',\n items: {\n suggestedItems: [],\n saasLinkingItems: [],\n linkedAddIns: [],\n },\n },\n appReviewsData: initialAppReviewsDataState,\n recommendedSizesState: initialRecommendedSizesState,\n linkedInProductGroup: null,\n};\n\nexport const initialCloudsIndustryDataState: ICloudsIndustryDataState = {\n idMap: {},\n dataList: [] as ICloudsIndustry[],\n dataMap: {} as IDataMap,\n dataMapLoaded: false,\n entityIdFailed: '',\n entityIdLoading: '',\n activeFilters: [] as IDataValues[],\n count: 0,\n curatedData: {},\n dataLoaded: {},\n pricingPayload: null,\n curatedDataLoaded: {},\n subsetData: [] as ICloudsIndustry[],\n subsetSearchQuery: '',\n tileDataRequestId: '',\n relatedAppsItems: {\n entityId: '',\n items: {\n suggestedItems: [],\n saasLinkingItems: [],\n linkedAddIns: [],\n },\n },\n appReviewsData: initialAppReviewsDataState,\n linkedInProductGroup: null,\n};\n\nexport const initialUserDataState: IUserDataState = {\n assignmentContext: '',\n id: '',\n group: [''],\n signedIn: false,\n idToken: '',\n idTokenPayload: '',\n accessToken: {},\n refreshToken: '',\n firstName: '',\n lastName: '',\n uniqueName: '',\n displayName: '',\n oid: '',\n tid: '',\n puid: '',\n email: '',\n graphApi: process.env.graphApi, // todo: this should be removed, will crash in client\n alternateEmail: '',\n isMSAUser: false,\n userSegment: UserSegment.unauthenticated,\n hasReview: false,\n fieldHubUserType: Constants.FieldHubUserType.None,\n isLoadingUserProfile: false,\n loading: true,\n givenName: '',\n familyName: '',\n profile: {\n firstName: '',\n lastName: '',\n email: '',\n phone: '',\n country: '',\n company: '',\n title: '',\n updateRequired: null,\n isLatestProfile: false,\n uiRole: null,\n managedLicenses: [],\n },\n tenantsDetails: {\n details: {\n countryCode: null,\n defaultDomain: null,\n displayName: null,\n domains: null,\n id: null,\n tenantCategory: null,\n tenantId: null,\n tenantType: null,\n azureSubscriptions: {},\n },\n status: {\n hasError: false,\n isLoading: false,\n },\n },\n purchases: {},\n privateOffers: {\n dataList: [],\n loading: true,\n },\n roleDefinitions: [],\n accounts: { isEAAccount: false, isLoading: false, accounts: null, hasError: false },\n isMaccTenant: false,\n};\n\nexport const initialExperimentState: IExperiment = {\n abTestingVersion_AMP: Constants.VaritionKey.Default,\n abTestingVersion_newFilterAMP: Constants.VaritionKey.Default,\n};\n\nexport const initialConfigState: IConfigState = {\n initLoading: true,\n isEmbedded: false,\n experiment: initialExperimentState,\n embedHost: null,\n locStrings: '',\n locale: 'en-us',\n user: '',\n flightCodes: null,\n partnersIframeUrl: '',\n partnersApiHost: '',\n partnersIframeUrlTest: '',\n correlationId: '',\n currentView: 'home',\n landingView: '',\n currentGalleryViewIsCurated: false,\n breadcrumbUrl: null,\n appViewTelemetryLoggedCount: 0,\n nationalCloud: '',\n billingCountryCode: 'us',\n pricingDataLoaded: false,\n featureFlags: new IFeatureFlags(),\n marketplaceApiHost: '',\n saasRPHost: '',\n MPRPHost: '',\n armApiHost: '',\n isMobile: false,\n isAppSource: process.env ? process.env.applicationTarget === Constants.appSourceApplicationTargetName : false,\n pidlEnv: '',\n query: {} as IURLQuery,\n requestFilteredLoading: false,\n requestFilteredEntityType: Constants.TileDataEntityType.All,\n requestFilteredQuery: {} as IURLQuery,\n responseFilteredQuery: {} as IURLQuery,\n cookiesConsent: {\n isManageModalOpen: false,\n WCPConsent: {\n Advertising: false,\n Analytics: false,\n Required: true,\n SocialMedia: false,\n },\n isConsentRequired: false,\n },\n siteEnvironment: '',\n newNpsEnabled: false,\n pcaInstanceInitialized: false,\n npsLogicVerified: false,\n reviewsAPIVersion: enrichReviewCommentsApiVersion,\n showContextPanel: false,\n commerceApiHost: '',\n storefrontName: null,\n marketplaceLeadsHost: '',\n marketplaceLeadsAudienceId: '',\n agreementAppId: '',\n};\n\nexport const initialServiceDataState: IServiceDataState = {\n idMap: {},\n dataList: [] as Service[],\n recommendedServices: {\n whatsNewService: [],\n trendingService: [],\n },\n countriesList: {} as ServiceCountries,\n dataMap: {} as IDataMap,\n dataMapLoaded: false,\n entityIdFailed: '',\n entityIdLoading: '',\n activeFilters: [] as IDataValues[],\n count: 0,\n curatedData: {},\n dataLoaded: {},\n curatedDataLoaded: {},\n subsetData: [] as Service[],\n subsetSearchQuery: '',\n tileDataRequestId: '',\n entityType: Constants.EntityType.Service,\n selectedCountry: Constants.usCountryCode.toUpperCase(),\n selectedRegion: Constants.allStatesCode.toUpperCase(),\n relatedAppsItems: {\n entityId: '',\n items: {\n suggestedItems: [],\n saasLinkingItems: [],\n linkedAddIns: [],\n },\n },\n appReviewsData: initialAppReviewsDataState,\n linkedInProductGroup: null,\n};\n\nexport const initialCheckoutState: IAppCheckoutState = {\n items: [],\n customers: null,\n activeCustomer: null,\n publisherToken: null,\n error: false,\n currentStep: 0,\n purchaseAttempt: 0,\n billingAddress: null,\n checkoutId: '',\n};\n\nexport const initialPartnersState: IPartnersState = {\n filterLocation: null,\n contactDetails: null,\n};\n\nexport const initialState: IState = {\n search: initialSearchState,\n modal: initialModalState,\n apps: initialAppDataState,\n cloudsIndustry: initialCloudsIndustryDataState,\n users: initialUserDataState,\n config: initialConfigState,\n services: initialServiceDataState,\n dynamicCampaign: initialDynamicCampaignState,\n userFavourite: initialUserFavouriteState,\n checkout: initialCheckoutState,\n partners: initialPartnersState,\n};\n\n// This function does a deep copy a 'DataMap' object\n// It is sometimes used (in the server code) after calling 'performFilter' to\n// ignore objects that have a 'count' property set to '0'. This removes 'filters'\n// from the filter pane that aren't applicable to a specific group of data\nexport function deepCopyDataMap(DataMap: any): any {\n const newDataMap: any = {};\n if (DataMap == null || typeof DataMap !== 'object') {\n return DataMap;\n } else if (DataMap.count === 0) {\n return null;\n }\n for (const k in DataMap) {\n const prop = DataMap[`${k}`];\n const copy = deepCopyDataMap(prop);\n if (copy) {\n newDataMap[`${k}`] = copy;\n }\n }\n return newDataMap;\n}\n\nexport function copyState(oldState: T): T {\n const newState: any = {};\n\n for (const k in oldState) {\n newState[`${k}`] = oldState[`${k}`];\n }\n\n return newState as T;\n}\n","import React, { FunctionComponent } from 'react';\nimport { mergeStyleSets } from '@fluentui/react';\nimport classNames from 'classnames';\n\nexport interface IHiddenComponentWrapperProps {\n hide: boolean;\n className?: string;\n children?: React.ReactChild;\n}\n\nconst contentStyles = mergeStyleSets({\n hiddenClass: {\n display: 'none',\n },\n});\n\nexport const HiddenComponentWrapper: FunctionComponent = ({\n className,\n hide,\n children,\n}: IHiddenComponentWrapperProps) => {\n const containerClass = classNames({\n [className]: className?.length > 0,\n [contentStyles.hiddenClass]: hide,\n });\n\n return
{children}
;\n};\n","import React from 'react';\nimport { IDataItem } from '@shared/Models';\nimport Ribbon from '@shared/components/ribbon';\nimport { AppTile } from '@shared/containers/appTile';\nimport { ServiceTile } from '@shared/containers/serviceTile';\nimport CommonTile from '@shared/components/commonTile';\nimport NewBadge from '@shared/images/NewBadge.svg';\n\nconst maxRibbonItems = 5;\n\nexport interface IRecommendationsRibbonProps {\n recommendedItems: IDataItem[];\n title: string;\n ribbonKey: string;\n selectedLocale: string;\n selectedCountry: string;\n tileType: typeof AppTile | typeof ServiceTile | typeof CommonTile;\n}\n\nexport const RecommendationsRibbon: React.FunctionComponent = ({\n recommendedItems = [],\n title,\n ribbonKey,\n selectedCountry,\n selectedLocale,\n tileType: TileType,\n}: IRecommendationsRibbonProps) => {\n return (\n <>\n {recommendedItems.length >= maxRibbonItems && (\n \n {recommendedItems.map((item: IDataItem, index: number) => {\n return (\n \n );\n })}\n \n )}\n \n );\n};\n","import React, { FormEvent, useCallback } from 'react';\nimport * as PropTypes from 'prop-types';\n\nimport { Text, Icon, Label, Stack, ChoiceGroup } from '@fluentui/react';\nimport type { IChoiceGroupOption } from '@fluentui/react';\nimport { Constants } from '@shared/utils/constants';\nimport { ILocParamsContext, ILocContext } from '@shared/interfaces/context';\nimport {\n ICartItem,\n IAvailabilityActions,\n AdvancedTerm,\n IFuturePriceFormat,\n TermsStrings,\n ISetTermIdAction,\n MPRPPlansEntity,\n} from '@shared/Models';\nimport { convertTermToString } from '@shared/utils/checkoutUtils';\nimport Tooltip from '@shared/components/tooltip';\nimport { addFuturePricing } from '@shared/utils/pricing';\n\nconst pbTelemetry = Constants.Telemetry.PurchaseBlendsTelemetry;\n\nexport interface IBillingFrequencyChoiceGroupProps {\n billingCountryCode: string;\n items: ICartItem[];\n setSelectedTerm: (termIdAction: ISetTermIdAction) => void;\n futurePriceMonthly: IFuturePriceFormat;\n futurePriceYearly: IFuturePriceFormat;\n locale?: string;\n}\n\nexport interface ILabelAndTooltipProps {\n context: ILocParamsContext & ILocContext;\n}\nexport const GenerateLabelAndTooltip: React.FunctionComponent = ({ context }: ILabelAndTooltipProps) => {\n return (\n \n );\n};\n\nexport const getBillingFrequencyOptions = (\n { billingCountryCode, items, futurePriceMonthly, futurePriceYearly, locale }: Partial,\n context: ILocParamsContext & ILocContext\n): IChoiceGroupOption[] => {\n const { product, id, termDuration, hasFreeTrial } = items[0];\n const plan: MPRPPlansEntity = product.getPlanByPlanId(id);\n const availability = plan.availabilities.find((availability) => {\n const { actions } = availability;\n return actions.includes(IAvailabilityActions.Renew) && actions.includes(IAvailabilityActions.Browse);\n });\n const termsByBillingTermDuration = availability.terms.filter((term) => term.termUnits === termDuration);\n\n const billingFrequencyOptions = termsByBillingTermDuration\n .map((term) => {\n const termEntity: AdvancedTerm = product.getTermByTermId({ planId: id, termId: term.termId });\n if (!termEntity) {\n return null;\n }\n return {\n // If there's no free trial termId, assign the renew termId\n key: termEntity.freeTrialTermId || termEntity.renewTermId,\n // This value isn't displayed, but rather used to pass in the relevant contract term in the case there's only one option\n value: termEntity.renewTermUnits,\n text: convertTermToString({\n context,\n term: termEntity,\n countryCode: billingCountryCode,\n isQuantifiable: items[0].isQuantifiable,\n hasFreeTrial,\n locale,\n }),\n onRenderLabel: (option, defaultRender) => (\n <>\n {defaultRender(option)}\n {addFuturePricing(term.renewTermUnits, futurePriceMonthly, futurePriceYearly)}\n \n ),\n } as IChoiceGroupOption;\n })\n .filter(Boolean);\n\n return billingFrequencyOptions;\n};\n\nexport const getDefaultOrSelectedBillingFrequency = (\n { items }: IBillingFrequencyChoiceGroupProps,\n options: IChoiceGroupOption[]\n): string => {\n const availableTermFrequencies = options?.map((option: IChoiceGroupOption) => option.key);\n const defaultBillingFrequency = availableTermFrequencies?.includes(items[0]?.termId)\n ? items[0]?.termId\n : availableTermFrequencies[0];\n\n return defaultBillingFrequency || '';\n};\n\nexport const BillingFrequencyChoiceGroup: React.FunctionComponent = (\n props: IBillingFrequencyChoiceGroupProps,\n context: ILocParamsContext & ILocContext\n) => {\n const { setSelectedTerm, futurePriceMonthly, futurePriceYearly, billingCountryCode, items, locale } = props;\n const { termId, id } = items[0];\n\n const options = useCallback(\n () => getBillingFrequencyOptions({ billingCountryCode, items, futurePriceMonthly, futurePriceYearly, locale }, context),\n [billingCountryCode, items, futurePriceMonthly, futurePriceYearly, context, locale]\n );\n\n const { text, value } = options()[0];\n\n return (\n <>\n \n \n \n {options().length > 1 ? (\n , option?: IChoiceGroupOption) =>\n setSelectedTerm({ id, termId: option.key })\n }\n selectedKey={termId}\n defaultSelectedKey={getDefaultOrSelectedBillingFrequency(props, options())}\n ariaLabelledBy={'billTermSelection'}\n />\n ) : (\n <>\n {text}\n {addFuturePricing(value as TermsStrings, futurePriceMonthly, futurePriceYearly)}\n \n )}\n \n \n \n );\n};\n\n(BillingFrequencyChoiceGroup as any).contextTypes = {\n locParams: PropTypes.func,\n loc: PropTypes.func,\n};\n","import React, { useMemo } from 'react';\nimport * as PropTypes from 'prop-types';\nimport { Constants } from '@shared/utils/constants';\nimport { ILocParamsContext, ILocContext } from '@shared/interfaces/context';\nimport { Text, Icon, Label, Stack, ChoiceGroup } from '@fluentui/react';\nimport type { IChoiceGroupOption } from '@fluentui/react';\nimport Tooltip from '@shared/components/tooltip';\nimport { ICartItem, ISetTermDurationAction, ISetTermIdAction, TermsStrings } from '@shared/Models';\nimport { getBillingTermTranslated } from '@shared/utils/pricing';\nimport { Product } from '@shared/services/product/product';\n\nconst pbTelemetry = Constants.Telemetry.PurchaseBlendsTelemetry;\n\ninterface MapBillingTermToBillingOptions {\n [key: string]: IChoiceGroupOption;\n}\n\nexport interface IBillingTermProps {\n setSelectedTerm: (termIdAction: ISetTermIdAction) => void;\n setBillingTerm: (termDurationAction: ISetTermDurationAction) => void;\n items: ICartItem[];\n}\n\nexport const BillingTermChoiceGroup: React.FunctionComponent = (\n { setSelectedTerm, setBillingTerm, items }: IBillingTermProps,\n context: ILocParamsContext & ILocContext\n) => {\n const { id: planId, product, hasFreeTrial, termDuration } = items[0];\n\n const handleBillingTermChanges = ({\n setSelectedTerm,\n setBillingTerm,\n planId,\n product,\n hasFreeTrial,\n option,\n }: {\n product: Product;\n planId: string;\n setBillingTerm: (termDurationAction: ISetTermDurationAction) => void;\n setSelectedTerm: (termIdAction: ISetTermIdAction) => void;\n hasFreeTrial: boolean;\n option: IChoiceGroupOption;\n }): void => {\n const firstTerm = product.getTermByPlanAndBillingTermDuration({\n planId,\n billingTermDuration: option.key as TermsStrings,\n });\n setBillingTerm({ id: planId, termDuration: option.key as TermsStrings });\n setSelectedTerm({ id: planId, termId: hasFreeTrial ? firstTerm.freeTrialTermId : firstTerm.termId });\n };\n\n const generateBillingOption = (term: TermsStrings, context: ILocParamsContext & ILocContext): IChoiceGroupOption => {\n return {\n key: term,\n text: `${getBillingTermTranslated({ context, term, isSubscription: true })}`,\n };\n };\n\n const getTermMapping = (context: ILocParamsContext & ILocContext): MapBillingTermToBillingOptions => {\n return {\n [TermsStrings.MonthString.toLowerCase()]: generateBillingOption(TermsStrings.Term1MString, context),\n [TermsStrings.Term1MString.toLowerCase()]: generateBillingOption(TermsStrings.Term1MString, context),\n [TermsStrings.YearString.toLowerCase()]: generateBillingOption(TermsStrings.Term1YString, context),\n [TermsStrings.Term1YString.toLowerCase()]: generateBillingOption(TermsStrings.Term1YString, context),\n [TermsStrings.Term2YString.toLowerCase()]: generateBillingOption(TermsStrings.Term2YString, context),\n [TermsStrings.Term3YString.toLowerCase()]: generateBillingOption(TermsStrings.Term3YString, context),\n };\n };\n\n const getBillingOptions = (items: ICartItem[], context: ILocParamsContext & ILocContext) => {\n // Since sort happens in place, cloning list instead of messing up redux.\n const listOfTerms = [...items[0].product.getTermDurationsByPlanId(items[0].id)];\n return listOfTerms.sort().map((term) => {\n const billingOptionsMap: MapBillingTermToBillingOptions = getTermMapping(context);\n return billingOptionsMap[term.toLowerCase()];\n });\n };\n\n const options = useMemo(() => getBillingOptions(items, context), [items, context]);\n\n return (\n <>\n \n \n \n {options?.length > 1 ? (\n , option?: IChoiceGroupOption) =>\n handleBillingTermChanges({ product, planId, setBillingTerm, setSelectedTerm, hasFreeTrial, option })\n }\n defaultSelectedKey={(termDuration as TermsStrings) || ''}\n />\n ) : (\n {options?.[0].text}\n )}\n \n \n \n );\n};\n\n(BillingTermChoiceGroup as any).contextTypes = {\n locParams: PropTypes.func,\n loc: PropTypes.func,\n};\n","import React, { useRef, useState } from 'react';\nimport { IAppDataItem, IBillingCountry } from '@shared/Models';\nimport FuturePriceWarning from '@shared/components/futurePriceWarning';\nimport { SaasPricingTableBase } from '@shared/components';\nimport { IConfigState, IState } from '@src/State';\nimport { connect } from 'react-redux';\nimport { mergeStyleSets } from '@fluentui/react';\nimport classNames from 'classnames';\n\ninterface OwnProps {\n app: IAppDataItem;\n billingCountry: IBillingCountry;\n onGetInNowClicked?: (planId?: string) => void;\n shouldBlockPrices?: boolean;\n}\n\ninterface StateProps {\n config: IConfigState;\n}\n\ntype SaasPricingTableProps = OwnProps & StateProps;\n\nconst classes = mergeStyleSets({\n limitHeight: {\n height: '70vh',\n },\n});\n\n\nexport const SaasPricingTableComponent = ({\n app,\n billingCountry,\n onGetInNowClicked,\n shouldBlockPrices,\n config: { locale },\n}: SaasPricingTableProps) => {\n const [isHorizontalScrollbarPresent, setIsHorizontalScrollbarPresent] = useState(false);\n const containerRef = useRef();\n const { futurePricesInformation: futurePrices } = app;\n // In SSR context there's no body\n const bodyElement = globalThis.document?.body;\n const isDocumentScrollableVertically = bodyElement?.scrollHeight > bodyElement?.clientHeight;\n\n return (\n \n \n \n \n );\n};\n\nconst mapStateToProps = ({ config }: IState): StateProps => ({ config });\n\nexport const SaasPricingTable = connect(mapStateToProps)(SaasPricingTableComponent);\n","import React, { useEffect } from 'react';\nimport {\n // eslint-disable-next-line import/named\n IProgressIndicatorStyles,\n mergeStyleSets,\n Stack,\n Text,\n FontSizes,\n ProgressIndicator,\n ScreenWidthMinXLarge,\n} from '@fluentui/react';\nimport OptimizingImage from '@shared/images/optimizingImage.svg';\nimport { TelemetryImage } from '@shared/components/telemetryImage';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\n\nconst OrderLoadingStyles: Partial = {\n itemDescription: { textAlign: 'center' },\n};\nconst contentStyles = mergeStyleSets({\n itemName: { fontSize: FontSizes.size28, fontWeight: 900, marginTop: '20px', textAlign: 'center' },\n progressContainer: {\n width: '640px',\n height: '680px',\n zIndex: '101',\n backgroundColor: '#FFFFFF',\n position: 'absolute',\n margin: 'auto',\n top: '0',\n right: '0',\n bottom: '0',\n left: '0',\n padding: '120px 80px',\n [`@media (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '320px',\n height: '440px',\n padding: '60px 40px',\n },\n },\n});\n\ninterface IOrderLoading {\n context: ILocContext & ILocParamsContext;\n}\n\nexport const OrderLoading: React.FunctionComponent = ({ context }: IOrderLoading) => {\n const progressMessages = ['Loader_Starting', 'Loader_Verifiying', 'Loader_Processing'];\n const intervalDelay = 2500;\n const intervalIncrement = 0.1;\n const [progressMessage, setProgressMessage] = React.useState(progressMessages[0]);\n const [percentComplete, setPercentComplete] = React.useState(0);\n\n useEffect(() => {\n if (percentComplete <= 0.9) {\n const id = setInterval(() => {\n setPercentComplete((intervalIncrement + percentComplete) % 1);\n switch (Math.round((percentComplete * 10) / 2)) {\n case 0:\n case 1:\n setProgressMessage(progressMessages[0]);\n break;\n case 2:\n setProgressMessage(progressMessages[1]);\n break;\n case 3:\n setProgressMessage(progressMessages[2]);\n break;\n }\n }, intervalDelay);\n return () => {\n clearInterval(id);\n };\n }\n }, [percentComplete, progressMessages]);\n\n return (\n \n \n \n \n \n {context.loc(progressMessage)}\n \n \n \n );\n};\n","import React, { useEffect } from 'react';\nimport { useSelector } from 'react-redux';\nimport { FontSizes, FontWeights, mergeStyleSets, PrimaryButton, ScreenWidthMinXLarge, Stack, Text } from '@fluentui/react';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\nimport { logger } from '@src/logger';\nimport { ITelemetryData } from '@shared/Models';\nimport { SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\n/* eslint-disable import/named */\nimport { UserType } from '@microsoft-commerce/purchase-blends-component-library/dist/lib/providers/app-dependencies/user-info';\nimport { IState } from '@src/State';\n\ninterface IRedirectModalProps {\n onClick: () => void;\n context: ILocContext & ILocParamsContext;\n userType: UserType;\n}\n\nconst contentStyles = mergeStyleSets({\n title: { fontSize: FontSizes.size20, fontWeight: FontWeights.semibold },\n container: {\n width: '560px',\n height: '216px',\n zIndex: '101',\n backgroundColor: '#FFFFFF',\n position: 'absolute',\n margin: 'auto',\n top: '0',\n right: '0',\n bottom: '0',\n left: '0',\n padding: '24px',\n [`@media (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '90%',\n },\n },\n});\nconst logClickEvent = ({ userType, checkoutId }: { userType: UserType; checkoutId: string }) => {\n const payload: ITelemetryData = {\n page: location.pathname,\n action: Constants.Telemetry.Action.Checkout,\n actionModifier: Constants.Telemetry.ActionModifier.RedirectButton,\n details: JSON.stringify({\n userType,\n checkoutId,\n }),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n appName: payload.appName,\n });\n};\n\nexport const RedirectModal: React.FunctionComponent = ({\n onClick,\n context,\n userType,\n}: IRedirectModalProps) => {\n const checkoutId = useSelector(({ checkout }: IState) => checkout.checkoutId);\n useEffect(() => {\n const payload: ITelemetryData = {\n page: location.pathname,\n action: Constants.Telemetry.Action.Checkout,\n actionModifier: Constants.Telemetry.ActionModifier.RedirectModal,\n details: JSON.stringify({\n userType,\n checkoutId,\n }),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n }, []);\n\n return (\n \n \n {context.loc('Checkout_Error_Title')}\n \n \n {context.loc('Checkout_Error_Description')}\n \n \n {\n onClick();\n logClickEvent(userType);\n }}\n >\n {context.loc('Checkout_Error_Button')}\n \n \n \n );\n};\n","import React, { useEffect } from 'react';\n// eslint-disable-next-line import/named\nimport {\n mergeStyleSets,\n Stack,\n Text,\n FontSizes,\n ScreenWidthMinXLarge,\n Spinner,\n ISpinnerStyleProps,\n ISpinnerStyles,\n IStyleFunctionOrObject,\n} from '@fluentui/react';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\n\nconst contentStyles = mergeStyleSets({\n itemName: { fontSize: FontSizes.size28, fontWeight: 600, marginTop: '20px', textAlign: 'center' },\n progressContainer: {\n width: '560px',\n height: '280px',\n zIndex: '101',\n backgroundColor: '#FFFFFF',\n position: 'absolute',\n margin: 'auto',\n top: '0',\n right: '0',\n bottom: '0',\n left: '0',\n padding: '120px 80px',\n [`@media (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '90%',\n height: '280px',\n },\n },\n});\n\nconst spinnerStyles: IStyleFunctionOrObject = {\n circle: {\n width: '56px',\n height: '56px',\n },\n};\n\ninterface IBusyLoader {\n context: ILocContext & ILocParamsContext;\n}\n\nexport const BusyLoader: React.FunctionComponent = ({ context }: IBusyLoader) => {\n const progressMessages = ['Loading_Selection', 'Working', 'Updating'];\n const intervalDelay = 2500;\n const intervalIncrement = 0.1;\n const [progressMessage, setProgressMessage] = React.useState(progressMessages[0]);\n const [percentComplete, setPercentComplete] = React.useState(0);\n\n useEffect(() => {\n if (percentComplete <= 0.9) {\n const id = setInterval(() => {\n setPercentComplete((intervalIncrement + percentComplete) % 1);\n switch (Math.round((percentComplete * 10) / 2)) {\n case 0:\n case 1:\n setProgressMessage(progressMessages[0]);\n break;\n case 2:\n setProgressMessage(progressMessages[1]);\n break;\n case 3:\n setProgressMessage(progressMessages[2]);\n break;\n }\n }, intervalDelay);\n return () => {\n clearInterval(id);\n };\n }\n }, [percentComplete, progressMessages]);\n\n return (\n \n \n \n {context.loc(progressMessage)}\n \n \n );\n};\n","import React from 'react';\nimport { mergeStyleSets, MessageBar, MessageBarType } from '@fluentui/react';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\n\ninterface IMSuserErrorBannerProps {\n context: ILocContext & ILocParamsContext;\n tenantId: string;\n}\n\nconst contentStyles = mergeStyleSets({\n root: {\n marginBottom: '10px',\n },\n});\n\nexport const MSuserErrorBanner: React.FunctionComponent = ({\n context,\n tenantId,\n}: IMSuserErrorBannerProps) => {\n return (\n tenantId === Constants.MSTenantId && (\n
\n \n {context.loc('MSuser_Banner', 'As a Microsoft Employee, you are not allowed to complete a purchase in AppSource')}\n \n
\n )\n );\n};\n","import React, { useState } from 'react';\nimport * as PropTypes from 'prop-types';\nimport type { FC } from 'react';\nimport { Text, mergeStyleSets, Stack, Icon } from '@fluentui/react';\nimport classNames from 'classnames';\nimport { isNil } from 'lodash-es';\nimport { useSelector } from 'react-redux';\nimport { NeutralColors, SharedColors } from '@fluentui/theme';\n\nimport { ILocContext } from '@shared/interfaces/context';\nimport { storeFeedback } from 'services/feedback';\nimport { Constants } from '@shared/utils/constants';\nimport { IState } from '@src/State';\nimport { logTelemetry } from '@shared/utils/telemetryUtils';\n\nexport type IThumbsUpDownProps = { localizedQuestion?: string };\n\nconst contentStyles = mergeStyleSets({\n containerClass: { paddingTop: 8 },\n feedbackButtons: { color: NeutralColors.gray160, '&:hover': { color: SharedColors.cyanBlue10 } },\n feedbackButtonsSelected: { color: SharedColors.cyanBlue10, cursor: 'default' },\n defaultCursor: { cursor: 'default' },\n pointerCursor: { cursor: 'pointer' },\n});\n\nconst Negative = 'Negative';\nconst Positive = 'Positive';\n\ntype feedbackType = typeof Positive | typeof Negative;\n\nconst getAriaLabel = ({\n isFeedbackNull,\n isFeedbackPositive,\n feedbackNoun,\n context,\n}: {\n isFeedbackNull: boolean;\n isFeedbackPositive: boolean;\n feedbackNoun: feedbackType;\n context: ILocContext;\n}): string => {\n const isPositiveFeedbackSelected = !isFeedbackNull && isFeedbackPositive;\n const isNegativeFeedbackSelected = !isFeedbackNull && !isFeedbackPositive;\n const selectionCondition = feedbackNoun === Negative ? isPositiveFeedbackSelected : isNegativeFeedbackSelected;\n const feedbackText = feedbackNoun === Positive ? Positive : Negative;\n\n return isFeedbackNull || selectionCondition\n ? context.loc(`Feedback_ShowingResultsFor_${feedbackText}Feedback`, `${feedbackText} feedback`)\n : context.loc(`Feedback_ShowingResultsFor_${feedbackText}FeedbackProvided`, `Provided ${feedbackText} feedback`);\n};\n\nexport const sendFeedback = ({\n isPositive,\n requestId,\n showingResultsFor,\n didYouMean,\n previousSearchText,\n}: {\n isPositive: boolean;\n requestId: string;\n showingResultsFor: string;\n didYouMean: string;\n previousSearchText: string;\n}) => {\n const details = { positiveFeedBack: isPositive, requestId, showingResultsFor, didYouMean, searchTerm: previousSearchText };\n\n logTelemetry(\n Constants.Telemetry.Action.Feedback,\n Constants.Telemetry.ActionModifier.ShowingResultsForFeedback,\n JSON.stringify(details),\n true\n );\n\n storeFeedback({\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.ShowingResultsForFeedback,\n details: JSON.stringify(details),\n });\n};\n\nconst likeIconName = 'Like';\nconst solidLikeIconName = 'LikeSolid';\nconst dislikeIconName = 'Dislike';\nconst solidDislikeIconName = 'DislikeSolid';\nexport const ThumbsUpDown: FC = ({ localizedQuestion = '' }: IThumbsUpDownProps, context: ILocContext) => {\n const { containerClass, feedbackButtons, feedbackButtonsSelected, defaultCursor, pointerCursor } = contentStyles;\n const [isFeedbackPositive, setFeedback] = useState(null);\n const { requestId, showingResultsFor, didYouMean, previousSearchText } = useSelector(\n ({ search: { requestId, showingResultsFor, didYouMean, previousSearchText } }: IState) => ({\n requestId,\n showingResultsFor,\n didYouMean,\n previousSearchText,\n })\n );\n const isFeedbackNull = isNil(isFeedbackPositive);\n\n const likeAria = getAriaLabel({ isFeedbackNull, feedbackNoun: Positive, isFeedbackPositive, context });\n const dislikeAria = getAriaLabel({ isFeedbackNull, feedbackNoun: Negative, isFeedbackPositive, context });\n\n const handleFeedbackClick = ({ isPositive }: { isPositive: boolean }) => {\n if (isFeedbackNull) {\n setFeedback(isPositive);\n sendFeedback({ isPositive, requestId, showingResultsFor, didYouMean, previousSearchText });\n }\n };\n\n const detectKeyboardClick = ({ event: { key }, isPositive }: { event: React.KeyboardEvent; isPositive: boolean }) => {\n if (key === Constants.SystemKeyAsString.Enter || key === Constants.SystemKeyAsString.Space) {\n handleFeedbackClick({ isPositive });\n }\n };\n\n const positiveIconName = isFeedbackNull ? likeIconName : isFeedbackPositive ? solidLikeIconName : likeIconName;\n const negativeIconName = isFeedbackNull ? dislikeIconName : !isFeedbackPositive ? solidDislikeIconName : dislikeIconName;\n const thumbsUpClass = classNames({\n [defaultCursor]: !isFeedbackNull && !isFeedbackPositive,\n [feedbackButtons]: isFeedbackNull,\n [pointerCursor]: isFeedbackNull,\n [feedbackButtonsSelected]: !isFeedbackNull && isFeedbackPositive,\n });\n const thumbsDownClass = classNames({\n [defaultCursor]: !isFeedbackNull && isFeedbackPositive,\n [feedbackButtons]: isFeedbackNull,\n [pointerCursor]: isFeedbackNull,\n [feedbackButtonsSelected]: !isFeedbackNull && !isFeedbackPositive,\n });\n\n return (\n
\n \n \n {localizedQuestion}\n \n \n handleFeedbackClick({ isPositive: true })}\n onKeyDown={(event: React.KeyboardEvent) => detectKeyboardClick({ event, isPositive: true })}\n iconName={positiveIconName}\n className={thumbsUpClass}\n />\n \n \n handleFeedbackClick({ isPositive: false })}\n onKeyDown={(event: React.KeyboardEvent) => detectKeyboardClick({ event, isPositive: false })}\n iconName={negativeIconName}\n className={thumbsDownClass}\n />\n \n \n
\n );\n};\n\n(ThumbsUpDown as React.FC).contextTypes = {\n loc: PropTypes.func,\n};\n","import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';\nimport { render, unmountComponentAtNode } from 'react-dom';\nimport { Provider, useStore } from 'react-redux';\nimport type { AnyAction, Dispatch } from 'redux';\nimport { unstable_HistoryRouter as HistoryRouter, Routes, Route } from 'react-router-dom';\nimport PropTypes from 'prop-types';\n\nimport { appHistory } from '@shared/routerHistory';\nimport { useHydration } from '@shared/hooks/useHydration';\n\nimport { IState } from '@src/State';\nimport { ensureCheckoutApp } from '@shared/actions/thunkActions';\nimport { EnsureAsyncDataSSR } from '@shared/hooks/useLocationChange';\n\nconst Checkout = React.lazy(() => import('./checkout'));\n\n/**\n * This component is in charge of hydrating only the PurchaseBlends Checkout wrapper by using React 17's hydrate function\n * on a pre-defined
root because The PurchaseBlends UI does not support React 18.\n * @param _ props is never used\n * @param context React.context inherited by \n * @returns JSX\n */\nconst LegacyCheckoutHydration = (_: never, context: Record string | string>) => {\n const checkoutDOMRef = useRef(null);\n const [contextValue] = useState(() => {\n return { ...context };\n });\n\n const isHydrated = useHydration();\n const store = useStore();\n\n const renderCheckout = useCallback(() => {\n render(\n \n \n \n \n \n \n }\n />\n \n \n ,\n checkoutDOMRef.current\n );\n }, [store, contextValue]);\n\n useEffect(() => {\n if (!isHydrated) {\n return;\n }\n\n const current = checkoutDOMRef.current;\n if (current && !current.children.length) {\n renderCheckout();\n }\n return () => {\n if (current) {\n unmountComponentAtNode(current);\n }\n };\n }, [checkoutDOMRef, renderCheckout, isHydrated]);\n\n return
;\n};\n\nLegacyCheckoutHydration.contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n locDateString: PropTypes.func,\n locale: PropTypes.string,\n buildHref: PropTypes.func,\n ctaCallback: PropTypes.func,\n contactCallback: PropTypes.func,\n openTileCallback: PropTypes.func,\n renderErrorModal: PropTypes.func,\n reportAbuseCallback: PropTypes.func,\n getAppHeaderHeight: PropTypes.func,\n};\n\nexport const ensureAsyncDataSSR: EnsureAsyncDataSSR = async (\n dispatch: Dispatch,\n getState: () => IState,\n params: Record\n) => {\n const { entityId } = params;\n await dispatch(ensureCheckoutApp({ entityId }));\n};\n\nexport default LegacyCheckoutHydration;\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from '../../shared/components/spzaComponent';\nimport { ILocContext, ILocParamsContext, ICommonContext } from '../../shared/interfaces/context';\nimport { Constants } from '../../shared/utils/constants';\nimport { IFilterElement } from '../../shared/Models';\nimport { InternalLink } from '../../shared/components/internalLink';\nimport * as DetailUtils from '../../shared/utils/detailUtils';\nimport { urlPush } from '../../shared/routerHistory';\n\nexport interface IUsefulInfoProps {\n metadata: UsefulInfoMetadataColumns[];\n ownerType: DetailUtils.OwnerType;\n}\n\nexport interface UsefulInfoMetadataColumns {\n id: string;\n title: string;\n cells: UsefulInfoMetadataCell[];\n titleFallback?: string;\n}\nexport interface UsefulInfoMetadataCell {\n id: string;\n title?: string;\n cellItems: UsefulInfoMetadataCellItemBase[];\n titleFallback?: string;\n}\nexport interface UsefulInfoMetadataCellItemBase {\n id: string;\n type: UsefulInfoMetadataCellItemType;\n text: string;\n props?: { [key: string]: any };\n}\n\n// modal\nexport interface UsefulInfoMetadataCellItemModal extends UsefulInfoMetadataCellItemBase {\n type: UsefulInfoMetadataCellItemType.Modal;\n getContent: () => JSX.Element;\n}\n\n// label\nexport interface UsefulInfoMetadataCellItemLabel extends UsefulInfoMetadataCellItemBase {\n type: UsefulInfoMetadataCellItemType.Label;\n}\n\n// link\nexport interface UsefulInfoMetadataCellItemLink extends UsefulInfoMetadataCellItemBase {\n type: UsefulInfoMetadataCellItemType.Link;\n entityId: string;\n information: any;\n}\n\n// internal link\nexport interface UsefulInfoMetadataCellItemInternalLink extends UsefulInfoMetadataCellItemBase {\n type: UsefulInfoMetadataCellItemType.InternalLink;\n information: any;\n onClick?: () => void;\n}\n\n// filter\nexport interface UsefulInfoMetadataCellItemFilter extends UsefulInfoMetadataCellItemBase {\n type: UsefulInfoMetadataCellItemType.Filter;\n filter: IFilterElement;\n entityId: string;\n isEmbedded?: boolean;\n nationalCloud?: string;\n}\n\nexport enum UsefulInfoMetadataCellItemType { // ask Gil where to put it\n Filter,\n Label,\n Link,\n Modal,\n InternalLink,\n}\n\nconst isUsefulInfoMetadataCellItemFilter = (\n object: UsefulInfoMetadataCellItemBase\n): object is UsefulInfoMetadataCellItemFilter => {\n return object.type === UsefulInfoMetadataCellItemType.Filter;\n};\n\nconst isUsefulInfoMetadataCellItemInternalLink = (\n object: UsefulInfoMetadataCellItemBase\n): object is UsefulInfoMetadataCellItemInternalLink => {\n return object.type === UsefulInfoMetadataCellItemType.InternalLink;\n};\n\nconst isUsefulInfoMetadataCellItemLink = (object: UsefulInfoMetadataCellItemBase): object is UsefulInfoMetadataCellItemLink => {\n return object.type === UsefulInfoMetadataCellItemType.Link;\n};\n\nconst isUsefulInfoMetadataCellItemModal = (object: UsefulInfoMetadataCellItemBase): object is UsefulInfoMetadataCellItemModal => {\n return object.type === UsefulInfoMetadataCellItemType.Modal;\n};\n\nexport class UsefulInfo extends SpzaComponent {\n context: ILocContext & ILocParamsContext & ICommonContext;\n\n getLinkElement(item: UsefulInfoMetadataCellItemLink) {\n if (item.information) {\n return (\n {\n DetailUtils.generateLinkPayloadAndLogTelemetry(\n this.props.ownerType,\n item.entityId,\n item.id,\n item.information,\n 'Default'\n );\n }}\n {...item.props}\n >\n {this.context.loc(item.text)}\n \n );\n } else {\n return null;\n }\n }\n\n getInternalLinkElement(item: UsefulInfoMetadataCellItemInternalLink) {\n return (\n \n {this.context.loc(item.text)}\n \n );\n }\n\n getFilterElement(item: UsefulInfoMetadataCellItemFilter) {\n const filter: IFilterElement = item.filter;\n if (item.nationalCloud) {\n return
{this.context.loc(filter.locKey, filter.title)}
;\n } else {\n return (\n {\n DetailUtils.generateLinkPayloadAndLogTelemetry(\n DetailUtils.OwnerType.App,\n item.entityId,\n filter.filterItem,\n filter.newPath,\n 'Default'\n );\n // The embedded app needs to open these links in a new window\n if (item.isEmbedded) {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n window.open(window.location.origin + filter.newPath, '_blank');\n } else {\n urlPush(filter.newPath);\n window.scrollTo(0, 0);\n }\n\n // The preventDefault prevents the route from transitioning and thus refreshing the page.\n e.preventDefault();\n }}\n key={filter.title}\n accEnabled={true}\n {...item.props}\n >\n {this.context.loc(filter.locKey, filter.title)}\n {filter.filterItem === Constants.filterMaps.categories ? (\n \n ) : null}\n \n );\n }\n }\n\n renderImpl() {\n return (\n
\n {this.props.metadata.map((col: UsefulInfoMetadataColumns) => {\n return (\n
\n

{this.context.loc(col.title, col.titleFallback || col.title)}

\n
\n {col.cells\n .map((cell: UsefulInfoMetadataCell) => {\n return (\n
\n {cell.title && (\n
\n {this.context.loc(cell.title, cell.titleFallback || cell.title)}\n
\n )}\n {cell.cellItems.map((item: UsefulInfoMetadataCellItemBase) => {\n if (isUsefulInfoMetadataCellItemFilter(item)) {\n return this.getFilterElement(item);\n } else if (isUsefulInfoMetadataCellItemLink(item)) {\n return this.getLinkElement(item);\n } else if (isUsefulInfoMetadataCellItemModal(item)) {\n return item.getContent();\n } else if (isUsefulInfoMetadataCellItemInternalLink(item)) {\n return this.getInternalLinkElement(item);\n } else {\n return (\n \n {this.context.loc(item.text)}\n \n );\n }\n })}\n
\n );\n })\n .filter(Boolean)}\n
\n
\n );\n })}\n
\n );\n }\n}\n\n(UsefulInfo as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","import {\n getSessionStorageItem,\n getLocalStorageItem,\n saveSessionStorageItem,\n saveLocalStorageItem,\n} from '@shared/utils/browserStorageUtils';\n\nexport const POPUP_DISPLAYED = 'PopupDisplayed';\n\n// The different types of popups - add new popup here.\nexport enum PopupType {\n LinkedinTeachingBubble = 'LinkedinTeachingBubble',\n}\n\nexport interface IPopupDisplayLogic {\n maxDisplayCount: number; // Maximum number popup displays to user.\n DisplayOnceInSession: boolean; // If true should popup once in session.\n}\n\n// Map used to define the display logic for each popup.\nconst popups = new Map([\n [PopupType.LinkedinTeachingBubble, { maxDisplayCount: 5, DisplayOnceInSession: true }],\n]);\n\nfunction getTotalDisplayCount(popupType: PopupType): number {\n const storageDisplayCount: string = getLocalStorageItem(popupType);\n return storageDisplayCount ? parseInt(storageDisplayCount) : 0;\n}\n\nfunction didPopupDisplayedInThisSession(popupType: PopupType): boolean {\n const displayedInSession: string = getSessionStorageItem(popupType);\n return POPUP_DISPLAYED === displayedInSession;\n}\n\nfunction didPopupExceededMaxCount(popupType: PopupType, displayLogic: IPopupDisplayLogic): boolean {\n return displayLogic.maxDisplayCount <= getTotalDisplayCount(popupType);\n}\n\n// True if popup should be displayed according to its display history and display logic.\nexport function shouldDisplayPopup(popupType: PopupType): boolean {\n const displayLogic = popups.get(popupType);\n if (displayLogic.DisplayOnceInSession) {\n return !didPopupDisplayedInThisSession(popupType) && !didPopupExceededMaxCount(popupType, displayLogic);\n } else {\n return !didPopupExceededMaxCount(popupType, displayLogic);\n }\n}\n\n// Call when popup displayed. This call update the popup's display history.\nexport function popupDisplayed(popupType: PopupType): void {\n const displayLogic = popups.get(popupType);\n const updatedTotalDisplayCount = getTotalDisplayCount(popupType) + 1;\n if (displayLogic.DisplayOnceInSession) {\n saveSessionStorageItem(popupType, POPUP_DISPLAYED);\n }\n saveLocalStorageItem(popupType, updatedTotalDisplayCount.toString());\n}\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport { Dropdown, getTheme } from '@fluentui/react';\nimport type { IDropdownStyles } from '@fluentui/react';\n\nimport { NeutralColors } from '@fluentui/theme';\n\nimport { routes, urlPush } from '@shared/routerHistory';\nimport { IBuildHrefContext, ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { IDataValues } from '@shared/utils/dataMapping';\nimport SpzaComponent from './spzaComponent';\nimport classNames from 'classnames';\n\nexport enum FiltersDropDownCollections {\n category = 'category',\n industry = 'industry',\n product = 'product',\n}\n\nexport interface IFiltersDropDownOptionData {\n icon?: string;\n link: string;\n}\n\nexport interface IFiltersDropDownOption {\n key: string;\n text: string;\n data: IFiltersDropDownOptionData;\n}\n\nexport interface IFiltersDropDownProps {\n collection: FiltersDropDownCollections;\n filters: IDataValues[];\n placeholder: string;\n showIcons: boolean;\n}\n\nconst theme = getTheme();\n\nconst height = 55;\nconst paddingLeft = 20;\n\nconst dropdownStyles: Partial = {\n dropdown: { width: '100%', height },\n dropdownItemSelected: { height, paddingLeft },\n dropdownItem: { height, paddingLeft },\n title: {\n height,\n lineHeight: height,\n paddingLeft: 20,\n borderRadius: 6,\n borderColor: NeutralColors.gray30,\n boxShadow: theme.effects.elevation8,\n },\n caretDownWrapper: { lineHeight: height, right: 16 },\n subComponentStyles: {\n panel: {\n main: {\n inset: '0px auto 0px 0px',\n },\n },\n label: {},\n multiSelectItem: {},\n },\n};\n\nexport class FiltersDropDown extends SpzaComponent {\n context: IBuildHrefContext & ICommonContext & ILocContext & ILocParamsContext;\n\n getDefaultSelectedKey(): string {\n // cast to any[] since https://github.com/microsoft/TypeScript/issues/36390\n const activeFilter: IDataValues = (this.props.filters as any[])?.find((filter: IDataValues) => filter.isActive);\n\n return activeFilter?.UrlKey;\n }\n\n getAllFilterOption(): IFiltersDropDownOption {\n return {\n key: 'ALL',\n text: this.context.loc('FiltersDropDown_All', 'ALL'),\n data: {\n icon: 'icon-all-16',\n link: this.context.buildHref(routes.home, null, {\n [this.props.collection]: '',\n }),\n },\n };\n }\n\n getFiltersOptions(): IFiltersDropDownOption[] {\n if (!(this.props.filters && this.props.collection)) {\n return [];\n }\n\n return [\n this.getAllFilterOption(),\n ...this.props.filters.map((filter: IDataValues) => {\n const icon = this.props.showIcons ? `icon-${filter.UrlKey}-16` : '';\n const link = this.context.buildHref(routes.home, null, {\n [this.props.collection]: (filter.isActive ? '!' : '') + filter.ShortcutUrlKey,\n });\n\n return {\n key: filter.UrlKey,\n text: filter.Title,\n data: {\n icon,\n link,\n },\n };\n }),\n ];\n }\n\n onRenderOption(option: IFiltersDropDownOption): JSX.Element {\n return (\n option && (\n
\n {option.data.icon && (\n
\n )}\n {option.text}\n
\n )\n );\n }\n\n onChange(option?: IFiltersDropDownOption) {\n if (option?.data.link) {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n urlPush(option.data.link);\n }\n }\n\n renderImpl() {\n const { placeholder } = this.props;\n\n return (\n this.onRenderOption(options[0])}\n onRenderOption={(option: IFiltersDropDownOption) => this.onRenderOption(option)}\n onChange={(_, option?: IFiltersDropDownOption) => this.onChange(option)}\n styles={dropdownStyles}\n ariaLabel={placeholder}\n />\n );\n }\n}\n\n(FiltersDropDown as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n buildHref: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","import { IState } from '@src/State';\nimport { ITileData, ITelemetryData } from '@shared/Models';\nimport { isPricingFilter } from 'utils/filterHelpers';\nimport { routes } from '@shared/routerHistory';\nimport { Constants } from '@shared/utils/constants';\nimport { deepEqual } from '@shared/utils/objectUtils';\nimport { stringifyError } from '@shared/utils/errorUtils';\nimport {\n generateGuid,\n getActiveFeaturedFlags,\n getTileDataEntityType,\n getTileDataQueryParams,\n getProductByUrlKey,\n} from '@shared/utils/appUtils';\nimport { createTileDataReceivedAction, createTileDataLoadingAction } from '@shared/actions/tileDataActions';\nimport {\n ExtendableRequest,\n getTileData,\n IGetTileDataParams,\n IGetTileDataParamsLog,\n} from '@shared/services/http/entityRestClient';\nimport { getWindow } from '@shared/services/window';\nimport { SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\n\nimport { createAppsPricingDataReceivedAction, createShowingResultsForReceivedAction } from '@shared/actions/actions';\nimport {\n createSetRequestFilteredEntityTypeAction,\n createSetRequestFilteredLoadedAction,\n createSetRequestFilteredLoadingAction,\n createSetRequestFilteredQueryAction,\n createSetResponseFilteredQueryAction,\n} from './queryActions';\nimport { logger } from '@src/logger';\n\nlet prevTileDataRequest: ExtendableRequest;\n\nfunction generateTileDataLog(requestParams: IGetTileDataParamsLog, tileData: ITileData, duration: number) {\n const details = {\n duration,\n requestParams,\n searchResponseId: tileData.apps.dataList[0]?.searchResponseId,\n appsResultsCount: tileData.apps && tileData.apps.count,\n appsTopResults: tileData.apps && tileData.apps.dataList && tileData.apps.dataList.slice(0, 20).map((app) => app.entityId),\n servicesResultsCount: tileData.services && tileData.services.count,\n servicesTopResults:\n tileData.services &&\n tileData.services.dataList &&\n tileData.services.dataList.slice(0, 20).map((service) => service.entityId),\n };\n\n const payload: ITelemetryData = {\n page: getWindow()?.location?.href,\n action: Constants.Telemetry.Action.TileData,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: JSON.stringify(details),\n };\n\n SpzaInstrumentService.getProvider().probe(Constants.Telemetry.ProbeName.LogInfo, payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n}\n\nexport function loadTileData() {\n return function (dispatch: any, getState: () => IState): Promise {\n const start = Date.now();\n\n const currentView = getState().config.currentView;\n\n const isBrowseView =\n currentView === routes.marketplace.name ||\n currentView === routes.marketplaceServices.name ||\n currentView === routes.marketplaceCloudsIndustry.name ||\n currentView === routes.home.name;\n\n // Load tile data only in Borwses Apps views, like Home and Gallery\n if (!isBrowseView) {\n return Promise.resolve(null);\n }\n\n const currentEntityType = getState().config.isAppSource\n ? getTileDataEntityType(currentView)\n : Constants.TileDataEntityType.All;\n\n const currentFilteredQuery = getTileDataQueryParams(getState().config.query);\n const prevEntityType = getState().config.requestFilteredEntityType;\n const prevFilteredQuery = getState().config.requestFilteredQuery;\n\n if (deepEqual(currentFilteredQuery, prevFilteredQuery) && currentEntityType === prevEntityType) {\n return Promise.resolve(null);\n }\n\n dispatch(\n createSetRequestFilteredEntityTypeAction({\n requestFilteredEntityType: currentEntityType,\n })\n );\n\n dispatch(\n createSetRequestFilteredQueryAction({\n requestFilteredQuery: currentFilteredQuery,\n })\n );\n\n dispatch(createSetRequestFilteredLoadingAction());\n\n if (currentView !== routes.home.name) {\n dispatch(\n createTileDataLoadingAction({\n urlQuery: currentFilteredQuery,\n })\n );\n }\n\n const urlQuery = {\n ...currentFilteredQuery,\n ...getActiveFeaturedFlags(getState().config.featureFlags),\n };\n\n const requestParams: IGetTileDataParams = {\n requestId: generateGuid(),\n flightCodes: getState().config.flightCodes,\n embedHost: getProductByUrlKey({ urlKey: getState().config.embedHost })?.UrlKey,\n billingRegion: isPricingFilter(currentFilteredQuery) ? getState().config.billingCountryCode : '',\n urlQuery,\n entityType: currentEntityType,\n };\n\n prevTileDataRequest?.request()?.abort();\n\n prevTileDataRequest = getTileData(requestParams);\n\n return prevTileDataRequest.data$\n .then((tileData: ITileData) => {\n prevTileDataRequest = null;\n\n generateTileDataLog(\n { ...requestParams, origin: tileData.origin, suggestionsRequestId: getState().search.suggestionsRequestId },\n tileData,\n Date.now() - start\n );\n\n dispatch(createSetRequestFilteredLoadedAction());\n\n dispatch(createSetResponseFilteredQueryAction({ responseFilteredQuery: currentFilteredQuery }));\n\n dispatch(\n createTileDataReceivedAction({\n entityType: Constants.EntityType.App,\n entityData: tileData.apps,\n urlQuery: currentFilteredQuery,\n requestId: requestParams.requestId,\n })\n );\n dispatch(\n createTileDataReceivedAction({\n entityType: Constants.EntityType.Service,\n entityData: tileData.services,\n urlQuery: currentFilteredQuery,\n requestId: requestParams.requestId,\n })\n );\n dispatch(\n createTileDataReceivedAction({\n entityType: Constants.EntityType.CloudsIndustry,\n entityData: tileData.cloudsIndustry,\n urlQuery: currentFilteredQuery,\n requestId: requestParams.requestId,\n })\n );\n\n if (getState().apps.pricingPayload) {\n dispatch(createAppsPricingDataReceivedAction(getState().apps.pricingPayload));\n }\n\n if (getState().cloudsIndustry.pricingPayload) {\n dispatch(createAppsPricingDataReceivedAction(getState().cloudsIndustry.pricingPayload));\n }\n\n dispatch(\n createShowingResultsForReceivedAction({\n showingResultsFor: tileData.showingResultsFor,\n previousSearchText: currentFilteredQuery.search,\n })\n );\n\n return tileData;\n })\n .catch((err) => {\n prevTileDataRequest = null;\n\n const payload: ITelemetryData = {\n page: getWindow()?.location?.href,\n action: Constants.Telemetry.Action.TileData,\n actionModifier: Constants.Telemetry.ActionModifier.Error,\n details: JSON.stringify({\n duration: Date.now() - start,\n requestParams,\n error: stringifyError(err),\n }),\n };\n\n SpzaInstrumentService.getProvider().probe(Constants.Telemetry.ProbeName.LogInfo, payload);\n logger.error(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n throw err;\n });\n };\n}\n","/* istanbul ignore file */\nimport { logClientError } from '@shared/utils/appUtils';\nimport { getWindow } from '@shared/services/window';\n\nconst KNOWN_HYDRATION_INTERRUPTION_ERRORS = ['Root did not complete', 'Minified React error #345'];\n\n/**\n * We are avoiding error propagation. This error happens when the client is not fully hydrated, yet user attempts to interact with it.\n * The client is still recoverable due to server-side navigation, although we will re-attempt to hydrate and trigger the client-side for full functionality.\n */\nexport function handleInterruptedHydration(errorMsg: string): boolean {\n const isKnownIssue = KNOWN_HYDRATION_INTERRUPTION_ERRORS.some((issue) => errorMsg?.includes?.(issue));\n\n if (isKnownIssue) {\n getWindow()?.HYDRATE?.();\n }\n\n return isKnownIssue;\n}\n\nexport function handleRecoverableHydrationError(error: unknown, env: string) {\n if (env !== 'prod') {\n // this error should be visible in non-production environments as awareness when recovrable hydration issues occur and should be fixed!\n console.error(error);\n throw error;\n }\n\n logClientError(error, `client side error: React recoverable hydration error ${error}`, '', true);\n}\n","import { getWindow } from '@shared/services/window';\nimport { Constants } from '@shared/utils/constants';\nimport { getSpzaUserIdAndNewUserModifier } from '@shared/utils/appUtils';\n\nexport interface PageLoadTime {\n hostType: string;\n landingView: string;\n responseStart: number;\n responseEnd: number;\n scriptStart: number;\n hydrationStart: number;\n performanceNow: number;\n duration: number;\n durationSinceRequestStart: number;\n slowestAssetDuration: number;\n slowestAssetURL: string;\n}\n\ninterface AssetsLoadTime {\n src: string;\n requestStart: number;\n responseEnd: number;\n duration: number;\n}\n\nconst predefinedPLT = {\n scriptStart: -1,\n hydrationStart: -1,\n};\n\nfunction currentSinceRequestStart() {\n const { navigationStart = 0, requestStart = 0 } = getWindow()?.performance?.timing || {};\n\n return getWindow()?.performance?.now() - (requestStart - navigationStart);\n}\n\nexport function setScriptStart() {\n predefinedPLT.scriptStart = currentSinceRequestStart();\n}\n\nexport function setHydrationStart() {\n predefinedPLT.hydrationStart = currentSinceRequestStart();\n}\n\nfunction getAssetsLoadTime(): AssetsLoadTime[] {\n const { navigationStart = 0, requestStart = 0 } = getWindow()?.performance?.timing || {};\n\n const perfEntries = (getWindow()?.performance?.getEntriesByType('resource') || []) as PerformanceResourceTiming[];\n\n return perfEntries\n .filter(({ initiatorType }) => initiatorType === 'script' || initiatorType === 'link')\n .map(({ name: src, responseEnd }) => ({\n src,\n requestStart,\n responseEnd,\n duration: responseEnd - (requestStart - navigationStart),\n }));\n}\n\nconst whiteListedAssets = ['staticstorage', 'agorasstatic'];\n\nfunction getSlowestAssetDuration({ assets }: { assets: AssetsLoadTime[] }): AssetsLoadTime {\n return assets\n .filter(({ src }) => whiteListedAssets.some((whiteListedAsset) => src.includes(whiteListedAsset)))\n .sort((a, b) => b.duration - a.duration)[0];\n}\n\nexport function telemetryPageLoadTime({ isEmbedded, landingView }: { isEmbedded: boolean; landingView: string }): void {\n if (!getWindow()) {\n return;\n }\n\n const performanceNow = getWindow().performance.now();\n const { navigationStart = 0, requestStart = 0, responseStart = 0, responseEnd = 0 } = getWindow().performance.timing;\n const duration = performanceNow - (requestStart - navigationStart);\n\n const { scriptStart, hydrationStart } = predefinedPLT;\n\n const assets = getAssetsLoadTime();\n\n const { duration: slowestAssetDuration, src: slowestAssetURL } = getSlowestAssetDuration({ assets }) || {};\n\n const { spzaUserId: spzaId } = getSpzaUserIdAndNewUserModifier() || {};\n\n const hostType = isEmbedded ? Constants.appSourceEmbed : Constants.appSource;\n\n const pageLoadTime: PageLoadTime = {\n hostType,\n landingView,\n responseStart: Math.max(responseStart - requestStart, 0),\n responseEnd: Math.max(responseEnd - requestStart, 0),\n scriptStart,\n hydrationStart,\n performanceNow,\n duration,\n durationSinceRequestStart: duration,\n slowestAssetDuration,\n slowestAssetURL,\n };\n\n getWindow().pageLoadTime = pageLoadTime;\n\n getWindow().telemetry({\n action: Constants.Telemetry.Action.RootHandler,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: pageLoadTime,\n spzaId,\n });\n\n getWindow().telemetry({\n action: Constants.Telemetry.Action.PerfResources,\n actionModifier: Constants.Telemetry.ActionModifier.Complete,\n details: { length: assets.length, assets },\n spzaId,\n });\n\n getWindow().flushTelemetry();\n}\n","import { IURLQuery, ITileDataApps, ITileDataServices, IDataItem } from '@shared/Models';\nimport { ActionType, actionCreator } from '@shared/actions/actions';\nimport { Constants } from '@shared/utils/constants';\nimport { IEntityDataState } from '@src/State';\n\nexport const TileDataLoadingAction: ActionType<{\n urlQuery: IURLQuery;\n}> = 'TileDataLoadingAction';\nexport const createTileDataLoadingAction = actionCreator(TileDataLoadingAction);\n\nexport const TileDataReceivedAction: ActionType<{\n entityType: Constants.EntityType;\n entityData: IEntityDataState | ITileDataApps | ITileDataServices;\n urlQuery: IURLQuery;\n requestId: string;\n}> = 'TileDataReceivedAction';\nexport const createTileDataReceivedAction = actionCreator(TileDataReceivedAction);\n","import { IURLQuery } from '@shared/Models';\nimport { Constants } from '@shared/utils/constants';\nimport { ActionType, actionCreator } from './actions';\n\nexport const SetQueryAction: ActionType<{ query: IURLQuery }> = 'SetQueryAction';\nexport const createSetQueryAction = actionCreator(SetQueryAction);\n\nexport const SetRequestFilteredQueryAction: ActionType<{ requestFilteredQuery: IURLQuery }> = 'SetRequestFilteredQueryAction';\nexport const createSetRequestFilteredQueryAction = actionCreator(SetRequestFilteredQueryAction);\n\nexport const SetResponseFilteredQueryAction: ActionType<{ responseFilteredQuery: IURLQuery }> = 'SetResponseFilteredQueryAction';\nexport const createSetResponseFilteredQueryAction = actionCreator(SetResponseFilteredQueryAction);\n\nexport const SetRequestFilteredEntityTypeAction: ActionType<{\n requestFilteredEntityType: Constants.TileDataEntityType;\n}> = 'SetRequestFilteredEntityTypeAction';\nexport const createSetRequestFilteredEntityTypeAction = actionCreator(SetRequestFilteredEntityTypeAction);\n\nexport const SetRequestFilteredLoadingAction: ActionType = 'SetRequestFilteredLoadingAction';\nexport const createSetRequestFilteredLoadingAction = actionCreator(SetRequestFilteredLoadingAction);\n\nexport const SetRequestFilteredLoadedAction: ActionType = 'SetRequestFilteredLoadedAction';\nexport const createSetRequestFilteredLoadedAction = actionCreator(SetRequestFilteredLoadedAction);\n","import React from 'react';\nimport { mergeStyleSets, Spinner } from '@fluentui/react';\nimport propTypes from 'prop-types';\nimport classNames from 'classnames';\n\nconst styles = mergeStyleSets({\n container: {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n width: '100%',\n height: '50vh',\n },\n});\n\nconst LoadingSpinner: React.FC = (_: never, context: { loc: (key: string, fallback: string) => string }) => (\n
\n \n
\n);\n\nLoadingSpinner.contextTypes = {\n loc: propTypes.func,\n};\n\nexport default LoadingSpinner;\n","/* istanbul ignore file */\nimport React, { Suspense, useMemo, useState } from 'react';\nimport { matchPath, useLocation } from 'react-router-dom';\nimport type { RouteObject } from 'react-router-dom';\nimport { useSelector } from 'react-redux';\nimport { IState } from '@src/State';\nimport LoadingSpinner from '@shared/components/LoadingSpinner';\n\nconst withSuspense = (Component: React.ReactNode) => {\n return }>{Component};\n};\n\ninterface UseClientRoutesOptions {\n routes: RouteObject[];\n}\n\nexport function useClientRoutes(options: UseClientRoutesOptions): RouteObject[] {\n const { routes } = options;\n const [localeRoutes, ...otherRoutes] = routes;\n const location = useLocation();\n\n const [ssrEntry] = useState(() => location.pathname);\n const isEmbedded = useSelector((state) => state.config.isEmbedded);\n\n return useMemo(() => {\n const children = localeRoutes.children.map((route) => {\n const path = `/:locale/${route.path ?? ''}`;\n const matchingPath = matchPath(path, ssrEntry);\n\n const shouldWrapWithSuspense = isEmbedded || !(route.index || matchingPath?.pattern?.path === path);\n\n return {\n ...route,\n element: shouldWrapWithSuspense ? withSuspense(route.element) : route.element,\n };\n });\n\n return [{ ...localeRoutes, children }, ...otherRoutes];\n }, [ssrEntry, routes, isEmbedded]);\n}\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from './spzaComponent';\nimport { ILocContext, ICommonContext } from '../interfaces/context';\nimport { getElement } from '../utils/appUtils';\nimport { Constants } from '../utils/constants';\nimport { SpzaInstrumentService } from '../services/telemetry/spza/spzaInstrument';\nimport { getWindow } from '../services/window';\nimport { ITelemetryData } from '../Models';\nimport { logger } from '@src/logger';\n\nexport interface ILongContentProps {\n // This is prefixed with 'longContent' to give a unique ID for the div\n entityId: string;\n // The content to be displayed\n content: string | JSX.Element;\n // The height which the content should exceed to see a 'More' button\n clipHeight: number;\n}\n\nexport interface ILongContentState {\n isExpanded: boolean;\n showExpansionButton: boolean;\n // This flag is to avoid an infinite loop on componentDidUpdate.\n // We are introducing the less/more button based on the height of the div.\n // The height of the div is available only when the component successfully mounts/updates.\n // So we MUST set the state in componentDidUpdate.\n // But if we set the state in componentDidUpdate, we are actually updating the component. :)\n // To avoid this infinite loop, we are setting this variable now.\n processed: boolean;\n}\n\n// Displays long content with more and less switches\nexport class LongContent extends SpzaComponent {\n context: ILocContext & ICommonContext;\n longContentIdPrefix = 'longContent';\n\n constructor(props: ILongContentProps, context: ILocContext & ICommonContext) {\n super(props, context);\n this.state = {\n // By default every content is expanded\n isExpanded: true,\n // By default we should not show the expansion button\n showExpansionButton: false,\n processed: false,\n };\n }\n\n changeExpandedState() {\n this.setState((prevState: ILongContentState) => {\n return {\n isExpanded: !prevState.isExpanded,\n showExpansionButton: prevState.showExpansionButton,\n processed: prevState.processed,\n };\n });\n\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: this.state.isExpanded ? Constants.Telemetry.Action.Collapse : Constants.Telemetry.Action.Expand,\n\n // if this is used somewhere else than my reviews need to update it by passing in with props\n // to determine where is 'more' clicked\n actionModifier: Constants.Telemetry.ActionModifier.ReviewItemMoreButton,\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info('', {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n }\n\n isLongContent() {\n const longContent = getElement(Constants.SearchBy.Id, this.longContentIdPrefix + this.props.entityId);\n return longContent && longContent.offsetHeight > this.props.clipHeight;\n }\n\n setStateBasedOnContentLength() {\n if (this.isLongContent()) {\n this.setState({\n isExpanded: false,\n showExpansionButton: true,\n processed: true,\n });\n } else {\n // Just updating the processed flag.\n this.setState((prevState: ILongContentState) => {\n return {\n isExpanded: prevState.isExpanded,\n showExpansionButton: prevState.showExpansionButton,\n processed: true,\n };\n });\n }\n }\n\n componentDidMount() {\n this.setStateBasedOnContentLength();\n }\n\n componentDidUpdate() {\n if (!this.state.processed) {\n this.setStateBasedOnContentLength();\n }\n }\n\n UNSAFE_componentWillReceiveProps() {\n // Reset to the original state so that more/less can be recalculated.\n this.state = {\n // By default every content is expanded\n isExpanded: true,\n // By default we should not show the expansion button\n showExpansionButton: false,\n // When new props are received, we have to reprocess this component.\n processed: false,\n };\n }\n\n renderImpl() {\n if (!this.props.content) {\n return null;\n }\n\n return (\n
\n \n {{this.props.content}}\n

\n {!this.state.showExpansionButton ? null : (\n \n )}\n
\n );\n }\n}\n\n(LongContent as any).contextTypes = {\n loc: PropTypes.func,\n};\n","import { Stack } from '@fluentui/react';\nimport { IAppReviewQAContent } from '@shared/Models';\nimport * as React from 'react';\n\nexport interface IReviewQAContent {\n QAs: IAppReviewQAContent[];\n}\n\nexport const ReviewQAContent = ({ QAs = [] }: IReviewQAContent) => {\n return (\n \n {QAs?.map((reviewQA: IAppReviewQAContent, index: number) => (\n
\n
{reviewQA.question}
\n
{reviewQA.answer}
\n
\n ))}\n
\n );\n};\n","import React, { useEffect, useRef } from 'react';\nimport { NeutralColors, SharedColors } from '@fluentui/theme';\n\nimport { Link, mergeStyles, mergeStyleSets, Persona, PersonaSize, Rating, RatingSize, Stack, Text, Icon } from '@fluentui/react';\nimport * as PropTypes from 'prop-types';\nimport { ILocContext, ICommonContext, IReviewContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { IAppReview, ITelemetryData } from '@shared/Models';\nimport { SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport { getWindow } from '@shared/services/window';\nimport { Constants } from '@shared/utils/constants';\nimport { LongContent } from '@shared/components/longContent';\nimport { getLocaleDate } from '@shared/utils/appUtils';\nimport { ReviewQAContent } from '@shared/components/reviewQAContent';\nimport { getProviderNameByReviewSource, launchTelemetry } from '@shared/utils/reviewsUtils';\nimport { logger } from '@src/logger';\nimport { AppReviewSocialContent } from '@shared/components/appReviewSocialContent';\nimport { noop } from 'lodash-es';\n\n// 60px is the height of 3 lines.\nconst contentClipHeight = 60;\n\nexport interface IAppReviewItemProps {\n appId: string;\n iconUrl: string;\n publisher: string;\n review: IAppReview;\n locale: string;\n isEmbed: boolean;\n reviewItemShown(reviewId: string, reviewCompRef: React.RefObject): void;\n isSignedIn?: boolean;\n isMarkedAsHelpful?: boolean;\n onToggleMarkAsHelpful: (args: { isHelpful: boolean }) => void;\n openSignInModal: () => void;\n fallbackReviewSourceName: string;\n authorPersona?: boolean;\n dateLabelClassName?: string;\n}\n\nconst contentStyles = mergeStyleSets({\n providerLogo: {\n height: 16,\n width: 16,\n marginRight: 10,\n },\n reviewLinkToSource: {\n ':link': {\n color: SharedColors.cyanBlue10,\n textDecoration: 'underline',\n },\n },\n externalReviewProvider: {\n display: 'inline-flex',\n alignItems: 'center',\n },\n providedByWrapper: [\n 'providedBy',\n 'c-paragraph-3',\n {\n lineHeight: 14,\n fontSize: 10,\n color: NeutralColors.gray130,\n display: 'flex',\n width: '75%',\n borderTopStyle: 'solid',\n borderTopWidth: 0.5,\n borderTopColor: NeutralColors.gray30,\n },\n ],\n reviewContent: ['reviewContent'],\n reviewAndIsvReply: ['reviewAndIsvReply'],\n reviewItem: ['reviewItem'],\n authorDetailsContainer: ['authorName', 'c-paragraph-3'],\n authorPersona: ['spza-user-default-thumbnail-image', { marginBottom: 8 }],\n reviewRating: ['reviewRating'],\n reviewTitle: ['c-heading-6', 'contentHeader'],\n reviewRightContent: ['rightContent', { overflow: 'hidden' }],\n reviewDateWrapper: ['c-paragraph-3', { marginBottom: 4 }],\n dateLabel: { fontSize: 12, lineHeight: 16 },\n editedLabel: { color: NeutralColors.gray130, overflow: 'hidden' },\n externalSummaryUrlImage: { height: 13, width: 13, color: SharedColors.cyanBlue10, marginTop: 1 },\n});\n\n// Displays an individual app review\nexport const AppReviewItem = (\n {\n authorPersona = false,\n appId,\n reviewItemShown = noop,\n review,\n locale,\n isEmbed,\n publisher,\n iconUrl,\n isSignedIn = false,\n isMarkedAsHelpful = false,\n onToggleMarkAsHelpful,\n openSignInModal,\n fallbackReviewSourceName,\n dateLabelClassName,\n }: IAppReviewItemProps,\n context: ILocContext & ILocParamsContext & ICommonContext & IReviewContext\n) => {\n const rootDivRef: React.MutableRefObject = useRef();\n const instrument = SpzaInstrumentService.getProvider();\n useEffect(() => {\n if (review) {\n reviewItemShown(review.id, rootDivRef);\n }\n }, []);\n\n useEffect(() => {\n if (review) {\n reviewItemShown(review.id, rootDivRef);\n }\n }, [review?.id]);\n\n const showReportAbuseDialog = () => {\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.ReportCTA,\n appName: appId,\n };\n\n instrument.probe('logAndFlushTelemetryInfo', payload);\n\n logger.info('', {\n action: payload.action,\n actionModifier: payload.actionModifier,\n appName: payload.appName,\n });\n\n context.reportAbuseCallback(appId, review.id, review.title, review.source);\n };\n\n const getFullName = (review: IAppReview) => {\n let result = '';\n if (review.customer_info && review.customer_info.name) {\n result += review.customer_info.name;\n }\n\n if (review.customer_info?.is_anonymous) {\n result = '';\n }\n\n if (!result) {\n return context.loc('ReviewItem_AnonymousName');\n }\n\n return result;\n };\n\n const renderProviderSection = (isThirdPartyReview: boolean) => {\n return (\n
\n {isThirdPartyReview ? (\n
\n \n \n {\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.ExternalReviewLink, {\n offerId: review.offer_id,\n source: review.source,\n reviewId: review.id,\n });\n logger.info(\n JSON.stringify({\n offerId: review.offer_id,\n source: review.source,\n reviewId: review.id,\n }),\n {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.ExternalReviewLink,\n }\n );\n }}\n >\n {getProviderNameByReviewSource(review.source, fallbackReviewSourceName)}{' '}\n {context.loc('Global_Loc_review', 'review')}\n \n \n \n \n
\n ) : (\n {context.loc('Global_Loc_Marketplace_review', 'Marketplace review')}\n )}\n
\n );\n };\n\n const getAriaLabel =\n (review: IAppReview) =>\n (rating: number, max: number): string =>\n context.locParams('ReviewItem_AriaText', [`${rating}`, `${max}`, getFullName(review)]);\n\n const isReviewEdited = (review: IAppReview) => review.updated_at !== review.created_at;\n\n const reviewerName = getFullName(review);\n const isThirdPartyReview = review.source === Constants.StorefrontName.G2;\n\n return (\n
\n
\n
\n
\n \n
\n
\n \n \n {getLocaleDate(review.updated_at, locale)}\n \n {isReviewEdited(review) && (\n \n {context.loc('ReviewItem_EditedLabel')}\n \n )}\n \n
\n \n {authorPersona ? (\n \n ) : (\n reviewerName\n )}\n \n
\n {renderProviderSection(isThirdPartyReview)}\n
\n
\n
\n
\n {review.title ?
{review.title}
: null}\n \n
\n
\n showReportAbuseDialog()}\n publisherName={publisher}\n review={review}\n isSignedIn={isSignedIn}\n isMarkedAsHelpful={isMarkedAsHelpful}\n onToggleMarkAsHelpful={onToggleMarkAsHelpful}\n openSignInModal={openSignInModal}\n />\n
\n
\n
\n );\n};\n\n(AppReviewItem as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n reportAbuseCallback: PropTypes.func,\n};\n","import * as React from 'react';\nimport { Label, mergeStyleSets } from '@fluentui/react';\nimport { ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\n\nexport interface IRatingPercentages {\n context: ILocContext & ILocParamsContext & ICommonContext;\n numberOfStars: number;\n percentagesOfTotal: number;\n}\n\nexport interface IRatingPercentagesClassNames {\n fillWidth: string;\n}\n\nexport const getClassNames = (fill: number): IRatingPercentagesClassNames => {\n return mergeStyleSets({\n fillWidth: {\n width: `${fill}%`,\n },\n });\n};\n\nexport const RatingPercentages = ({ context, numberOfStars = 0, percentagesOfTotal = 0 }: IRatingPercentages) => {\n const classNames = require('classnames');\n const { fillWidth } = getClassNames(percentagesOfTotal);\n const chartClasses = classNames('ratingsPercentagesChartFill', fillWidth);\n return (\n
\n
\n \n ({percentagesOfTotal}%)\n
\n
\n
\n
\n
\n );\n};\n","import * as React from 'react';\nimport { Stack, Label, Text, mergeStyleSets, Icon, Image } from '@fluentui/react';\nimport { NeutralColors } from '@fluentui/theme';\n\nimport type { IIconStyles } from '@fluentui/react';\nimport { ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { IRatingSummary } from '@shared/Models';\nimport { RatingPercentages } from './ratingPercentages';\nimport microsoftLogo from '@shared/images/microsoft-icon.svg';\n\nconst contentStyles = mergeStyleSets({\n ratingSummaryContainer: ['ratingSummaryContainer', { minWidth: 280 }],\n singleInternalRatingSourceImage: {\n width: 16,\n height: 16,\n marginRight: 10,\n marginTop: 3,\n },\n ratingSummaryAverageSection: [\n 'ratingSummaryAverageSection',\n {\n margin: '16px 0',\n },\n ],\n});\n\nexport interface IRatingSummaryComponent {\n ratingSummary: IRatingSummary;\n title: string;\n context: ILocContext & ILocParamsContext & ICommonContext;\n}\n\nexport function getPercentagesOfTotal(params: { timesRated: number; totalRatings: number }): number {\n const percentages: number = params.totalRatings ? (params.timesRated * 100) / params.totalRatings : 0;\n return Math.round(percentages);\n}\n\nexport function getEmptyRatingSummary(): IRatingSummary {\n return {\n AverageRating: 0,\n TotalRatings: 0,\n Source: '',\n OfferId: null,\n CreatedAt: null,\n StarsDistribution: {\n OneStars: 0,\n TwoStars: 0,\n ThreeStars: 0,\n FourStars: 0,\n FiveStars: 0,\n },\n };\n}\n\nexport function getAverageRatingToDisplay(ratingSummary: IRatingSummary): string {\n return ratingSummary.AverageRating && !isNaN(ratingSummary.AverageRating) ? ratingSummary.AverageRating.toFixed(1) : '0';\n}\n\nexport const renderRatingPercentagesByNumOfStars = (\n numOfStars: number,\n timesRated: number,\n totalRatings: number,\n context: ILocContext & ILocParamsContext & ICommonContext\n): JSX.Element => {\n return (\n \n );\n};\n\nexport const RatingSummary = ({\n ratingSummary = getEmptyRatingSummary(),\n context = null,\n title = 'Rating Summary',\n}: IRatingSummaryComponent) => {\n const totalRatingSummary = ratingSummary || getEmptyRatingSummary();\n const averageRating = getAverageRatingToDisplay(totalRatingSummary);\n const ratingSummaryAriaText = context?.locParams('Reviews_Summary_AriaText', [\n averageRating,\n '5',\n `${totalRatingSummary.TotalRatings}`,\n ]);\n\n const iconStyles: IIconStyles = {\n root: [\n 'ratingSummaryStar',\n {\n fontSize: 17,\n },\n !totalRatingSummary.TotalRatings && { color: NeutralColors.gray90 },\n ],\n };\n\n return (\n
\n \n \"Microsoft\n {title}\n \n\n
\n \n
\n \n \n ({totalRatingSummary.TotalRatings} {context?.loc('Global_Loc_ratingsLower', 'ratings')})\n \n
\n
\n {totalRatingSummary.TotalRatings >= 5 && (\n \n {renderRatingPercentagesByNumOfStars(\n 5,\n totalRatingSummary.StarsDistribution?.FiveStars ?? 0,\n totalRatingSummary.TotalRatings,\n context\n )}\n {renderRatingPercentagesByNumOfStars(\n 4,\n totalRatingSummary.StarsDistribution?.FourStars ?? 0,\n totalRatingSummary.TotalRatings,\n context\n )}\n {renderRatingPercentagesByNumOfStars(\n 3,\n totalRatingSummary.StarsDistribution?.ThreeStars ?? 0,\n totalRatingSummary.TotalRatings,\n context\n )}\n {renderRatingPercentagesByNumOfStars(\n 2,\n totalRatingSummary.StarsDistribution?.TwoStars ?? 0,\n totalRatingSummary.TotalRatings,\n context\n )}\n {renderRatingPercentagesByNumOfStars(\n 1,\n totalRatingSummary.StarsDistribution?.OneStars ?? 0,\n totalRatingSummary.TotalRatings,\n context\n )}\n \n )}\n
\n );\n};\n","/* eslint-disable react/forbid-dom-props */\nimport { ICommonContext, ILocContext, ILocParamsContext } from 'interfaces/context';\nimport React from 'react';\nimport { IAppReview } from '@shared/Models';\nimport { ISortByHelper, IUpdateReviewsStateByDropDowns, launchTelemetry } from '@shared/utils/reviewsUtils';\nimport { Constants } from '@shared/utils/constants';\nimport { Dropdown } from '@fluentui/react';\nimport type { IDropdownOption } from '@fluentui/react';\n\nimport { logger } from '@src/logger';\n\nexport interface IReviewDropDowns {\n curSourceFilter: string;\n curSortBy: string;\n curRatingFilter: number;\n originalReviewList: IAppReview[];\n curReviewsList: IAppReview[];\n context: ILocContext & ILocParamsContext & ICommonContext;\n updateReviewsState: IUpdateReviewsStateByDropDowns;\n externalReviewsExist: boolean;\n internalReviewsExist: boolean;\n userReview: IAppReview;\n userCommentedReviewIds: string[];\n showMyCommentsFilter: boolean;\n}\n\nexport enum SourceFilterOptions {\n AllReviews = 'All reviews',\n MarketplaceReviews = 'Marketplace reviews',\n G2Reviews = 'G2 reviews',\n MyReview = 'My review',\n MyComments = 'My comments',\n}\n\nexport enum RatingsFilterOptions {\n AllRatings = 'All ratings',\n OneStar = '1 star',\n TwoStars = '2 stars',\n ThreeStars = '3 stars',\n FourStars = '4 stars',\n FiveStars = '5 stars',\n}\n\nexport enum SortByOptions {\n MostRecent = 'Most recent',\n HighToLow = 'High to low',\n LowToHigh = 'Low to high',\n MostHelpful = 'Most Helpful',\n}\n\nexport const isUserCommentsExist = (userCommentedReviewIds: string[]): boolean => userCommentedReviewIds.length > 0;\n\nexport function getFilterBySourceCategories(\n context: ILocContext,\n externalReviewsExist: boolean,\n userReviewExists: boolean,\n internalReviewsExist: boolean,\n userCommentsExist = false,\n showMyCommentsFilter = false\n): IDropdownOption[] {\n const filterBySourceCategories: IDropdownOption[] = [\n {\n text: context.loc('AppReviewCollection_sourceFilter_AllReviews', 'All reviews'),\n key: SourceFilterOptions.AllReviews,\n },\n ];\n\n filterBySourceCategories.push({\n text: context.loc('AppReviewCollection_sourceFilter_MarketplaceReviews', 'Marketplace reviews'),\n key: SourceFilterOptions.MarketplaceReviews,\n disabled: !internalReviewsExist,\n });\n\n filterBySourceCategories.push({\n text: context.loc('AppReviewCollection_sourceFilter_G2Reviews', 'G2 reviews'),\n key: SourceFilterOptions.G2Reviews,\n disabled: !externalReviewsExist,\n });\n\n filterBySourceCategories.push({\n text: context.loc('AppReviewCollection_sourceFilter_MyReview', 'My review'),\n key: SourceFilterOptions.MyReview,\n disabled: !userReviewExists,\n });\n\n if (showMyCommentsFilter) {\n filterBySourceCategories.push({\n text: context.loc('AppReviewCollection_sourceFilter_MyComments', 'My comments'),\n key: SourceFilterOptions.MyComments,\n disabled: !userCommentsExist,\n });\n }\n\n return filterBySourceCategories;\n}\n\nexport function getSourceFilterOptionByLocaliztionText({\n text,\n context,\n userCommentsExist = false,\n showMyCommentsFilter = false,\n}: {\n text: string;\n context: ILocContext;\n userCommentsExist: boolean;\n showMyCommentsFilter: boolean;\n}): IDropdownOption {\n const allSourceOptions: IDropdownOption[] = getFilterBySourceCategories(\n context,\n true,\n true,\n true,\n userCommentsExist,\n showMyCommentsFilter\n );\n const optionsByText: IDropdownOption = allSourceOptions.find((option) => option.text === text);\n return optionsByText || allSourceOptions[0];\n}\n\nexport function getSortByCategories(context: ILocContext): IDropdownOption[] {\n const sortByCategories: IDropdownOption[] = [\n { text: context.loc('AppReviewCollection_sortReviews_MostRecent', 'Most recent'), key: SortByOptions.MostRecent },\n { text: context.loc('AppReviewCollection_sortReviews_ByRatingHighToLow', 'Highest rating'), key: SortByOptions.HighToLow },\n { text: context.loc('AppReviewCollection_sortReviews_ByRatingLowToHigh', 'Lowest rating'), key: SortByOptions.LowToHigh },\n { text: context.loc('AppReviewCollection_sortReviews_MostHelpful', 'Most helpful'), key: SortByOptions.MostHelpful },\n ];\n return sortByCategories;\n}\n\nexport function getSortByOptionByLocaliztionText(text: string, context: ILocContext): IDropdownOption {\n const allSortByOptions: IDropdownOption[] = getSortByCategories(context);\n const optionsByText: IDropdownOption = allSortByOptions.find((option) => option.text === text);\n return optionsByText || allSortByOptions[0];\n}\n\nexport function getRatingsFilterNumber(option: IDropdownOption): number {\n switch (option.key) {\n case RatingsFilterOptions.OneStar:\n return 1;\n\n case RatingsFilterOptions.TwoStars:\n return 2;\n\n case RatingsFilterOptions.ThreeStars:\n return 3;\n\n case RatingsFilterOptions.FourStars:\n return 4;\n\n case RatingsFilterOptions.FiveStars:\n return 5;\n\n default:\n return null;\n }\n}\n\nexport function getRatingsFilterOptionByRating(rating: number, context: ILocContext): IDropdownOption {\n switch (rating) {\n case 1:\n return {\n text: context.loc('AppReviewCollection_ratingsFilter_OneStar', '1 star'),\n key: RatingsFilterOptions.OneStar,\n };\n\n case 2:\n return {\n text: context.loc('AppReviewCollection_ratingsFilter_TwoStars', '2 stars'),\n key: RatingsFilterOptions.TwoStars,\n };\n\n case 3:\n return {\n text: context.loc('AppReviewCollection_ratingsFilter_ThreeStars', '3 stars'),\n key: RatingsFilterOptions.ThreeStars,\n };\n\n case 4:\n return { text: context.loc('AppReviewCollection_ratingsFilter_FourStars', '4 stars'), key: RatingsFilterOptions.FourStars };\n\n case 5:\n return { text: context.loc('AppReviewCollection_ratingsFilter_FiveStars', '5 stars'), key: RatingsFilterOptions.FiveStars };\n\n default:\n return {\n text: context.loc('AppReviewCollection_ratingsFilter_AllRatings', 'All ratings'),\n key: RatingsFilterOptions.AllRatings,\n };\n }\n}\n\nexport function getRatingsFilterCategories(context: ILocContext): IDropdownOption[] {\n const ratingsFilterCategories: IDropdownOption[] = [\n {\n text: context.loc('AppReviewCollection_ratingsFilter_AllRatings', 'All ratings'),\n key: RatingsFilterOptions.AllRatings,\n },\n {\n text: context.loc('AppReviewCollection_ratingsFilter_OneStar', '1 star'),\n key: RatingsFilterOptions.OneStar,\n },\n {\n text: context.loc('AppReviewCollection_ratingsFilter_TwoStars', '2 stars'),\n key: RatingsFilterOptions.TwoStars,\n },\n {\n text: context.loc('AppReviewCollection_ratingsFilter_ThreeStars', '3 stars'),\n key: RatingsFilterOptions.ThreeStars,\n },\n { text: context.loc('AppReviewCollection_ratingsFilter_FourStars', '4 stars'), key: RatingsFilterOptions.FourStars },\n { text: context.loc('AppReviewCollection_ratingsFilter_FiveStars', '5 stars'), key: RatingsFilterOptions.FiveStars },\n ];\n return ratingsFilterCategories;\n}\n\nexport function handleRatingsFilter(\n option: IDropdownOption,\n isDropDownChanged: boolean,\n originalReviewList: IAppReview[]\n): IAppReview[] {\n let filteredArray: IAppReview[] = [];\n const updatedRatingsFilter: number = getRatingsFilterNumber(option);\n\n if (isDropDownChanged) {\n filteredArray = updatedRatingsFilter\n ? originalReviewList.filter((review: IAppReview) => review.rating === updatedRatingsFilter)\n : originalReviewList;\n\n const telemetryDetails = {\n ratingsFilter: updatedRatingsFilter,\n };\n\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.FilterPDPReviews, telemetryDetails);\n logger.info(JSON.stringify(telemetryDetails), {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.FilterPDPReviews,\n });\n }\n\n return filteredArray;\n}\n\nexport function filterBySource(\n option: IDropdownOption,\n reviewsList: IAppReview[],\n userReview: IAppReview,\n userCommentedReviewIds: string[]\n) {\n switch (option.key) {\n case SourceFilterOptions.MarketplaceReviews:\n return reviewsList.filter((review) => Constants.InternalStoreFronts.indexOf(review.source) > -1);\n case SourceFilterOptions.G2Reviews:\n return reviewsList.filter((review) => review.source === Constants.StorefrontName.G2);\n case SourceFilterOptions.MyReview:\n reviewsList = userReview ? [userReview] : [];\n return reviewsList;\n case SourceFilterOptions.MyComments:\n return reviewsList.filter((review) => userCommentedReviewIds.indexOf(review.id) > -1);\n default:\n return reviewsList;\n }\n}\n\nexport function handleSourceFilter(\n option: IDropdownOption,\n isDropDownChanged: boolean,\n reviewsList: IAppReview[],\n userReview: IAppReview,\n userCommentedReviewIds: string[]\n): IAppReview[] {\n let filteredArray: IAppReview[] = [];\n\n if (isDropDownChanged) {\n filteredArray = filterBySource(option, reviewsList, userReview, userCommentedReviewIds);\n\n const telemetryDetails = {\n reviewsSourceFilter: option.text,\n };\n\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.FilterPDPReviews, telemetryDetails);\n logger.info(JSON.stringify(telemetryDetails), {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.FilterPDPReviews,\n });\n }\n\n return filteredArray;\n}\n\nexport function getSortByStatus(\n curReviewsList: IAppReview[],\n option: IDropdownOption,\n isDropDownChanged: boolean,\n curSortBy: string,\n context: ILocContext\n) {\n const sortByHelper: ISortByHelper = { sortedReviewsList: curReviewsList, updatedSortBy: curSortBy };\n switch (option.key) {\n case SortByOptions.HighToLow:\n sortByHelper.updatedSortBy = context.loc('AppReviewCollection_sortReviews_ByRatingHighToLow');\n if (isDropDownChanged) {\n sortByHelper.sortedReviewsList = sortByHelper.sortedReviewsList.sort((first, second) => second.rating - first.rating);\n }\n break;\n\n case SortByOptions.LowToHigh:\n sortByHelper.updatedSortBy = context.loc('AppReviewCollection_sortReviews_ByRatingLowToHigh');\n if (isDropDownChanged) {\n sortByHelper.sortedReviewsList = sortByHelper.sortedReviewsList.sort((first, second) => first.rating - second.rating);\n }\n break;\n case SortByOptions.MostHelpful:\n sortByHelper.updatedSortBy = context.loc('AppReviewCollection_sortReviews_MostHelpful');\n if (isDropDownChanged) {\n sortByHelper.sortedReviewsList = sortByHelper.sortedReviewsList.sort(\n (first, second) => second.mark_as_helpful_count - first.mark_as_helpful_count\n );\n }\n break;\n\n default:\n sortByHelper.updatedSortBy = context.loc('AppReviewCollection_sortReviews_MostRecent');\n if (isDropDownChanged) {\n sortByHelper.sortedReviewsList = sortByHelper.sortedReviewsList.sort(\n (first, second) => new Date(second.updated_at).valueOf() - new Date(first.updated_at).valueOf()\n );\n }\n break;\n }\n return sortByHelper;\n}\n\nexport function handleSortBy(\n option: IDropdownOption,\n updatedReviewsList: IAppReview[],\n curReviewsList: IAppReview[],\n isDropDownChanged: boolean,\n sortBy: string,\n context: ILocContext,\n updateReviewsSortByState: (updatedSortBy: string) => void\n): IAppReview[] {\n const sortByHelper: ISortByHelper = getSortByStatus(updatedReviewsList, option, isDropDownChanged, sortBy, context);\n\n if (curReviewsList && isDropDownChanged) {\n updateReviewsSortByState(sortByHelper.updatedSortBy);\n\n const telemetryDetails = {\n sortBy: sortByHelper.updatedSortBy,\n };\n\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.SortPDPReviews, telemetryDetails);\n logger.info(JSON.stringify(telemetryDetails), {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.SortPDPReviews,\n });\n }\n\n return sortByHelper.sortedReviewsList;\n}\n\nexport function handleDropDowns(\n sourceFilterOption: IDropdownOption,\n ratingFilterOption: IDropdownOption,\n sortByOption: IDropdownOption,\n updateReviewsState: IUpdateReviewsStateByDropDowns,\n originalReviewList: IAppReview[],\n curReviewsList: IAppReview[],\n sourceFilter: string,\n sortBy: string,\n ratingFilter: number,\n context: ILocContext,\n userReview: IAppReview,\n userCommentedReviewIds: string[] = []\n) {\n const updatedRatingsFilter = getRatingsFilterNumber(ratingFilterOption);\n const isDropDownChanged: boolean =\n ratingFilter !== updatedRatingsFilter || sortBy !== sortByOption.text || sourceFilterOption.text !== sourceFilter;\n let filteredArray: IAppReview[] = handleRatingsFilter(ratingFilterOption, isDropDownChanged, originalReviewList);\n filteredArray = handleSourceFilter(sourceFilterOption, isDropDownChanged, filteredArray, userReview, userCommentedReviewIds);\n const fliteredAndSortedArray = handleSortBy(\n sortByOption,\n filteredArray,\n curReviewsList,\n isDropDownChanged,\n sortBy,\n context,\n updateReviewsState.updateStateBySortFilter\n );\n\n if (isDropDownChanged) {\n updateReviewsState.updateStateByFilters(updatedRatingsFilter, fliteredAndSortedArray, sourceFilterOption.text);\n }\n\n // Go to first page and update pagination if the reviews list was changed\n if (fliteredAndSortedArray.length) {\n const updatedCurrentEnd =\n Constants.MaxItemsPerPageInReviews < filteredArray.length ? Constants.MaxItemsPerPageInReviews : filteredArray.length;\n updateReviewsState.updatePaginationState(1, 1, updatedCurrentEnd);\n }\n}\n\nexport function renderSourceFilterDropDown({\n curSortBy,\n curRatingFilter,\n context,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSourceFilter,\n externalReviewsExist,\n userReview,\n internalReviewsExist,\n userCommentedReviewIds,\n showMyCommentsFilter,\n}: IReviewDropDowns) {\n const sourceFilterCategories: IDropdownOption[] = getFilterBySourceCategories(\n context,\n externalReviewsExist,\n !!userReview,\n internalReviewsExist,\n isUserCommentsExist(userCommentedReviewIds),\n showMyCommentsFilter\n );\n let activeFilterOption: IDropdownOption = sourceFilterCategories.find((option) => option.text === curSourceFilter);\n activeFilterOption = activeFilterOption ?? sourceFilterCategories[0];\n return (\n {\n const sortBy: IDropdownOption = getSortByOptionByLocaliztionText(curSortBy, context);\n const ratingFilter: IDropdownOption = getRatingsFilterOptionByRating(curRatingFilter, context);\n handleDropDowns(\n option,\n ratingFilter,\n sortBy,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSourceFilter,\n curSortBy,\n curRatingFilter,\n context,\n userReview,\n userCommentedReviewIds\n );\n }}\n />\n );\n}\n\nexport function renderRatingFilterDropDown(\n curSortBy: string,\n curSourceFilter: string,\n updateReviewsState: IUpdateReviewsStateByDropDowns,\n originalReviewList: IAppReview[],\n curReviewsList: IAppReview[],\n curRatingFilter: number,\n context: ILocContext & ILocParamsContext & ICommonContext,\n userReview: IAppReview,\n userCommentedReviewIds: string[],\n showMyCommentsFilter: boolean\n) {\n const ratingsFilterCategories = getRatingsFilterCategories(context);\n const curRatingFilterOption: IDropdownOption = getRatingsFilterOptionByRating(curRatingFilter, context);\n let activeFilterOption: IDropdownOption = ratingsFilterCategories.find((option) => option.text === curRatingFilterOption.text);\n activeFilterOption = activeFilterOption ?? ratingsFilterCategories[0];\n return (\n {\n const sortBy: IDropdownOption = getSortByOptionByLocaliztionText(curSortBy, context);\n const sourceFilter: IDropdownOption = getSourceFilterOptionByLocaliztionText({\n text: curSourceFilter,\n context: context,\n userCommentsExist: isUserCommentsExist(userCommentedReviewIds),\n showMyCommentsFilter: showMyCommentsFilter,\n });\n handleDropDowns(\n sourceFilter,\n option,\n sortBy,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSourceFilter,\n curSortBy,\n curRatingFilter,\n context,\n userReview,\n userCommentedReviewIds\n );\n }}\n />\n );\n}\n\nexport function renderSortByDropDown({\n curRatingFilter,\n context,\n curSourceFilter,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSortBy,\n userReview,\n userCommentedReviewIds,\n showMyCommentsFilter,\n}: {\n curRatingFilter: number;\n context: ILocContext & ILocParamsContext & ICommonContext;\n curSourceFilter: string;\n updateReviewsState: IUpdateReviewsStateByDropDowns;\n originalReviewList: IAppReview[];\n curReviewsList: IAppReview[];\n curSortBy: string;\n userReview: IAppReview;\n userCommentedReviewIds: string[];\n showMyCommentsFilter: boolean;\n}) {\n const sortByCategories = getSortByCategories(context);\n let activeFilterOption: IDropdownOption = sortByCategories.find((option) => option.text === curSortBy);\n activeFilterOption = activeFilterOption ?? sortByCategories[0];\n return (\n {\n const ratingFilter: IDropdownOption = getRatingsFilterOptionByRating(curRatingFilter, context);\n const sourceFilter: IDropdownOption = getSourceFilterOptionByLocaliztionText({\n text: curSourceFilter,\n context: context,\n userCommentsExist: isUserCommentsExist(userCommentedReviewIds),\n showMyCommentsFilter: showMyCommentsFilter,\n });\n handleDropDowns(\n sourceFilter,\n ratingFilter,\n option,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSourceFilter,\n curSortBy,\n curRatingFilter,\n context,\n userReview,\n userCommentedReviewIds\n );\n }}\n />\n );\n}\n\nexport const ReviewsDropDowns = ({\n curSourceFilter = null,\n curSortBy = null,\n curRatingFilter = null,\n context = null,\n updateReviewsState = null,\n originalReviewList = [],\n curReviewsList = [],\n externalReviewsExist = false,\n userReview = null,\n internalReviewsExist = false,\n userCommentedReviewIds = [],\n showMyCommentsFilter = false,\n}: IReviewDropDowns) => {\n return (\n
\n
\n
\n {renderSourceFilterDropDown({\n curSortBy,\n curRatingFilter,\n context,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSourceFilter,\n externalReviewsExist,\n internalReviewsExist,\n userReview,\n userCommentedReviewIds,\n showMyCommentsFilter,\n })}\n
\n
\n
\n {renderRatingFilterDropDown(\n curSortBy,\n curSourceFilter,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curRatingFilter,\n context,\n userReview,\n userCommentedReviewIds,\n showMyCommentsFilter\n )}\n
\n
\n
\n {context.loc('AppReviewCollection_sortReviews_TitleText')}\n
\n {renderSortByDropDown({\n curRatingFilter,\n context,\n curSourceFilter,\n updateReviewsState,\n originalReviewList,\n curReviewsList,\n curSortBy,\n userReview,\n userCommentedReviewIds,\n showMyCommentsFilter,\n })}\n
\n
\n );\n};\n","import * as React from 'react';\nimport { PrimaryButton, Stack } from '@fluentui/react';\nimport noReviewsImg from '@shared/images/noReviewsImg.svg';\nimport { Constants } from '@shared/utils/constants';\nimport { ILocContext } from '@shared/interfaces/context';\n\nexport interface INoReviewsToShow {\n context: ILocContext;\n isOfferPurchased: boolean;\n openReviewModal: () => void;\n}\n\nexport const getDescriptionText = (context: ILocContext, isOfferPurchased: boolean): string => {\n if (isOfferPurchased) {\n return context.loc(\n 'NoReviewsToShow_addReviewDescription_WriteReviewText',\n \"This offer doesn't have any reviews yet. \\nBe the first to add a review.\"\n );\n }\n\n return context.loc(\n 'NoReviewsToShow_addReviewDescription_PurchaseNeededText',\n \"This offer doesn't have any reviews yet. \\nTo add the first review, get the offer and write your review.\"\n );\n};\n\nexport const NoReviewsToShow = ({ context, isOfferPurchased = false, openReviewModal }: INoReviewsToShow) => {\n return (\n \n \n \n \n \n {context.loc('NoReviewsToShow_addReviewTitle_Text', 'No reviews yet')}\n \n \n {getDescriptionText(context, isOfferPurchased)}\n \n \n openReviewModal()}\n data-bi-id={Constants.JsllCTAId.WriteReview}\n data-bi-area=\"Review\"\n disabled={!isOfferPurchased}\n />\n \n \n );\n};\n","import * as React from 'react';\nimport { Link, Image, Text, mergeStyleSets, Stack } from '@fluentui/react';\nimport { getEmptyRatingSummary } from './ratingSummary';\nimport redirectLogo from '@shared/images/Redirect.svg';\nimport { launchTelemetry } from '@shared/utils/reviewsUtils';\nimport { Constants } from '@shared/utils/constants';\nimport { logger } from '@src/logger';\nimport { IRatingSummary } from '@shared/Models';\nimport * as PropTypes from 'prop-types';\nimport { ILocContext } from '@shared/interfaces/context';\n\nexport function getExternalProductLink(productName: string): string {\n return productName ? `https://www.g2.com/products/${productName}/reviews` : '';\n}\n\ninterface IExternalRatingSummaryProps {\n ratingSummary: IRatingSummary;\n image: string;\n showImage?: boolean;\n source: string;\n}\nconst contentStyles = mergeStyleSets({\n externalRatingSummariesTitle: {\n fontSize: 16,\n lineHeight: 22,\n fontWeight: 600,\n },\n externalRatingSourceImage: {\n width: 16,\n height: 16,\n marginTop: 3,\n marginRight: 10,\n },\n externalSummaryUrlImage: {\n height: 13,\n width: 13,\n marginBottom: 1,\n },\n});\n\nexport const ExternalRatingSummary = (\n { ratingSummary = getEmptyRatingSummary(), image = '', showImage = true, source }: IExternalRatingSummaryProps,\n context: ILocContext\n) => {\n ratingSummary = ratingSummary || getEmptyRatingSummary();\n return (\n
\n {showImage && (\n
\n \n
\n )}\n \n \n {context.loc(`ExternalRatingSummaries_${source}_Title`, `${source} ratings`)}\n \n
\n {\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.ExternalRatingSummaryLink, {\n source: ratingSummary.Source,\n });\n logger.info(\n JSON.stringify({\n source: ratingSummary.Source,\n }),\n {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.ExternalRatingSummaryLink,\n }\n );\n }}\n >\n \n \n
\n
\n
\n );\n};\n\n(ExternalRatingSummary as any).contextTypes = {\n loc: PropTypes.func,\n};\n","import * as React from 'react';\nimport { Label, Stack, Text, mergeStyleSets, Image } from '@fluentui/react';\nimport { ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { IRatingSummary } from '@shared/Models';\nimport { ExternalRatingSummary } from './externalRatingSummary';\nimport ratingStar from '@shared/images/RatingStarFilled.svg';\nimport { getTotalExternalRatingSummariesInfo } from '@shared/utils/reviewsUtils';\nimport { IAllExternalRatingSummaries } from '@shared/interfaces/reviews/rating';\nimport { IExternalRatingSummary } from '@shared/interfaces/reviews/external/rating';\nimport * as PropTypes from 'prop-types';\n\nconst contentStyles = mergeStyleSets({\n ratingDisplayWrapper: { marginTop: 16 },\n externalRatingSummariesAverage: [\n 'externalRatingSummariesAverage',\n {\n '& .externalRatingSummariesNumOfStars': { padding: 0 },\n },\n ],\n});\n\nexport interface IExternalRatingSummariesProps {\n externalRatingSummaries: IExternalRatingSummary[];\n storefrontName?: string;\n}\n\nexport const ExternalRatingSummaries = (\n { externalRatingSummaries = [] }: IExternalRatingSummariesProps,\n context: ILocContext & ICommonContext & ILocParamsContext\n) => {\n const rawRatingSummaries: IRatingSummary[] = externalRatingSummaries?.map(\n (externalRatingSummary: IExternalRatingSummary) => externalRatingSummary.ratingSummary\n );\n const externalSummariesInfo: IAllExternalRatingSummaries = getTotalExternalRatingSummariesInfo(rawRatingSummaries);\n\n return (\n
\n \n {externalRatingSummaries?.map((externalRatingSummary: IExternalRatingSummary) => (\n <>\n \n \n ))}\n \n
\n {externalRatingSummaries && (\n \n \n \n \n \n ({context.locParams('Global_Loc_NumOfRatingsLower', [externalSummariesInfo.totalRatings.toString()])})\n \n \n \n )}\n
\n
\n );\n};\n\n(ExternalRatingSummaries as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import {\n ExternalRatingSummaries as ExternalRatingSummariesComponent,\n IExternalRatingSummariesProps,\n} from '@shared/components/externalRatingSummaries';\nimport { IState } from '../../State';\nimport { connect } from 'react-redux';\n\nexport const mapStateToProps = (state: IState, ownProps: IExternalRatingSummariesProps) => {\n return {\n ...ownProps,\n storefrontName: state.config.storefrontName,\n };\n};\n\nexport const ExternalRatingSummaries = connect(mapStateToProps)(ExternalRatingSummariesComponent);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { SafeHtmlWrapper } from '@shared/components/safeHtmlWrapper';\nimport { IAppDataItem, ITermsLink } from '@shared/Models';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\n\nexport interface IUserTermsProps {\n app: IAppDataItem;\n}\n\nconst getTermsLink = (app: IAppDataItem) => {\n const { UsesEnterpriseContract = false, licenseTermsUrl = '' } = app;\n let terms: ITermsLink;\n if (UsesEnterpriseContract) {\n terms = {\n link: Constants.enterpriseContractLink,\n locKey: 'ECA_Link',\n };\n } else {\n terms = {\n link: licenseTermsUrl,\n locKey: 'TRY_Terms2',\n };\n }\n return terms;\n};\n\nconst generateLink = (text: string, link: string): string => {\n return `${text}`;\n};\n\nexport const UserTerms: React.FunctionComponent = (\n { app }: IUserTermsProps,\n context: ILocContext & ILocParamsContext\n) => {\n const terms = getTermsLink(app);\n const { detailInformation } = app;\n const privacyPolicyUrl = detailInformation?.PrivacyPolicyUrl;\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const termsOfUse = generateLink(context.loc('Provider_Terms', 'terms of use'), terms.link);\n const privacyPolicy = generateLink(context.loc('TRY_PrivacyPolicy', 'privacy policy'), privacyPolicyUrl);\n const termsLink = generateLink(context.loc('TRY_Terms', 'terms'), Constants.MicrosoftTermsURL);\n const privacyLink = generateLink(context.loc('TRY_Privacy', 'privacy'), Constants.MicrosoftPrivacyStatementURL);\n const userTermsFallback = `By getting this product, I give Microsoft permission to use or share my account\n information so that the provider can contact me regarding this product and related products.\n I agree to the provider's ${termsOfUse} and ${privacyPolicy} and understand that the rights to use this product do not come from Microsoft, unless Microsoft is the provider.\n Use of AppSource is governed by separate ${termsLink} and ${privacyLink}.`;\n\n return (\n \n );\n};\n\n(UserTerms as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from '@shared/components/spzaComponent';\nimport { IAppReview, IAppDataItem } from '@shared/Models';\nimport { AppReviewItem } from '@shared/components/appReviewItem';\nimport { Constants } from '@shared/utils/constants';\nimport { ILocContext, ICommonContext, ILocParamsContext, IComponentsInfoContext } from '@shared/interfaces/context';\nimport { IReviewsState, IUserDataState } from '@src/State';\nimport { RatingSummary } from '@shared/components/ratingSummary';\nimport {\n createDeepLinkPagination,\n createDefaultPagination,\n getAddReviewSectionDescription,\n getReviewCtaButtonText,\n IUpdateReviewsStateByDropDowns,\n launchTelemetry,\n IPaginationIndexes,\n getExternalRatingSummaries,\n findReviewsSources,\n getRatingSummaryByType,\n} from '@shared/utils/reviewsUtils';\nimport { ReviewsDropDowns } from '@shared/components/reviewsDropDowns';\nimport { DefaultButton, mergeStyleSets, Spinner, Stack, ScreenWidthMinXLarge, ScreenWidthMinXXLarge } from '@fluentui/react';\nimport type { IStackTokens } from '@fluentui/react';\nimport { NoReviewsToShow } from '@shared/components/noReviewsToShow';\nimport { getWindow } from '@shared/services/window';\nimport { logger } from '@src/logger';\nimport { isSignedInChanged } from '@shared/utils/appUtils';\nimport {\n IAppEntityMarkAsHelpfulBaseThunkActionParams,\n IAppEntitySetReviewMarkAsHelpfulParams,\n IAppReviewMarkedAsHelpfulState,\n} from '@shared/interfaces/reviews/markAsHelpful';\nimport { NeutralColors, CommunicationColors } from '@fluentui/theme';\nimport { IExternalRatingSummary } from '@shared/interfaces/reviews/external/rating';\nimport { ExternalRatingSummaries } from '@shared/containers/externalRatingSummaries';\nimport { shouldShowUserTerms } from '@shared/utils/detailUtils';\nimport { UserTerms } from '@appsource/components/userTerms';\nimport classNames from 'classnames';\n\nexport interface IAppReviewCollectionState {\n reviewList?: IAppReview[];\n originalReviewList?: IAppReview[];\n currentStart: number;\n currentEnd: number;\n currentPageNumber: number;\n sourceFilter?: string;\n ratingsFilter?: number;\n sortBy?: string;\n scrollToReview: boolean;\n scrollToReviewItemRef: React.RefObject;\n}\n\nexport interface IAppReviewCollectionProps {\n reviewsData: IReviewsState;\n appId: string;\n locale: string;\n appData: IAppDataItem;\n location: {\n query: {\n [key: string]: string;\n };\n };\n isEmbed: boolean;\n reviewId: string;\n openRatingModal: (app: IAppDataItem, accessKey: string, ctaType: string, callback: () => void) => void;\n ensureAppReviewsData(entityId: string, forceUpdate?: boolean): Promise;\n userInfo: IUserDataState;\n toggleMarkAsHelpful: (args: IAppEntitySetReviewMarkAsHelpfulParams) => void;\n loadReviewIdsMarkedAsHelpful: (args: IAppEntityMarkAsHelpfulBaseThunkActionParams) => Promise;\n markedAsHelpful?: IAppReviewMarkedAsHelpfulState;\n openSignInModal: () => void;\n userCommentedReviewIds?: string[];\n loadUserProductReviewMetadata: (args: { legacyId: string }) => Promise;\n billingCountryCode: string;\n showMyCommentsFilter: boolean;\n}\n\nconst DEFAULT_STICKY_HEIGHT = 64;\nconst DEFAULT_MENU_HEIGHT = 54;\nconst SCROLL_TO_REVIEW_TOP_MARGIN = 16;\n\nconst contentStyles = mergeStyleSets({\n reviewsTopBar: [\n 'reviewsTopBar',\n {\n marginBottom: 24,\n },\n ],\n reviewsDateLabel: {\n color: NeutralColors.gray130,\n },\n reviewsDetailsTopBlockContainer: {\n display: 'flex',\n width: '100%',\n [`@media (min-width: ${ScreenWidthMinXLarge}px)`]: {\n flexDirection: 'row',\n },\n [`@media (max-width: ${ScreenWidthMinXLarge - 1}px)`]: {\n flexDirection: 'column-reverse',\n },\n },\n reviewsTabTop: {\n paddingTop: 24,\n [`@media (max-width: ${ScreenWidthMinXLarge - 1}px)`]: {\n paddingTop: 0,\n },\n },\n reviewsContainer: {\n display: 'flex',\n flexWrap: 'wrap',\n },\n userTermsContainer: {\n backgroundColor: NeutralColors.white,\n border: '1px solid #EAEAEA',\n borderRadius: '2px',\n height: 'fit-content',\n [`@media (max-width: ${ScreenWidthMinXLarge - 1}px)`]: {\n margin: '36 0 24 0',\n },\n [`@media (min-width: ${ScreenWidthMinXLarge - 1}px)`]: {\n marginBottom: 28,\n },\n [`@media (min-width: ${ScreenWidthMinXLarge}px) and (max-width: ${ScreenWidthMinXXLarge - 1}px)`]: {\n marginLeft: 40,\n minWidth: 200,\n maxWidth: 200,\n },\n [`@media (min-width: ${ScreenWidthMinXXLarge}px)`]: {\n marginLeft: 32,\n minWidth: 256,\n maxWidth: 256,\n },\n '& a:link': {\n color: CommunicationColors.primary,\n textDecoration: 'underline',\n },\n '& a:visited': {\n color: CommunicationColors.primary,\n },\n '& a:hover': {\n color: CommunicationColors.shade20,\n },\n '& a:active': {\n color: CommunicationColors.shade30,\n },\n },\n});\n\nconst consentDisclaimerTokens: IStackTokens = { padding: 16 };\n\n// Displays a collection of AppReviews\nexport class AppReviewCollection extends SpzaComponent {\n context: ILocContext & ILocParamsContext & ICommonContext & IComponentsInfoContext;\n\n constructor(props: IAppReviewCollectionProps, state?: IAppReviewCollectionState) {\n super(props);\n this.state = state || {\n currentStart: 1,\n currentEnd: 1,\n currentPageNumber: 1,\n scrollToReview: false,\n scrollToReviewItemRef: null,\n };\n }\n\n componentDidMount() {\n const { ensureAppReviewsData, appId, userInfo } = this.props;\n ensureAppReviewsData(appId).then(() => this.loadReviewsToState(true));\n if (userInfo.signedIn) {\n this.loadReviewIdsMarkedAsHelpful();\n this.loadUserProductReviewMetadata();\n }\n }\n\n componentDidUpdate(prevProps: IAppReviewCollectionProps) {\n const { ensureAppReviewsData, appId } = this.props;\n if (prevProps.reviewsData !== this.props.reviewsData) {\n this.loadReviewsToState(false);\n }\n if (isSignedInChanged(this.props.userInfo.signedIn, prevProps.userInfo.signedIn)) {\n ensureAppReviewsData(appId);\n this.loadReviewIdsMarkedAsHelpful();\n this.loadUserProductReviewMetadata();\n }\n this.scrollToReviewIfNeeded();\n }\n\n loadReviewIdsMarkedAsHelpful() {\n const { loadReviewIdsMarkedAsHelpful, appId: offerId } = this.props;\n loadReviewIdsMarkedAsHelpful({ offerId });\n }\n\n loadUserProductReviewMetadata() {\n const { loadUserProductReviewMetadata, appId: legacyId } = this.props;\n loadUserProductReviewMetadata({ legacyId });\n }\n\n loadReviewsToState(deepLinkMode: boolean): IPaginationIndexes {\n const reviews: IAppReview[] = this.props.reviewsData.reviews;\n // If there are no reviews we get an empty string, we just need to convert it into an array.\n let returnedReviews: IAppReview[] = [];\n if (reviews && Array.isArray(returnedReviews)) {\n // Filter out any reviews that lack both a title and a description\n returnedReviews = reviews.filter((review) => review.content || review.title);\n }\n\n let pagination: IPaginationIndexes = createDefaultPagination(returnedReviews);\n let scrollToReview = false;\n\n // Deep link into review. Select the right page and \"trigger\" scroll action.\n if (deepLinkMode && this.props.reviewId) {\n pagination = createDeepLinkPagination(reviews, this.props.reviewId);\n scrollToReview = true;\n }\n\n this.setState({\n reviewList: returnedReviews,\n originalReviewList: returnedReviews,\n currentStart: pagination.startIndex,\n currentEnd: pagination.endIndex,\n currentPageNumber: pagination.pageIndex,\n scrollToReview: scrollToReview,\n });\n return pagination;\n }\n\n onReviewItemShown = (reviewId: string, reviewCompRef: React.RefObject) => {\n if (this.props.reviewId && reviewCompRef && this.props.reviewId === reviewId) {\n this.setState({ scrollToReviewItemRef: reviewCompRef });\n }\n };\n\n scrollToReviewIfNeeded = () => {\n if (\n this.state.scrollToReview &&\n this.state.scrollToReviewItemRef &&\n this.props.reviewId &&\n this.state.scrollToReviewItemRef.current\n ) {\n const elementPosition = this.state.scrollToReviewItemRef.current.getBoundingClientRect().top;\n const offsetPosition = elementPosition - this.getReviewsScrollOffset();\n getWindow().scrollTo({\n top: offsetPosition,\n behavior: 'smooth',\n });\n this.setState({ scrollToReview: false });\n }\n };\n\n getReviewsScrollOffset = (): number => {\n const stickyCardHeight = this.context.getAppDetailsStickyCardHeight();\n const mainMenuHeight = this.context.getAppHeaderHeight();\n const headerOffset =\n (stickyCardHeight || DEFAULT_STICKY_HEIGHT) + (mainMenuHeight || DEFAULT_MENU_HEIGHT) + SCROLL_TO_REVIEW_TOP_MARGIN;\n return headerOffset;\n };\n\n renderReviews(start: number, end: number, reviewList: IAppReview[]): JSX.Element[] {\n const { appId, locale, isEmbed, appData, userInfo, openSignInModal, reviewsData, markedAsHelpful } = this.props;\n const { userReviewIdsMarkedAsHelpful = [] } = markedAsHelpful;\n\n if (!reviewList) {\n return [\n
\n \n
,\n ];\n }\n\n if (reviewList && reviewList.length === 0) {\n return [\n ,\n ];\n }\n\n // This is to handle the last page. If there number of reviews is less than the end of page, then\n // we don't have to loop until end. We can stop at the point when there are no more reviews.\n\n const listEnd = reviewList.length < end ? reviewList.length : end;\n\n const appReviewItems: JSX.Element[] = [];\n\n let i = 0;\n // 'start' begins from 1.\n for (i = start - 1; i < listEnd; i++) {\n const review = reviewList[`${i}`];\n appReviewItems.push(\n this.toggleMarkAsHelpful({ review, isHelpful })}\n openSignInModal={openSignInModal}\n fallbackReviewSourceName={Constants.StorefrontName.AppSource}\n authorPersona\n dateLabelClassName={contentStyles.reviewsDateLabel}\n />\n );\n }\n\n return appReviewItems;\n }\n\n toggleMarkAsHelpful(args: { review: IAppReview; isHelpful: boolean }) {\n const { appId: offerId, toggleMarkAsHelpful } = this.props;\n toggleMarkAsHelpful({ ...args, offerId });\n }\n\n getReviewCounterText(): string {\n if (!this.state.reviewList || this.state.reviewList.length === 0) {\n return null;\n }\n\n return this.context.locParams(\n 'ReviewCollection_ReviewsCounterOfTotal',\n [\n this.state.currentStart?.toString() || '',\n this.state.currentEnd?.toString() || '',\n this.state.reviewList.length?.toString() || '',\n ],\n 'Showing {0}-{1} of {2} reviews'\n );\n }\n\n setCurrentPage(event: React.MouseEvent) {\n const idClicked = (event?.target as HTMLElement)?.id;\n if (!idClicked) {\n return;\n }\n\n // This indicates the current page which should be displayed\n let pageNumberClicked = 0;\n if (idClicked === 'previous') {\n pageNumberClicked = this.state.currentPageNumber - 1;\n } else if (idClicked === 'next') {\n pageNumberClicked = this.state.currentPageNumber + 1;\n } else {\n // if it was not prev/next, then we have a page number.\n pageNumberClicked = parseInt(idClicked, 10);\n }\n\n // Page number * number of items per page gives the total number of items displayed.\n // Since we want the total number of items displayed so far, we do -1.\n const startArrayIndex = (pageNumberClicked - 1) * Constants.MaxItemsPerPageInReviews;\n\n // startArrayIndex is the total number of items displayed so far. We don't want to display the\n // last item from the prev page. So currentPageStart should be a +1 from that.\n const currentPageStart = startArrayIndex + 1;\n let currentPageEnd = startArrayIndex + Constants.MaxItemsPerPageInReviews;\n\n // the number of reviews in the last page can be less then 10, in this case currentPageEnd should be the current total number of reviews\n currentPageEnd = currentPageEnd > this.state.reviewList.length ? this.state.reviewList.length : currentPageEnd;\n\n this.setState((prevState: IAppReviewCollectionState) => {\n return {\n reviewList: prevState.reviewList,\n currentStart: currentPageStart,\n currentEnd: currentPageEnd,\n currentPageNumber: pageNumberClicked,\n };\n });\n\n const telemetryDetails = {\n currentPageNumber: pageNumberClicked,\n previousPageNumber: this.state.currentPageNumber,\n currentReviewListStart: currentPageStart,\n currentReviewListEnd: currentPageEnd,\n reviewListLength: (this.state.reviewList && this.state.reviewList.length) || 0,\n };\n\n launchTelemetry(\n Constants.Telemetry.Action.Click,\n Constants.Telemetry.ActionModifier.ReviewPageNavigationButton,\n telemetryDetails\n );\n logger.info(JSON.stringify(telemetryDetails), {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.ReviewPageNavigationButton,\n });\n }\n\n getPaginationList() {\n // If the number of reviews is less than the items per page, then pagination div is not necessary.\n if (!this.state.reviewList || this.state.reviewList.length < Constants.MaxItemsPerPageInReviews) {\n return null;\n }\n\n const reviewListLength = this.state.reviewList.length;\n let numberOfPages = Math.floor(reviewListLength / Constants.MaxItemsPerPageInReviews);\n\n // The final page has lesser number of items than 'MaxItemsPerPageInReviews'\n // then we'll have a reminder set of reviews which should go into its own page.\n if (reviewListLength % Constants.MaxItemsPerPageInReviews > 0) {\n numberOfPages++;\n }\n\n let i = 0;\n const pageNumberList: JSX.Element[] = [];\n // Only for pages greater than 1 we would show 'previous'.\n if (this.state.currentPageNumber > 1) {\n pageNumberList.push(\n
  • \n this.setCurrentPage(event)}\n >\n {this.context.loc('PAGINATION_Previous')}\n \n
  • \n );\n }\n\n let pageNumberStart = 0;\n let pageNumberEnd = Math.min(numberOfPages, 9);\n\n if (this.state.currentPageNumber > 4 && numberOfPages >= 9) {\n // if there are more than 9 pages, then we want to show only 9 at a time.\n // 4 (or how many ever exists) after the current one - if exists. Otherwise upto the maximum number of pages.\n pageNumberEnd = Math.min(this.state.currentPageNumber + 4, numberOfPages);\n\n // pageNumberStart should be 4 pages before the current one.\n // pageNumberStart can never be negative for the following reasons.\n // This condition is executed only if number of pages is greater than 9.\n // Also min value of currentPageNumber > 4 is 5. pageNumberEnd can either be currentPageNumber + 4 (which is 9)\n // or number of pages (which, if it has entered this block is at least 9).\n pageNumberStart = pageNumberEnd - 9;\n }\n\n for (i = pageNumberStart; i < pageNumberEnd; i++) {\n const isActive = this.state.currentPageNumber === i + 1;\n\n pageNumberList.push(\n
  • \n this.setCurrentPage(event)}\n >\n {i + 1}\n \n
  • \n );\n }\n\n // If we are at the last page we shouldn't show the 'next' button.\n if (this.state.currentPageNumber < numberOfPages) {\n pageNumberList.push(\n
  • \n ) => this.setCurrentPage(event)}\n >\n {this.context.loc('PAGINATION_Next')}\n \n
  • \n );\n }\n\n return
      {pageNumberList}
    ;\n }\n\n openReviewModal = () => {\n let ctaType: string = null;\n if (this.props.location?.query?.ctatype) {\n ctaType = this.props.location.query.ctatype;\n }\n\n launchTelemetry(Constants.Telemetry.Action.Click, Constants.Telemetry.ActionModifier.OpenRatingModalFromReviewTab, {\n appId: this.props.appId,\n });\n logger.info(\n JSON.stringify({\n appId: this.props.appId,\n }),\n {\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.OpenRatingModalFromReviewTab,\n }\n );\n\n this.props.openRatingModal(this.props.appData, null, ctaType, () => {\n launchTelemetry(Constants.Telemetry.Action.Open, Constants.Telemetry.ActionModifier.OpenRatingModalFromReviewTab, {\n appId: this.props.appId,\n });\n logger.info(\n JSON.stringify({\n appId: this.props.appId,\n }),\n {\n action: Constants.Telemetry.Action.Open,\n actionModifier: Constants.Telemetry.ActionModifier.OpenRatingModalFromReviewTab,\n }\n );\n this.clearFiltersByState();\n });\n };\n\n updatePaginationState = (curPageNumber: number, curStart: number, currentEnd: number) => {\n this.setState({ currentPageNumber: curPageNumber, currentStart: curStart, currentEnd: currentEnd });\n };\n\n updateStateByFilters = (updatedRatingsFilter: number, updatedReviewList: IAppReview[], updatedSourceFilter: string) => {\n this.setState({ ratingsFilter: updatedRatingsFilter, reviewList: updatedReviewList, sourceFilter: updatedSourceFilter });\n };\n\n clearFiltersByState = () => {\n this.setState({ sourceFilter: null, ratingsFilter: null, sortBy: null });\n };\n\n updateStateBySortFilter = (updatedSortBy: string) => {\n this.setState({\n sortBy: updatedSortBy,\n });\n };\n\n renderReviewsDetailsTopBlock(externalRatingSummaries: IExternalRatingSummary[]) {\n const { appData, billingCountryCode } = this.props;\n const internalSummaries = getRatingSummaryByType(appData?.ratingSummaries, Constants.Reviews.ReviewsSource.Internal);\n const textButton = getReviewCtaButtonText(this.context, this.props.reviewsData.userReview);\n\n return (\n this.props.reviewsData.entityId === this.props.appId && (\n \n \n \n \n {externalRatingSummaries?.length > 0 && (\n \n )}\n
    \n
    {this.context.loc('AppReviewCollection_SubHeaderTitle_Text', 'Your review')}
    \n \n
    \n {getAddReviewSectionDescription(this.context, this.props.reviewsData.isPurchased)}\n
    \n
    \n
    \n {shouldShowUserTerms({ app: appData, billingCountryCode }) && (\n \n \n \n )}\n
    \n
    \n )\n );\n }\n\n renderImpl() {\n const externalRatingSummaries = getExternalRatingSummaries(this.props.appData?.ratingSummaries);\n\n const updateStateByFilters: IUpdateReviewsStateByDropDowns = {\n updatePaginationState: this.updatePaginationState,\n updateStateByFilters: this.updateStateByFilters,\n updateStateBySortFilter: this.updateStateBySortFilter,\n };\n\n const { userCommentedReviewIds, reviewsData, showMyCommentsFilter } = this.props;\n const { hasInternalReviews, hasExternalReviews } = findReviewsSources(reviewsData);\n\n return (\n \n {this.props.isEmbed ? null : this.renderReviewsDetailsTopBlock(externalRatingSummaries)}\n {this.props.reviewsData.entityId === this.props.appId && (\n
    \n
    \n
    {this.getReviewCounterText()}
    \n
    \n \n
    \n )}\n {this.renderReviews(this.state.currentStart, this.state.currentEnd, this.state.reviewList)}\n
    \n {this.state.reviewList && this.state.reviewList.length >= Constants.MaxItemsPerPageInReviews\n ? this.getReviewCounterText()\n : null}\n
    \n
    \n {this.getPaginationList()}\n
    \n
    \n );\n }\n}\n\n(AppReviewCollection as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n getAppDetailsStickyCardHeight: PropTypes.func,\n getAppHeaderHeight: PropTypes.func,\n};\n","import * as React from 'react';\nimport {\n AppReviewCollection as AppReviewCollectionComponent,\n IAppReviewCollectionProps,\n} from '@appsource/components/appReviewCollection';\nimport {\n ensureAppReviewsData,\n loadUserMarkedAsHelpfulReviews,\n setReviewMarkedAsHelpful,\n loadUserProductReviewMetadata,\n} from '@shared/actions/thunkActions';\nimport { createModalAction, createRatingAction } from '@shared/actions/actions';\nimport { IState } from '../../State';\nimport { Constants } from '@shared/utils/constants';\nimport { IAppDataItem } from '@shared/Models';\nimport {\n IAppEntityMarkAsHelpfulBaseThunkActionParams,\n IAppEntitySetReviewMarkAsHelpfulParams,\n} from '@shared/interfaces/reviews/markAsHelpful';\nimport { connect } from 'react-redux';\nimport { withRouter } from '@shared/routerHistory';\n\nexport const mapStateToProps = (state: IState, ownProps: IAppReviewCollectionProps) => {\n return {\n reviewsData: state.apps.appReviewsData,\n appId: ownProps.appId,\n locale: state.config.locale,\n appData: ownProps.appData,\n location: ownProps.location,\n reviewId: state.config.query?.reviewId,\n userInfo: state.users,\n markedAsHelpful: state.apps.appReviewsData.markedAsHelpful,\n userCommentedReviewIds: state.apps.appReviewsData.userCommentedReviewIds,\n billingCountryCode: state.config.billingCountryCode,\n showMyCommentsFilter: state.config.featureFlags.ReviewsMyCommentsFilter === 'true',\n };\n};\n\nconst mapDispatchToProps = (dispatch: any) => {\n return {\n ensureAppReviewsData: (entityId: string, forceUpdate = false) => dispatch(ensureAppReviewsData(entityId, forceUpdate)),\n openRatingModal: (app: IAppDataItem, accessKey: string, ctaType: string, callback: any) =>\n dispatch(\n createRatingAction({\n showModal: true,\n app: app,\n accessKey: accessKey,\n ctaType: ctaType,\n callback: callback,\n })\n ),\n openSignInModal: () =>\n dispatch(\n createModalAction({\n showModal: true,\n modalId: Constants.ModalType.SignIn,\n })\n ),\n toggleMarkAsHelpful: (args: IAppEntitySetReviewMarkAsHelpfulParams) => dispatch(setReviewMarkedAsHelpful(args)),\n loadReviewIdsMarkedAsHelpful: (args: IAppEntityMarkAsHelpfulBaseThunkActionParams) =>\n dispatch(loadUserMarkedAsHelpfulReviews(args)),\n loadUserProductReviewMetadata: (args: { legacyId: string }) => dispatch(loadUserProductReviewMetadata(args)),\n };\n};\n\nexport const AppReviewCollection = withRouter(\n connect(mapStateToProps, mapDispatchToProps)(AppReviewCollectionComponent)\n) as React.StatelessComponent;\n","export interface ICapabilities {\n [id: string]: string[];\n}\n\nexport const Capabilities: ICapabilities = {\n Capability1: ['CP_Office1', 'CP_Office2'],\n Capability2: ['CP_Office3', 'CP_Office2'],\n Capability3: ['CP_Office4', 'CP_Office2'],\n Capability4: [],\n Capability5: ['CP_Office2', 'CP_Office5'],\n Capability6: ['CP_Office2', 'CP_Office6'],\n Capability7: ['CP_Office2', 'CP_Office7'],\n Capability8: [],\n Capability9: [],\n Capability10: ['CP_Office2', 'CP_Office8'],\n Capability11: ['CP_Office9', 'CP_Office2'],\n Capability14: ['CP_Office10'],\n};\n","export interface ILoc {\n [loc: string]: string;\n}\n\nexport interface IAutoRun {\n [loc: string]: ILoc;\n}\n\nexport const AutoRuns: IAutoRun = {\n OnNewMessageCompose: { loc: 'AutoRun_OnNewMessageCompose', fallback: 'The user composes a new mail.' },\n OnNewAppointmentOrganizer: { loc: 'AutoRun_OnNewAppointmentOrganizer', fallback: 'The user creates a new event.' },\n OnMessageAttachmentsChanged: {\n loc: 'AutoRun_OnMessageAttachmentsChanged',\n fallback: 'The user adds or removes attachments from an email.',\n },\n OnAppointmentAttachmentsChanged: {\n loc: 'AutoRun_OnAppointmentAttachmentsChanged',\n fallback: 'The user adds or removes attachments from an event.',\n },\n OnMessageRecipientsChanged: {\n loc: 'AutoRun_OnMessageRecipientsChanged',\n fallback: 'The user adds or removes recipients from an email.',\n },\n OnAppointmentAttendeesChanged: {\n loc: 'AutoRun_OnAppointmentAttendeesChanged',\n fallback: 'The user adds or removes attendees from an event.',\n },\n OnAppointmentTimeChanged: {\n loc: 'AutoRun_OnAppointmentTimeChanged',\n fallback: 'The user changes the date or time of an event.',\n },\n OnAppointmentRecurrenceChanged: {\n loc: 'AutoRun_OnAppointmentRecurrenceChanged',\n fallback: 'The user changes the recurrence of an event.',\n },\n OnInfoBarDismissClicked: {\n loc: 'AutoRun_OnInfoBarDismissClicked',\n fallback: 'The user dismisses a notification while composing an email or creating an event.',\n },\n OnMessageSend: {\n loc: 'AutoRun_OnMessageSend',\n fallback: 'The user sends an email.',\n },\n OnAppointmentSend: {\n loc: 'AutoRun_OnAppointmentSend',\n fallback: 'The user sends an event invite.',\n },\n};\n","/* eslint-disable react/no-danger */\nimport * as React from 'react';\nimport { Text, mergeStyleSets } from '@fluentui/react';\nimport * as PropTypes from 'prop-types';\n\nimport { ServiceTile } from '@shared/containers/serviceTile';\n\nimport SpzaComponent from '@shared/components/spzaComponent';\nimport { IAppDataItem, IDemoVideos, IImages, IMedia, IMediaModalPayload, MediaType } from '@shared/Models';\nimport { ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\nimport { Capabilities } from '@shared/utils/capabilities';\nimport * as DetailUtils from '@shared/utils/detailUtils';\nimport Ribbon from '@shared/components/ribbon';\nimport { TelemetryImage } from '@shared/components/telemetryImage';\nimport { Service } from '@shared/serviceViewModel';\nimport { AppTile } from '@shared/containers/appTile';\nimport { IProductValue, DataMap } from '@shared/utils/dataMapping';\nimport { ExternalLink } from '@shared/components/externalLink';\nimport { bitMasksMatch } from '@shared/utils/datamappingHelpers';\nimport { useTelemetry } from '@shared/hooks/useTelemetry';\nimport videoOverlay from '@shared/images/videoOverlay.png';\nimport classnames from 'classnames';\nimport { AutoRuns } from '@shared/utils/autoruns';\nimport { getProdcutBitMaskInfo } from '@shared/utils/appUtils';\nimport { SafeHtmlWrapper } from '@shared/components/safeHtmlWrapper';\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst [{ pageAction }] = useTelemetry();\n\nexport interface IOverviewDispatchProps {\n openMediaModal?: (mediaModalPayload: IMediaModalPayload) => void;\n}\n\nexport interface IOverviewProps extends IOverviewDispatchProps {\n ownerType: DetailUtils.OwnerType;\n ownerId: string;\n ownerTitle: string;\n shortDescription: string;\n description: string;\n images: IImages[];\n videos: IDemoVideos[];\n capabilities: string | string[];\n autoRunLaunchEvents?: string[];\n TileType: typeof ServiceTile;\n crossListings: (IAppDataItem | Service)[];\n products?: IProductValue;\n isPowerBICertified?: boolean;\n isSaaSTransactableApp?: string;\n countryCode?: string;\n isOneTimePayment?: boolean;\n}\n\ninterface IOverviewState {\n failures: { [thumbnail: string]: boolean };\n}\n\nconst contentStyles = mergeStyleSets({\n ulAutorunStyle: {\n 'list-style': 'unset',\n 'padding-left': 40,\n 'margin-top': 8,\n },\n autorunTitleStyle: {\n 'margin-top': 8,\n },\n ilTextAutorunStyle: {\n 'font-size': 13,\n },\n});\n\nexport class Overview extends SpzaComponent {\n context: ILocContext & ILocParamsContext & ICommonContext;\n\n constructor(props: IOverviewProps, context: ILocContext & ILocParamsContext & ICommonContext) {\n super(props, context);\n\n this.state = { failures: {} };\n }\n\n getLocalizationKey = (tileType: typeof AppTile | typeof ServiceTile): string => {\n const { isOneTimePayment } = this.props;\n if (tileType === AppTile) {\n return isOneTimePayment ? 'AppDetails_RibbonHeader_TeamOneTimePayment' : 'AppDetails_RibbonHeader';\n }\n if (tileType === ServiceTile) {\n return 'ServiceDetails_RibbonHeader';\n }\n return ''; // Shouldn't reach this case.\n };\n\n getMedia(): IMedia[] {\n const images: IImages[] = this.props.images || [];\n const videos: IDemoVideos[] = this.props.videos || [];\n\n const mediaImages: IMedia[] = images.map(\n (image: IImages): IMedia => ({\n name: image.ImageName,\n link: image.ImageUri,\n thumbnail: image.ImageUri,\n type: MediaType.Image,\n })\n );\n\n const mediaVideos: IMedia[] = videos.map(\n (video: IDemoVideos): IMedia => ({\n name: video.VideoName,\n link: video.VideoLink,\n thumbnail: video.ThumbnailURL,\n type: MediaType.Video,\n })\n );\n\n return [...mediaVideos, ...mediaImages];\n }\n\n mediaRendering() {\n const mediaRender: JSX.Element[] = [];\n const list: IMedia[] = this.getMedia();\n\n if (!list.length) {\n return null;\n }\n\n for (let i = 0; i < list.length; i++) {\n const item: IMedia = list[`${i}`];\n\n if (!this.state.failures[item.thumbnail]) {\n const isVideo = item.type === MediaType.Video;\n\n mediaRender.push(\n {\n if (event.charCode === Constants.SystemKey.Enter) {\n this.onMediaItemSelect(item, i, list);\n }\n }}\n onClick={() => this.onMediaItemSelect(item, i, list)}\n >\n {\n this.setState({\n failures: {\n ...this.state.failures,\n [item.thumbnail]: true,\n },\n });\n }}\n />\n {isVideo ? : null}\n
    \n );\n }\n }\n\n if (!mediaRender.length) {\n return null;\n }\n\n return (\n
    \n

    {this.context.loc('Overview_Thumbnails')}

    \n
    {mediaRender}
    \n
    \n );\n }\n\n isPowerBiVisual() {\n const { property, mask } = getProdcutBitMaskInfo(DataMap.products.PowerBICustomVisual);\n return !!(this.props.products && bitMasksMatch(this.props.products, property, mask));\n }\n\n createDisclaimer(): JSX.Element {\n if (!this.isPowerBiVisual()) {\n return
    {this.context.loc('CP_Disclaimer')}
    ;\n }\n\n if (this.isPowerBiVisual() && !this.props.isPowerBICertified) {\n return
    {this.context.loc('CP_Disclaimer_PowerBI')}
    ;\n }\n\n return (\n <>\n
    {this.context.loc('CP_Disclaimer_Certified_PowerBI')}
    \n
    \n \n {this.context.loc('PBI_LearnMore')}.\n \n
    \n \n );\n }\n\n capabilitiesRendering() {\n const capabilitiesPane: JSX.Element[] = [];\n\n if (this.isPowerBiVisual() && this.props.isPowerBICertified) {\n return null;\n }\n\n if (this.isPowerBiVisual()) {\n return (\n
      \n
    • \n {this.context.loc('CP_10')}\n
    • \n
    \n );\n }\n\n for (let i = 0; i < this.props.capabilities.length; i++) {\n const capability = this.props.capabilities[`${i}`];\n const subCapabilities = Capabilities[`${capability}`];\n const subCapabilitiesCount = subCapabilities && subCapabilities.length ? subCapabilities.length : 0;\n\n for (let j = 0; j < subCapabilitiesCount; j++) {\n capabilitiesPane.push(\n
  • \n {this.context.loc(subCapabilities[`${j}`])}\n
  • \n );\n }\n }\n\n return capabilitiesPane.length ?
      {capabilitiesPane}
    : null;\n }\n\n autorunRendering() {\n const { autoRunLaunchEvents } = this.props;\n const autorunsPane: JSX.Element[] = autoRunLaunchEvents\n .filter((autorun) => AutoRuns[`${autorun}`])\n .map((autorun) => {\n const autorunInfo = AutoRuns[`${autorun}`];\n return (\n
  • \n {this.context.loc(autorunInfo.loc, autorunInfo.fallback)}\n
  • \n );\n });\n\n return autorunsPane?.length ? (\n
    \n {this.context.loc('Autorun_Title', 'This add-in can launch itself when:')}\n
      {autorunsPane}
    \n
    \n ) : null;\n }\n\n showMediaDialog(index: number, list: IMedia[]) {\n DetailUtils.generateLinkPayloadAndLogTelemetry(this.props.ownerType, this.props.ownerId, 'OpenMediaModal', '', 'Overview');\n const jsllMediaType = list[`${index}`]\n ? list[`${index}`].type === MediaType.Image\n ? Constants.JsllSaas.ViewImage\n : Constants.JsllSaas.WatchVideo\n : '';\n pageAction(document.getElementById(this.props.ownerId), {\n contentTags: {\n contentId: jsllMediaType,\n contentName: this.props.ownerId,\n contentType: this.props.isSaaSTransactableApp,\n },\n });\n\n this.props.openMediaModal({\n defaultIndex: index,\n media: list,\n });\n }\n\n onMediaItemSelect(selected: IMedia, index: number, list: IMedia[]) {\n DetailUtils.generateLinkPayloadAndLogTelemetry(\n this.props.ownerType,\n this.props.ownerId,\n `Select${selected.type}`,\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n selected.link,\n 'Overview'\n );\n\n this.showMediaDialog(index, list);\n }\n\n renderDescription(): JSX.Element {\n return ;\n }\n\n renderShortDescription() {\n return (\n \n
    \n
    \n );\n }\n\n renderImpl() {\n const { TileType, crossListings } = this.props;\n const localizationTexts: string[] = [];\n let ribbonUrl = 'openTab';\n let ribbonSeeAllText = this.context.loc('Link_SeeAll', 'See all');\n let ribbonTitleParam = '';\n let ribbonKey = '';\n\n if (TileType === AppTile || TileType === ServiceTile) {\n const localizedTitleKey = this.getLocalizationKey(TileType);\n localizationTexts.push(localizedTitleKey);\n localizationTexts.push('');\n localizationTexts.push('');\n ribbonSeeAllText = '';\n ribbonUrl = null;\n ribbonTitleParam = crossListings?.[0]?.publisher || '';\n ribbonKey = `${localizedTitleKey} ${ribbonTitleParam}`;\n }\n\n const appDetailContentMaxWidthClasses = classnames({\n appDetailContent: true,\n });\n\n return (\n
    \n
    \n
    \n
    \n {this.renderShortDescription()}\n {this.renderDescription()}\n
    \n {this.props.capabilities || this.props.autoRunLaunchEvents?.length ? (\n
    \n

    \n {this.context.loc(this.isPowerBiVisual() ? 'CP_Title_PowerBI' : 'CP_Title')}\n

    \n {this.props.capabilities && (\n <>\n
    {this.createDisclaimer()}
    \n {this.capabilitiesRendering()}\n \n )}\n {!!this.props.autoRunLaunchEvents?.length && <>{this.autorunRendering()}}\n
    \n ) : null}\n
    \n {this.mediaRendering()}\n
    \n {crossListings?.length ? (\n \n {crossListings\n .slice(0, Constants.crossListingRibbonItemCount)\n .map((value: any, index: number, tiles: (IAppDataItem | Service)[]) => {\n return (\n \n );\n })}\n \n ) : null}\n
    \n );\n }\n}\n\n(Overview as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","import { connect } from 'react-redux';\nimport { IMediaModalPayload } from '@shared/Models';\nimport { IState } from '../../State';\nimport { Overview as OverviewComponent, IOverviewProps, IOverviewDispatchProps } from '../components/overview';\nimport { createMediaModalAction } from '@shared/actions/actions';\nimport type { Dispatch } from 'redux';\n\nexport const mapStateToProps = (state: IState, ownProps: IOverviewProps): IOverviewProps => {\n return ownProps;\n};\n\nexport const mapDispatchToProps = (dispatch: Dispatch): IOverviewDispatchProps => {\n return {\n openMediaModal: (payload: IMediaModalPayload) => {\n dispatch(createMediaModalAction({ showModal: true, payload }));\n },\n };\n};\n\nconst Overview = connect(mapStateToProps, mapDispatchToProps)(OverviewComponent);\n\nexport default Overview;\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from './spzaComponent';\nimport {\n IUserFavouritePostPayload,\n IUserFavouriteTileDetailButtonProps,\n IUserFavouriteTileDetailButtonState,\n} from '@shared/interfaces/userFavouriteModels';\nimport { ITelemetryData } from '@shared/Models';\nimport { SpzaInstrumentProvider, SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport { IBuildHrefContext, ILocContext, ILocParamsContext, ICommonContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\nimport { getUserFavouriteEntity } from '@shared/utils/userFavouriteUtils';\nimport { getWindow } from '@shared/services/window';\nimport { ActionButton } from '@fluentui/react';\nimport type { IIconProps } from '@fluentui/react';\nimport { logger } from '@src/logger';\n\nconst favoriteStarHollow: IIconProps = { iconName: 'FavoriteStar' };\nconst favoriteStarFilled: IIconProps = { iconName: 'FavoriteStarFill' };\n\nexport class UserFavouriteTileDetailButton extends SpzaComponent<\n IUserFavouriteTileDetailButtonProps,\n IUserFavouriteTileDetailButtonState\n> {\n context: ILocContext & ILocParamsContext & IBuildHrefContext & ICommonContext;\n private instrument: SpzaInstrumentProvider;\n\n constructor(props: IUserFavouriteTileDetailButtonProps, context: ILocContext & ICommonContext & IBuildHrefContext) {\n super(props, context);\n\n this.instrument = SpzaInstrumentService.getProvider();\n this.state = {\n // this state property is to fix the flicker when user (un)save an favourite, due to the delay of a REST call to server\n // makes user think (un)save is an immediate action to effect,\n // this is done by changing the classname before the REST call, and reset the classname after REST call\n // in order to change and change it back, we need to record the user action\n itemModifyStatus: Constants.UserFavourite.DataItemModifyStatus.None,\n };\n\n this.onClickUserFavouriteButton = this.onClickUserFavouriteButton.bind(this);\n }\n\n componentDidMount() {\n this.props.fetchUserFavouriteData();\n }\n\n setFavouriteItemModifyComplete() {\n this.setState({\n itemModifyStatus: Constants.UserFavourite.DataItemModifyStatus.Done,\n });\n }\n\n onClickUserFavouriteButton() {\n const userFavouriteEntity = getUserFavouriteEntity(this.props.userFavourite, this.props.entityId);\n\n const upsertPayload: IUserFavouritePostPayload = {\n applicationType: this.props.entityType,\n applicationId: this.props.entityId,\n };\n\n // telemetry\n const details = userFavouriteEntity\n ? {\n action: Constants.Telemetry.Action.Delete,\n userFavouriteInfo: {\n ...userFavouriteEntity,\n },\n }\n : {\n action: Constants.Telemetry.Action.Upsert,\n userFavouriteInfo: {\n ...upsertPayload,\n },\n };\n\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.UserFavouriteTileDetailButton,\n details: JSON.stringify(details),\n };\n\n this.instrument.probe(Constants.Telemetry.ProbeName.LogInfo, payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n\n // toggle user favourite on entity\n if (userFavouriteEntity) {\n // Delete a favourite\n // save the user action\n // display saved result beforehand\n this.setState({\n itemModifyStatus: Constants.UserFavourite.DataItemModifyStatus.Delete,\n });\n\n // REST DELETE\n this.props\n .deleteFavourite(userFavouriteEntity.entityGuid, userFavouriteEntity.applicationType)\n\n // TO-DO: cannot use .finally() on promise\n .then(() => {\n this.setFavouriteItemModifyComplete();\n })\n .catch(() => {\n this.setFavouriteItemModifyComplete();\n });\n } else {\n // Upsert a favourite\n // save the user action\n // display saved result beforehand\n this.setState({\n itemModifyStatus: Constants.UserFavourite.DataItemModifyStatus.Upsert,\n });\n\n // REST POST\n this.props\n .savefavourite({ ...upsertPayload }, this.props.item)\n\n // TO-DO: cannot use .finally() on promise\n .then(() => {\n this.setFavouriteItemModifyComplete();\n })\n .catch(() => {\n this.setFavouriteItemModifyComplete();\n });\n }\n }\n\n renderImpl() {\n const hasUserSavedFavourite = !!getUserFavouriteEntity(this.props.userFavourite, this.props.entityId);\n const itemModifyStatus = this.state.itemModifyStatus;\n const shouldShowSavedStatus =\n (hasUserSavedFavourite &&\n (itemModifyStatus === Constants.UserFavourite.DataItemModifyStatus.Done ||\n itemModifyStatus === Constants.UserFavourite.DataItemModifyStatus.None)) ||\n (!hasUserSavedFavourite && itemModifyStatus === Constants.UserFavourite.DataItemModifyStatus.Upsert);\n\n const title = shouldShowSavedStatus\n ? this.context.loc('UserFavourite_TileDetailButton_Saved')\n : this.context.loc('UserFavourite_TileDetailButton_Save');\n\n return (\n this.props.userInfo.signedIn && (\n \n {title}\n \n )\n );\n }\n}\n\n(UserFavouriteTileDetailButton as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n buildHref: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","import * as React from 'react';\nimport { IState } from '@src/State';\nimport { upsertUserFavourite, deleteUserFavourite, fetchUserFavourites } from '@shared/actions/userFavouriteThunkActions';\nimport { UserFavouriteTileDetailButton as UserFavouriteTileDetailButtonComponent } from '@shared/components/userFavouriteTileDetailButton';\nimport {\n IUserFavouritePostPayload,\n IUserFavouriteTileDetailButtonContainerProps,\n IUserFavouriteItem,\n} from '@shared/interfaces/userFavouriteModels';\nimport { Constants } from '@shared/utils/constants';\nimport { connect, Dispatch } from 'react-redux';\n\nexport const mapStateToProps = (state: IState, ownProps: IUserFavouriteTileDetailButtonContainerProps) => {\n return {\n userInfo: state.users,\n userFavourite: state.userFavourite,\n entityId: ownProps.entityId,\n entityType: ownProps.entityType,\n item: ownProps.item,\n className: ownProps.className,\n };\n};\n\nexport const mapDispatchToProps = (dispatch: Dispatch) => {\n return {\n fetchUserFavouriteData: () => dispatch(fetchUserFavourites()),\n savefavourite: (payload: IUserFavouritePostPayload, item: IUserFavouriteItem) => dispatch(upsertUserFavourite(payload, item)),\n deleteFavourite: (entityGuid: string, applicationType: Constants.UserFavourite.ApplicationTypes) =>\n dispatch(deleteUserFavourite(entityGuid, applicationType)),\n };\n};\n\nconst UserFavouriteTileDetailButton = connect(mapStateToProps, mapDispatchToProps)(UserFavouriteTileDetailButtonComponent);\n\nexport default UserFavouriteTileDetailButton as React.StatelessComponent;\n","import React, { FunctionComponent } from 'react';\nimport { ISortedTableProps } from './sortedTable';\n\ninterface MultilineColumnProps {\n columnKey: string;\n columnValue: any[];\n shouldUseSetInnerHTML: boolean;\n}\n\ninterface SortedTableColumnProps {\n columnKey: string;\n columnValue: any;\n className?: string;\n tableProps: ISortedTableProps;\n}\n\nconst MultilineColumn: FunctionComponent = ({\n columnKey,\n columnValue,\n shouldUseSetInnerHTML,\n}: MultilineColumnProps) => {\n const lines = columnValue.map((item: any, key: number) => {\n if (shouldUseSetInnerHTML) {\n return (\n // eslint-disable-next-line react/no-danger\n \n
    \n
    \n );\n }\n return (\n \n {item}\n
    \n
    \n );\n });\n\n return (\n // eslint-disable-next-line react/forbid-dom-props\n \n {lines}\n \n );\n};\n\nexport const SortedTableColumn: FunctionComponent = ({\n columnKey,\n columnValue,\n className,\n tableProps,\n}) => {\n const { spanColumns, setInnerHTMLColumns, multiLineColumns } = tableProps;\n if (!columnValue) {\n return null;\n }\n\n const columnClassName = `${columnKey} ${className}`;\n\n // If this flag is set a span tag is added around the content of table data. Default - no span tag added.\n const shouldAddSpanTag = spanColumns ? spanColumns.indexOf(columnKey) >= 0 : false;\n\n // If this flag is set, we will use dangerouslySetInnerHTML\n const shouldUseSetInnerHTML = setInnerHTMLColumns ? setInnerHTMLColumns.indexOf(columnKey) >= 0 : false;\n\n // If this flag is set the content of table data can have breaks within it. Default - A single line.\n const shouldShowMultiLine = multiLineColumns ? multiLineColumns.indexOf(columnKey) >= 0 : false;\n\n if (columnValue instanceof Array && shouldShowMultiLine) {\n return ;\n } else if (typeof columnValue === 'string' || typeof columnValue === 'number' || typeof columnValue === 'object') {\n if (shouldAddSpanTag) {\n return (\n // eslint-disable-next-line react/forbid-dom-props\n \n {columnValue}\n \n );\n } else if (shouldUseSetInnerHTML) {\n return (\n \n );\n } else {\n return (\n // eslint-disable-next-line react/forbid-dom-props\n \n {columnValue}\n \n );\n }\n }\n};\n","import classNames from 'classnames';\nimport * as React from 'react';\nimport SpzaComponent from '../spzaComponent';\nimport { SortedTableColumn } from './sortedTableColumn';\n\nconst SortOrder = {\n None: 'none',\n Ascending: 'ascending',\n Descending: 'descending',\n} as const;\n\nfunction SortArrayByProperty(inputArray: Array, propertyName: string, order: string): T[] {\n const comparer: any = function (arg0: any, arg1: any) {\n if (typeof arg0[`${propertyName}`] !== 'number' || typeof arg1[`${propertyName}`] !== 'number') {\n return 0;\n }\n\n return order === SortOrder.Ascending\n ? arg0[`${propertyName}`] - arg1[`${propertyName}`]\n : arg1[`${propertyName}`] - arg0[`${propertyName}`];\n };\n\n return inputArray.sort(comparer);\n}\n\ninterface IMultiLineRowConfiguration {\n multilineColumns: string[];\n multilineColumnClass?: string;\n emptyColumnClassName?: string;\n lastRowClassName?: string;\n}\n\nexport interface ISortConfig {\n sortColumnName?: string;\n sortOrder?: string;\n}\n\nexport interface ISortedTableProps {\n thead: Array;\n tbody: Array;\n sortConfig: ISortConfig;\n // Columns specified in this array get a rendered around its content.\n spanColumns?: Array;\n // Columns specified in this array are not rendered.\n ignore?: Array;\n // Columns specified in this area have html in their values so use dangerouslySetInnerHTML\n setInnerHTMLColumns?: Array;\n\n // Columns specified in this array that shows an item per line/\n multiLineColumns?: Array;\n\n /** Use this to order the columns instead of using Object.keys result */\n columns?: string[];\n\n columnsClasses?: { [key: string]: string };\n multiLineRowsConfiguration?: IMultiLineRowConfiguration;\n defaultSortingDisabled?: boolean;\n}\n\nexport class SortedTable extends SpzaComponent {\n // This is the old implementation of the component and new implemnantion is under multiyear feature flag\n renderOldSingleLineRow = (row: any) => {\n const columns: Array = [];\n\n Object.keys(row).forEach((propertyName) => {\n // If this flag is set the property is not rendered. Default - property is rendered.\n const shouldIgnore = this.props.ignore ? this.props.ignore.indexOf(propertyName) >= 0 : false;\n\n // If this flag is set a span tag is added around the content of table data. Default - no span tag added.\n const shouldAddSpanTag = this.props.spanColumns ? this.props.spanColumns.indexOf(propertyName) >= 0 : false;\n\n // If this flag is set, we will use dangerouslySetInnerHTML\n const shouldUseSetInnerHTML = this.props.setInnerHTMLColumns\n ? this.props.setInnerHTMLColumns.indexOf(propertyName) >= 0\n : false;\n\n // If this flag is set the content of table data can have breaks within it. Default - A single line.\n const shouldShowMultiLine = this.props.multiLineColumns ? this.props.multiLineColumns.indexOf(propertyName) >= 0 : false;\n\n if (!shouldIgnore && row[`${propertyName}`]) {\n if (row[`${propertyName}`] instanceof Array && shouldShowMultiLine) {\n if (row[`${propertyName}`]) {\n const lines = row[`${propertyName}`].map(function (item: any, key: any) {\n if (shouldUseSetInnerHTML) {\n return (\n // eslint-disable-next-line react/no-danger\n \n
    \n
    \n );\n }\n return (\n \n {item}\n
    \n
    \n );\n });\n columns.push(\n // eslint-disable-next-line react/forbid-dom-props\n \n {lines}\n \n );\n }\n } else if (\n typeof row[`${propertyName}`] === 'string' ||\n typeof row[`${propertyName}`] === 'number' ||\n typeof row[`${propertyName}`] === 'object'\n ) {\n if (shouldAddSpanTag) {\n columns.push(\n // eslint-disable-next-line react/forbid-dom-props\n \n {row[`${propertyName}`]}\n \n );\n } else if (shouldUseSetInnerHTML) {\n columns.push(\n \n );\n } else {\n columns.push(\n // eslint-disable-next-line react/forbid-dom-props\n \n {row[`${propertyName}`]}\n \n );\n }\n }\n }\n });\n\n return (\n \n {columns}\n \n );\n };\n\n getColumnKeysToRender = (row: any): string[] => {\n if (!row) {\n return [];\n }\n\n const { columns } = this.props;\n if (columns) {\n return columns;\n }\n\n return Object.keys(row).filter((columnKey) => {\n const columnValue = row[columnKey.toString()];\n const shouldIgnore = this.props.ignore?.some((ignoreKey) => ignoreKey === columnKey);\n return columnValue && !shouldIgnore;\n });\n };\n\n renderSingleLineRow = (row: any) => {\n const { columnsClasses } = this.props;\n\n const columns = this.getColumnKeysToRender(row).map((columnKey) => (\n \n ));\n\n return (\n \n {columns}\n \n );\n };\n\n renderMultilineRow = (multilineConfig: IMultiLineRowConfiguration) => (rowData: any) => {\n const { columnsClasses } = this.props;\n const { multilineColumns, emptyColumnClassName, lastRowClassName, multilineColumnClass } = multilineConfig;\n\n if (!multilineColumns.length) {\n return null;\n }\n\n const numberOfMultilineRows = rowData[multilineColumns[0]]?.length || 0;\n const rows = [];\n for (let rowIndex = 0; rowIndex < numberOfMultilineRows; rowIndex++) {\n const columns = this.getColumnKeysToRender(rowData).map((columnKey) => {\n const columnValue = rowData[columnKey.toString()];\n const isMultilineColumn = multilineColumns.find((x) => x === columnKey);\n\n if (rowIndex === 0) {\n return (\n \n );\n } else {\n if (isMultilineColumn) {\n return (\n \n );\n } else {\n return (\n \n );\n }\n }\n });\n rows.push(columns);\n }\n\n return rows.map((row, index) => (\n \n {row}\n \n ));\n };\n\n renderImpl() {\n const { multiLineRowsConfiguration, tbody, defaultSortingDisabled } = this.props;\n\n let tableData = defaultSortingDisabled\n ? tbody\n : tbody.sort((a: any, b: any) => {\n const titleA = a.title ? a.title.toUpperCase() : a.category.toUpperCase();\n const titleB = b.title ? b.title.toUpperCase() : b.category.toUpperCase();\n return titleA < titleB ? 1 : titleA > titleB ? -1 : 0;\n });\n tableData = this.props.sortConfig.sortColumnName\n ? SortArrayByProperty(tableData, this.props.sortConfig.sortColumnName, this.props.sortConfig.sortOrder)\n : tableData;\n\n const hasMultilineConfig = !!multiLineRowsConfiguration?.multilineColumns?.length;\n const rowRenderingFunction = hasMultilineConfig\n ? this.renderMultilineRow(multiLineRowsConfiguration)\n : this.renderOldSingleLineRow;\n\n return (\n \n {this.props.thead}\n {tableData.map((row) => rowRenderingFunction(row))}\n
    \n );\n }\n}\n","import * as React from 'react';\nimport SpzaComponent from '../spzaComponent';\n\nexport const SortOrder = {\n None: 'none',\n Ascending: 'ascending',\n Descending: 'descending',\n} as const;\n\nexport const SortOrderStyles = {\n NoneStyle: 'c-glyph',\n AscendingStyle: 'c-glyph f-ascending',\n DescendingStyle: 'c-glyph f-descending',\n};\n\nexport interface ISortedTHProps {\n // This is the name of the property in td which the header is bound to.\n boundProperty: string;\n handleSortChange: any;\n currentlySelected: string;\n}\n\ninterface ISortedTHState {\n ariaSort: 'ascending' | 'descending' | 'none' | 'other';\n buttonClass: string;\n}\n\nexport class SortedTH extends SpzaComponent {\n constructor(props: ISortedTHProps) {\n super(props);\n this.state = {\n ariaSort: SortOrder.None,\n buttonClass: SortOrderStyles.NoneStyle,\n };\n }\n\n UNSAFE_componentWillReceiveProps(nextProps: ISortedTHProps) {\n if (this.props.boundProperty !== nextProps.currentlySelected) {\n this.setState({\n ariaSort: SortOrder.None,\n buttonClass: SortOrderStyles.NoneStyle,\n });\n }\n }\n\n handleHeaderClick() {\n let newState: ISortedTHState = {\n ariaSort: SortOrder.None,\n buttonClass: SortOrderStyles.NoneStyle,\n };\n\n if (this.state.ariaSort === SortOrder.None || this.state.ariaSort === SortOrder.Descending) {\n newState = {\n ariaSort: SortOrder.Ascending,\n buttonClass: SortOrderStyles.AscendingStyle,\n };\n } else {\n newState = {\n ariaSort: SortOrder.Descending,\n buttonClass: SortOrderStyles.DescendingStyle,\n };\n }\n\n this.setState(newState);\n this.props.handleSortChange(this.props.boundProperty, newState.ariaSort);\n }\n\n renderImpl() {\n return (\n \n this.handleHeaderClick()}\n >\n {this.props.children}\n \n \n );\n }\n}\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from './spzaComponent';\nimport FuturePriceTooltip, { IFuturePriceTooltipProps } from 'components/futurePriceTooltip';\n\ninterface ISimplePriceCell {\n price: string;\n futurePrice?: IFuturePriceTooltipProps;\n className?: string;\n}\n\nexport class SimplePriceCell extends SpzaComponent {\n renderImpl() {\n const { price, futurePrice, className } = this.props;\n return (\n
    \n \n {futurePrice && }\n
    \n );\n }\n}\n\n(SimplePriceCell as any).contextTypes = {\n renderErrorModal: PropTypes.func,\n};\n","import * as React from 'react';\nimport { logClientError } from '@shared/utils/appUtils';\nimport { ICommonContext } from '@shared/interfaces/context';\n\nexport default class PureSpzaComponent extends React.PureComponent {\n context: ICommonContext;\n renderImpl(): any {\n // should never be called\n throw new Error(\n 'PureSpzaComponent renderImpl method should never be called!\\nMake sure you are implementing renderImpl in your Component'\n );\n }\n\n renderFailImpl(err: any): any {\n // rendering must _never_ let an error escape. This puts react in a bad state and\n // current versions (15.x) cannot deal with that.\n logClientError(err, 'client side error: [From pureSpzaComponent] ');\n // no longer we render the error modal from here the main client hook up handles all errors.\n throw err;\n }\n\n render() {\n try {\n return this.renderImpl();\n } catch (err) {\n return this.renderFailImpl(err);\n }\n }\n}\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport { IBillingCountry, IPricingInformation, ISimpleSKU } from '@shared/Models';\nimport { IBuildHrefContext, ILocContext, ILocParamsContext, ICTACallbackContext, ICommonContext } from '../interfaces/context';\nimport { renderNotAvailablePriceUI, getVariablePriceStringWithDefault } from '@shared/utils/pricing';\nimport { SortedTable, ISortConfig, SortedTH, SortOrder } from './sortedTable';\nimport { Plan } from '@shared/utils/constants';\nimport { SimplePriceCell } from './simplePriceCell';\nimport FuturePriceWarning from '@shared/components/futurePriceWarning';\nimport PureSpzaComponent from '@shared/components/pureSpzaComponent';\nimport pdpPrivateBadge from '@shared/images/pdp-privateBadge.svg';\n\nexport interface ISimplePlanPricingProps {\n pricing: IPricingInformation;\n futurePrices?: IPricingInformation;\n billingCountry: IBillingCountry;\n isMicrosoftManaged?: boolean;\n shouldBlockPrices?: boolean;\n isOneTimePaymentOffer?: boolean;\n locale?: string;\n}\n\ninterface ISimplePlanPricingState {\n sortConfig?: ISortConfig;\n}\n\nexport class SimplePlanPricing extends PureSpzaComponent {\n context: IBuildHrefContext & ILocContext & ILocParamsContext & ICTACallbackContext & ICommonContext;\n\n constructor(\n props: ISimplePlanPricingProps,\n context: IBuildHrefContext & ILocContext & ILocParamsContext & ICTACallbackContext & ICommonContext\n ) {\n super(props, context);\n\n this.state = {\n sortConfig: {\n sortColumnName: Plan.displayRank,\n sortOrder: SortOrder.Ascending,\n },\n };\n this.handleSortChange = this.handleSortChange.bind(this);\n }\n\n handleSortChange(sortColumnName: string, order: string) {\n this.setState({\n sortConfig: {\n sortColumnName,\n sortOrder: order,\n },\n });\n }\n\n showColumnByUnit(skus: ISimpleSKU[], unit: string) {\n return skus.some((sku) => sku.termPrices && sku.termPrices.some((termPrice) => termPrice.unit.toLowerCase() === unit));\n }\n\n getBadge(sku: ISimpleSKU) {\n if (sku.isPrivate) {\n return ;\n }\n }\n\n getPriceColumnsData(skus: ISimpleSKU[], renderBadges: boolean) {\n const { futurePrices, isOneTimePaymentOffer } = this.props;\n const showPriceColumn = skus.some((sku) => !!sku.startingPrice);\n const showYearlyPrice = this.showColumnByUnit(skus, 'year');\n let showMonthlyPrice = this.showColumnByUnit(skus, 'month');\n\n // If the offer is Microsoft managed and without prices.\n if (this.props.isMicrosoftManaged && skus.length && !showYearlyPrice && !showMonthlyPrice) {\n showMonthlyPrice = true;\n }\n\n const formattedSkus = skus.map((sku) => {\n const futureSku = futurePrices?.skus.find((futurePriceSku: ISimpleSKU) => futurePriceSku.id === sku.id) as ISimpleSKU;\n const { monthly, yearly, price, futurePriceMonthly, futurePriceYearly } = getVariablePriceStringWithDefault({\n sku,\n showPriceColumn,\n showYearlyPrice,\n showMonthlyPrice,\n isMicrosoftManaged: this.props.isMicrosoftManaged,\n countryCode: this.props.billingCountry.countryCode,\n currency: this.props.billingCountry.currency,\n context: this.context,\n futureSku,\n isOneTimePaymentOffer,\n locale: this.props.locale,\n shouldBlockPrices: this.props.shouldBlockPrices,\n });\n let row: { [key: string]: any } = {\n title: sku.title,\n description: sku.description,\n displayRank: sku.displayRank,\n monthly: monthly && ,\n yearly: yearly && ,\n price: sku.startingPrice?.meterId ? `${price}/${sku.startingPrice.meterId}` : price,\n priceInternal: showPriceColumn && sku.startingPrice ? sku.startingPrice.value : 0,\n rowClassName: 'pricingListItem',\n rowKey: sku.title,\n };\n\n if (renderBadges) {\n row = {\n badge: (renderBadges && this.getBadge(sku)) || ' ',\n ...row,\n };\n }\n return row;\n });\n\n const showGeneralPrice = showPriceColumn && !showMonthlyPrice && !showYearlyPrice;\n\n return { showMonthlyPrice, showYearlyPrice, formattedSkus, showGeneralPrice };\n }\n\n renderImpl() {\n const { pricing, isOneTimePaymentOffer } = this.props;\n\n if (!pricing) {\n return
    {this.context.loc('Loading', 'Loading...')}
    ;\n }\n\n const skus = pricing.skus as ISimpleSKU[];\n const hasBadges = skus.some((sku) => sku.isPrivate === true);\n const { showMonthlyPrice, showYearlyPrice, showGeneralPrice, formattedSkus } = this.getPriceColumnsData(skus, hasBadges);\n return skus.length === 0 ? (\n renderNotAvailablePriceUI(this.context)\n ) : (\n
    \n
    \n \n \n {hasBadges && }\n \n {this.context.loc('Plan', 'Plan')}\n \n \n {this.context.loc('Description', 'Description')}\n \n {showMonthlyPrice ? (\n \n {isOneTimePaymentOffer\n ? this.context.loc('Price', 'Price')\n : this.context.loc('Monthly_Price', 'Monthly Price')}\n \n ) : null}\n {showYearlyPrice ? (\n \n {this.context.loc('Annual_Price', 'Annual Price')}\n \n ) : null}\n {showGeneralPrice ? (\n \n {this.context.loc('Price', 'Price')}\n \n ) : null}\n ,\n ]}\n tbody={formattedSkus}\n sortConfig={this.state.sortConfig}\n ignore={['rowKey', 'rowClassName', 'priceInternal', Plan.displayRank]}\n setInnerHTMLColumns={['description']}\n multiLineColumns={['monthly', 'yearly', 'title']}\n />\n
    \n
    \n );\n }\n}\n\n(SimplePlanPricing as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n buildHref: PropTypes.func,\n ctaCallback: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","/* eslint-disable react/prop-types */\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { InternalLink } from './internalLink';\n\ninterface IconLinkProps {\n accEnabled: boolean;\n className: string;\n iconClassName?: string;\n href: string;\n onClick: (event: Event) => void;\n title: string;\n}\n\nexport const IconLink: React.FC> = ({\n iconClassName,\n accEnabled,\n href,\n className,\n onClick,\n title,\n children,\n}) => {\n return (\n
    \n
    \n \n {children}\n \n
    \n );\n};\n","/* eslint-disable react/prop-types */\nimport * as React from 'react';\nimport { TelemetryImage } from './telemetryImage';\n\ninterface CardProps {\n title: string;\n subtitle: string;\n logoURL: string;\n}\n\nexport const Card: React.FC> = ({ logoURL, title, subtitle, children }) => {\n return (\n
    \n {logoURL && (\n
    \n \n
    \n )}\n
    \n
    \n

    {title}

    \n

    {subtitle}

    \n
    {children}
    \n
    \n
    \n
    \n );\n};\n","import { ICommonContext } from 'interfaces/context';\nimport SpzaComponent from './spzaComponent';\n// eslint-disable-next-line import/named\nimport { Text, ITextStyles } from '@fluentui/react';\nimport * as React from 'react';\nimport { ITabsProps, Tab, Tabs } from './tabs';\nimport { TabChild } from '@shared/Models';\n\nexport interface IStickyCard {\n title: string;\n logoURL: string;\n tabsProps: ITabsProps;\n tabsChilds: TabChild[];\n}\n\nexport class StickyCard extends SpzaComponent {\n context: ICommonContext;\n\n static handleScroll(curWindow: Window, appearUnderElementId: string): boolean {\n const elementToAppearAfter = document.getElementById(appearUnderElementId);\n if (curWindow && elementToAppearAfter && elementToAppearAfter.offsetTop) {\n const tabsYOffset = elementToAppearAfter.offsetTop + 43;\n if (curWindow.pageYOffset > tabsYOffset) {\n return true;\n }\n return false;\n }\n }\n\n renderImpl() {\n const titleStyles: ITextStyles = { root: { marginLeft: 8, fontWeight: 600, lineHeight: 20 } };\n\n return (\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n \n e && e.target && (e.target as HTMLElement).style\n ? ((e.target as HTMLElement).style.display = 'none')\n : null\n }\n />\n
    \n
    \n \n {this.props.title}\n \n
    \n
    \n \n {this.props.tabsChilds.map((tabChild, index) => (\n \n
    \n
    \n ))}\n \n
    \n
    \n
    \n
    \n
    \n
    {this.props.children}
    \n
    \n
    \n
    \n
    \n );\n }\n}\n","import { IAppDataItem } from '../../shared/Models';\nimport React, { ReactNode } from 'react';\nimport { Constants } from '../../shared/utils/constants';\nimport SpzaComponent from '../../shared/components/spzaComponent';\n\nexport interface MicrosoftManagedIndicatorWrapperProps {\n uiRole?: Constants.UiRole;\n app: IAppDataItem;\n isAvailableInRegion: boolean;\n renderIndicator(): ReactNode;\n}\n\nexport class MicrosoftManagedIndicatorWrapper extends SpzaComponent {\n renderImpl() {\n const { uiRole, app, isAvailableInRegion, renderIndicator } = this.props;\n\n const userIsAdmin = uiRole === Constants.UiRole.Admin;\n\n if (!app?.licenseManagement?.isMicrosoftManaged || !isAvailableInRegion || !uiRole || userIsAdmin) {\n return null;\n }\n\n return renderIndicator();\n }\n}\n","import { Constants } from '@shared/utils/constants';\n\nconst fallbackCountryCode = 'US';\n\nexport const getLocaleLanguage = ({ locale }: { locale?: string }) => {\n const localeLanguage = locale?.includes('-') ? locale.split('-')[0] : Constants.DefaultLocale.split('-')[0];\n return localeLanguage;\n};\n\nfunction getCountryCodeRegion({ countryCode }: { countryCode?: string }) {\n const countryCodeRegion = countryCode\n ? countryCode.includes('-')\n ? countryCode.split('-')[1]\n : countryCode\n : fallbackCountryCode;\n return countryCodeRegion;\n}\n\nexport function getLocale({ locale, countryCode }: { locale?: string; countryCode?: string }) {\n const localeLanguage = getLocaleLanguage({ locale });\n const countryCodeRegion = getCountryCodeRegion({ countryCode });\n const localeString = `${localeLanguage.toLowerCase()}-${countryCodeRegion.toUpperCase()}`;\n return localeString;\n}\n\nexport function timeToLocalLocaleString({\n timeUTCSeconds,\n locale,\n countryCode,\n}: {\n timeUTCSeconds: number;\n locale: string;\n countryCode: string;\n}): string {\n const date = new Date(timeUTCSeconds * 1000);\n const localeString = getLocale({ locale, countryCode });\n return date.toLocaleTimeString(localeString);\n}\n\nexport function timeToLocalString({\n timeUTCSeconds,\n locale,\n countryCode,\n}: {\n timeUTCSeconds: number;\n locale: string;\n countryCode: string;\n}): string {\n const date = new Date(timeUTCSeconds * 1000);\n const localeString = getLocale({ locale, countryCode });\n return date.toLocaleString(localeString);\n}\n\nexport function numberToLocaleString({\n number,\n locale,\n countryCode,\n}: {\n number: number;\n locale: string;\n countryCode: string;\n}): string {\n const localeString = getLocale({ locale, countryCode });\n return number.toLocaleString(localeString);\n}\n\nconst catalogSupportedLocales = [\n 'en',\n 'cs',\n 'de',\n 'es',\n 'fr',\n 'hu',\n 'it',\n 'ja',\n 'ko',\n 'nl',\n 'pl',\n 'ru',\n 'sv',\n 'tr',\n 'pt-br',\n 'pt-pt',\n 'zh-hans',\n 'zh-hant',\n];\nconst languageCodesThatShouldUseLocaleInstead = ['pt', 'zh'];\n\nexport const doesSearchSupportLanguage = ({ language }: { language: string }) => catalogSupportedLocales.indexOf(language) >= 0;\nexport const shouldUseLocaleInsteadOfLanguageCode = ({ language }: { language: string }) =>\n languageCodesThatShouldUseLocaleInstead.indexOf(language) >= 0;\n","import React, { FunctionComponent } from 'react';\nimport * as PropTypes from 'prop-types';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { ILinkedInProductGroup } from '@src/State';\nimport { FontIcon, Link, Text, mergeStyleSets, Stack } from '@fluentui/react';\nimport { numberToLocaleString } from '@shared/utils/localeUtils';\nimport { CommunicationColors } from '@fluentui/theme';\nimport { Constants } from '@shared/utils/constants';\nimport { SpzaInstrumentService } from 'services/telemetry/spza/spzaInstrument';\nimport { ITelemetryData } from '@shared/Models';\nimport { withRouter, WithRouterProps } from '@shared/routerHistory';\nimport { logger } from '@src/logger';\n\nconst contentStyles = mergeStyleSets({\n linkedinSection: {\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n marginTop: 8,\n },\n linkedinLogoIcon: {\n width: 18,\n height: 18,\n padding: 0,\n lineHeight: 20,\n verticalAlignment: 'center',\n color: CommunicationColors.primary,\n },\n linkedinMembersText: {\n marginLeft: 6,\n fontSize: 14,\n lineHeight: 20,\n fontWeight: 400,\n },\n linkedinLink: {\n marginLeft: 8,\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n textDecoration: 'none',\n },\n linkedinLinkText: {\n fontSize: 14,\n fontWeight: 400,\n textDecoration: 'underline',\n },\n externallinkIcon: {\n paddingLeft: 4,\n width: 13,\n height: 13,\n color: CommunicationColors.primary,\n lineHeight: 20,\n },\n});\n\nexport interface ILinkedinProductGroupBarProps {\n linkedInProductGroup: ILinkedInProductGroup;\n locale: string;\n countryCode: string;\n}\n\nconst LinkedinProductGroupBarFunction: FunctionComponent = (\n { linkedInProductGroup, locale, countryCode, location }: ILinkedinProductGroupBarProps & WithRouterProps,\n context: ILocContext & ILocParamsContext\n) => {\n const onLinkClick = () => {\n const payload: ITelemetryData = {\n page: location?.pathname,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.LinkedinLink,\n details: JSON.stringify({ offerId: linkedInProductGroup.offerId }),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n page: payload.page,\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n };\n\n return (\n \n \n \n {context.locParams('Linkedin_Members', [\n numberToLocaleString({ number: linkedInProductGroup.membersCount, locale, countryCode }),\n ])}\n \n \n \n {context.loc('Explore_Group_On_Linkedin')}\n \n \n \n \n );\n};\n\nexport const LinkedinProductGroupBar = withRouter(LinkedinProductGroupBarFunction);\n\n(LinkedinProductGroupBarFunction as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import React, { FunctionComponent } from 'react';\nimport * as PropTypes from 'prop-types';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { ILinkedInEvent } from '@src/State';\nimport { DefaultButton, FontIcon, Stack, Image, Text, mergeStyleSets, ImageFit } from '@fluentui/react';\nimport { numberToLocaleString, timeToLocalLocaleString, timeToLocalString } from '@shared/utils/localeUtils';\nimport { NeutralColors } from '@fluentui/theme';\n\nexport interface ILinkedinUpcomingEventProps {\n event: ILinkedInEvent;\n locale: string;\n countryCode: string;\n onExternalLinkClick: () => void;\n}\n\nconst contentStyles = mergeStyleSets({\n upcomingEvent: {\n width: 512,\n borderStyle: 'solid',\n borderWidth: 1,\n borderColor: NeutralColors.gray30,\n padding: 16,\n display: 'flex',\n flexDirection: 'column',\n },\n upcomingEventLogo: {\n width: 60,\n height: 60,\n position: 'relative',\n overflow: 'hidden',\n },\n upcomingEventLargeIcon: {\n width: 60,\n height: 60,\n position: 'relative',\n overflow: 'hidden',\n color: '#69AFE5',\n },\n upcomingEventLargeIconImg: {\n width: '100%',\n height: '100%',\n },\n upcomingEventNameText: {\n marginTop: 8,\n fontSize: 18,\n lineHeight: 24,\n fontWeight: 600,\n color: NeutralColors.gray160,\n },\n upcomingEventDefaultIcon: {\n fontSize: 40,\n width: 40,\n height: 50,\n color: '#69AFE5',\n },\n upcomingEventDefaultPanel: {\n padding: 10,\n width: 60,\n height: 60,\n backgroundColor: '#EFF6FC',\n },\n upcomingEventNormalText: {\n marginTop: 8,\n fontSize: 12,\n lineHeight: 16,\n fontWeight: 600,\n color: NeutralColors.gray130,\n },\n upcomingEventLinkButton: {\n width: 212,\n marginTop: 24,\n fontSize: 14,\n lineHeight: 20,\n fontWeight: 600,\n color: NeutralColors.gray190,\n },\n externallinkIcon: {\n marginLeft: 8,\n height: 24,\n fontSize: 14,\n color: NeutralColors.gray190,\n },\n});\n\nexport const LinkedinUpcomingEvent: FunctionComponent = (\n { event, locale, countryCode, onExternalLinkClick }: ILinkedinUpcomingEventProps,\n context: ILocContext & ILocParamsContext\n) => {\n const { startTime, endTime, name, logoUrl, eventType, attendeeCount, detailsUrl } = event;\n\n const startTimeStr = timeToLocalString({ timeUTCSeconds: startTime / 1000, locale, countryCode });\n const endTimeStr = timeToLocalLocaleString({ timeUTCSeconds: endTime / 1000, locale, countryCode });\n return (\n
    \n {logoUrl ? (\n
    \n
    \n \n
    \n
    \n ) : (\n
    \n \n
    \n )}\n {name}\n {eventType}\n \n {context.locParams('Linkedin_Event_Time', [startTimeStr, endTimeStr])}\n \n \n {context.locParams('Linkedin_Attendees_Count', [numberToLocaleString({ number: attendeeCount, locale, countryCode })])}\n \n \n \n {context.loc('Linkedin_Event_Link', 'View event on LinkedIn')}\n \n \n \n
    \n );\n};\n\n(LinkedinUpcomingEvent as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import React, { FunctionComponent } from 'react';\nimport * as PropTypes from 'prop-types';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { ILinkedInCustomer } from '@src/State';\nimport { numberToLocaleString } from '@shared/utils/localeUtils';\nimport { Image, ImageFit, mergeStyleSets, Text } from '@fluentui/react';\nimport { NeutralColors } from '@fluentui/theme';\n\nexport interface ILinkedinFeaturedCustomerProps {\n customer: ILinkedInCustomer;\n locale: string;\n countryCode: string;\n}\n\nconst contentStyles = mergeStyleSets({\n featuredCustomer: {\n width: 240,\n height: 190,\n borderStyle: 'solid',\n borderWidth: 1,\n borderColor: NeutralColors.gray30,\n padding: 16,\n display: 'flex',\n flexDirection: 'column',\n },\n featuredCustomerLogo: {\n width: 60,\n height: 60,\n position: 'relative',\n overflow: 'hidden',\n },\n featuredCustomerLargeIcon: {\n width: 60,\n height: 60,\n position: 'relative',\n overflow: 'hidden',\n },\n featuredCustomerLargeIconImg: {\n width: '100%',\n height: '100%',\n },\n featuredCustomerNameText: {\n marginTop: 8,\n fontSize: 18,\n lineHeight: 24,\n fontWeight: 600,\n color: NeutralColors.gray160,\n },\n featuredCustomerNormalText: {\n marginTop: 8,\n fontSize: 12,\n lineHeight: 16,\n fontWeight: 600,\n color: NeutralColors.gray130,\n },\n});\n\nexport const LinkedinFeaturedCustomer: FunctionComponent = (\n { customer, locale, countryCode }: ILinkedinFeaturedCustomerProps,\n context: ILocContext & ILocParamsContext\n) => {\n const { name, logoUrl, businessCategory, followersCount } = customer;\n\n return (\n
    \n
    \n
    \n
    \n \n
    \n
    \n {name}\n {businessCategory}\n \n {context.locParams('Linkedin_Followers_Count', [numberToLocaleString({ number: followersCount, locale, countryCode })])}\n \n
    \n
    \n );\n};\n\n(LinkedinFeaturedCustomer as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import React, { ReactNode } from 'react';\nimport { Dialog, DialogType, DialogFooter, PrimaryButton, Stack, FontIcon, mergeStyleSets } from '@fluentui/react';\nimport type { IDialogContentStyles, IDialogContentProps, IModalProps, IStackTokens, IDialogStyles } from '@fluentui/react';\nimport { NeutralColors } from '@fluentui/theme';\nimport type { ICSSPixelUnitRule } from '@fluentui/merge-styles';\n\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\n\ninterface ISubmittedModalProps {\n hidden: boolean;\n onDismiss: () => void;\n title: string;\n children: ReactNode;\n context: ILocContext & ILocParamsContext;\n}\n\nconst closeButtonText = 'Done';\nconst iconName = 'Completed';\nconst modalMinWidth: ICSSRule | ICSSPixelUnitRule = '560px';\nconst modalProps: IModalProps = { isDarkOverlay: false };\nconst stackTokens: IStackTokens = { childrenGap: 8 };\nconst classNames = mergeStyleSets({\n icon: [\n {\n fontSize: 16,\n height: 16,\n width: 16,\n color: '#107C10',\n marginTop: 2,\n },\n ],\n});\n\nconst dialogStyles: Partial = {\n main: {\n minHeight: 100,\n },\n};\n\nconst dialogContentStyles: Partial = {\n header: { color: NeutralColors.gray160 },\n};\n\nconst dialogContentProps = (context: ILocContext & ILocParamsContext): IDialogContentProps => {\n return {\n type: DialogType.close,\n closeButtonAriaLabel: context.loc('SubmittedModal_CloseButton_AriaText', 'Close'),\n styles: dialogContentStyles,\n };\n};\n\nexport const SubmittedModal: React.FunctionComponent = ({\n hidden,\n onDismiss,\n title,\n children,\n context,\n}: ISubmittedModalProps) => {\n return (\n
    `,\n ]),\n }}\n />\n ),\n image: instructionsImg3,\n },\n stepFour: {\n text: {context.loc('PowerBIVisual_Instructions_4')},\n image: instructionsImg4,\n },\n };\n return (\n
    \n {Object.keys(stepsMap).map((key: string, step: number) => (\n \n ))}\n
    \n );\n};\n","/* eslint-disable react/forbid-dom-props */\nimport * as React from 'react';\nimport { Checkbox, IconButton, Modal, PrimaryButton, Text } from '@fluentui/react';\nimport type { IIconProps } from '@fluentui/react';\nimport { useBoolean } from '@fluentui/react-hooks';\nimport { InstructionsModalBody } from './instructionsModalBody';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { Constants } from '@shared/utils/constants';\nimport { saveLocalStorageItem } from '@shared/utils/browserStorageUtils';\nimport { getNpsModule } from '@appsource/utils/nps';\n\nexport interface IInstructionsModalProps {\n context: ILocContext & ILocParamsContext;\n isFromDownload: boolean;\n dismissModal: () => void;\n}\n\nconst cancelIcon: IIconProps = { iconName: 'Cancel' };\n\nexport const InstructionsModal = ({ context, isFromDownload, dismissModal }: IInstructionsModalProps) => {\n const [isModalOpen, { setFalse: hideModal }] = useBoolean(true);\n const [isChecked, setIsChecked] = React.useState(false);\n\n const closeModal = () => {\n if (isFromDownload && isChecked) {\n saveLocalStorageItem(Constants.LocalStorage.dontShowInstructions, true);\n }\n hideModal();\n dismissModal();\n getNpsModule()?.ctaClicked();\n };\n\n return (\n \n
    \n
    \n {context.loc('PowerBIVisual_Instructions_Header')}\n \n
    \n\n ${context.loc('PowerBIVisual_Instructions')}`,\n ]),\n }}\n />\n\n \n\n
    \n {isFromDownload && (\n setIsChecked(!isChecked)}\n />\n )}\n \n {context.loc('Dialog_Close')}\n \n
    \n
    \n
    \n );\n};\n","import * as React from 'react';\nimport { Dialog, DialogFooter, IDialogContentProps, IDialogContentStyles, PrimaryButton } from '@fluentui/react';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\n\nconst dialogContentStyles: Partial = {\n subText: {\n whiteSpace: 'pre-line',\n },\n};\n\nconst dialogContentProps = (context: ILocContext & ILocParamsContext): IDialogContentProps => {\n return {\n title: context.loc('PowerBIVisual_DownloadSampleModal_Header'),\n subText: [\n context.loc('PowerBIVisual_DownloadSampleModal_Body_1'),\n context.loc('PowerBIVisual_DownloadSampleModal_Body_2'),\n ].join('\\n'),\n showCloseButton: true,\n styles: dialogContentStyles,\n };\n};\n\nexport interface IDownloadSampleModalProps {\n context: ILocContext & ILocParamsContext;\n dismissModal: () => void;\n}\n\nexport const DownloadSampleModal = ({ context, dismissModal }: IDownloadSampleModalProps) => {\n return (\n \n );\n};\n","import { createStore, applyMiddleware } from 'redux';\nimport type { Store } from 'redux';\nimport { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';\n\nexport function buildStore({\n combinedReducers,\n initialState,\n middleware = [],\n}: {\n combinedReducers: any;\n initialState: any;\n middleware: any;\n}): Store {\n return createStore(combinedReducers, initialState, composeWithDevTools({})(applyMiddleware(...middleware)));\n}\n","import { ICuratedSection, IHashMap } from '../Models';\n\n// hashmap that maps the passed identifier value to the index in the array\n// i.e. if we pass an array of apps with appid as the identifier\n// the returned map will be h[appid] = index in array\nexport function generateHashMap(data: any[], identifiers: string[]) {\n let hash: IHashMap = {};\n const dataLength = data.length;\n const idsLength = identifiers.length;\n\n for (let i = 0; i < dataLength; i++) {\n // data is allowed to be null\n if (data[i]) {\n for (let j = 0; j < idsLength; j++) {\n if (data[i][identifiers[j]]) {\n hash[data[i][identifiers[j]].toString().toLowerCase()] = i;\n }\n }\n }\n }\n\n return hash;\n}\n\nexport function parseCuratedSections(sections: ICuratedSection[], data: T[], hashMap: IHashMap, dataMapSubCats?: any) {\n let result: ICuratedSection[] = [];\n\n if (sections) {\n sections.forEach((section: ICuratedSection) => {\n //dataMapSubCats is only supplied when filling mac app swimlanes\n //when it's given, use it to make sure each subcategory is in datamapping\n //otherwise, continue on without checking\n if (!dataMapSubCats || dataMapSubCats.includes(section.titleId)) {\n let newItems: T[] = [];\n\n let items: any[] = section.items;\n if (items) {\n items.forEach((item: any) => {\n const id = item.id;\n const index = hashMap[id];\n\n if (index >= 0) {\n newItems.push(data[index]);\n }\n });\n }\n\n // if we can't find any data for any item in the curated section\n // let's not show the section at all\n if (newItems.length > 0) {\n let newSection: ICuratedSection = {\n titleId: section.titleId,\n items: newItems,\n };\n if (section.titleLocId) {\n newSection.titleLocId = section.titleLocId;\n }\n result.push(newSection);\n }\n }\n });\n }\n return result;\n}\n","import { updateMatchFunctions } from 'utils/filterHelpers';\nimport { getEntityRegistration } from 'utils/entityRegistration';\nimport { DataMap } from '@shared/utils/dataMapping';\n\nimport {\n Action,\n isType,\n EntityDataReceivedAction,\n CuratedDataReceivedAction,\n SearchDataReceivedAction,\n EntityDetailsReceivedAction,\n RehydrateClientStateAction,\n SearchQueryChangedAction,\n DataMapReceivedAction,\n SuggestedChangeAction,\n LoadFavouriteDataStateAction,\n RelatedAppsItemsReceivedAction,\n RelatedAppsItemsLoadingAction,\n SetEntityIdLoadingAction,\n SetEntityIdFailedAction,\n GetAppReviewsAction,\n EntityFullDataReceivedAction,\n} from '@shared/actions/actions';\nimport { TileDataReceivedAction, TileDataLoadingAction } from '@shared/actions/tileDataActions';\nimport {\n initialEntityDataState,\n IEntityDataState,\n copyState,\n deepCopyDataMap,\n IAppDataState,\n IServiceDataState,\n} from '@src/State';\nimport { Constants } from '@shared/utils/constants';\nimport { IDataItem, PricingStates } from '@shared/Models';\nimport { IUserFavouriteApp, IUserFavouriteService } from '@shared/interfaces/userFavouriteModels';\nimport { generateHashMap } from '@shared/utils/hashMapUtils';\nimport { performFilter } from '@shared/utils/filterModule';\nimport { AzureEnvironmentSettings } from '@shared/AzureEnvironmentSettings';\nimport { IDataMap } from '@shared/utils/dataMapping';\n\nfunction preserveData(oldEntity: IDataItem, newEntity: IDataItem) {\n if (!oldEntity || !newEntity) {\n return;\n }\n\n function preserveDataHelper(data: string) {\n if (oldEntity[`${data}`] || (Object.prototype.hasOwnProperty.call(oldEntity, data) && !newEntity[`${data}`])) {\n newEntity[`${data}`] = oldEntity[`${data}`];\n }\n }\n\n // preserve all the data that was already present\n // that way, we do not get flicker when loading data\n const dataList = Object.keys(oldEntity);\n dataList.forEach((data: string) => {\n preserveDataHelper(data);\n });\n}\n\nexport function buildFullSearchData(\n ids: any[],\n sourceData: Source[],\n hashMap: { [id: string]: number },\n identifier: string\n) {\n const tempArray: Source[] = [];\n\n if (ids.length > 0) {\n // we care about showing the top 5 percent, which means that we will take the first result (highest score) and\n // ignore everyone that is lower that 5% of that\n const cutoff: number = ids[0]['@search.score'] * 0.05;\n\n ids.forEach(function (id) {\n if (id['@search.score'] > cutoff) {\n const index = hashMap[id[`${identifier}`].toString().toLowerCase()];\n // make sure we actually understand the app\n if (index >= 0) {\n tempArray.push(sourceData[`${index}`]);\n }\n }\n });\n }\n\n return tempArray;\n}\n\nfunction isServiceDataState(state: IEntityDataState): string {\n return state.selectedCountry;\n}\n\nfunction isSelectedCountryValidDataMap(dataMap: IDataMap, selectedCountry: string): boolean {\n // If countries not defined in data map yet, assume it is valid country selection.\n if (\n !dataMap?.serviceGlobalFilter1?.country?.subCategoryDataMapping ||\n Object.keys(dataMap.serviceGlobalFilter1.country.subCategoryDataMapping).length === 0\n ) {\n return true;\n }\n // Make sure country in data map.\n const countries = dataMap.serviceGlobalFilter1.country.subCategoryDataMapping;\n\n return Object.keys(countries).some((countryKey) => countries[`${countryKey}`].BackendKey === selectedCountry);\n}\n\nexport function commonEntityDataReducer(\n entityType: Constants.EntityType,\n state: IEntityDataState = initialEntityDataState,\n action: Action\n): IEntityDataState {\n if (action.payload && action.payload.entityType && action.payload.entityType !== entityType) {\n return state;\n }\n\n let newState = copyState(state);\n if (isType(action, EntityDataReceivedAction)) {\n // we have received filter data from the backend\n if (!state.dataLoaded[action.payload.userSegment]) {\n updateMatchFunctions(DataMap);\n\n newState.dataList = action.payload.dataList;\n newState.count = action.payload.count;\n\n const dataMap = deepCopyDataMap(state.dataMap);\n const results = performFilter(null, action.payload.urlQuery, [], dataMap, false, false);\n\n newState.activeFilters = results.activeFilters;\n newState.dataMap = dataMap;\n\n if (state.dataList && state.dataList.length > 0) {\n state.dataList.forEach((entity) => {\n const index = newState.idMap[entity.entityId];\n if (index >= 0) {\n // preserve already fetched data that are not part of the tile data\n // i.e. detail information and pricing\n preserveData(entity, newState.dataList[`${index}`]);\n }\n });\n }\n\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n newState.dataLoaded = copyState(state.dataLoaded);\n newState.dataLoaded[action.payload.userSegment] = true;\n }\n } else if (isType(action, RehydrateClientStateAction)) {\n updateMatchFunctions(DataMap);\n\n const dataMap = deepCopyDataMap(state.dataMap);\n updateMatchFunctions(dataMap);\n newState.dataMap = dataMap;\n\n newState.idMap = generateHashMap(state.dataList, ['entityId']);\n } else if (isType(action, LoadFavouriteDataStateAction)) {\n let favouriteItems: IUserFavouriteApp[] | IUserFavouriteService[];\n if (entityType === Constants.EntityType.Service) {\n favouriteItems = action.payload.userFavouriteState.services;\n } else if (entityType === Constants.EntityType.App) {\n favouriteItems = action.payload.userFavouriteState.apps;\n }\n\n if (favouriteItems) {\n const items = favouriteItems\n .map((favouriteItem) => favouriteItem.item)\n .filter((item) => !!item && !!item.entityId && !(item.entityId.toLowerCase() in newState.idMap));\n\n newState.dataList = [...newState.dataList, ...items];\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n }\n } else if (isType(action, SuggestedChangeAction)) {\n const entityId = action.payload.item.entityId && action.payload.item.entityId.toString().toLowerCase();\n const appIndex = state.idMap[`${entityId}`];\n if (!(appIndex >= 0)) {\n newState.dataList = [...newState.dataList, action.payload.item];\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n }\n } else if (isType(action, DataMapReceivedAction)) {\n const dataMap = deepCopyDataMap(action.payload.dataMap);\n updateMatchFunctions(dataMap);\n newState.dataMap = dataMap;\n newState.dataMapLoaded = true;\n } else if (isType(action, TileDataLoadingAction)) {\n const dataMap = deepCopyDataMap(state.dataMap);\n const results = performFilter(null, action.payload.urlQuery, [], dataMap, false, false);\n\n newState.dataMap = dataMap;\n newState.activeFilters = results.activeFilters;\n } else if (isType(action, TileDataReceivedAction)) {\n if (state.dataList) {\n newState = {\n ...newState,\n ...action.payload.entityData,\n };\n\n newState.tileDataRequestId = action.payload.requestId;\n\n let currentEntityDetail: IDataItem = null;\n // If there is only one item in appData, it means the app detail is loaded at server side.\n // So when appData is loaded with this lazy loaded tile data, we need to make sure the original appData item\n // with detail data is still preserved.\n if (state.dataList.length === 1) {\n currentEntityDetail = state.dataList[0];\n }\n\n for (let i = 0, len = newState.dataList.length; i < len; i++) {\n const entityId =\n newState.dataList[`${i}`] && newState.dataList[`${i}`].entityId && newState.dataList[`${i}`].entityId.toLowerCase();\n\n if (\n currentEntityDetail &&\n newState.dataList[`${i}`] &&\n newState.dataList[`${i}`].entityId === currentEntityDetail.entityId\n ) {\n newState.dataList[`${i}`] = currentEntityDetail;\n }\n // preserve already fetched data that are not part of the tile data\n // i.e. detail information and pricing\n const indexInOldState = state.dataMap?.[`${entityId}`];\n // this is not a guaranteed 1to1 list\n // => so make sure that we have the new app in the old list\n if (indexInOldState >= 0) {\n preserveData(state.dataList[`${indexInOldState}`], newState.dataList[`${i}`]);\n }\n }\n\n // Services can have completely diff filters than apps hence\n // update the map again based on the services data list to dynamically light up services filters\n if (action.payload.entityType === Constants.EntityType.Service) {\n updateMatchFunctions(DataMap);\n }\n\n const dataMap = deepCopyDataMap(state.dataMap);\n const results = performFilter(null, action.payload.urlQuery, [], dataMap, false, false);\n\n newState.activeFilters = results.activeFilters;\n newState.dataMap = dataMap;\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n\n if (action.payload.entityType === Constants.EntityType.App) {\n (newState as IAppDataState).pricingPayload = (state as IAppDataState).pricingPayload;\n }\n\n if (isServiceDataState(state) && isServiceDataState(newState)) {\n if (isSelectedCountryValidDataMap(newState.dataMap, state.selectedCountry)) {\n newState.selectedCountry = state.selectedCountry;\n } else {\n newState.selectedCountry = AzureEnvironmentSettings.defaultCountryCode.toUpperCase();\n }\n newState.selectedRegion = state.selectedRegion;\n }\n }\n } else if (isType(action, SetEntityIdLoadingAction)) {\n newState.entityIdLoading = action.payload.entityId;\n } else if (isType(action, SetEntityIdFailedAction)) {\n newState.entityIdFailed = action.payload.entityId;\n } else if (isType(action, RelatedAppsItemsLoadingAction)) {\n newState.relatedAppsItems = {\n entityId: action.payload.entityId,\n items: { saasLinkingItems: [], suggestedItems: [], linkedAddIns: [] },\n };\n } else if (isType(action, RelatedAppsItemsReceivedAction)) {\n newState.relatedAppsItems = {\n entityId: action.payload.entityId,\n items: action.payload.relatedAppsItems,\n };\n } else if (isType(action, GetAppReviewsAction)) {\n const { markedAsHelpful: prevMarkAsHelpful } = newState.appReviewsData;\n newState.appReviewsData = {\n ...newState.appReviewsData,\n entityId: action.payload.entityId,\n reviews: action.payload.reviews,\n isPurchased: action.payload.isPurchased,\n userReview: action.payload.userReview,\n markedAsHelpful: {\n ...prevMarkAsHelpful,\n },\n };\n } else if (isType(action, EntityDetailsReceivedAction)) {\n let index = -1;\n\n // valid error situation: we got back no appdetails\n if (action.payload.entityDetails) {\n index = state.idMap[action.payload.entityDetails.entityId.toString().toLowerCase()];\n const startingPrice = action.payload.entityDetails.startingPrice;\n if (startingPrice) {\n if (!startingPrice.pricingData) {\n startingPrice.pricingData = PricingStates.NoPricingData;\n }\n } else {\n action.payload.entityDetails.startingPrice = {\n pricingData: PricingStates.Loading, // Loading is the default state at server side.\n };\n }\n }\n\n if (index >= 0) {\n let newData: IDataItem = null;\n\n // we will now deal with app detail information not loading\n if (action.payload.entityDetails === null) {\n // todo: Object.assign({}, state.appData[`${index}`]); cannot work or we need to polyfill it\n newData = JSON.parse(JSON.stringify(state.dataList[`${index}`]));\n newData.detailLoadFailed = true;\n } else {\n // find the app to replace\n newData = action.payload.entityDetails;\n\n // Here we need to preserve the starting price and pricing information since we may get app data again from raw data cache which doesn't contain price data.\n // If we don't reserve starting price and pricing information retreived earlier at client side, the they will be lost in the new data.\n preserveData(state.dataList[`${index}`], newData);\n\n if (newData.startingPrice && newData.startingPrice.pricingBitmask) {\n // make sure we create/append the global filters bitmask to the app data\n Object.keys(newData.startingPrice.pricingBitmask).forEach((key) => {\n // if the bitmask has not been created yet\n // let's create it and set it to 0\n if (!newData[`${key}`]) {\n newData[`${key}`] = 0;\n }\n newData[`${key}`] |= newData.startingPrice.pricingBitmask[`${key}`];\n });\n }\n }\n const newDataList = state.dataList\n .slice(0, index)\n .concat(newData)\n .concat(state.dataList.slice(index + 1));\n newState.dataList = newDataList;\n } else {\n // This will be called only once when there is no appdata in the server cache.\n // This called when app details page is the landing page and it is the first page to be rendered by the server.\n // If we open home page before the app details page is opened, then the appdata is populated into the cache.\n newState.dataList = [...newState.dataList, action.payload.entityDetails];\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n\n if (\n Object.prototype.hasOwnProperty.call(newState, 'selectedCountry') &&\n action.payload.entityDetails?.detailInformation?.countryRegion\n ) {\n (newState as IServiceDataState).selectedCountry = action.payload.entityDetails.detailInformation.countryRegion;\n }\n }\n } else if (isType(action, CuratedDataReceivedAction)) {\n // we have received curated data from the backend\n if (!state.curatedDataLoaded[action.payload.userSegment]) {\n newState.curatedData = action.payload.curatedData as any;\n\n const newCuratedDataLoadedState = copyState(state.curatedDataLoaded);\n newCuratedDataLoadedState[action.payload.userSegment] = true;\n newState.curatedDataLoaded = newCuratedDataLoadedState;\n }\n } else if (isType(action, EntityFullDataReceivedAction)) {\n let index = -1;\n\n if (action.payload.entityDetails) {\n index = state.idMap[action.payload.entityDetails.entityId.toString().toLowerCase()];\n }\n\n if (index >= 0) {\n const newData: IDataItem = action.payload.entityDetails;\n const newDataList = [...state.dataList];\n newDataList.splice(index, 1, newData);\n newState.dataList = newDataList;\n } else {\n newState.dataList = [...newState.dataList, action.payload.entityDetails];\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n }\n } else if (isType(action, SearchQueryChangedAction)) {\n newState.subsetSearchQuery = action.payload.performedQuery;\n } else if (isType(action, SearchDataReceivedAction)) {\n newState.subsetData = [];\n newState.subsetSearchQuery = action.payload.performedQuery;\n const dataList = action.payload.entityIdData;\n if (dataList.length <= 0 || !state.dataLoaded) {\n return newState;\n }\n newState.subsetData = buildFullSearchData(\n dataList,\n state.dataList,\n state.idMap,\n getEntityRegistration(entityType).identifier\n );\n } else {\n return state;\n }\n return newState;\n}\n","import { Constants } from '../../shared/utils/constants';\nimport { IAppDataItem, IAppDetailInformation } from '../../shared/Models';\nimport { DataMap, IProductValue } from '../../shared/utils/dataMapping';\n\nconst findItem = (arr: any[], callback: (item: any) => boolean): any => {\n for (const item of arr) {\n if (callback(item)) {\n return item;\n }\n }\n\n return null;\n};\n\n// TODO: follow up with Jamie Warner's team to figure out how they can add this to their list\nconst appIdsToIgnore = [\n '89230731-57f8-48c1-9f19-4307de3b7dbd',\n 'dc0bfbad-9508-40b3-bff7-fd86639bea78',\n 'b019fa14-15cf-4baf-bc0f-0b4b07b2a2dc',\n '09ea17d2-a321-4021-94b9-bb8949fd19b7',\n '0d2d1835-934a-486d-b712-b898e02a4353',\n '4f1864d0-5c45-49e9-a721-8bf017e6d905',\n 'e334cc9e-62f7-41b0-8e7f-6e1306541c8e',\n '835677b9-83ed-493f-ae58-da107b750344',\n];\n\nconst parsePBIApp = (app: any, index: number): IAppDataItem => {\n let iconURL = app.iconUrl;\n let largeIcon = app.iconUrl;\n\n // One of the items in the 'images' array has key 'main' -- this is the large icon\n if (app.images) {\n let mainIndex = -1;\n for (let i = 0, len = app.images.length; i < len; i++) {\n const img = app.images[`${i}`];\n if (img.key === 'main') {\n largeIcon = img.url;\n mainIndex = i;\n } else {\n img.ImageName = img.key;\n img.ImageUri = img.url;\n delete img.key;\n delete img.url;\n }\n }\n\n if (mainIndex !== -1) {\n app.images.splice(mainIndex, 1);\n }\n }\n\n if (!iconURL || iconURL.charAt(iconURL.length - 1) === '/') {\n iconURL = '/images/defaultIcon.png';\n }\n\n if (!largeIcon) {\n largeIcon = '/images/defaultIcon.png';\n }\n\n // More PBI fields: emailAddress, description, images, learnMoreUrl,\n // publishTime, version\n\n let details: any = null;\n if (app.additionalMetadata) {\n try {\n details = JSON.parse(app.additionalMetadata);\n if (details) {\n const industryNumbers: number[] = [];\n for (const industry of details.industries) {\n for (const industryMap in DataMap.industries) {\n if (DataMap.industries[`${industryMap}`].Title === industry) {\n industryNumbers.push(DataMap.industries[`${industryMap}`].FilterID);\n break;\n }\n }\n }\n if (industryNumbers.length > 0) {\n details.industries = industryNumbers;\n }\n\n // convert the collateralDocuments format =(\n if (details.collateralDocuments) {\n for (let i = 0, len = details.collateralDocuments.length; i < len; i++) {\n const doc = details.collateralDocuments[`${i}`];\n doc.DocumentName = doc.documentName;\n doc.DocumentUri = doc.documentUrl;\n delete doc.documentName;\n delete doc.documentUrl;\n }\n }\n }\n } catch (e) {\n details = null;\n }\n }\n\n const detailInformation = {\n // if there is no short description -- use the description field only for shortDescription\n // this is to prevent a duplicated despcription from showing on the detail page\n Description: app.shortDescription ? app.description : null,\n LargeIconUri: largeIcon,\n Subcategories: [] as string[],\n Keywords: details && details.keyWords ? details.keyWords : ([] as string[]),\n HelpLink: app.learnMoreUrl,\n SupportLink: null,\n Images: app.images,\n DemoVideos: [],\n CollateralDocuments: details && details.collateralDocuments ? details.collateralDocuments : null,\n AdditionalPurchasesRequired: false,\n Countries: [],\n LanguagesSupported: [],\n AppVersion: app.version,\n PrivacyPolicyUrl: details && details.privacyPolicyUrl ? details.privacyPolicyUrl : null,\n PlatformVersion: null,\n ReleaseDate: details && details.releaseDate ? details.releaseDate : app.publishTime,\n } as IAppDetailInformation;\n\n let friendlyURL: string = null;\n if (app.url) {\n const nameStart = app.url.lastIndexOf('/') + 1;\n if (nameStart <= app.url.length) {\n friendlyURL = app.url.substr(nameStart).toLowerCase();\n }\n }\n const productMask: IProductValue = {\n [DataMap.products.PowerBI.FilterGroup]: DataMap.products.PowerBI.FilterID,\n };\n const appDataItem = {\n index,\n entityId: app.applicationId,\n title: app.applicationName,\n publisher: app.publishedBy,\n shortDescription: app.shortDescription ? app.shortDescription : app.description,\n builtFor: 'Power BI',\n iconURL,\n actionString: Constants.ActionStrings.Get,\n ctaTypes: [Constants.CTAType.Get],\n primaryProduct: productMask,\n products: productMask,\n industries: details && details.industries ? details.industries : 0,\n categories: details && details.categories ? details.categories : 0,\n handoffURL: app.url,\n privateApp: app.category === 'private',\n ranking: app.ranking,\n friendlyURL,\n licenseTermsUrl: details && details.licenseTermsUrl ? details.licenseTermsUrl : '',\n detailInformation,\n filtermatch: true,\n detailLoadFailed: false,\n };\n\n return appDataItem;\n};\n\nexport const parsePBIApps = (appData: any): IAppDataItem[] => {\n const appList: IAppDataItem[] = [];\n\n for (let i = 0; i < appData.length; i++) {\n const match = findItem(appIdsToIgnore, (appid: string) => {\n return appid === appData[`${i}`].applicationId;\n });\n\n if (match) {\n continue;\n }\n\n // only get org apps\n const pbiCategory = appData[`${i}`].PBIContentProviderCategory;\n if (pbiCategory !== 1 && pbiCategory !== 3) {\n appList.push(parsePBIApp(appData[`${i}`], i));\n }\n }\n return appList;\n};\n","import { Constants } from '../../shared/utils/constants';\nimport { IAppDataItem, IAppDetailInformation } from '../../shared/Models';\nimport { DataMap, IProductValue } from '../../shared/utils/dataMapping';\nimport { getProductByUrlKey } from '@shared/utils/appUtils';\n\ninterface IPartnerApp {\n id: string;\n appActionType: string;\n applicationId: string;\n categories: number;\n details: IPartnerDetailInformation;\n enableLeadSharing: boolean;\n flightCodes: string[];\n handOffURL: string;\n iconBackgroundColor: string;\n industries: number;\n products: IProductValue;\n licenseTermsUrl: string;\n privateApp: boolean;\n publisher: string;\n shortDescription: string;\n smallIconUri: string;\n title: string;\n}\n\ninterface IPartnerDocument {\n documentName: string;\n documentUri: string;\n}\n\ninterface IPartnerVideo {\n thumbnailUrl: string;\n videoLink: string;\n videoName: string;\n}\n\ninterface IPartnerImage {\n imageName: string;\n imageUri: string;\n}\n\ninterface IPartnerDetailInformation {\n additionalPurchasesRequired: boolean;\n appVersion: string;\n collateralDocuments: IPartnerDocument[];\n countries: string[];\n demoVideos: IPartnerVideo[];\n description: string;\n helpLink: string;\n images: IPartnerImage[];\n keywords: string[];\n languagesSupported: string[];\n largeIconUri: string;\n platformVersion: string;\n privacyPolicyUrl: string;\n releaseDate: string;\n subCategories: string[];\n supportLink: string;\n}\n\nconst convertPartnerDocument = (d: IPartnerDocument) => ({\n DocumentName: d.documentName,\n DocumentUri: d.documentUri,\n});\n\nconst convertPartnerImage = (i: IPartnerImage) => ({\n ImageName: i.imageName,\n ImageUri: i.imageUri,\n});\n\nconst convertPartnerVideo = (v: IPartnerVideo) => ({\n ThumbnailURL: v.thumbnailUrl,\n VideoLink: v.videoLink,\n VideoName: v.videoName,\n});\n\nfunction attemptMap(a: T[], f: (v: T) => any) {\n if (a) {\n return a.map(f);\n } else {\n return [];\n }\n}\n\nexport const parsePartnerApp = (app: IPartnerApp, product: string): IAppDataItem => {\n let detailInformation: IAppDetailInformation = null;\n if (app.details) {\n const d = app.details;\n detailInformation = {\n AdditionalPurchasesRequired: d.additionalPurchasesRequired,\n AppVersion: d.appVersion,\n CollateralDocuments: attemptMap(d.collateralDocuments, convertPartnerDocument),\n Countries: d.countries,\n DemoVideos: attemptMap(d.demoVideos, convertPartnerVideo),\n Description: d.description,\n HelpLink: d.helpLink,\n Images: attemptMap(d.images, convertPartnerImage),\n Keywords: d.keywords,\n LanguagesSupported: d.languagesSupported,\n LargeIconUri: d.largeIconUri,\n PlatformVersion: d.platformVersion,\n PrivacyPolicyUrl: d.privacyPolicyUrl,\n ReleaseDate: d.releaseDate,\n Subcategories: d.subCategories,\n SupportLink: d.supportLink,\n };\n }\n\n let builtFor = '';\n let productMask: IProductValue;\n\n const productInfo = getProductByUrlKey({ urlKey: product });\n if (productInfo?.UrlKey) {\n builtFor = productInfo.LongTitle;\n productMask = {\n [productInfo.FilterGroup]: productInfo.FilterID,\n };\n }\n\n const appDataItem: IAppDataItem = {\n entityId: app.applicationId,\n title: app.title,\n publisher: app.publisher,\n shortDescription: app.shortDescription,\n builtFor, // TODO -- convert ProductEnum to whatever builtFor string\n iconURL: app.smallIconUri,\n iconBackgroundColor: app.iconBackgroundColor,\n actionString: Constants.ActionStrings.Get,\n ctaTypes: [Constants.CTAType.Get],\n primaryProduct: productMask,\n privateApp: app.privateApp,\n products: app.products ? app.products : productMask,\n industries: app.industries ? app.industries : 0,\n categories: app.categories ? app.categories : 0, // TODO Support L2 categories\n handoffURL: app.handOffURL,\n leadgenEnabled: app.enableLeadSharing,\n licenseTermsUrl: app.licenseTermsUrl,\n detailInformation,\n filtermatch: true,\n detailLoadFailed: false,\n };\n\n return appDataItem;\n};\n\nexport const parsePartnerApps = (appData: IPartnerApp[], embedHost: string): IAppDataItem[] => {\n let product = embedHost;\n if (embedHost === DataMap.products.Dynamics365.UrlKey) {\n product = DataMap.products.PowerApps.UrlKey;\n }\n const appList = appData.map((a) => parsePartnerApp(a, product));\n\n // TODO: sort?\n // PowerBI Apps are wanting to be sorted alphabetically\n /* appList = appList.sort((a: IAppDataItem, b: IAppDataItem) => {\n let aTitle = a.title.toLowerCase();\n let bTitle = b.title.toLowerCase();\n return aTitle > bTitle ? 1 : ( aTitle < bTitle ? -1 : 0 );\n }); */\n\n return appList;\n};\n","import { Action, ActionType, isType } from '@shared/actions/actions';\nimport { IAppCheckoutState, initialCheckoutState, copyState } from '@src/State';\nimport {\n SetItemInCartAction,\n UpdateCustomerAction,\n SetActiveCustomerAction,\n SetPublisherTokenResponse,\n ClearCheckoutState,\n SetCustomersAction,\n ErrorAction,\n ClearErrorAction,\n SetActiveStep,\n SetReceiptAction,\n SetBillingAddressAction,\n SetBillingTerm,\n SetRecurringBilling,\n SetSeatNumber,\n SetProduct,\n SetTermId,\n FetchingProductAction,\n SetCheckoutSource,\n SetCheckoutId,\n} from '@shared/actions/checkoutActions';\nimport { ICartItem, CreateNewCartActionType } from '@shared/Models';\n\nconst newCartActions = [SetItemInCartAction, SetBillingTerm, SetRecurringBilling, SetSeatNumber, SetTermId];\n\nexport default function checkoutReducer(state: IAppCheckoutState = initialCheckoutState, action: Action): IAppCheckoutState {\n const newState = copyState(state);\n\n if (isType(action, SetItemInCartAction)) {\n // currently we only allow 1 item in cart, so override the existing item\n newState.items = newState.items.slice();\n if (newState.items.length > 0) {\n newState.items.splice(0, 1, action.payload);\n } else {\n newState.items.splice(0, 0, action.payload);\n }\n } else if (isType(action, SetCheckoutSource)) {\n return { ...newState, source: action.payload.source };\n } else if (isType(action, SetProduct)) {\n const indexToUpdate = newState.items.findIndex((cartItem: ICartItem) => cartItem.entityId === action.payload.id);\n if (indexToUpdate >= 0) {\n newState.items[`${indexToUpdate}`].product = action.payload.product;\n }\n newState.productFetched = true;\n } else if (isType(action, FetchingProductAction)) {\n newState.productFetched = false;\n } else if (isType(action, SetBillingTerm)) {\n const indexToUpdate = newState.items.findIndex((cartItem: ICartItem) => cartItem.id === action.payload.id);\n if (indexToUpdate >= 0) {\n newState.items = state.items.map((item) => ({ ...item, termDuration: action.payload.termDuration }));\n }\n } else if (isType(action, SetTermId)) {\n const indexToUpdate = newState.items.findIndex((cartItem: ICartItem) => cartItem.id === action.payload.id);\n if (indexToUpdate >= 0) {\n newState.items = state.items.map((item) => ({ ...item, termId: action.payload.termId }));\n }\n } else if (isType(action, SetSeatNumber)) {\n const indexToUpdate = newState.items.findIndex((cartItem: ICartItem) => cartItem.id === action.payload.id);\n if (indexToUpdate >= 0) {\n newState.items[`${indexToUpdate}`].quantity = action.payload.numSeats;\n newState.items[`${indexToUpdate}`].quantityString = action.payload.numSeats.toString();\n }\n } else if (isType(action, SetRecurringBilling)) {\n const indexToUpdate = newState.items.findIndex((cartItem: ICartItem) => cartItem.id === action.payload.id);\n if (indexToUpdate >= 0) {\n newState.items[`${indexToUpdate}`].recurringBilling = action.payload.recurringBilling;\n }\n } else if (isType(action, UpdateCustomerAction)) {\n newState.activeCustomer = {\n ...newState.activeCustomer,\n address: action.payload,\n };\n } else if (isType(action, SetActiveStep)) {\n newState.currentStep = action.payload;\n } else if (isType(action, SetCustomersAction)) {\n newState.customers = action.payload;\n } else if (isType(action, SetActiveCustomerAction)) {\n // Implies customer identity has been changed to another customer identity\n newState.activeCustomer = action.payload;\n } else if (isType(action, SetPublisherTokenResponse)) {\n newState.publisherToken = action.payload;\n } else if (isType(action, ClearCheckoutState)) {\n // clear everything except the activeCustomer and selectedPi information\n newState.items = [];\n newState.retryAfter = null;\n newState.publisherToken = null;\n } else if (isType(action, SetReceiptAction)) {\n newState.receipt = action.payload;\n } else if (isType(action, SetBillingAddressAction)) {\n newState.billingAddress = action.payload;\n } else if (isType(action, SetCheckoutId)) {\n newState.checkoutId = action.payload.checkoutId;\n } else if (isType(action, ClearErrorAction)) {\n newState.error = false;\n } else if (isType(action, ErrorAction)) {\n newState.error = true;\n }\n // Upon incrementing purchaseAttempt, a new cart will generate and checkout will reload\n if (newCartActions.some((newCartAction: ActionType) => isType(action, newCartAction))) {\n newState.purchaseAttempt++;\n }\n\n return newState;\n}\n","import { copyState, IServiceDataState, initialServiceDataState, ServiceCountries } from '@src/State';\nimport { Constants } from '@shared/utils/constants';\nimport {\n Action,\n CountriesDataReceivedAction,\n isType,\n RecommendedTrendingServiceReceivedAction,\n RecommendedWhatsNewServiceReceivedAction,\n SetEntityDataRegionFiltersAction,\n} from '@shared/actions/actions';\nimport { commonEntityDataReducer } from '@shared/reducers/commonEntityDataReducer';\nimport { AzureEnvironmentSettings } from '@shared/AzureEnvironmentSettings';\n\nfunction isSelectedCountryValid(countriesList: ServiceCountries, selectedCountry: string): boolean {\n // If countries list not defined yet, this is valid country selection.\n // We will change the selected country to the default one if we get list with out it.\n if (!countriesList || Object.keys(countriesList).length === 0 || !selectedCountry) {\n return true;\n }\n return selectedCountry in countriesList;\n}\n\nexport default function serviceDataReducer(\n state: IServiceDataState = initialServiceDataState,\n action: Action\n): IServiceDataState {\n const newState = copyState(state);\n\n if (isType(action, CountriesDataReceivedAction)) {\n newState.countriesList = action.payload.countries;\n\n if (!isSelectedCountryValid(action.payload.countries, state.selectedCountry)) {\n newState.selectedCountry = AzureEnvironmentSettings.defaultCountryCode.toUpperCase();\n }\n } else if (isType(action, SetEntityDataRegionFiltersAction)) {\n if (state.selectedCountry !== action.payload.countryCode || state.selectedRegion !== action.payload.regionCode) {\n if (isSelectedCountryValid(state.countriesList, action.payload.countryCode)) {\n newState.selectedCountry = action.payload.countryCode;\n }\n newState.selectedRegion = action.payload.regionCode;\n }\n } else if (isType(action, RecommendedWhatsNewServiceReceivedAction)) {\n newState.recommendedServices.whatsNewService = action.payload;\n } else if (isType(action, RecommendedTrendingServiceReceivedAction)) {\n newState.recommendedServices.trendingService = action.payload;\n } else {\n return commonEntityDataReducer(Constants.EntityType.Service, state, action) as IServiceDataState;\n }\n return newState;\n}\n","/* istanbul ignore file */\n\nimport { combineReducers } from 'redux';\n\nimport { IState } from '../../State';\nimport searchReducer from '@shared/reducers/searchReducer';\nimport modalReducer from '@shared/reducers/modalReducer';\nimport appDataReducer from '@shared/reducers/appDataReducer';\nimport userDataReducer from '@shared/reducers/userDataReducer';\nimport configReducer from '@shared/reducers/configReducer';\nimport dynamicCampaignReducer from '@shared/reducers/dynamicCampaignReducer';\nimport userFavouriteReducer from '@shared/reducers/userFavouriteReducer';\nimport checkoutReducer from '@shared/reducers/checkoutReducer';\nimport partnersReducer from '@shared/reducers/partnersReducer';\nimport cloudsIndustryDataReducer from '@shared/reducers/cloudsIndustryDataReducer';\nimport serviceDataReducer from '@shared/reducers/serviceDataReducer';\n\n// ordering matters here, we first update the filters and then the app data\nexport default combineReducers({\n search: searchReducer,\n modal: modalReducer,\n apps: appDataReducer,\n cloudsIndustry: cloudsIndustryDataReducer,\n users: userDataReducer,\n config: configReducer,\n dynamicCampaign: dynamicCampaignReducer,\n userFavourite: userFavouriteReducer,\n checkout: checkoutReducer,\n partners: partnersReducer,\n services: serviceDataReducer,\n});\n","import {\n Action,\n isType,\n SearchboxInputChangedAction,\n LiveSearchboxFilterAction,\n SearchSortingOptionChangedAction,\n FilteredSearchResultsReceivedAction,\n PartnerSearchResultsReceivedAction,\n ShowingResultsForReceviedAction,\n SearchSuggestionsRequestId,\n} from '@shared/actions/actions';\nimport { ISearchState, initialSearchState, copyState } from '@src/State';\n\nexport default function searchReducer(state: ISearchState = initialSearchState, action: Action): ISearchState {\n const newState = copyState(state);\n if (isType(action, SearchboxInputChangedAction)) {\n // search box is being searched\n newState.searchText = action.payload.searchString;\n\n // leave the search results be, since they will be updated when new search results come in\n // however, when the dropdown is emptied out, clear the results.\n if (newState.searchText.length === 0) {\n newState.appSearchResults = [];\n newState.servicesSearchResults = [];\n newState.ecaSearchResults = [];\n }\n\n // make sure we only react to the latest request\n newState.searchIdCurrentlyOngoing = state.searchIdCurrentlyOngoing + 1;\n } else if (isType(action, LiveSearchboxFilterAction)) {\n newState.searchText = action.payload.searchText;\n } else if (isType(action, FilteredSearchResultsReceivedAction)) {\n if (newState.searchIdCurrentlyOngoing !== action.payload.searchid) {\n return state;\n } else {\n const {\n appSearchResults,\n servicesSearchResults,\n cloudsIndustrySearchResults,\n appsCount,\n servicesCount,\n cloudsIndustryCount,\n requestId,\n } = action.payload;\n\n return {\n ...newState,\n filteredAppSearchResults: appSearchResults,\n filteredServicesSearchResults: servicesSearchResults,\n filteredCloudsIndustrySearchResults: cloudsIndustrySearchResults,\n appsCount,\n servicesCount,\n cloudsIndustryCount,\n requestId,\n };\n }\n } else if (isType(action, PartnerSearchResultsReceivedAction)) {\n newState.partnerSearchResults = action.payload.partnerSearchResults;\n } else if (isType(action, ShowingResultsForReceviedAction)) {\n const { previousSearchText = '', showingResultsFor = '' } = action.payload;\n\n return {\n ...newState,\n showingResultsFor,\n previousSearchText,\n searchText: previousSearchText,\n };\n } else if (isType(action, SearchSortingOptionChangedAction)) {\n newState.searchSortingOption = action.payload.searchSortingOption;\n } else if (isType(action, SearchSuggestionsRequestId)) {\n newState.suggestionsRequestId = action.payload.suggestionsRequestId;\n } else {\n return state;\n }\n\n return newState;\n}\n","import {\n Action,\n isType,\n ModalAction,\n VideoModalAction,\n RatingAction,\n DriveModalAction,\n IFieldHubModalAction,\n IDisclaimerModalAction,\n MediaModalAction,\n InstructionsModalAction,\n EntityReviewCommentModalAction,\n EntityReviewMarkAsHelpfulModalAction,\n} from './../actions/actions';\nimport { IModalState, initialModalState, copyState } from './../../State';\nimport { Constants } from '../utils/constants';\nimport { IVideo, IReviewPayload, IDrive, IFieldHub, IDisclaimer, IInstructions } from './../Models';\n\nexport default function modalReducer(state: IModalState = initialModalState, action: Action): IModalState {\n const newState = copyState(state);\n\n if (isType(action, ModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.entityId = action.payload.entityId ? action.payload.entityId : '';\n newState.modalId = action.payload.modalId ? action.payload.modalId : Constants.ModalType.NoModalShow;\n newState.options = action.payload.options;\n newState.entityType = action.payload.entityType;\n newState.onModalDismiss = action.payload.onModalDismiss;\n newState.disableDismissModal = action.payload.disableDismissModal;\n newState.isOpenedFromPDP = action.payload.isOpenedFromPDP;\n newState.payload = null;\n } else if (isType(action, VideoModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.Video;\n const videoPayload: IVideo = {\n videoLink: action.payload.videoUrl,\n thumbnailURL: action.payload.videoThumbnail,\n videoName: action.payload.videoName,\n };\n newState.payload = videoPayload;\n } else if (isType(action, MediaModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.MediaModal;\n newState.payload = action.payload.payload;\n } else if (isType(action, DriveModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.Drive;\n const drivePayload: IDrive = {\n urlLink: action.payload.driveUrl,\n };\n newState.payload = drivePayload;\n } else if (isType(action, IFieldHubModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.Iframe;\n const iframePayload: IFieldHub = {\n reportName: action.payload.reportName,\n };\n newState.payload = iframePayload;\n } else if (isType(action, IDisclaimerModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.Disclaimer;\n const payload: IDisclaimer = {\n title: action.payload.title,\n description: action.payload.description,\n };\n newState.payload = payload;\n } else if (isType(action, RatingAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.Rating;\n const reviewPayload: IReviewPayload = {\n showModal: true,\n app: action.payload.app,\n accessKey: action.payload.accessKey,\n ctaType: action.payload.ctaType,\n callback: action.payload.callback,\n };\n newState.payload = reviewPayload;\n } else if (isType(action, EntityReviewCommentModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.ReviewComment;\n newState.payload = { ...action.payload };\n } else if (isType(action, EntityReviewMarkAsHelpfulModalAction)) {\n newState.showModal = action.payload.showModal;\n newState.modalId = Constants.ModalType.ReviewMarkAsHelpful;\n newState.payload = { ...action.payload };\n } else if (isType(action, InstructionsModalAction)) {\n newState.modalId = Constants.ModalType.Instructions;\n newState.showModal = action.payload.showModal;\n const instructionsPayload: IInstructions = {\n isFromDownload: action.payload.isFromDownload,\n };\n newState.payload = instructionsPayload;\n } else {\n return state;\n }\n\n return newState;\n}\n","import {\n Action,\n isType,\n PartnerAppDataReceivedAction,\n PowerBIDataReceivedAction,\n AppsPricingDataReceivedAction,\n FirstPartyPricingDataReceivedAction,\n AppDetailPricingReceivedAction,\n RelatedAppsItemsPricingDataReceivedAction,\n AppPricingDataReceivedAction,\n EntityReviewCommentsReceivedAction,\n EntityReviewCommentsCleanupAction,\n EntityReviewCommentDeleteResult,\n EntityReviewCommentUpdateResult,\n EntityReviewCommentsLoadingAction,\n EntityReviewCommentsInitializeStateAction,\n EntityReviewCommentsUpdateTotalCommentCountAction,\n EntityReviewCommentCreateResult,\n EntityReviewCommentsUnloadPagesAction,\n EntityMarkedAsHelpfulReviewUpdateListAction,\n EntityLinkedInProductGroupUpdateAction,\n EntityReviewSetIsHelpfulAction,\n EntityReviewSetMarkAsHelpfulCount,\n EntityReviewSetIsHelpfulLoadingAction,\n SetRecommenededSizes,\n LoadingRecommenededSizes,\n EntityUserCommentedReviewIdsUpdateListAction,\n RecommendedWhatsNewAppsReceivedAction,\n RecommendedTrendingAppsReceivedAction,\n RecommendedAppsItemsPricingDataReceivedAction,\n RecommendedMicrosoft365WhatsNewAppsReceivedAction,\n RecommendedMicrosoft365TrendingAppsReceivedAction,\n} from '@shared/actions/actions';\nimport { commonEntityDataReducer } from './commonEntityDataReducer';\nimport { IAppDataState, initialAppDataState, copyState, deepCopyDataMap } from './../../State';\nimport { Constants } from './../utils/constants';\nimport { loadAppPricing } from './../utils/pricing';\nimport { parsePBIApps } from '../../embed/reducers/pbiAppDataReducer';\nimport { parsePartnerApps } from '../../embed/reducers/partnerAppDataReducer';\nimport { IAppDataItem, ITelemetryData } from '@shared/Models';\nimport { SpzaInstrumentService } from '../services/telemetry/spza/spzaInstrument';\nimport { generateHashMap } from '../utils/hashMapUtils';\nimport { updateMatchFunctions } from 'utils/filterHelpers';\nimport { getFuturePricesInformation } from '@shared/utils/futurePricesUtils';\nimport {\n appendReviewCommentsToState,\n calculateTotalReviewCommentsPagesCount,\n combineDeletedComments,\n createInitialReviewCommentsState,\n} from '@shared/utils/reviewsUtils';\nimport { IAppReviewCommentsState } from '@shared/interfaces/reviews/comments';\nimport { take, pick, values, map, differenceBy } from 'lodash-es';\nimport { logger } from '@src/logger';\nimport { getProductByUrlKey } from '@shared/utils/appUtils';\n\nexport default function appDataReducer(state: IAppDataState = initialAppDataState, action: Action): IAppDataState {\n const newState = copyState(state);\n const newCommonState = commonEntityDataReducer(Constants.EntityType.App, state, action) as IAppDataState;\n if (isType(action, AppPricingDataReceivedAction)) {\n if (!action.payload?.pricingPayload?.pricingData) {\n return state;\n }\n\n newState.dataList = newState.dataList.map((app: IAppDataItem) => {\n if (app.entityId?.toLowerCase() === action.payload.entityId?.toLowerCase()) {\n return loadAppPricing(app, action.payload.pricingPayload.pricingData);\n }\n\n return app;\n });\n } else if (isType(action, RelatedAppsItemsPricingDataReceivedAction)) {\n if (!action.payload.pricingData || Object.keys(action.payload.pricingData).length === 0) {\n return state;\n }\n\n if (state.relatedAppsItems && state.relatedAppsItems.items) {\n let updatedLinkedAddIns: IAppDataItem[] = null;\n let updatedSaasLinkingItems: IAppDataItem[] = null;\n let updatedSuggestedItems: IAppDataItem[] = null;\n let updatedDataList: IAppDataItem[] = null;\n\n if (state.relatedAppsItems.items.saasLinkingItems) {\n updatedSaasLinkingItems = map(state.relatedAppsItems.items.saasLinkingItems, (app: IAppDataItem) =>\n loadAppPricing(app, action.payload.pricingData)\n );\n }\n\n if (state.relatedAppsItems.items.suggestedItems) {\n updatedSuggestedItems = map(state.relatedAppsItems.items.suggestedItems, (app: IAppDataItem) =>\n loadAppPricing(app, action.payload.pricingData)\n );\n }\n\n if (state.relatedAppsItems.items.linkedAddIns) {\n updatedLinkedAddIns = map(state.relatedAppsItems.items.linkedAddIns, (app: IAppDataItem) =>\n loadAppPricing(app, action.payload.pricingData)\n );\n\n updatedDataList = [\n ...state.dataList,\n ...differenceBy(state.relatedAppsItems.items.linkedAddIns, state.dataList, 'entityId'),\n ];\n }\n\n return {\n ...state,\n relatedAppsItems: {\n ...state.relatedAppsItems,\n items: {\n linkedAddIns: updatedLinkedAddIns ?? state.relatedAppsItems.items.linkedAddIns,\n suggestedItems: updatedSuggestedItems ?? state.relatedAppsItems.items.suggestedItems,\n saasLinkingItems: updatedSaasLinkingItems ?? state.relatedAppsItems.items.saasLinkingItems,\n },\n },\n dataList: updatedDataList ?? state.dataList,\n };\n }\n } else if (isType(action, RecommendedWhatsNewAppsReceivedAction)) {\n newState.recommendedApps.whatsNewApps = action.payload;\n } else if (isType(action, RecommendedTrendingAppsReceivedAction)) {\n newState.recommendedApps.trendingApps = action.payload;\n } else if (isType(action, RecommendedMicrosoft365WhatsNewAppsReceivedAction)) {\n newState.recommendedApps.microsoft365WhatsNewApps = action.payload;\n } else if (isType(action, RecommendedMicrosoft365TrendingAppsReceivedAction)) {\n newState.recommendedApps.microsoft365TrendingApps = action.payload;\n } else if (isType(action, AppsPricingDataReceivedAction)) {\n const { payload } = action;\n const { pricingData = {} } = payload;\n\n newState.dataList = state.dataList.map((app: IAppDataItem) => loadAppPricing(app, pricingData));\n const dataMap = deepCopyDataMap(state.dataMap);\n updateMatchFunctions(dataMap);\n newState.dataMap = dataMap;\n newState.pricingPayload = payload;\n } else if (isType(action, RecommendedAppsItemsPricingDataReceivedAction)) {\n const { payload } = action;\n const { pricingData = {} } = payload;\n\n newState.recommendedApps.whatsNewApps = state.recommendedApps.whatsNewApps.map((app: IAppDataItem) =>\n loadAppPricing(app, pricingData)\n );\n newState.recommendedApps.trendingApps = state.recommendedApps.trendingApps.map((app: IAppDataItem) =>\n loadAppPricing(app, pricingData)\n );\n\n newState.recommendedApps.microsoft365WhatsNewApps = state.recommendedApps.microsoft365WhatsNewApps.map((app: IAppDataItem) =>\n loadAppPricing(app, pricingData)\n );\n newState.recommendedApps.microsoft365TrendingApps = state.recommendedApps.microsoft365TrendingApps.map((app: IAppDataItem) =>\n loadAppPricing(app, pricingData)\n );\n\n newState.pricingPayload = payload;\n } else if (isType(action, PowerBIDataReceivedAction)) {\n if (action.payload.appData) {\n newState.dataList = newState.dataList.concat(parsePBIApps(action.payload.appData));\n newState.idMap = generateHashMap(newState.dataList, ['entityId', 'friendlyURL']);\n newState.partnerAppDataLoaded = true;\n\n // This event is used to indicate that the Power BI data is fetched and parsing is done.\n const payload: ITelemetryData = {\n page: 'In App Gallery(PowerBI)',\n action: Constants.Telemetry.Action.PageLoad,\n actionModifier: Constants.Telemetry.ActionModifier.Info,\n details: '[End] PowerBI data parsing done',\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n }\n } else if (isType(action, PartnerAppDataReceivedAction)) {\n if (action.payload.appData) {\n const appData = parsePartnerApps(action.payload.appData, action.payload.embedHost);\n newState.dataList = state.dataList.concat(appData).sort((a: IAppDataItem, b: IAppDataItem) => {\n const aTitle = a.title.toLowerCase();\n const bTitle = b.title.toLowerCase();\n return aTitle > bTitle ? 1 : aTitle < bTitle ? -1 : 0;\n });\n newState.partnerAppDataLoaded = true;\n newState.idMap = generateHashMap(newState.dataList, ['entityId']);\n } else {\n newState.partnerAppDataLoaded = true;\n newState.partnerAppDataLoadedError = true;\n }\n\n // This event is used to indicate that the Power BI data is fetched and parsing is done.\n const payload: ITelemetryData = {\n page: 'In App Gallery(' + getProductByUrlKey({ urlKey: action.payload.embedHost })?.UrlKey + ')',\n action: Constants.Telemetry.Action.PageLoad,\n actionModifier: Constants.Telemetry.ActionModifier.Info,\n details: '[End] Partner App data parsing done',\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n } else if (isType(action, AppDetailPricingReceivedAction)) {\n if (action.payload.pricing) {\n const index = state.idMap[action.payload.entityId.toString().toLowerCase()];\n if (index !== -1) {\n newState.dataList = newState.dataList.slice();\n const currentData = newState.dataList[`${index}`] as any;\n const { futurePrices, pricing } = action.payload;\n newState.dataList[`${index}`] = {\n ...currentData,\n pricingInformation: action.payload.pricing,\n futurePricesInformation: getFuturePricesInformation(futurePrices, pricing),\n };\n }\n }\n } else if (isType(action, EntityReviewCommentsReceivedAction)) {\n const { appReviewsData } = newState;\n // Merge data with the previous comment data for the review id\n const { [action.payload.reviewId]: prevReviewCommentsState } = appReviewsData.reviewComments;\n const newReviewCommentsState: IAppReviewCommentsState = {\n ...appendReviewCommentsToState(prevReviewCommentsState, action.payload.comments),\n };\n\n appReviewsData.reviewComments = {\n ...appReviewsData.reviewComments,\n // Convert the new comment list to normalized data\n [action.payload.reviewId]: {\n ...newReviewCommentsState,\n // Remove newly added comments if they were now loaded into the main list\n newlyAddedComments: newReviewCommentsState.newlyAddedComments.filter((c) => !newReviewCommentsState.commentsById[c.id]),\n currentLoadedPage: action.payload.pageNumber,\n },\n };\n } else if (isType(action, EntityReviewCommentsCleanupAction)) {\n if (newState.appReviewsData.reviewComments && newState.appReviewsData.reviewComments[action.payload.reviewId]) {\n // Clean the comments of the given review from the state\n const { [action.payload.reviewId]: removedCommentData, ...newReviewCommentsState } = newState.appReviewsData.reviewComments;\n newState.appReviewsData.reviewComments = newReviewCommentsState;\n }\n } else if (isType(action, EntityReviewCommentUpdateResult) || isType(action, EntityReviewCommentDeleteResult)) {\n const { payload } = action;\n const { reviewId, isNewlyAddedComment, actionResult } = payload;\n const { [reviewId]: prevReviewCommentsState } = newState.appReviewsData.reviewComments;\n const newReviewCommentsState = { ...prevReviewCommentsState };\n if (isNewlyAddedComment) {\n const updatedComment = actionResult.reviewComment;\n newReviewCommentsState.newlyAddedComments = newReviewCommentsState.newlyAddedComments.map((c) =>\n c.id === updatedComment.id ? updatedComment : c\n );\n } else {\n // Upon deletion or update of a comment, update it in the state immediately with the returned comment representation\n const commentId = actionResult.reviewComment.id;\n newReviewCommentsState.commentsById = {\n ...newReviewCommentsState.commentsById,\n [commentId]: actionResult.reviewComment,\n };\n newReviewCommentsState.reducedCommentIds = combineDeletedComments(newReviewCommentsState);\n }\n // When deleting comments, reduce the comments counter\n if (isType(action, EntityReviewCommentDeleteResult)) {\n const reviewIdx = newState.appReviewsData.reviews.findIndex((r) => r.id === action.payload.reviewId);\n if (reviewIdx > -1) {\n newState.appReviewsData.reviews[`${reviewIdx}`].comments_count -= 1;\n }\n }\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [reviewId]: newReviewCommentsState,\n };\n } else if (isType(action, EntityReviewCommentsLoadingAction)) {\n // Update the loading state for a given review's comments list (ex. when initially expanding the comments section for a review)\n const { [action.payload.reviewId]: prevReviewCommentsState } = newState.appReviewsData.reviewComments;\n\n // Update the review comments state with the change\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [action.payload.reviewId]: { ...prevReviewCommentsState, isLoading: action.payload.isLoading },\n };\n } else if (isType(action, EntityReviewCommentsUnloadPagesAction)) {\n // Update the current page for a given review's comments list\n const { [action.payload.reviewId]: prevReviewCommentsState } = newState.appReviewsData.reviewComments;\n const { commentIds: prevCommentIds, commentsById: prevCommentsById } = prevReviewCommentsState;\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [action.payload.reviewId]: appendReviewCommentsToState(\n // Erase loaded data from the comments state\n { ...prevReviewCommentsState, reducedCommentIds: [], commentsById: {}, commentIds: [], currentLoadedPage: 1 },\n // Collect the comments from the first page and add them to the state\n values(pick(prevCommentsById, take(prevCommentIds, Constants.Reviews.CommentsPerPage)))\n ),\n };\n } else if (isType(action, EntityReviewCommentsUpdateTotalCommentCountAction)) {\n const { reviewId, totalComments } = action.payload;\n // Update the loading state for a given review's comments list (ex. when initially expanding the comments section for a review)\n const { [reviewId]: reviewCommentsState = createInitialReviewCommentsState() } = newState.appReviewsData.reviewComments;\n\n // Update the review comments state with the change\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [reviewId]: {\n ...reviewCommentsState,\n totalComments,\n totalPages: calculateTotalReviewCommentsPagesCount(totalComments),\n },\n };\n } else if (isType(action, EntityReviewCommentsInitializeStateAction)) {\n const { [action.payload.reviewId]: newReviewCommentsState = createInitialReviewCommentsState() } =\n newState.appReviewsData.reviewComments || {};\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [action.payload.reviewId]: { ...newReviewCommentsState, totalComments: action.payload.totalComments },\n };\n } else if (isType(action, EntityReviewCommentCreateResult)) {\n if (!action.payload.actionResult.error) {\n const { [action.payload.reviewId]: prevReviewCommentsState } = newState.appReviewsData.reviewComments;\n let newReviewCommentsState: IAppReviewCommentsState;\n if (action.payload.isNewlyAddedComment) {\n // Add the comment to the newly added comments list\n newReviewCommentsState = {\n ...prevReviewCommentsState,\n newlyAddedComments: [...prevReviewCommentsState.newlyAddedComments, action.payload.actionResult.reviewComment],\n };\n } else {\n // Add the comment to the main list\n newReviewCommentsState = {\n ...appendReviewCommentsToState(prevReviewCommentsState, [action.payload.actionResult.reviewComment]),\n };\n }\n // Inject the new comments state and append the total comments count\n newState.appReviewsData.reviewComments = {\n ...newState.appReviewsData.reviewComments,\n [action.payload.reviewId]: { ...newReviewCommentsState, totalComments: prevReviewCommentsState.totalComments + 1 },\n };\n const reviewIdx = newState.appReviewsData.reviews.findIndex((r) => r.id === action.payload.reviewId);\n if (reviewIdx > -1) {\n newState.appReviewsData.reviews[`${reviewIdx}`].comments_count += 1;\n }\n }\n } else if (isType(action, EntityMarkedAsHelpfulReviewUpdateListAction)) {\n const { reviewIds } = action.payload;\n newState.appReviewsData.markedAsHelpful.userReviewIdsMarkedAsHelpful = reviewIds;\n } else if (isType(action, EntityLinkedInProductGroupUpdateAction)) {\n const { linkedInProductGroup } = action.payload;\n newState.linkedInProductGroup = linkedInProductGroup;\n } else if (isType(action, EntityReviewSetIsHelpfulAction)) {\n const { reviewId, isHelpful } = action.payload;\n const { markedAsHelpful } = newState.appReviewsData;\n const { userReviewIdsMarkedAsHelpful: prevReviewIds } = markedAsHelpful;\n if (isHelpful) {\n markedAsHelpful.userReviewIdsMarkedAsHelpful = [...prevReviewIds, reviewId];\n } else {\n markedAsHelpful.userReviewIdsMarkedAsHelpful = prevReviewIds.filter((r) => r !== reviewId);\n }\n } else if (isType(action, EntityReviewSetIsHelpfulLoadingAction)) {\n const { isLoading, reviewId } = action.payload;\n const { markedAsHelpful: prevMarkedAsHelpfulState } = newState.appReviewsData;\n const { userReviewIdsLoading: prevLoadingIds } = prevMarkedAsHelpfulState;\n if (isLoading) {\n prevMarkedAsHelpfulState.userReviewIdsLoading = { ...prevLoadingIds, [reviewId]: true };\n } else {\n const { [reviewId]: removedListing, ...newUserReviewIdsLoading } = prevMarkedAsHelpfulState.userReviewIdsLoading;\n prevMarkedAsHelpfulState.userReviewIdsLoading = newUserReviewIdsLoading;\n }\n } else if (isType(action, EntityReviewSetMarkAsHelpfulCount)) {\n const { reviewId, count } = action.payload;\n const { reviews } = newState.appReviewsData;\n const reviewIdx = reviews.findIndex((r) => r.id === reviewId);\n if (reviewIdx > -1) {\n reviews[`${reviewIdx}`].mark_as_helpful_count = count;\n }\n } else if (isType(action, LoadingRecommenededSizes)) {\n const { entityId, planId } = action.payload;\n newState.recommendedSizesState = {\n entityId,\n planId,\n loading: true,\n recommendedSizes: [],\n };\n } else if (isType(action, SetRecommenededSizes)) {\n const { entityId, planId, recommendedSizes } = action.payload;\n newState.recommendedSizesState = {\n entityId,\n planId,\n loading: false,\n recommendedSizes,\n };\n } else if (isType(action, EntityUserCommentedReviewIdsUpdateListAction)) {\n const { commentReviewIds } = action.payload;\n newState.appReviewsData.userCommentedReviewIds = commentReviewIds;\n } else {\n return newCommonState;\n }\n return newState;\n}\n","import { updateMatchFunctions } from '@shared/utils/filterHelpers';\nimport { IAppDataItem } from '@shared/Models';\nimport { Constants } from '@shared/utils/constants';\nimport { loadAppPricing } from '@shared/utils/pricing';\nimport { Action, AppsPricingDataReceivedAction, isType } from '@shared/actions/actions';\nimport { copyState, deepCopyDataMap, ICloudsIndustryDataState, initialCloudsIndustryDataState } from './../../State';\nimport { commonEntityDataReducer } from './commonEntityDataReducer';\n\nexport default function cloudsIndustryDataReducer(\n state: ICloudsIndustryDataState = initialCloudsIndustryDataState,\n action: Action\n): ICloudsIndustryDataState {\n const newState = copyState(state);\n\n if (isType(action, AppsPricingDataReceivedAction)) {\n const { payload } = action;\n const { pricingData = {} } = payload;\n\n newState.dataList = state.dataList.map((app: IAppDataItem) => loadAppPricing(app, pricingData));\n\n const dataMap = deepCopyDataMap(state.dataMap);\n updateMatchFunctions(dataMap);\n newState.dataMap = dataMap;\n newState.pricingPayload = payload;\n } else {\n return commonEntityDataReducer(Constants.EntityType.CloudsIndustry, state, action) as ICloudsIndustryDataState;\n }\n return newState;\n}\n","import {\n Action,\n isType,\n UserSignInAction,\n UserLoginDetailsAction,\n EmbedUserSignInAction,\n UserReviewUpdateAction,\n UserSegmentInitializeAction,\n UserSignOutAction,\n UserFieldHubTypeAction,\n UserProfileAction,\n UpdateUserProfileLicenseAction,\n UpdateUserProfileAction,\n ClearUserPropertiesAction,\n UserOrdersAction,\n SetUserOrdersLoadingAction,\n UserPrivateOffersAction,\n SetUserPrivateOffersLoadingAction,\n UserTenantsDetailsAction,\n UserTenantsDetailsLoadingAction,\n SetAccessTokenAction,\n UserProfileLoadingAction,\n UpdateUserRoleDefinitions,\n AddSubscriptionsInTenant,\n UserSignInFetchStatusAction,\n UserSignInStatusDataAction,\n UserSignInStatusAction,\n UserAccesstokensAction,\n SetAccountAction,\n SetAccountIsLoadingAction,\n SetIsMaccTenantAction,\n} from './../actions/actions';\nimport { IUserDataState, initialUserDataState, copyState } from './../../State';\nimport { Constants } from '../utils/constants';\nimport { IAppDataItem } from './../Models';\nimport { pick } from 'lodash-es';\n\nexport default function userDataReducer(state: IUserDataState = initialUserDataState, action: Action): IUserDataState {\n const newState = copyState(state);\n newState.profile = copyState(state.profile);\n if (isType(action, UserSignInAction) || isType(action, EmbedUserSignInAction)) {\n newState.id = action.payload.id || '';\n newState.group = action.payload.group;\n newState.email = action.payload.email || '';\n newState.signedIn = action.payload.signedIn;\n newState.idToken = action.payload.idToken || '';\n newState.firstName = action.payload.firstName;\n newState.lastName = action.payload.lastName;\n newState.displayName = action.payload.displayName;\n newState.oid = action.payload.oid;\n newState.tid = action.payload.tid;\n newState.alternateEmail = action.payload.alternateEmail;\n newState.isMSAUser = action.payload.isMSAUser;\n newState.userSegment = action.payload.userSegment;\n newState.fieldHubUserType = action.payload.fieldHubUserType;\n newState.graphApi = action.payload.graphApi;\n\n if (!newState.displayName) {\n newState.displayName = (newState.firstName + ' ' + newState.lastName).trim();\n newState.email = newState.email.substr(newState.email.indexOf('#') + 1);\n }\n\n // Embedded scenario does not send access token in the required format. So we call a different action\n // so that it is formatted the right way.\n if (isType(action, EmbedUserSignInAction)) {\n newState.accessToken = {\n spza: action.payload.accessToken,\n };\n }\n if (isType(action, UserSignInAction)) {\n newState.accessToken = action.payload.accessToken;\n }\n } else if (isType(action, UserSignInStatusAction)) {\n return {\n ...state,\n ...pick(action.payload, ['signedIn', 'refreshToken', 'homeAccountId']),\n };\n } else if (isType(action, UserSignInStatusDataAction)) {\n return {\n ...state,\n ...pick(action.payload, ['signedIn', 'refreshToken', 'homeAccountId']),\n };\n } else if (isType(action, UserSignInFetchStatusAction)) {\n return {\n ...state,\n ...pick(action.payload, ['loading']),\n };\n } else if (isType(action, UserAccesstokensAction)) {\n return {\n ...state,\n accessToken: action.payload,\n };\n } else if (isType(action, UserLoginDetailsAction)) {\n return {\n ...state,\n ...action.payload,\n };\n } else if (isType(action, UpdateUserRoleDefinitions)) {\n newState.roleDefinitions = action.payload;\n } else if (isType(action, UserReviewUpdateAction)) {\n newState.hasReview = action.payload;\n } else if (isType(action, SetAccessTokenAction)) {\n newState.accessToken = action.payload;\n } else if (isType(action, AddSubscriptionsInTenant)) {\n newState.tenantsDetails.details.azureSubscriptions = action.payload;\n } else if (isType(action, UserSegmentInitializeAction)) {\n newState.userSegment = action.payload.userSegment;\n } else if (isType(action, UserSignOutAction)) {\n newState.id = '';\n newState.group = [''];\n newState.email = '';\n newState.signedIn = false;\n newState.idToken = '';\n newState.refreshToken = '';\n newState.firstName = '';\n newState.lastName = '';\n newState.displayName = '';\n newState.oid = '';\n newState.tid = '';\n newState.alternateEmail = '';\n newState.isMSAUser = false;\n newState.fieldHubUserType = Constants.FieldHubUserType.None;\n newState.phone = '';\n newState.company = '';\n newState.country = '';\n newState.profile.firstName = '';\n newState.profile.lastName = '';\n newState.profile.updateRequired = true;\n newState.profile.isLatestProfile = false;\n newState.profile.phone = '';\n newState.profile.email = '';\n newState.profile.title = '';\n newState.profile.company = '';\n newState.profile.country = '';\n newState.profile.uiRole = null;\n newState.profile.managedLicenses = [];\n\n if (!newState.displayName) {\n newState.displayName = newState.firstName + ' ' + newState.lastName;\n newState.email = newState.email.substr(newState.email.indexOf('#') + 1);\n }\n } else if (isType(action, UserFieldHubTypeAction)) {\n newState.fieldHubUserType = action.payload;\n } else if (isType(action, UserProfileLoadingAction)) {\n newState.isLoadingUserProfile = action.payload.isLoading;\n } else if (isType(action, UserProfileAction)) {\n newState.isLoadingUserProfile = false;\n newState.profile.firstName = action.payload.firstName;\n newState.profile.lastName = action.payload.lastName;\n newState.profile.updateRequired = action.payload.updateRequired === true;\n newState.profile.isLatestProfile = true;\n newState.profile.phone = action.payload.phone;\n newState.profile.email = action.payload.email;\n newState.profile.title = action.payload.title;\n newState.profile.company = action.payload.company;\n newState.profile.country = action.payload.country;\n newState.profile.uiRole = action.payload.uiRole;\n newState.profile.managedLicenses = action.payload.managedLicenses;\n if (action.payload.licenses && action.payload.licenses.length > 0) {\n newState.profile.licenses = [];\n for (let i = 0; i < action.payload.licenses.length; i++) {\n newState.profile.licenses.push(action.payload.licenses[`${i}`]);\n }\n }\n } else if (isType(action, UserTenantsDetailsAction)) {\n newState.tenantsDetails.details = { ...action.payload };\n } else if (isType(action, UserTenantsDetailsLoadingAction)) {\n newState.tenantsDetails.status = action.payload;\n } else if (isType(action, UpdateUserProfileAction)) {\n newState.profile.firstName = action.payload.firstName;\n newState.profile.lastName = action.payload.lastName;\n newState.profile.updateRequired = false;\n newState.profile.isLatestProfile = true;\n newState.profile.phone = action.payload.phone;\n newState.profile.title = action.payload.title;\n newState.profile.company = action.payload.company;\n newState.profile.country = action.payload.country;\n newState.profile.email = action.payload.email;\n newState.profile.uiRole = action.payload.uiRole;\n newState.profile.managedLicenses = action.payload.managedLicenses;\n // todo: post me update with license info (both of them)\n } else if (isType(action, UpdateUserProfileLicenseAction)) {\n if (action.payload.license) {\n if (newState.profile.licenses.length === Constants.dynamicsLicense.currentD365LicenseNumber) {\n if (action.payload.licenseType === Constants.Dynamics365LicenseType.dynamics365License) {\n newState.profile.licenses[Constants.Dynamics365LicenseIndex.dynamics365LicenseIndex].isDisputed =\n action.payload.license.isDisputed;\n }\n if (action.payload.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense) {\n newState.profile.licenses[Constants.Dynamics365LicenseIndex.dynamics365ForBusinessLicenseIndex].isDisputed =\n action.payload.license.isDisputed;\n }\n }\n }\n } else if (isType(action, ClearUserPropertiesAction)) {\n newState.phone = '';\n } else if (isType(action, UserOrdersAction)) {\n newState.purchases.hasPurchases = action.payload;\n } else if (isType(action, SetUserOrdersLoadingAction)) {\n newState.purchases.loading = action.payload;\n } else if (isType(action, UserPrivateOffersAction)) {\n const offers: IAppDataItem[] = action.payload.apps || [];\n newState.privateOffers.dataList = offers;\n } else if (isType(action, SetUserPrivateOffersLoadingAction)) {\n newState.privateOffers.loading = action.payload;\n } else if (isType(action, SetAccountAction)) {\n newState.accounts = action.payload;\n } else if (isType(action, SetAccountIsLoadingAction)) {\n newState.accounts.isLoading = action.payload.isLoading;\n } else if (isType(action, SetIsMaccTenantAction)) {\n newState.isMaccTenant = action.payload.isMacc;\n } else {\n return state;\n }\n\n return newState;\n}\n","import {\n Action,\n isType,\n NationalCloudSettingAction,\n EmbeddedAppSettingAction,\n EmbeddedHostAction,\n LocStringsReceivedAction,\n ChangeLocaleLanguage,\n FlightCodesReceivedAction,\n CorrelationIdReceivedAction,\n BreadcrumbAction,\n TelemetryLoggedAction,\n SetCurrentViewAction,\n SetLandingViewAction,\n UpdateCurrentGalleryViewIsCuratedAction,\n AppPricingDataRequestedAction,\n AppsPricingDataReceivedAction,\n FeatureFlagsReceivedAction,\n BillingRegionReadFromCookieAction,\n ABTestingEnableAction,\n GlobalSettingsReceivedAction,\n IsMobileReceivedAction,\n CheckoutConfigAction,\n IsManageModalOpenAction,\n SetWCPConsentAction,\n SetIsConsentRequired,\n SetInitLoadingAction,\n EnableNewNpsAction,\n SetIsNpsLogicVerifiedAction,\n ToggleShowContextPanel,\n RequestIdReceivedAction,\n SetIsDisabledBilling,\n SetStorefrontNameAction,\n I18nextReceivedAction,\n SetInitializedPCAInstanceAction,\n IncludeTestProductsReceivedAction,\n IsConverged,\n} from '@shared/actions/actions';\nimport {\n SetQueryAction,\n SetRequestFilteredEntityTypeAction,\n SetRequestFilteredLoadedAction,\n SetRequestFilteredLoadingAction,\n SetRequestFilteredQueryAction,\n SetResponseFilteredQueryAction,\n} from './../actions/queryActions';\nimport { pick } from 'lodash-es';\n\nimport { IConfigState, initialConfigState, copyState } from './../../State';\n\nexport default function configReducer(state: IConfigState = initialConfigState, action: Action): IConfigState {\n const newState = copyState(state);\n\n if (isType(action, NationalCloudSettingAction)) {\n newState.nationalCloud = action.payload.nationalCloud;\n } else if (isType(action, EmbeddedAppSettingAction)) {\n newState.isEmbedded = action.payload.isEmbedded;\n } else if (isType(action, SetInitLoadingAction)) {\n newState.initLoading = action.payload.initLoading;\n } else if (isType(action, LocStringsReceivedAction)) {\n newState.locStrings = action.payload.localizedstrings;\n newState.locale = action.payload.locale;\n } else if (isType(action, I18nextReceivedAction)) {\n newState.i18nInstance = action.payload.i18nInstance;\n } else if (isType(action, EmbeddedHostAction)) {\n newState.embedHost = action.payload.embedHost;\n } else if (isType(action, FlightCodesReceivedAction)) {\n newState.flightCodes = action.payload.flightCodes;\n } else if (isType(action, IncludeTestProductsReceivedAction)) {\n newState.includeTestProducts = action.payload.includeTestProducts;\n } else if (isType(action, IsConverged)) {\n newState.isConverged = action.payload.isConverged;\n } else if (isType(action, CorrelationIdReceivedAction)) {\n newState.correlationId = action.payload.correlationId;\n } else if (isType(action, RequestIdReceivedAction)) {\n newState.requestId = action.payload.requestId;\n } else if (isType(action, BreadcrumbAction)) {\n newState.breadcrumbUrl = action.payload.breadcrumbUrl;\n } else if (isType(action, TelemetryLoggedAction)) {\n newState.appViewTelemetryLoggedCount = state.appViewTelemetryLoggedCount + 1;\n } else if (isType(action, SetCurrentViewAction)) {\n // currentView is one of routes property `name` in routerHistory.tsx\n newState.currentView = action.payload.currentView;\n } else if (isType(action, SetLandingViewAction)) {\n // landingView is one of routes property `name` in routerHistory.tsx\n newState.landingView = action.payload.landingView;\n } else if (isType(action, UpdateCurrentGalleryViewIsCuratedAction)) {\n // determine whether current render gallery view is curated\n newState.currentGalleryViewIsCurated = action.payload.isCuratedGalleryView;\n } else if (isType(action, AppPricingDataRequestedAction)) {\n newState.pricingDataLoaded = false;\n } else if (isType(action, AppsPricingDataReceivedAction)) {\n newState.billingCountryCode = action.payload.countryCode;\n newState.pricingDataLoaded = true;\n } else if (isType(action, FeatureFlagsReceivedAction)) {\n const keys = Object.keys(action.payload);\n const keysLength = keys.length;\n\n for (let i = 0; i < keysLength; i++) {\n newState.featureFlags[keys[`${i}`]] = action.payload[keys[`${i}`]];\n }\n } else if (isType(action, SetQueryAction)) {\n newState.query = action.payload.query;\n } else if (isType(action, SetRequestFilteredLoadingAction)) {\n newState.requestFilteredLoading = true;\n } else if (isType(action, SetRequestFilteredLoadedAction)) {\n newState.requestFilteredLoading = false;\n } else if (isType(action, SetRequestFilteredQueryAction)) {\n newState.requestFilteredQuery = action.payload.requestFilteredQuery;\n } else if (isType(action, SetRequestFilteredEntityTypeAction)) {\n newState.requestFilteredEntityType = action.payload.requestFilteredEntityType;\n } else if (isType(action, SetResponseFilteredQueryAction)) {\n newState.responseFilteredQuery = action.payload.responseFilteredQuery;\n } else if (isType(action, BillingRegionReadFromCookieAction)) {\n newState.billingCountryCode = action.payload.billingRegion;\n } else if (isType(action, ABTestingEnableAction)) {\n newState.experiment.abTestingVersion_AMP = action.payload.abTestingVersion_AMP;\n newState.experiment.abTestingVersion_newFilterAMP = action.payload.abTestingVersion_newFilterAMP;\n } else if (isType(action, GlobalSettingsReceivedAction)) {\n Object.assign(newState, {\n ...pick(action.payload.globalSettings, [\n 'marketplaceApiHost',\n 'saasRPHost',\n 'marketplaceLeadsHost',\n 'marketplaceLeadsAudienceId',\n 'armApiHost',\n 'MPRPHost',\n 'partnersIframeUrl',\n 'partnersIframeUrlTest',\n 'telemetryInstrumentationKey',\n 'loggingInstrumentationKey',\n 'partnersApiHost',\n 'appVersion',\n 'region',\n 'requestId',\n 'correlationId',\n 'reviewsAPI',\n 'linkedinAPI',\n 'linkedinAPIVersion',\n 'communityAPI',\n 'communityAPIVersion',\n 'siteEnvironment',\n 'aadApplicationId',\n 'aadLoginUrl',\n 'commerceApiHost',\n 'oneCatalogApiHost',\n 'telemetryEndpoint',\n 'msClarityId',\n 'searchApiEndpoint',\n 'expHeaderDisabled',\n 'enableOneTaxonomy',\n 'jarvisCM',\n 'mprpAadApplicationId',\n 'pifdEndpoint',\n 'commerceApi',\n 'graphApi',\n 'agreementAppId',\n 'isConverged',\n ]),\n });\n } else if (isType(action, IsMobileReceivedAction)) {\n newState.isMobile = action.payload.isMobile;\n } else if (isType(action, CheckoutConfigAction)) {\n newState.pidlEnv = action.payload.checkoutSettings.pidlEnv;\n } else if (isType(action, IsManageModalOpenAction)) {\n newState.cookiesConsent.isManageModalOpen = action.payload;\n } else if (isType(action, SetWCPConsentAction)) {\n newState.cookiesConsent.WCPConsent = action.payload;\n } else if (isType(action, SetIsConsentRequired)) {\n newState.cookiesConsent.isConsentRequired = action.payload;\n } else if (isType(action, EnableNewNpsAction)) {\n newState.newNpsEnabled = action.payload.enable;\n } else if (isType(action, SetInitializedPCAInstanceAction)) {\n newState.pcaInstanceInitialized = action.payload.initialized;\n } else if (isType(action, SetIsNpsLogicVerifiedAction)) {\n newState.npsLogicVerified = action.payload.verified;\n } else if (isType(action, ToggleShowContextPanel)) {\n newState.showContextPanel = !state.showContextPanel;\n } else if (isType(action, ChangeLocaleLanguage)) {\n newState.locale = action.payload.locale;\n } else if (isType(action, SetIsDisabledBilling)) {\n newState.isDisabledBilling = action.payload;\n } else if (isType(action, SetStorefrontNameAction)) {\n newState.storefrontName = action.payload;\n } else {\n return state;\n }\n\n return newState;\n}\n","import { Action, isType, DynamicCampaignReceivedAction } from './../actions/actions';\nimport { IDynamicCampaignState, initialDynamicCampaignState, copyState } from './../../State';\n\nexport default function campaignReducer(\n state: IDynamicCampaignState = initialDynamicCampaignState,\n action: Action\n): IDynamicCampaignState {\n let newState = copyState(state);\n if (isType(action, DynamicCampaignReceivedAction)) {\n newState = action.payload.campaignInfo;\n } else {\n return state;\n }\n return newState;\n}\n","import { IAppDataItem } from '@shared/Models';\nimport { Action, isType } from './../actions/actions';\nimport {\n ReceiveUserFavouriteDataAction,\n UpdateUserFavouriteFetchDataStatusAction,\n UpsertUserFavouriteItemAction,\n DeleteUserFavouriteItemAction,\n FavouriteAppsPricingDataReceivedAction,\n} from './../actions/userFavouriteActions';\nimport { initialUserFavouriteState, copyState } from '@src/State';\nimport {\n IUserFavouriteState,\n IReceiveUserFavouriteDataActionPayload,\n IUpdateUserFavouriteFetchDataStatusActionPayload,\n IUpsertUserFavouriteItemActionPayload,\n IDeleteUserFavouriteItemActionPayload,\n IRawUserFavourite,\n IUserFavouriteApp,\n} from './../interfaces/userFavouriteModels';\nimport {\n isApplication,\n isConsultingService,\n getFavouriteEntityState,\n upsertsUserFavouriteEntityState,\n deleteUserFavouriteEntityState,\n} from './../utils/userFavouriteUtils';\nimport { Constants } from './../utils/constants';\nimport { loadAppPricing } from './../utils/pricing';\n\nexport default function userFavouriteReducer(\n state: IUserFavouriteState = initialUserFavouriteState,\n action: Action<\n | IReceiveUserFavouriteDataActionPayload\n | IUpdateUserFavouriteFetchDataStatusActionPayload\n | IUpsertUserFavouriteItemActionPayload\n | IDeleteUserFavouriteItemActionPayload\n >\n): IUserFavouriteState {\n const newState = copyState(state);\n\n if (isType(action, ReceiveUserFavouriteDataAction)) {\n const payload = action.payload as IReceiveUserFavouriteDataActionPayload;\n const userFavourites = payload.userFavorites || [];\n\n userFavourites.forEach((userFavourite: IRawUserFavourite) => {\n if (isApplication(userFavourite.applicationType)) {\n newState.apps = getFavouriteEntityState(userFavourite.favorites, userFavourite.applicationType);\n } else if (isConsultingService(userFavourite.applicationType)) {\n newState.services = getFavouriteEntityState(userFavourite.favorites, userFavourite.applicationType);\n }\n });\n } else if (isType(action, UpdateUserFavouriteFetchDataStatusAction)) {\n const payload = action.payload as IUpdateUserFavouriteFetchDataStatusActionPayload;\n\n if (newState.fetchDataStatus === payload.fetchDataStatus) {\n return state;\n }\n\n newState.fetchDataStatus = payload.fetchDataStatus;\n } else if (isType(action, UpsertUserFavouriteItemAction)) {\n if (newState.fetchDataStatus === Constants.UserFavourite.FetchDataStatus.Valid) {\n const payload = action.payload as IUpsertUserFavouriteItemActionPayload;\n\n if (isApplication(payload.applicationType)) {\n upsertsUserFavouriteEntityState(newState.apps, payload);\n } else if (isConsultingService(payload.applicationType)) {\n upsertsUserFavouriteEntityState(newState.services, payload);\n }\n }\n } else if (isType(action, DeleteUserFavouriteItemAction)) {\n if (newState.fetchDataStatus === Constants.UserFavourite.FetchDataStatus.Valid) {\n const payload = action.payload as IDeleteUserFavouriteItemActionPayload;\n\n if (isApplication(payload.applicationType)) {\n deleteUserFavouriteEntityState(newState.apps, payload);\n } else if (isConsultingService(payload.applicationType)) {\n deleteUserFavouriteEntityState(newState.services, payload);\n }\n }\n } else if (isType(action, FavouriteAppsPricingDataReceivedAction)) {\n if (!state.apps || !action.payload.pricingData || Object.keys(action.payload.pricingData).length === 0) {\n return state;\n }\n\n newState.apps = state.apps.map((favouriteApp: IUserFavouriteApp) => {\n const app = favouriteApp.item as IAppDataItem;\n\n const item = loadAppPricing(app, action.payload.pricingData);\n\n return {\n ...favouriteApp,\n item,\n };\n });\n } else {\n return state;\n }\n\n return newState;\n}\n","import { Action, isType } from '../actions/actions';\nimport { IPartnersState, initialPartnersState, copyState } from '../../State';\nimport { SetContactDetailsAction, SetFilterLocationAction } from '../actions/partnersActions';\n\nexport default function partnersReducer(state: IPartnersState = initialPartnersState, action: Action): IPartnersState {\n if (isType(action, SetFilterLocationAction)) {\n const newState = { ...state };\n newState.filterLocation = action.payload;\n return newState;\n } else if (isType(action, SetContactDetailsAction)) {\n const newState = { ...state };\n newState.contactDetails = action.payload;\n return newState;\n }\n return state;\n}\n","import * as React from 'react';\nimport { buildStore } from '@shared/store/createStore';\nimport ErrorModal from '../containers/modals/errorModal';\nimport { render } from 'react-dom';\nimport { getWindow } from '../services/window';\nimport combineReducers from '../reducers/reducers';\nimport { isEmbedded } from '../utils/appUtils';\nimport thunk from 'redux-thunk';\nimport { IState } from '@src/State';\nimport { Provider } from 'react-redux';\nimport classNames from 'classnames';\n\ndeclare let window: any;\n\nexport function renderErrorModal(store: any, isEmbed = false) {\n if (!store) {\n const initialState: IState = window.__INITIAL_STATE__;\n store = buildStore({ combinedReducers: combineReducers, initialState, middleware: [thunk] });\n\n isEmbed = initialState.config.isEmbedded;\n }\n\n const modalBackgroundClasses = classNames({\n spza_presentation: true,\n spza_background_1: true,\n spza_background_2: false,\n });\n\n const modalClasses = classNames({\n spza_dialog: true,\n spza_dialog_design2: false,\n });\n\n let dismissModal: any = null;\n if (!isEmbed) {\n dismissModal = () => {\n const href = getWindow().location.origin + '/home';\n getWindow().location.assign(href);\n };\n } else {\n dismissModal = () => {\n getWindow().parent.postMessage({ msgType: 'closeSaasMarketplace' }, '*');\n };\n }\n\n render(\n \n
    \n
    \n
    \n \n
    \n
    \n
    \n
    ,\n document.getElementById('react-view')\n );\n}\n","import { IState } from '../../State';\nimport * as httpProtocol from '@shared/services/http/httpProtocol';\nimport { createPowerBIDataReceivedAction, createPartnerAppDataReceivedAction } from '@shared/actions/actions';\nimport { logPageLoadFinished } from '../embedMessaging';\nimport { SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport { ITelemetryData } from '@shared/Models';\nimport { Constants } from '@shared/utils/constants';\nimport { DataMap } from '@shared/utils/dataMapping';\nimport { logger } from '@src/logger';\nimport type { Dispatch } from 'redux';\nimport { getProductByUrlKey } from '@shared/utils/appUtils';\n\nconst PUBLISH_RETRY_COUNT = 2;\n\ninterface IEmbedHostData {\n context: any;\n accessToken: string;\n getAppsEndpoint: string;\n}\n\nexport function finishProcessingAppData(dispatch: Dispatch, embedHost: string, apps: any[]): any {\n const payload: ITelemetryData = {\n page: `In App Gallery(${getProductByUrlKey({ urlKey: embedHost })?.UrlKey})`,\n action: Constants.Telemetry.Action.NetworkCall,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: '[End] Partner App data is received. Data dispatched for parsing',\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, payload);\n\n dispatch(\n createPartnerAppDataReceivedAction({\n appData: apps,\n embedHost,\n })\n );\n\n logPageLoadFinished(embedHost);\n\n return apps;\n}\n\nexport function fetchEmbedAppData(embedHost: string, hostData: IEmbedHostData) {\n return function (dispatch: Dispatch) {\n const getAppsEndpoint = hostData.getAppsEndpoint;\n if (!getAppsEndpoint) {\n throw new Error('No getAppsEndpoint specified');\n }\n\n const options: httpProtocol.IHttpOption = {\n authenticationType: httpProtocol.AuthenticationTypes.Unauthenticated,\n parseResult: true,\n retry: PUBLISH_RETRY_COUNT,\n };\n\n return new Promise((resolve, reject) => {\n httpProtocol\n .get(getAppsEndpoint, options)\n .setAuthHeader(hostData.accessToken)\n .setHeader('Accept', 'application/json')\n .setHeader('Context', JSON.stringify(hostData.context))\n .request()\n .then(\n (result: any) => {\n const appData = finishProcessingAppData(dispatch, embedHost, result.applications);\n resolve(appData);\n },\n (error: any) => {\n reject(error);\n }\n );\n });\n };\n}\n\n// TODO: Add error handling for PowerBI failure\nexport function fetchPowerBIData(hostData: any) {\n return function (dispatch: Dispatch, getState: () => IState) {\n const state = getState();\n // TODO: this should probably be api.powerbi.com for the default\n let pbiApi = 'https://api.powerbi.com/v1.0/spza/applications';\n if (hostData.backendUrlOverride && hostData.backendUrlOverride.length > 1) {\n pbiApi = hostData.backendUrlOverride + '/v1.0/spza/applications';\n }\n\n const options: httpProtocol.IHttpOption = {\n authenticationType: httpProtocol.AuthenticationTypes.Unauthenticated,\n parseResult: true,\n allowOrigin: true,\n retry: PUBLISH_RETRY_COUNT,\n };\n\n return new Promise((resolve, reject) => {\n httpProtocol\n .get(pbiApi, options)\n .setAuthHeader(hostData.accessToken)\n .setHeader('Accept', 'application/json')\n .setHeader('ActivityId', state.config.correlationId)\n .setHeader('X-PowerBI-User-GroupId', hostData.context.groupId)\n .request()\n .then(\n (appData: any) => {\n // This event is used to indicate that the Power BI data is fetched. Parsing and rendering starts after this step\n const payload: ITelemetryData = {\n page: 'In App Gallery(PowerBI)',\n action: Constants.Telemetry.Action.NetworkCall,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: '[End] PowerBI data is received. Data dispatched for parsing',\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n dispatch(\n createPowerBIDataReceivedAction({\n appData: appData.applications,\n })\n );\n\n logPageLoadFinished(DataMap.products.PowerBI.UrlKey);\n resolve(appData);\n },\n (error: any) => {\n reject(error);\n }\n );\n });\n };\n}\n","/* eslint-disable @typescript-eslint/no-use-before-define */\nimport { DataMap } from '@shared/utils/dataMapping';\nimport { getWindow } from '@shared/services/window';\nimport { ITelemetryData, IAppDataItem, UserSegment } from '@shared/Models';\nimport { Constants } from '@shared/utils/constants';\nimport { createEmbedUserSignInAction } from '@shared/actions/actions';\nimport { SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport {\n getEmbedHostName,\n getTelemetryResponseUrl,\n getHandoffUrlForProduct,\n checkOriginSource,\n getPaymentRedirectUrl,\n postMessageToHost,\n getProductByUrlKey,\n} from '@shared/utils/appUtils';\nimport { HttpModule } from '@shared/services/http/httpProtocol';\nimport { renderErrorModal } from '@shared/utils/errorHandlerUtil';\nimport { getPrimaryProductUrl } from '@shared/utils/datamappingHelpers';\nimport { logger } from '@src/logger';\nimport * as embedHostUtils from './embedHostUtils';\nimport { constants } from './constants';\nimport { fetchPowerBIData, fetchEmbedAppData, finishProcessingAppData } from './actions/embedThunks';\nimport { stringifyError } from '@shared/utils/errorUtils';\n\nlet appDataRequestStart = 0;\n\n// DO NOT EVER CHANGE THE MAPPING FOR dynamics-365-business-central THIS WILL BREAK EMBED CTA\nconst legacyProductsMap = {\n 'dynamics-365-for-finance-and-operations-enterprise-edition': 'dynamics-365-for-operations',\n 'dynamics-365-for-sales-enterprise-edition': 'dynamics-365-for-sales',\n 'dynamics-365-for-finance-and-operations-business-edition': 'dynamics-365-for-financials',\n 'dynamics-365-for-customer-services-enterprise-edition': 'dynamics-365-for-customer-services',\n 'dynamics-365-for-field-services-enterprise-edition': 'dynamics-365-for-field-services',\n 'dynamics-365-for-project-service-automation-enterprise-edition': 'dynamics-365-for-project-service-automation',\n 'dynamics-365-business-central': 'dynamics-365-for-financials',\n};\n\nexport function postEmbedCTAMessage(app: IAppDataItem) {\n const data = {\n applicationId: app.entityId,\n };\n\n postMessageToHost({\n msgType: constants.actionTypes.ctaClicked,\n data,\n });\n}\n\nexport function postEmbedAcquisitionMessage(app: IAppDataItem, ctaClicked: Constants.CTAType, embedHost = '') {\n const productTitle = getPrimaryProductUrl(app.primaryProduct);\n // eslint-disable-next-line no-prototype-builtins\n const productName = legacyProductsMap.hasOwnProperty(productTitle) ? legacyProductsMap[`${productTitle}`] : productTitle;\n const data = {\n applicationId: app.entityId,\n category: app.privateApp ? 'private' : 'public',\n redirectUrl:\n app.handoffURL || getHandoffUrlForProduct(productTitle, app.entityId, false, app.products, app.locale, ctaClicked),\n product: productName,\n responseUrl: getTelemetryResponseUrl(app.entityId),\n };\n\n if (embedHost === DataMap.products.AdminPortal.UrlKey && ctaClicked === Constants.CTAType.Purchase) {\n data.paymentRedirectUrl = getPaymentRedirectUrl(app.entityId, Constants.M365Admin);\n }\n\n postMessageToHost({\n msgType: constants.actionTypes.acquireApp,\n data,\n });\n}\n\nexport function convertBitmaskValueToRawData(result: number, dataMapping: any) {\n const data: string[] = [];\n if (result) {\n const keys = Object.keys(dataMapping);\n\n for (let i = 0; i < keys.length; i++) {\n const value = dataMapping[keys[`${i}`]];\n if (result & value) {\n data.push(keys[`${i}`]);\n }\n }\n }\n\n return data;\n}\n\nexport function logInitListenerFinished(embedHost: string) {\n const payload: ITelemetryData = {\n page: 'In App Gallery',\n action: Constants.Telemetry.Action.EmbedMessageListener,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: `embedHost: ${getEmbedHostName(embedHost)}`,\n };\n\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n\n postMessageToHost({\n msgType: constants.actionTypes.initListenerFinished,\n });\n}\n\nexport function logPageLoadFinished(embedHost?: string) {\n const timeAtTelemetryMethodCall = new Date().getTime();\n\n // This event is used to indicate that the In App gallery apps rendering is finished.\n const payload: ITelemetryData = {\n page: 'In App Gallery',\n action: Constants.Telemetry.Action.PageLoad,\n actionModifier: Constants.Telemetry.ActionModifier.End,\n details: 'In App Gallery tiles rendering finished',\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n\n const currentWindow = getWindow();\n\n if (currentWindow && currentWindow.performance && embedHostUtils.hasDynamicData(embedHost)) {\n const timing = currentWindow.performance.timing;\n // Telemetry for Network Latency\n const networkLatency = timing.responseEnd - timing.fetchStart;\n\n const currentTime = new Date().getTime();\n // The time taken for page load once the page is received from the server\n // we may find timing.loadEventEnd as 0 if called before load event fires.\n // If so, use current time as approximation till we fix it properly\n const loadEventEnd = timing.loadEventEnd !== 0 ? timing.loadEventEnd : currentTime;\n\n // The time taken for page load once the page is received from the server\n const pageLoadTime = loadEventEnd - timing.responseEnd;\n\n // The whole process of navigation and page load\n const navigationToPageLoad = currentTime - timing.navigationStart;\n const dataLatency = currentTime - appDataRequestStart;\n\n // the data we are getting here is valuable, but seems very browser specific (some browsers do not properly support)\n // so introducing a new number here that will always be consistent:\n const timePassedSinceNavigationStart = timeAtTelemetryMethodCall - timing.navigationStart;\n\n const perfTimings = {\n networkLatency,\n pageLoadTime,\n navigationToPageLoad,\n timePassedSinceNavigationStart,\n allTimings: timing, // Dumping raw timings for perf telemetry analysis\n isEmbedded: true,\n embedHost: getEmbedHostName(embedHost),\n isAuthenticated: false,\n dataLatency,\n };\n\n const perfPayload = {\n eventName: Constants.Telemetry.Action.PerfEvents,\n data: JSON.stringify(perfTimings),\n flushLog: true,\n };\n\n SpzaInstrumentService.getProvider().probe('logOneTimeInfo', perfPayload);\n logger.info(perfPayload.data, {\n action: perfPayload.eventName,\n actionModifier: Constants.Telemetry.ActionModifier.Info,\n page: '',\n });\n }\n\n postMessageToHost({\n msgType: constants.actionTypes.finishedLoadingContentProviderList,\n });\n}\n\nexport function initializeListener(dispatch: any, embedHost: string) {\n const currWindow = getWindow();\n if (currWindow.parent !== currWindow) {\n currWindow.addEventListener('message', (e: any) => {\n receiveMessage(e, dispatch, embedHost);\n });\n }\n\n logInitListenerFinished(embedHost);\n\n if (!embedHostUtils.hasDynamicData(embedHost)) {\n // If it is not dynamic data on embed host, we are done\n // with fetching the app data.\n appDataRequestStart = new Date().getTime();\n logPageLoadFinished(embedHost);\n }\n}\n\n// handle message from parent window\nexport function receiveMessage(event: any = {}, dispatch: any, embedHost: string) {\n const { origin, data = {} } = event;\n const { hostData, msgType } = data;\n const validOrigin = checkOriginSource(origin);\n\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.EmbedMessaging,\n actionModifier: Constants.Telemetry.ActionModifier.ReceiveMessage,\n details: JSON.stringify({\n origin,\n validOrigin,\n msgType,\n }),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n\n if (!validOrigin) {\n return;\n }\n\n if (hostData) {\n hostData.userSegment = UserSegment.unauthenticated;\n\n const {\n error,\n hostCorrelationId,\n accessToken,\n privateApps,\n getAppsEndpoint,\n firstName,\n lastName,\n workEmail,\n backendUrlOverride,\n } = hostData;\n\n switch (msgType) {\n case Constants.embedMessage.loadMarketplaceV2:\n if (error) {\n handleError(true, hostCorrelationId, error);\n } else if (!accessToken) {\n handleError(true, hostCorrelationId, 'No access token');\n } else if (!hostCorrelationId) {\n handleError(true, '', 'No host correlation ID');\n } else {\n if (embedHostUtils.hasPrivateApps(embedHost) && !privateApps) {\n handleError(\n false,\n hostCorrelationId,\n 'privateApps is required but is null. EmbedHost: ' + getProductByUrlKey({ urlKey: embedHost })?.UrlKey\n );\n }\n finishProcessingAppData(dispatch, embedHost, privateApps);\n dispatch(createEmbedUserSignInAction(hostData));\n HttpModule.updateAccessToken(hostData);\n }\n break;\n case Constants.embedMessage.loadMarketplace:\n // PowerBI won't log page load finished until after it fetches and parses\n // the app list from the PowerBI backend\n if (getAppsEndpoint) {\n // This event is used to indicate that the additional data is about to be fetched.\n appDataRequestStart = new Date().getTime();\n const detailsObject = {\n message: '[Start] Partner App data request start (' + getProductByUrlKey({ urlKey: embedHost })?.UrlKey + ')',\n endPoint: getAppsEndpoint,\n receivedFirstNameFromEmbedHost: !!firstName,\n receivedLastNameFromEmbedHost: !!lastName,\n receivedWorkEmailFromEmbedHost: !!workEmail,\n receivedAccessTokenFromEmbedHost: !!accessToken,\n };\n\n const payload: ITelemetryData = {\n page: 'In App Gallery(' + getProductByUrlKey({ urlKey: embedHost })?.UrlKey + ')',\n action: Constants.Telemetry.Action.NetworkCall,\n actionModifier: Constants.Telemetry.ActionModifier.Start,\n details: JSON.stringify(detailsObject),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n\n dispatch(fetchEmbedAppData(embedHost, hostData));\n } else if (backendUrlOverride) {\n // This event is used to indicate that the Power BI data is about to be fetched.\n appDataRequestStart = new Date().getTime();\n const detailsObject = {\n message: '[Start] Power BI Request Start. loadMarketplace message Received from Power BI',\n endPoint: backendUrlOverride,\n receivedFirstNameFromEmbedHost: !!firstName,\n receivedLastNameFromEmbedHost: !!lastName,\n receivedWorkEmailFromEmbedHost: !!workEmail,\n receivedAccessTokenFromEmbedHost: !!accessToken,\n };\n const payload: ITelemetryData = {\n page: 'In App Gallery(PowerBI)',\n action: Constants.Telemetry.Action.NetworkCall,\n actionModifier: Constants.Telemetry.ActionModifier.Start,\n details: JSON.stringify(detailsObject),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n dispatch(fetchPowerBIData(hostData));\n }\n\n hostData.email = workEmail;\n dispatch(createEmbedUserSignInAction(hostData));\n HttpModule.updateAccessToken(hostData);\n break;\n default:\n // eslint-disable-next-line no-useless-return\n return;\n }\n }\n}\n\nfunction handleError(showErrorModal: boolean, hostCorrelationId: string, error: any) {\n renderErrorModal(null);\n\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.MyOrgApps,\n actionModifier: Constants.Telemetry.ActionModifier.Error,\n details: JSON.stringify({\n hostCorrelationId,\n error,\n }),\n };\n\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.error(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n}\n","import * as React from 'react';\nimport * as PropTypes from 'prop-types';\nimport SpzaComponent from '@shared/components/spzaComponent';\nimport { isValidInput, serializeUserProfileEmbedInfo, readUserProfileEmbedCookie } from '@shared/utils/appUtils';\nimport { getCookieItem, saveCookieItem } from '@shared/utils/browserStorageUtils';\nimport { IUserLeadGenProfile, ITelemetryData, IUserLeadProfileAgreement, ILicense } from '@shared/Models';\nimport { ILocContext, ICommonContext } from '@shared/interfaces/context';\nimport { Constants, allBillingCountries } from '@shared/utils/constants';\nimport Animation from '@shared/components/animation';\nimport { getWindow } from '@shared/services/window';\nimport { SpzaInstrumentProvider, SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport { logger } from '@src/logger';\nimport { Text, Stack, mergeStyleSets, TextField, Dropdown, Link } from '@fluentui/react';\n\nimport type { IStackTokens, IDropdownOption, IDropdownStyles } from '@fluentui/react';\n\nexport interface IUserProfileProps {\n renderProfile?: boolean;\n userLeadgenProfile: IUserLeadGenProfile;\n}\n\nexport interface IUserProfileMethods {\n getUserProfile: () => Promise;\n updateUserProfile: (profile: IUserLeadProfileAgreement) => Promise;\n}\n\nexport interface IUserProfileModal extends IUserProfileMethods, IUserProfileProps {\n isEmbedded?: boolean;\n userSubmittedForm?: boolean;\n licenseType?: number;\n currentLicense?: ILicense;\n compactUserProfile?: boolean;\n profileCompleted?: (profileFilledOut: boolean) => void;\n callBackHandler?: () => void;\n shouldRenderHeader?: boolean;\n getExpandHeaderText?: string;\n shouldHideAccountInfo?: () => void;\n accountInfoUpdate?: (id: string, value: string) => void;\n isContactForm?: boolean;\n}\n\ninterface IUserProfileField {\n id: string;\n defaultValue: string;\n title: string;\n placeholder: string;\n isRequired?: boolean;\n}\n\nexport class UserProfileModalData {\n user: IUserLeadProfileAgreement;\n firstTimeEmbed: boolean;\n licenseType: number;\n\n constructor(user: IUserLeadGenProfile, isEmbedHost: boolean, licenseType: number) {\n this.updateUserProfile(user, isEmbedHost, licenseType);\n }\n\n updateUserProfile(user: IUserLeadGenProfile, isEmbedHost: boolean, licenseType: number): void {\n this.licenseType = licenseType;\n if (isEmbedHost && user?.firstName === '' && user?.lastName === '' && user?.email === '' && user?.isLatestProfile === false) {\n this.user = {\n ...user,\n phone: '',\n company: '',\n country: '',\n title: '',\n accepted: true,\n };\n const userProfileInCookie = getCookieItem(Constants.userProfileEmbedCookieName, false);\n if (userProfileInCookie) {\n readUserProfileEmbedCookie(userProfileInCookie, this.user);\n } else {\n this.firstTimeEmbed = true;\n }\n } else {\n this.user = {\n ...user,\n accepted: true,\n licenses: this.setLicensesToLocalUserProfile(user.licenses),\n };\n }\n }\n\n setLicensesToLocalUserProfile(newLicenses: ILicense[]) {\n if (\n this.licenseType !== Constants.Dynamics365LicenseType.noLicense &&\n newLicenses?.length === Constants.dynamicsLicense.currentD365LicenseNumber\n ) {\n const localLicenses: ILicense[] = [];\n for (let i = 0; i < newLicenses.length; i++) {\n const licenseInState = newLicenses[`${i}`];\n const licenseNewObj: ILicense = {\n type: licenseInState.type,\n hasLicense: licenseInState.hasLicense,\n isDisputed: licenseInState.isDisputed,\n };\n localLicenses.push(licenseNewObj);\n }\n return localLicenses;\n }\n return [];\n }\n\n isDirty(originalUser: IUserLeadGenProfile): boolean {\n for (const i in this.user) {\n if (i !== 'accepted' && i !== 'licenses') {\n if (originalUser[`${i}`] !== this.user[`${i}`]) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n licenseChange(newCurrentLicense: ILicense, isEmbeddedHost: boolean) {\n if (isEmbeddedHost) {\n return false;\n }\n if (\n newCurrentLicense &&\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365License &&\n this.user.licenses[Constants.Dynamics365LicenseIndex.dynamics365LicenseIndex].isDisputed !== newCurrentLicense.isDisputed\n ) {\n return true;\n }\n\n if (\n newCurrentLicense &&\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense &&\n this.user.licenses[Constants.Dynamics365LicenseIndex.dynamics365ForBusinessLicenseIndex].isDisputed !==\n newCurrentLicense.isDisputed\n ) {\n return true;\n }\n\n return false;\n }\n}\n\nconst userProfileModalClassNames = mergeStyleSets({\n editButton: {\n paddingLeft: 3,\n },\n boldText: {\n fontWeight: 600,\n },\n doubleCellContainer: {\n [`@media (max-width: 425px)`]: {\n flexWrap: 'wrap',\n },\n },\n secondCell: {\n [`@media (min-width: 425px)`]: {\n marginLeft: 12,\n },\n [`@media (max-width: 425px)`]: {\n marginTop: 15,\n },\n },\n});\n\nconst userProfileFieldsContainer: IStackTokens = { childrenGap: 8 };\nconst expandedUserProfileTokens: IStackTokens = { childrenGap: 21 };\n\nconst dropDownStyles: Partial = {\n callout: {\n maxHeight: 300,\n display: 'flex',\n flexDirection: 'column',\n },\n};\n\nexport interface IUserProfileModalState {\n showProfile: boolean;\n userProfileUpdated: boolean;\n}\n\nexport class UserProfileModal extends SpzaComponent {\n public context: ILocContext & ICommonContext;\n private instrument: SpzaInstrumentProvider;\n private countryList = this.getCountries();\n private localUserInfo: UserProfileModalData = new UserProfileModalData(\n this.props.userLeadgenProfile,\n this.props.isEmbedded,\n this.props.licenseType\n );\n\n constructor(props: IUserProfileModal, context: ILocContext & ICommonContext) {\n super(props, context);\n this.instrument = SpzaInstrumentService.getProvider();\n\n this.state = {\n showProfile: !!this.localUserInfo.firstTimeEmbed || !props.compactUserProfile,\n userProfileUpdated: false,\n };\n this.onChange = this.onChange.bind(this);\n }\n\n shouldProfileBeRendered(): boolean {\n const { renderProfile } = this.props;\n return renderProfile;\n }\n\n UNSAFE_componentWillMount(): void {\n const { profileCompleted, userLeadgenProfile, isEmbedded, licenseType } = this.props;\n\n if (!this.shouldProfileBeRendered()) {\n profileCompleted(true);\n } else {\n this.localUserInfo.updateUserProfile(userLeadgenProfile, isEmbedded, licenseType);\n this.checkMandatoryFields();\n }\n }\n\n onChange(id: string, value: string): void {\n const { accountInfoUpdate } = this.props;\n this.localUserInfo.user[`${id}`] = value;\n accountInfoUpdate(id, value);\n this.checkMandatoryFields();\n }\n\n checkMandatoryFields(): void {\n const { profileCompleted } = this.props;\n profileCompleted(this.mandatoryFieldsDidUpdate());\n }\n\n mandatoryFieldsDidUpdate(): boolean {\n // TODO: Add validation for international phone numbers +52 353 333 7777\n // TODO: Validate phone number with code number.\n const { firstName, lastName, country } = this.localUserInfo.user;\n if (\n isValidInput(firstName) &&\n isValidInput(lastName) &&\n country &&\n allBillingCountries.some((c) => c.countryCode === this.localUserInfo.user.country)\n ) {\n return true;\n }\n return false;\n }\n\n onClickEditButton(eventKeyCode?: number) {\n const { shouldHideAccountInfo } = this.props;\n if (!eventKeyCode || eventKeyCode === Constants.SystemKey.Space || eventKeyCode === Constants.SystemKey.Enter) {\n this.setState((prevState: IUserProfileModalState) => ({\n showProfile: true,\n userProfileUpdated: prevState.userProfileUpdated,\n }));\n shouldHideAccountInfo();\n }\n }\n\n renderCompactedUserProfile(): JSX.Element {\n return (\n \n this.onClickEditButton()}\n onKeyDown={(event) => this.onClickEditButton(event.keyCode)}\n underline\n >\n {this.context.loc('UserProfileCompacted_EditButton', 'Edit your details')}\n \n \n );\n }\n\n UNSAFE_componentWillReceiveProps(nextProps: IUserProfileModal): void {\n const {\n userLeadgenProfile: currentUserLeadgenProfile,\n isEmbedded,\n licenseType,\n userSubmittedForm: currenttUserSubmittedForm,\n updateUserProfile,\n callBackHandler,\n } = this.props;\n const { userLeadgenProfile: nextUserLeadgenProfile, userSubmittedForm: nextUserSubmittedForm, currentLicense } = nextProps;\n\n if (!this.areEqual(currentUserLeadgenProfile, nextUserLeadgenProfile)) {\n this.localUserInfo.updateUserProfile(nextUserLeadgenProfile, isEmbedded, licenseType);\n }\n if (\n licenseType &&\n licenseType !== Constants.Dynamics365LicenseType.noLicense &&\n !this.licenseInfoEqual(this.localUserInfo.user.licenses, nextUserLeadgenProfile.licenses)\n ) {\n this.localUserInfo.setLicensesToLocalUserProfile(nextUserLeadgenProfile.licenses);\n }\n if (nextUserSubmittedForm && nextUserSubmittedForm !== currenttUserSubmittedForm) {\n if (\n this.localUserInfo.isDirty(nextUserLeadgenProfile) ||\n this.localUserInfo.licenseChange(currentLicense, isEmbedded) ||\n nextUserLeadgenProfile.updateRequired\n ) {\n if (\n !isEmbedded &&\n licenseType &&\n licenseType !== Constants.Dynamics365LicenseType.noLicense &&\n this.localUserInfo.user.licenses &&\n this.localUserInfo.user.licenses.length === Constants.dynamicsLicense.currentD365LicenseNumber\n ) {\n this.localUserInfo.user.licenses[licenseType - 1].isDisputed = currentLicense.isDisputed;\n }\n updateUserProfile(this.localUserInfo.user).then(() => {\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.Info,\n details: 'User Updated profile, Will run acquisition.',\n };\n this.instrument.probe('logAndFlushTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n if (isEmbedded) {\n saveCookieItem(Constants.userProfileEmbedCookieName, serializeUserProfileEmbedInfo(this.localUserInfo.user));\n }\n callBackHandler();\n });\n } else {\n const payload: ITelemetryData = {\n page: getWindow().location.href,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.Info,\n details: 'User did not updated profile, Will run acquisition.',\n };\n this.instrument.probe('logAndFlushTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n });\n callBackHandler();\n }\n }\n }\n\n shouldComponentUpdate(nextProps: IUserProfileModal, nextState: IUserProfileModalState): boolean {\n const { compactUserProfile: currentCompactUserProfile, userLeadgenProfile: currentUserLeadgenProfile } = this.props;\n const { showProfile: currentShowProfile } = this.state;\n const { showProfile: nextShowProfile } = nextState;\n const { compactUserProfile: nextCompactUserProfile, userLeadgenProfile: nextUserLeadgenProfile } = nextProps;\n\n return (\n currentShowProfile !== nextShowProfile ||\n currentCompactUserProfile !== nextCompactUserProfile ||\n !this.areEqual(nextUserLeadgenProfile, currentUserLeadgenProfile)\n );\n }\n\n getDefaultCountry(defaultCountry: string): IDropdownOption {\n const country = this.countryList.find((c) => c.key === defaultCountry.toUpperCase());\n return country || { key: '', text: '' };\n }\n\n getCountries(): IDropdownOption[] {\n return allBillingCountries.map((c) => ({ key: c.countryCode, text: c.name }));\n }\n\n dropDownCountry(defaultValue?: string): JSX.Element {\n const id = Constants.Country;\n const selectedCountry = defaultValue ? this.getDefaultCountry(defaultValue) : { key: '', text: '' };\n const { key: selectedCountryKey } = selectedCountry;\n\n return (\n \n {\n this.onChange(id, option.key.toString());\n }}\n placeholder={this.context.loc('Leads_Modal_Country_Placeholder', 'Select country')}\n styles={dropDownStyles}\n disabled={!!selectedCountryKey}\n required={!selectedCountryKey}\n />\n \n );\n }\n\n inputCell(field: IUserProfileField, className?: string): JSX.Element {\n const { id, title, placeholder, defaultValue, isRequired } = field;\n return (\n \n this.onChange(id, value)}\n key={id + defaultValue}\n defaultValue={defaultValue || ''}\n aria-required={isRequired}\n ariaLabel={title}\n placeholder={placeholder}\n className={className}\n />\n \n );\n }\n\n doubleInputCells(isRequired: boolean, firstCell: IUserProfileField, secondCell: IUserProfileField, title: string): JSX.Element {\n const {\n id: firstCellId,\n defaultValue: firstCellDefaultValue,\n title: firstCellTitle,\n placeholder: firstCellPlaceholder,\n } = firstCell;\n\n const {\n id: secondCellId,\n defaultValue: secondCellDefaultValue,\n title: secondCellTitle,\n placeholder: secondCellPlaceholder,\n } = secondCell;\n\n return (\n \n \n this.onChange(firstCellId, value)}\n key={firstCellId + firstCellDefaultValue}\n defaultValue={firstCellDefaultValue || ''}\n ariaLabel={firstCellTitle}\n aria-required={isRequired}\n placeholder={firstCellPlaceholder}\n />\n \n \n this.onChange(secondCellId, value)}\n key={secondCellId + secondCellDefaultValue}\n defaultValue={secondCellDefaultValue || ''}\n ariaLabel={secondCellTitle}\n aria-required={isRequired}\n placeholder={secondCellPlaceholder}\n className={userProfileModalClassNames.secondCell}\n />\n \n \n );\n }\n\n renderExpandedHeader(): JSX.Element {\n const { shouldRenderHeader, isContactForm, getExpandHeaderText } = this.props;\n const text: string =\n shouldRenderHeader && isContactForm\n ? this.context.loc(\n 'CTA_UserProfile_Title',\n 'To continue, we need some basic profile information. We’ve pulled your Microsoft Account data to help you get started.'\n )\n : '';\n\n return {getExpandHeaderText || text};\n }\n\n renderExpandedUserProfile(): JSX.Element {\n const { isEmbedded, userLeadgenProfile } = this.props;\n const firstName: IUserProfileField = {\n id: 'firstName',\n defaultValue:\n isEmbedded && !this.localUserInfo.user.firstName ? userLeadgenProfile.firstName : this.localUserInfo.user.firstName,\n title: this.context.loc('RT_FirstName', 'First name'),\n placeholder: this.context.loc('RT_FirstName', 'First name'),\n };\n\n const lastName: IUserProfileField = {\n id: 'lastName',\n defaultValue:\n isEmbedded && !this.localUserInfo.user.lastName ? userLeadgenProfile.lastName : this.localUserInfo.user.lastName,\n title: this.context.loc('RT_LastName', 'Last name'),\n placeholder: this.context.loc('RT_LastName', 'Last name'),\n };\n\n const title: IUserProfileField = {\n id: 'title',\n defaultValue: isEmbedded && !this.localUserInfo.user.title ? userLeadgenProfile.title : this.localUserInfo.user.title,\n title: this.context.loc('RT_Title', 'Job title'),\n placeholder: this.context.loc('Leads_Modal_Title_Placeholder', 'Enter your job title'),\n };\n\n const company: IUserProfileField = {\n id: 'company',\n defaultValue: isEmbedded && !this.localUserInfo.user.company ? userLeadgenProfile.company : this.localUserInfo.user.company,\n title: this.context.loc('RT_Company', 'Company'),\n placeholder: this.context.loc('Leads_Modal_Company_Placeholder', 'Enter your company name'),\n };\n\n const phone: IUserProfileField = {\n id: 'phone',\n defaultValue: isEmbedded && !this.localUserInfo.user.phone ? userLeadgenProfile.phone : this.localUserInfo.user.phone,\n title: this.context.loc('RT_Phone', 'Phone number'),\n placeholder: this.context.loc('Leads_Modal_Phone_Number_Placeholder', 'Write your phone number'),\n };\n\n return (\n \n {this.renderExpandedHeader()}\n \n {this.doubleInputCells(true, firstName, lastName, this.context.loc('RT_Name', 'Name'))}\n \n {this.inputCell(title)}\n {this.inputCell(company, userProfileModalClassNames.secondCell)}\n \n {this.dropDownCountry(\n isEmbedded && !this.localUserInfo.user.country ? userLeadgenProfile.country : this.localUserInfo.user.country\n )}\n {this.inputCell(phone)}\n \n \n );\n }\n\n renderAnimation(): JSX.Element {\n return (\n
    \n {' '}\n {' '}\n
    \n );\n }\n\n componentDidMount() {\n const { getUserProfile } = this.props;\n if (this.shouldGetUserProfile()) {\n getUserProfile();\n } else {\n this.checkMandatoryFields();\n }\n }\n\n shouldGetUserProfile(): boolean {\n const { userLeadgenProfile, isEmbedded } = this.props;\n return !userLeadgenProfile.isLatestProfile && this.shouldProfileBeRendered() && !isEmbedded;\n }\n\n shouldRenderExpandedProfile() {\n // if update required or one of the user props are not filled out.\n const { showProfile } = this.state;\n return showProfile || !this.mandatoryFieldsDidUpdate() || this.updateRequiredFromAPI();\n }\n\n updateRequiredFromAPI(): boolean {\n const { userLeadgenProfile } = this.props;\n return userLeadgenProfile.updateRequired === true;\n }\n\n renderImpl(): JSX.Element {\n const { compactUserProfile } = this.props;\n if (compactUserProfile) {\n return this.renderCompactedUserProfile();\n } else if (!this.shouldProfileBeRendered()) {\n return null;\n } else if (this.shouldGetUserProfile()) {\n return this.renderAnimation();\n } else if (this.shouldRenderExpandedProfile()) {\n return this.renderExpandedUserProfile();\n } else {\n return this.renderCompactedUserProfile();\n }\n }\n\n componentDidUpdate(prevProps: IUserProfileModal) {\n const { userLeadgenProfile: currentUserLeadgenProfile } = this.props;\n const { userLeadgenProfile: prevUserLeadgenProfile } = prevProps;\n if (!this.areEqual(prevUserLeadgenProfile, currentUserLeadgenProfile)) {\n this.checkMandatoryFields();\n }\n }\n\n areEqual(profile: IUserLeadGenProfile, newProfile: IUserLeadGenProfile): boolean {\n return (\n profile.isLatestProfile === newProfile.isLatestProfile &&\n profile.updateRequired === newProfile.updateRequired &&\n profile.firstName === newProfile.firstName &&\n profile.lastName === newProfile.lastName &&\n profile.company === newProfile.company &&\n profile.country === newProfile.country &&\n profile.phone === newProfile.phone &&\n profile.title === newProfile.title &&\n profile.email === newProfile.email\n );\n }\n\n licenseInfoEqual(license: ILicense[], newLicense: ILicense[]) {\n if (!newLicense || !license) {\n return false;\n }\n\n if (license.length !== newLicense.length) {\n return false;\n }\n\n for (let i = 0; i < license.length; i++) {\n if (license[`${i}`].isDisputed !== newLicense[`${i}`].isDisputed) {\n return false;\n }\n }\n return true;\n }\n}\n\n(UserProfileModal as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n renderErrorModal: PropTypes.func,\n};\n","import React from 'react';\nimport { IState } from '@src/State';\nimport {\n UserProfileModal as UserProfileModalComponent,\n IUserProfileMethods,\n IUserProfileProps,\n} from '@appsource/components/modals/userProfileModal';\nimport { getUserProfile, updateUserProfile } from '@shared/actions/thunkActions';\nimport { Dispatch } from 'redux';\nimport { connect } from 'react-redux';\nimport { IUserLeadProfileAgreement } from '@shared/Models';\n\nconst mapStateToProps = (state: IState): IUserProfileProps => {\n if (state.config.featureFlags.ProfileExp === 'true') {\n return {\n renderProfile: true,\n userLeadgenProfile: state.users.profile,\n };\n }\n return {\n userLeadgenProfile: state.users.profile,\n };\n};\n\nconst mapDispatchToProps = (dispatch: Dispatch): IUserProfileMethods => {\n return {\n getUserProfile: () => dispatch(getUserProfile()),\n updateUserProfile: (profile: IUserLeadProfileAgreement) => dispatch(updateUserProfile(profile)),\n };\n};\n\nexport const UserProfileModal = connect(\n mapStateToProps,\n mapDispatchToProps\n)(UserProfileModalComponent) as React.StatelessComponent;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport {\n IconButton,\n Stack,\n Text,\n Modal,\n ScreenWidthMinXLarge,\n ScreenWidthMinLarge,\n mergeStyleSets,\n PrimaryButton,\n} from '@fluentui/react';\n\nimport type { IButtonStyles, IIconProps, IModalStyles, IStackTokens } from '@fluentui/react';\nimport { NeutralColors } from '@fluentui/theme';\nimport consentSuccessModalIcon from '@shared/images/consentSuccessModalIcon.png';\nimport { Constants } from '@shared/utils/constants';\nimport { getNpsModule } from '@appsource/utils/nps';\n\nexport interface ISuccessConsentModalProps {\n publisher: string;\n dismissModal: () => void;\n shouldSkipModal?: boolean;\n}\n\nconst successConsentModalClassNames = mergeStyleSets({\n description: {\n display: 'flex',\n textAlign: 'center',\n flexDirection: 'column',\n },\n centeredText: {\n textAlign: 'center',\n },\n imgSize: {\n width: 100,\n height: 100,\n },\n});\n\nconst iconButtonStyles: Partial = {\n root: {\n color: NeutralColors.gray160,\n },\n};\n\nconst modalStyles: Partial = {\n main: {\n [`@media (min-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '28%',\n },\n [`@media (min-width: ${ScreenWidthMinLarge}px) and (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '50%',\n },\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n width: '80%',\n },\n display: 'flex',\n maxWidth: 560,\n },\n scrollableContent: {\n padding: '16px 27px 24px 24px',\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n padding: '16px 20px 24px 20px',\n },\n },\n};\n\nconst cancelIcon: IIconProps = { iconName: 'Cancel' };\nconst titleAndContentTokens: IStackTokens = { childrenGap: 60 };\nconst descriptionAndIconTokens: IStackTokens = { childrenGap: 20 };\nconst descriptionTokens: IStackTokens = { maxWidth: 280 };\nconst contentAndBottomTokens: IStackTokens = { childrenGap: 66 };\n\nexport const SuccessConsentModal: React.FunctionComponent = (\n { publisher, dismissModal, shouldSkipModal }: ISuccessConsentModalProps,\n context: ILocContext & ILocParamsContext\n) => {\n return (\n \n \n \n \n \n {context.loc('Consent_Modal_Success_Title', 'Thanks for getting in touch!')}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {context.loc('Success_Consent_Modal_Someone_From', `Someone from`)} \n \n {publisher}\n \n \n \n {context.loc('Success_Consent_Modal_Will_Contact_You', 'will contact you soon.')}\n \n \n \n \n \n {\n dismissModal();\n getNpsModule()?.ctaClicked();\n }}\n data-bi-id={Constants.JsllCTAId.Close}\n data-bi-area=\"Consent Dialog\"\n />\n \n \n \n \n \n );\n};\n\n(SuccessConsentModal as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n};\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { ILocContext } from '@shared/interfaces/context';\nimport {\n Modal,\n Stack,\n Text,\n IconButton,\n ScreenWidthMinXLarge,\n ScreenWidthMinLarge,\n Icon,\n mergeStyleSets,\n PrimaryButton,\n} from '@fluentui/react';\nimport type { IStackTokens, IButtonStyles, IIconProps, IIconStyles, IModalStyles } from '@fluentui/react';\nimport { NeutralColors, SharedColors } from '@fluentui/theme';\n\nexport interface IFailureConsentModal {\n shouldSkipModal?: boolean;\n dismissModal: () => void;\n showContactMeError: boolean;\n}\n\nconst titleAndContentTokens: IStackTokens = { childrenGap: 20 };\nconst contentAndBottomTokens: IStackTokens = { childrenGap: 24 };\nconst cancelIcon: IIconProps = { iconName: 'Cancel' };\n\nconst iconButtonStyles: Partial = {\n root: {\n color: NeutralColors.gray160,\n },\n};\n\nconst infoIconStyles: Partial = {\n root: {\n fontSize: 16,\n marginRight: 8,\n color: SharedColors.red20,\n },\n};\n\nconst failureConsentModalClassNames = mergeStyleSets({\n description: {\n color: NeutralColors.gray130,\n },\n});\n\nconst modalStyles: Partial = {\n main: {\n [`@media (min-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '30%',\n },\n [`@media (min-width: ${ScreenWidthMinLarge}px) and (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '50%',\n },\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n width: '80%',\n },\n display: 'flex',\n },\n scrollableContent: {\n padding: '16px 27px 24px 24px',\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n padding: '16px 20px 24px 20px',\n },\n },\n};\n\nexport const FailureConsentModal: React.FunctionComponent = (\n { dismissModal, shouldSkipModal, showContactMeError }: IFailureConsentModal,\n context: ILocContext\n) => {\n return (\n \n \n \n \n \n {context.loc('Failure_Consent_Modal_Title', \"Couldn't send contact request\")}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {showContactMeError\n ? context.loc(\n 'ContactMeError_Description_Text',\n 'There was a problem sending your request. Please try again later.'\n )\n : context.loc(\n 'Failure_Consent_Modal_Description',\n 'There was a problem sending the contact request. Check your network connection and try again.'\n )}\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\n(FailureConsentModal as any).contextTypes = {\n loc: PropTypes.func,\n};\n","import React from 'react';\nimport { ILocContext, ILocParamsContext, ICommonContext, IBuildHrefContext } from '@shared/interfaces/context';\nimport { Stack, Text, IconButton, Modal, ScreenWidthMinXLarge, ScreenWidthMinLarge } from '@fluentui/react';\nimport type { IStackTokens, IButtonStyles, IIconProps, IModalStyles } from '@fluentui/react';\n\nimport PropTypes from 'prop-types';\nimport { NeutralColors } from '@fluentui/theme';\nimport { useSelector } from 'react-redux';\nimport { IState } from '@src/State';\n\ninterface IConsentModalContentProps {\n showAnimation: boolean;\n handoffTitle: string;\n isTransactApp: boolean;\n isContactForm: boolean;\n publisher: string;\n shouldSkipModal: boolean;\n dismissModal: () => void;\n children: React.ReactNode;\n}\n\nconst modalContentItemsTokens: IStackTokens = { childrenGap: 20 };\nconst cancelIcon: IIconProps = { iconName: 'Cancel' };\n\nconst iconButtonStyles: Partial = {\n root: {\n color: NeutralColors.gray160,\n },\n};\n\nconst modalStyles: Partial = {\n main: {\n [`@media (min-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '35%',\n },\n [`@media (min-width: ${ScreenWidthMinLarge}px) and (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '60%',\n },\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n width: '90%',\n },\n display: 'flex',\n },\n scrollableContent: {\n padding: '16px 27px 24px 24px',\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n padding: '16px 20px 24px 20px',\n },\n },\n};\n\nexport const ConsentModalContent: React.FunctionComponent = (\n {\n showAnimation,\n handoffTitle,\n isTransactApp,\n isContactForm,\n publisher,\n shouldSkipModal,\n dismissModal,\n children,\n }: IConsentModalContentProps,\n context: IBuildHrefContext & ICommonContext & ILocContext & ILocParamsContext\n) => {\n const isEmbedded = useSelector((state) => state.config.isEmbedded);\n const withAnimationHeaderText = isContactForm\n ? context.locParams(\n 'TRY_ContactMe',\n [publisher || 'the publisher'],\n `Sending your request to ${[publisher || 'the publisher']}`\n )\n : context.locParams(\n 'TRY_Redirect',\n [handoffTitle || 'Azure Portal'],\n `Taking you to ${[handoffTitle || 'Azure Portal']} to complete this process`\n );\n\n const headerText =\n isEmbedded && !isContactForm\n ? context.loc('Confirm_to_continue_Modal_Title', 'Confirm to continue')\n : !isTransactApp && !isContactForm\n ? context.loc('Confirm_Details_Modal_Title', 'Confirm your details to continue')\n : isContactForm\n ? context.locParams('Contact_Me_Modal_Title', [publisher], `Ask someone from ${publisher} to contact you`)\n : null;\n\n return (\n \n \n \n \n \n {showAnimation ? (\n {withAnimationHeaderText}\n ) : (\n {headerText}\n )}\n \n \n \n \n \n \n {children}\n \n \n );\n};\n\n(ConsentModalContent as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n renderErrorModal: PropTypes.func,\n buildHref: PropTypes.func,\n};\n","const URL_PATTERN = /((http|ftp|https):\\/\\/)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/i;\n\nexport const containsURLOrEmail = (input: string): boolean => {\n if (input && input.trim().length > 0) {\n return URL_PATTERN.test(input.trim());\n }\n return false;\n};\n","import React from 'react';\nimport * as PropTypes from 'prop-types';\nimport Animation from '@shared/components/animation';\nimport SpzaComponent from '@shared/components/spzaComponent';\nimport {\n dynamicsLicenseTypeForEmbed,\n setInitialLocalLicenseStore,\n setInitialLicenseDisplayText,\n isTransactApp,\n getTrailIdAndData,\n dynamicsLicenseType,\n readUserProfileEmbedCookie,\n getAppTelemetryDetailContentText,\n getServiceTelemetryDetailContentText,\n shouldDownloadPowerBIVisual,\n getActionStringForCTAType,\n isPowerBIVisuals,\n processHandoffURLAndTitle,\n isOpenInNewWindowButton,\n openInNewWindow,\n getProductByUrlKey,\n generateGuid,\n} from '@shared/utils/appUtils';\nimport {\n ILicense,\n IAppDataItem,\n ITelemetryData,\n IAppDetailInformation,\n IUserLeadProfileAgreement,\n SendLeadInfoOptions,\n} from '@shared/Models';\nimport { Constants, OfferType } from '@shared/utils/constants';\nimport { getLocalStorageItem, getCookieItem } from '@shared/utils/browserStorageUtils';\nimport { postEmbedAcquisitionMessage } from '@embed/embedMessaging';\nimport { SpzaInstrumentProvider, SpzaInstrumentService } from '@shared/services/telemetry/spza/spzaInstrument';\nimport { NpsModule } from '@shared/utils/npsUtils';\nimport { Service } from '@shared/serviceViewModel';\nimport { UserProfileModal } from '@appsource/containers/modals/userProfileModal';\nimport { IFeatureFlags, IModalState, IUserDataState } from '@src/State';\nimport { isLeadgenEnabled, isETERunning } from '@shared/utils/modals';\nimport { urlPush, routes, WithRouterProps } from '@shared/routerHistory';\nimport { IBuildHrefContext, ICommonContext, ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { getPrimaryProductUrl } from '@shared/utils/datamappingHelpers';\nimport { getNpsModule } from '@appsource/utils/nps';\nimport { logger } from '@src/logger';\nimport {\n Stack,\n Text,\n mergeStyleSets,\n PrimaryButton,\n Icon,\n ScreenWidthMinXLarge,\n ScreenWidthMinLarge,\n TextField,\n} from '@fluentui/react';\nimport type { IModalStyles, IStackTokens, IIconStyles } from '@fluentui/react';\nimport { NeutralColors, CommunicationColors } from '@fluentui/theme';\nimport { TelemetryImage } from '@shared/components/telemetryImage';\nimport classNames from 'classnames';\nimport { SuccessConsentModal } from '@appsource/components/modals/successConsentModal';\nimport { FailureConsentModal } from '@appsource/components/modals/failureConsentModal';\nimport { SafeHtmlWrapper } from '@shared/components/safeHtmlWrapper';\nimport { ConsentModalContent } from './consentModalContent';\nimport { getTooltipSilentLogIn } from '@shared/utils/silentLogInUtils';\nimport { useTelemetry } from '@shared/hooks/useTelemetry';\nimport { containsURLOrEmail } from '@shared/utils/InputValidationUtils';\nimport { openInNewWindowButtonStyles } from '@shared/components/contentStyles';\nimport { hidePowerBiVisualPricing } from '@shared/utils/pricing';\nimport { stringifyError } from '@shared/utils/errorUtils';\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst [{ pageAction }] = useTelemetry();\n\nexport interface IConsentModal {\n userInfo: IUserDataState;\n userProfile?: IUserLeadProfileAgreement;\n embedHost?: string;\n appInfo?: IAppDataItem;\n serviceInfo?: Service;\n isContactForm: boolean;\n isEmbedded: boolean;\n dismissModal: () => void;\n isNationalCloud: boolean;\n openInstructionsModal: () => void;\n openDriveModal?: (driveUrl: string) => void;\n ctaType: Constants.CTAType;\n billingCountryCode: string;\n fetchAppDetails?: (targetApp: IAppDataItem) => void;\n fetchServiceDetails?: (targetService: Service) => void;\n sendLeadInfo: (options: SendLeadInfoOptions) => Promise;\n getUserAuthStatus: () => Promise;\n updatePurchaseStatus: () => void;\n updateLicense?: (licenseType: number, hasLicense: boolean) => void;\n featureFlags?: IFeatureFlags;\n signOutUser?: () => void;\n query?: { [key: string]: string };\n currentView?: string;\n isTransactApp?: boolean;\n modal: IModalState;\n isLoadingUserProfile: boolean;\n isOpenedFromPDP?: boolean;\n ribbonKey?: string;\n flightCodes: string;\n}\n\nconst modalStyles: Partial = {\n main: {\n [`@media (min-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '35%',\n },\n [`@media (min-width: ${ScreenWidthMinLarge}px) and (max-width: ${ScreenWidthMinXLarge}px)`]: {\n width: '60%',\n },\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n width: '90%',\n },\n display: 'flex',\n },\n scrollableContent: {\n padding: '16px 27px 24px 24px',\n [`@media (max-width: ${ScreenWidthMinLarge}px)`]: {\n padding: '16px 20px 24px 20px',\n },\n },\n};\n\nconst infoIconStyles: Partial = {\n root: {\n fontSize: 16,\n marginRight: 8,\n },\n};\n\nconst consentModalClassNames = mergeStyleSets({\n appImg: {\n width: 44,\n height: 44,\n padding: 4,\n border: `1px solid ${NeutralColors.gray20}`,\n },\n appImgContainer: {\n marginRight: 8,\n width: 44,\n height: 44,\n },\n boldText: {\n fontWeight: 600,\n },\n desclaimerContainer: {\n backgroundColor: NeutralColors.gray20,\n marginBottom: 20,\n marginTop: 12,\n '& a:link': {\n color: CommunicationColors.primary,\n textDecoration: 'underline',\n },\n '& a:visited': {\n color: CommunicationColors.primary,\n },\n '& a:hover': {\n color: CommunicationColors.shade20,\n },\n '& a:active': {\n color: CommunicationColors.shade30,\n },\n },\n userProfileContainer: {\n marginBottom: 8,\n },\n notes: {\n marginBottom: 8,\n },\n animationImg: {\n display: 'flex',\n justifyContent: 'center',\n },\n});\n\nconst loginUserInfoTokens: IStackTokens = { padding: '20px 0px 4px' };\nconst desclaimerTokens: IStackTokens = { padding: 8 };\nconst animationStackTokens: IStackTokens = { childrenGap: 25 };\n\n/** These offer types should not trigger sending leads */\nconst offerTypesWithoutLeads = [Constants.PowerBI.visuals];\n\nexport class ConsentModal extends SpzaComponent {\n context: IBuildHrefContext & ICommonContext & ILocContext & ILocParamsContext;\n protected handoffTitle = '';\n private instrument: SpzaInstrumentProvider;\n private licenseType = -1;\n\n constructor(props: IConsentModal & WithRouterProps) {\n super(props);\n this.instrument = SpzaInstrumentService.getProvider();\n\n const { userProfile, isContactForm, serviceInfo, isEmbedded, appInfo } = this.props;\n\n // 1 is D365 license, 2 is D365 for financial license, -1 is don't display account info\n this.licenseType = isEmbedded\n ? dynamicsLicenseTypeForEmbed(appInfo.products)\n : appInfo\n ? dynamicsLicenseType(userProfile, appInfo.products)\n : -1;\n\n this.state = {\n isProfileCompleted: false,\n showAnimation: false,\n showUserSignInError: false,\n showContactMeError: false,\n // To get the privacy URL for an app, we need to fetch the App Details. This is lazy loaded.\n // Hence it is in the state.\n appPrivacyPolicyUrl: '',\n showSuccessDialog: false,\n formSubmitted: false,\n showAccountInfo: false,\n forceUserProfileCompacted: !isContactForm && !serviceInfo && this.isMandatoryFieldsFilled(userProfile),\n accountInfo: {\n firstName: userProfile?.firstName ? userProfile.firstName : '',\n lastName: userProfile?.lastName ? userProfile.lastName : '',\n email: userProfile?.email ? userProfile.email : '',\n title: userProfile?.title ? userProfile.title : '',\n company: userProfile?.company ? userProfile.company : '',\n country: userProfile?.country ? userProfile.country : '',\n phone: userProfile?.phone ? userProfile.phone : '',\n },\n notes: '',\n isNotesInvalid: false,\n localLicenseStore:\n (this.licenseType === Constants.Dynamics365LicenseType.dynamics365License ||\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense) &&\n !isEmbedded\n ? setInitialLocalLicenseStore(userProfile.licenses, this.licenseType)\n : null,\n licenseDisplayText:\n (this.licenseType === Constants.Dynamics365LicenseType.dynamics365License ||\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense) &&\n !isEmbedded\n ? setInitialLicenseDisplayText(userProfile.licenses, this.licenseType)\n : '',\n shouldSkipModal: this.shouldSkipModal(userProfile),\n trailId: generateGuid(),\n };\n\n const { accountInfo } = this.state;\n if (\n isEmbedded &&\n accountInfo.firstName === '' &&\n accountInfo.lastName === '' &&\n accountInfo.email === '' &&\n accountInfo.phone === ''\n ) {\n const userProfileInCookie = getCookieItem(Constants.userProfileEmbedCookieName, false);\n if (userProfileInCookie) {\n const userInfoInCache: IUserLeadProfileAgreement = {\n firstName: '',\n lastName: '',\n phone: '',\n company: '',\n country: '',\n email: '',\n title: '',\n accepted: true,\n managedLicenses: [],\n };\n readUserProfileEmbedCookie(userProfileInCookie, userInfoInCache);\n const accountInfoFromCache = {\n firstName: userInfoInCache.firstName ? userInfoInCache.firstName : '',\n lastName: userInfoInCache.lastName ? userInfoInCache.lastName : '',\n email: userInfoInCache.email ? userInfoInCache.email : '',\n title: userInfoInCache.title ? userInfoInCache.title : '',\n company: userInfoInCache.company ? userInfoInCache.company : '',\n country: userInfoInCache.country ? userInfoInCache.country : '',\n phone: userInfoInCache.phone ? userInfoInCache.phone : '',\n };\n this.state = {\n accountInfo: accountInfoFromCache,\n };\n }\n }\n }\n\n isMandatoryFieldsFilled({ firstName, lastName, email, country }: IUserLeadProfileAgreement) {\n return !!(firstName && lastName && email && country);\n }\n\n telemetry(action: string, actionModifier: string, details: any) {\n const { trailId } = this.state;\n const { location } = this.props;\n const payload = {\n page: location?.pathname,\n action,\n actionModifier,\n details: JSON.stringify({ trailId, ...details }),\n };\n SpzaInstrumentService.getProvider().probe('logTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n page: payload.page,\n });\n }\n\n logConsentDialogEvents(action: string, actionModifier: string, details: string = this.getTelemetryDetailsContent()) {\n const { appInfo, serviceInfo, location } = this.props;\n const entityInfo = appInfo || serviceInfo;\n\n const payload: ITelemetryData = {\n page: location?.pathname,\n action,\n actionModifier,\n appName: entityInfo && entityInfo.entityId,\n details,\n };\n this.instrument.probe('logAndFlushTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n appName: payload.appName,\n });\n }\n\n setAccountInfo(consentProps: IConsentModal) {\n const { userProfile, isEmbedded, appInfo } = consentProps;\n\n this.licenseType = isEmbedded\n ? dynamicsLicenseTypeForEmbed(appInfo.products)\n : appInfo\n ? dynamicsLicenseType(userProfile, appInfo.products)\n : -1;\n\n this.setState({\n accountInfo: {\n firstName: userProfile?.firstName ? userProfile.firstName : '',\n lastName: userProfile?.lastName ? userProfile.lastName : '',\n email: userProfile?.email ? userProfile.email : '',\n title: userProfile?.title ? userProfile.title : '',\n company: userProfile?.company ? userProfile.company : '',\n country: userProfile?.country ? userProfile.country : '',\n phone: userProfile?.phone ? userProfile.phone : '',\n },\n localLicenseStore:\n (this.licenseType === Constants.Dynamics365LicenseType.dynamics365License ||\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense) &&\n !isEmbedded\n ? setInitialLocalLicenseStore(userProfile.licenses, this.licenseType)\n : null,\n licenseDisplayText:\n (this.licenseType === Constants.Dynamics365LicenseType.dynamics365License ||\n this.licenseType === Constants.Dynamics365LicenseType.dynamics365forBusinessLicense) &&\n !isEmbedded\n ? setInitialLicenseDisplayText(userProfile.licenses, this.licenseType)\n : '',\n });\n }\n\n isCTAShouldSkipModal() {\n const { ctaType, isEmbedded, modal } = this.props;\n const isOpenedFromPDP = modal?.isOpenedFromPDP;\n const shouldCTATypeBeSkip = [Constants.CTAType.Get, Constants.CTAType.Try].includes(ctaType);\n\n return isOpenedFromPDP && !isEmbedded && shouldCTATypeBeSkip;\n }\n\n /** Determines whether should skip the consent modal */\n shouldSkipModal(userProfile: IUserLeadProfileAgreement): boolean {\n const { isContactForm, isEmbedded, appInfo } = this.props;\n\n if (isContactForm) {\n return false;\n }\n\n if (isEmbedded && isPowerBIVisuals(getPrimaryProductUrl(appInfo.primaryProduct))) {\n return true;\n }\n\n return this.isCTAShouldSkipModal() && this.isMandatoryFieldsFilled(userProfile);\n }\n\n componentDidMount() {\n const { isTransactApp, appInfo, fetchAppDetails, userProfile, userInfo, isContactForm, dismissModal, isEmbedded } =\n this.props;\n\n this.handoffTitle = appInfo && appInfo.builtFor;\n\n this.setState({ isEmbeddedApp: isEmbedded && !isContactForm });\n\n if (this.shouldSkipModal(userProfile)) {\n this.logConsentDialogEvents(\n Constants.Telemetry.ActionModifier.ConsentModal,\n Constants.Telemetry.Action.Skip,\n JSON.stringify({ AutomaticallyAccepted: true })\n );\n this.setState({ shouldSkipModal: true });\n // Embed doesn't dismiss the modal when skipping it, so dismiss manually\n if (!userInfo?.signedIn && isEmbedded) {\n dismissModal();\n }\n this.handleContinue(userProfile);\n } else if (this.isCTAShouldSkipModal() && !this.isMandatoryFieldsFilled(userProfile) && !userInfo?.loading) {\n this.logConsentDialogEvents(\n Constants.Telemetry.ActionModifier.ConsentModal,\n Constants.Telemetry.Action.Open,\n JSON.stringify({ AutomaticallyAccepted: false })\n );\n } else {\n this.logConsentDialogEvents(Constants.Telemetry.ActionModifier.ConsentModal, Constants.Telemetry.Action.Open);\n }\n\n if (isTransactApp) {\n this.handleTransactCheckout(this.props);\n }\n\n // For the privacy policy URL to show up, we need the app details.\n if (appInfo && appInfo.detailInformation == null) {\n fetchAppDetails(appInfo);\n } else if (appInfo) {\n this.setState({\n appPrivacyPolicyUrl: (appInfo.detailInformation as IAppDetailInformation).PrivacyPolicyUrl,\n });\n }\n\n if (userInfo.loading && !isContactForm && this.isCTAShouldSkipModal()) {\n this.setState({ showAnimation: true });\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps: IConsentModal) {\n const {\n userProfile: currentUserProfile,\n appInfo: currentAppInfo,\n isLoadingUserProfile: currentIsLoadingUserProfile,\n } = this.props;\n const {\n userProfile: nextUserProfile,\n appInfo: nextAppInfo,\n userInfo: nextUserInfo,\n isLoadingUserProfile: nextIsLoadingUserProfile,\n isContactForm: nextIsContactForm,\n serviceInfo: nextServiceInfo,\n } = nextProps;\n\n if (nextProps.isTransactApp) {\n this.handleTransactCheckout(nextProps);\n }\n if (\n nextAppInfo &&\n currentAppInfo &&\n nextAppInfo.detailInformation &&\n currentAppInfo.detailInformation !== nextAppInfo.detailInformation\n ) {\n this.setState({\n appPrivacyPolicyUrl: (nextAppInfo.detailInformation as IAppDetailInformation).PrivacyPolicyUrl,\n });\n }\n\n if (\n (nextUserProfile.firstName !== '' && currentUserProfile.firstName === '') ||\n (nextUserProfile.lastName !== '' && currentUserProfile.lastName === '') ||\n (nextUserProfile.company !== '' && currentUserProfile.company === '') ||\n (nextUserProfile.country !== '' && currentUserProfile.country === '') ||\n (nextUserProfile.email !== '' && currentUserProfile.email === '') ||\n (nextUserProfile.phone !== '' && currentUserProfile.phone === '') ||\n (nextUserProfile.title !== '' && currentUserProfile.title === '')\n ) {\n this.setAccountInfo(nextProps);\n }\n\n if (!nextUserInfo.loading) {\n if (this.shouldSkipModal(nextUserProfile) && !this.shouldSkipModal(currentUserProfile)) {\n this.logConsentDialogEvents(\n Constants.Telemetry.ActionModifier.ConsentModal,\n Constants.Telemetry.Action.Skip,\n JSON.stringify({ AutomaticallyAccepted: true })\n );\n this.setState({ shouldSkipModal: true });\n this.handleContinue(nextUserProfile);\n } else if (this.isCTAShouldSkipModal() && !this.isMandatoryFieldsFilled(nextUserProfile)) {\n this.logConsentDialogEvents(\n Constants.Telemetry.ActionModifier.ConsentModal,\n Constants.Telemetry.Action.Open,\n JSON.stringify({ AutomaticallyAccepted: false })\n );\n }\n }\n\n if (!nextIsLoadingUserProfile && currentIsLoadingUserProfile !== nextIsLoadingUserProfile) {\n this.userProfilePostLoadingActions(nextUserProfile, nextIsContactForm, nextServiceInfo);\n }\n }\n\n userProfilePostLoadingActions(userProfile: IUserLeadProfileAgreement, isContactForm: boolean, serviceInfo?: Service) {\n this.setState({\n forceUserProfileCompacted: !isContactForm && !serviceInfo && this.isMandatoryFieldsFilled(userProfile),\n });\n if (!this.isCTAShouldSkipModal()) {\n this.setState({ showAnimation: false });\n }\n }\n\n handleTransactCheckout(props: IConsentModal) {\n const { userInfo, modal, dismissModal, appInfo: { entityId } = {} } = props;\n\n this.logConsentDialogEvents(\n Constants.Telemetry.Action.Open,\n Constants.Telemetry.ActionModifier.HandleTransactCheckout,\n JSON.stringify({ tenantType: userInfo.tenantType })\n );\n\n if (userInfo && userInfo.signedIn && !userInfo.isMSAUser && userInfo.tenantType !== Constants.TenantType.Msa) {\n this.logConsentDialogEvents(\n Constants.Telemetry.Action.Redirecting,\n Constants.Telemetry.ActionModifier.HandleTransactCheckout,\n 'Redirecting to checkout page'\n );\n urlPush(\n this.context.buildHref(\n routes.checkout,\n { entityId },\n { [Constants.Checkout.DefaultPlanIdQueryParam]: modal?.options?.planId },\n true,\n false,\n true\n )\n );\n dismissModal();\n } else {\n this.logConsentDialogEvents(\n Constants.Telemetry.Action.Error,\n Constants.Telemetry.ActionModifier.HandleTransactCheckout,\n JSON.stringify({\n tenantType: userInfo.tenantType,\n isMSAUser: userInfo.isMSAUser,\n isSignedIn: userInfo.signedIn,\n })\n );\n this.setState({ showUserSignInError: true });\n }\n }\n\n updateUserLicenseInState() {\n const { updateLicense } = this.props;\n const { localLicenseStore } = this.state;\n updateLicense(this.licenseType, localLicenseStore);\n }\n\n shouldProcessLead(): boolean {\n const { isEmbedded, embedHost } = this.props;\n\n const productShouldntGenerateLead = offerTypesWithoutLeads.includes(getProductByUrlKey({ urlKey: embedHost })?.UrlKey);\n\n if (isEmbedded && productShouldntGenerateLead) {\n return false;\n }\n\n return true;\n }\n\n shouldProcessLeadInfo(): boolean {\n const { appInfo, serviceInfo, flightCodes, embedHost, userInfo, isEmbedded } = this.props;\n const isService = !!serviceInfo;\n const uniqueProductId = isService ? serviceInfo.entityId : appInfo?.entityId;\n\n if (!userInfo?.signedIn) {\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Skip, {\n reason: Constants.Telemetry.LeadsDropReason.UserNotSignedIn,\n uniqueProductId,\n isEmbedded,\n embedHost,\n });\n return false;\n }\n\n if (flightCodes) {\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Skip, {\n reason: Constants.Telemetry.LeadsDropReason.PreviewOffer,\n uniqueProductId,\n isEmbedded,\n embedHost,\n });\n return false;\n }\n\n if (!isService) {\n if (!appInfo || appInfo.privateApp === true) {\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Skip, {\n reason: Constants.Telemetry.LeadsDropReason.AppNotAvailableOrPrivate,\n uniqueProductId,\n isEmbedded,\n embedHost,\n });\n return false;\n }\n }\n\n if (!this.shouldProcessLead()) {\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Skip, {\n reason: Constants.Telemetry.LeadsDropReason.EmbedWithoutLead,\n uniqueProductId,\n isEmbedded,\n embedHost,\n });\n return false;\n }\n\n return true;\n }\n\n async processLeadInfo() {\n const { ctaType, appInfo, sendLeadInfo, isContactForm, serviceInfo, isEmbedded, embedHost } = this.props;\n const { notes } = this.state;\n const isService = !!serviceInfo;\n const item = isService ? serviceInfo : appInfo;\n\n try {\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Start, {\n data: `Processing the lead. isService is ${isService}.`,\n uniqueProductId: item.entityId,\n isEmbedded,\n embedHost,\n });\n\n if (this.shouldProcessLeadInfo()) {\n await sendLeadInfo({\n item,\n ctaType,\n notes,\n });\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.End, {\n uniqueProductId: item.entityId,\n isEmbedded,\n embedHost,\n });\n }\n } catch (error) {\n const shouldStopFlow = isContactForm || isService;\n\n this.telemetry(Constants.Telemetry.Action.ProcessLeadInfo, Constants.Telemetry.ActionModifier.Error, {\n error,\n shouldStopFlow,\n uniqueProductId: item.entityId,\n isEmbedded,\n embedHost,\n });\n\n if (shouldStopFlow) {\n throw error;\n }\n }\n }\n\n handleContinueHelper(showAnimation = true, shouldGenerateLead = true) {\n const {\n getUserAuthStatus,\n serviceInfo,\n isContactForm,\n ctaType,\n isEmbedded,\n appInfo,\n openDriveModal,\n embedHost,\n dismissModal,\n signOutUser,\n featureFlags,\n billingCountryCode,\n } = this.props;\n const { isEmbeddedApp } = this.state;\n\n if (this.licenseType && this.licenseType !== -1) {\n this.updateUserLicenseInState();\n }\n getUserAuthStatus().then(async (result: any) => {\n if (isEmbeddedApp || result.status !== Constants.SigninStatus.ExpiredTokenInvalidCookie) {\n try {\n this.setState({ showAnimation: true });\n\n const { url, handoffTitle } = processHandoffURLAndTitle(appInfo);\n const isOpenInNewWindow =\n featureFlags?.openInNewWindowButton &&\n isOpenInNewWindowButton({\n isEmbedded,\n ctaType,\n appData: appInfo,\n billingRegion: billingCountryCode,\n url,\n });\n\n const actions = [this.processLeadInfo()];\n\n await Promise.all(actions);\n\n this.setState({ showAnimation: false });\n\n if (serviceInfo || isContactForm) {\n this.setState({ showSuccessDialog: true });\n }\n\n if (!shouldGenerateLead) {\n this.logConsentDialogEvents(\n Constants.Telemetry.Action.Click,\n Constants.Telemetry.ActionModifier.CTAInfo,\n 'User choose not to generate lead'\n );\n }\n\n if (isEmbedded && !isContactForm) {\n // TODO : Publish lead in embed experience using the host data\n // We need to fetch lastName, firstName and email. If firstName/lastName are null, use FNU/LNU\n postEmbedAcquisitionMessage(appInfo, ctaType, embedHost);\n setTimeout(() => {\n dismissModal();\n }, 1000);\n\n return;\n }\n\n if (!isContactForm && !serviceInfo) {\n if (shouldDownloadPowerBIVisual(appInfo, isEmbedded)) {\n this.handlePowerBIVisualDownload();\n return;\n }\n\n if (handoffTitle) {\n this.handoffTitle = handoffTitle;\n }\n this.logOutgoingHandoffRedirect(url);\n if (!url) {\n dismissModal();\n return;\n }\n NpsModule.IncreaseAppAcquisition();\n getNpsModule()?.ctaClicked(false);\n\n if (featureFlags?.openInNewWindowButton) {\n if (showAnimation) {\n this.setState({ showAnimation: true, handoffUrl: url });\n dismissModal();\n }\n openInNewWindow(url);\n } else {\n if (showAnimation) {\n this.setState({ showAnimation: true, handoffUrl: url });\n setTimeout(function () {\n window.open(url, '_self');\n }, 2500);\n } else {\n window.open(url, '_self');\n }\n }\n }\n } catch (error) {\n const errorString = stringifyError(error);\n\n const actionModifier: string = isContactForm\n ? Constants.Telemetry.ActionModifier.SubmitLeadGen\n : Constants.Telemetry.ActionModifier.ConsentModal;\n\n const action: string =\n errorString === Constants.Telemetry.Action.RefreshToken\n ? Constants.Telemetry.Action.RefreshToken\n : Constants.Telemetry.Action.Error;\n\n this.logConsentDialogEvents(action, actionModifier, errorString);\n\n if (errorString === Constants.Telemetry.Action.RefreshToken) {\n signOutUser();\n }\n\n if (isContactForm || serviceInfo) {\n this.setState({ showContactMeError: true, showAnimation: false });\n } else {\n this.setState({ showAnimation: false });\n }\n }\n }\n });\n }\n\n hideAccountInfoUI() {\n const { forceUserProfileCompacted } = this.state;\n\n if (forceUserProfileCompacted) {\n this.setState({ forceUserProfileCompacted: false });\n }\n this.setState({ showAccountInfo: true });\n }\n\n onUpdateAccountInfo(id: string, value: string) {\n this.setState({ accountInfo: this.updateAccountInfo(id, value) });\n }\n\n updateAccountInfo(id: string, value: string) {\n const { accountInfo } = this.state;\n\n const newAccountInfo = {\n firstName: accountInfo.firstName,\n lastName: accountInfo.lastName,\n email: accountInfo.email,\n title: accountInfo.title,\n company: accountInfo.company,\n country: accountInfo.country,\n phone: accountInfo.phone,\n };\n newAccountInfo[`${id}`] = value;\n return newAccountInfo;\n }\n\n handlePowerBIVisualDownload() {\n const { appInfo, location, openInstructionsModal, dismissModal } = this.props;\n\n if (appInfo?.downloadLink) {\n const payload: ITelemetryData = {\n page: location.pathname,\n action: Constants.Telemetry.Action.Click,\n actionModifier: Constants.Telemetry.ActionModifier.DownloadPBIVisual,\n appName: appInfo.entityId,\n };\n this.instrument.probe(Constants.Telemetry.ProbeName.LogInfo, payload);\n logger.info('', {\n action: payload.action,\n actionModifier: payload.actionModifier,\n appName: payload.appName,\n });\n\n window.location.href = appInfo.downloadLink;\n if (!getLocalStorageItem(Constants.LocalStorage.dontShowInstructions)) {\n openInstructionsModal();\n } else {\n dismissModal();\n }\n }\n }\n\n handleContinue(userProfile: IUserLeadProfileAgreement, showAnimation = true, shouldGenerateLead = true) {\n const { isEmbeddedApp } = this.state;\n const { query, updatePurchaseStatus, isContactForm, appInfo, serviceInfo } = this.props;\n const { entityId } = appInfo || serviceInfo;\n\n const shouldGenerateLeadGen: boolean = shouldGenerateLead && !isETERunning(query);\n getTrailIdAndData(true, 'consent form - continue btn');\n\n if (!isEmbeddedApp && !this.shouldSkipModal(userProfile)) {\n const actionModifier: string = isContactForm\n ? Constants.Telemetry.ActionModifier.SubmitLeadGen\n : Constants.Telemetry.ActionModifier.ConsentModal;\n\n this.logConsentDialogEvents(Constants.Telemetry.Action.Click, actionModifier);\n this.logConsentTelemetry({\n contentId: Constants.JsllCTAId.Continue,\n areaName: Constants.Telemetry.AreaName.ConsentModal,\n entityId,\n });\n\n this.setState({ formSubmitted: true });\n } else {\n this.logConsentTelemetry({\n contentId: Constants.Telemetry.ContentType.SkipConsentModal,\n areaName: Constants.Telemetry.AreaName.ConsentModal,\n entityId,\n });\n this.handleContinueHelper(showAnimation, shouldGenerateLeadGen);\n }\n\n updatePurchaseStatus();\n }\n\n logConsentTelemetry({ contentId, areaName, entityId }: { contentId: string; areaName: string; entityId: string }) {\n pageAction(null, {\n content: {\n contentId,\n areaName,\n contentName: entityId,\n },\n });\n }\n\n logOutgoingHandoffRedirect = (url: string) => {\n const { appInfo, serviceInfo, location } = this.props;\n const entityInfo = appInfo || serviceInfo;\n\n const payload: ITelemetryData = {\n page: location.pathname,\n action: Constants.Telemetry.Action.Redirecting,\n actionModifier: Constants.Telemetry.ActionModifier.OfficeOffersPurchase,\n appName: entityInfo && entityInfo.entityId,\n details: url,\n };\n this.instrument.probe('logAndFlushTelemetryInfo', payload);\n logger.info(payload.details, {\n action: payload.action,\n actionModifier: payload.actionModifier,\n appName: payload.appName,\n });\n };\n\n getTelemetryDetailsContent() {\n const { trailId } = this.state;\n const { appInfo, serviceInfo, isContactForm, isNationalCloud, ctaType, currentView } = this.props;\n const { shouldSkipModal } = this.state;\n const entityInfo: IAppDataItem | Service = appInfo || serviceInfo;\n const hasCheckbox = !isContactForm;\n const hasUserProfile = !shouldSkipModal || !!serviceInfo;\n\n // application\n if (appInfo) {\n return getAppTelemetryDetailContentText({\n appInfo: entityInfo as IAppDataItem,\n isNationalCloud,\n ctaTypes: [ctaType],\n consentDialogHasUserProfile: hasUserProfile,\n consentDialogHasCheckbox: hasCheckbox,\n currentView,\n trailId,\n });\n }\n\n // consulting services\n return getServiceTelemetryDetailContentText({\n serviceInfo: entityInfo as Service,\n isNationalCloud,\n ctaTypes: [ctaType],\n consentDialogHasUserProfile: hasUserProfile,\n consentDialogHasCheckbox: hasCheckbox,\n currentView,\n trailId,\n });\n }\n\n profileCompleted(profileFilledOut: boolean) {\n const { isProfileCompleted } = this.state;\n\n if (isProfileCompleted !== profileFilledOut) {\n this.setState({ isProfileCompleted: profileFilledOut });\n }\n }\n\n disableSubmitButton() {\n const { showUserSignInError, shouldSkipModal, isProfileCompleted, isNotesInvalid } = this.state;\n const { isContactForm } = this.props;\n\n if (showUserSignInError) {\n return false;\n }\n\n if (!shouldSkipModal) {\n return !isProfileCompleted || (isContactForm && isNotesInvalid);\n }\n\n return isLeadgenEnabled();\n }\n\n getTermsLink() {\n const { appInfo } = this.props;\n const terms: any = {};\n if (appInfo?.UsesEnterpriseContract) {\n terms.link = Constants.enterpriseContractLink;\n terms.locKey = 'ECA_Link';\n return terms;\n }\n terms.link = appInfo.licenseTermsUrl;\n terms.locKey = 'TRY_Terms2';\n return terms;\n }\n\n renderConsentDisclaimer() {\n const { ctaType, isContactForm, appInfo } = this.props;\n const terms = appInfo && this.getTermsLink();\n const { appPrivacyPolicyUrl } = this.state;\n\n return (\n
    \n \n \n {isContactForm ? (\n ${this.context.loc(\n 'RT_Terms',\n 'terms'\n )}`,\n ])}\n />\n ) : appInfo ? (\n ${this.context.loc(getActionStringForCTAType(ctaType))}`,\n `${this.context.loc(\n 'Provider_Terms',\n 'terms of use'\n )}`,\n `${this.context.loc(\n 'TRY_PrivacyPolicy',\n 'privacy policy'\n )}`,\n `${this.context.loc(\n 'TRY_Terms',\n 'terms'\n )}`,\n `${this.context.loc(\n 'TRY_Privacy',\n 'privacy'\n )}`,\n ])}\n />\n ) : (\n ${this.context.loc(\n 'TRY_Terms',\n 'terms'\n )}`,\n `${this.context.loc(\n 'TRY_Privacy',\n 'privacy'\n )}`,\n ])}\n />\n )}\n \n
    \n );\n }\n\n renderConsentBottomBar() {\n const {\n appInfo,\n isContactForm,\n isEmbedded,\n billingCountryCode,\n ctaType,\n serviceInfo,\n dismissModal,\n userProfile,\n isLoadingUserProfile,\n } = this.props;\n const { showUserSignInError, isEmbeddedApp } = this.state;\n let ctaButton: JSX.Element;\n\n const isCTADisabled = !isEmbeddedApp && (this.disableSubmitButton() || isLoadingUserProfile);\n\n if (isContactForm || !!serviceInfo) {\n ctaButton = (\n this.handleContinue(userProfile)}\n disabled={isCTADisabled}\n />\n );\n } else {\n const { url } = processHandoffURLAndTitle(appInfo);\n const isOpenInNewWindow =\n this.props.featureFlags?.openInNewWindowButton &&\n isOpenInNewWindowButton({\n isEmbedded,\n ctaType,\n appData: appInfo,\n billingRegion: billingCountryCode,\n url,\n });\n\n const isPBIVisualPriceHidden = hidePowerBiVisualPricing({ app: appInfo, isEmbedded });\n\n ctaButton = (\n (showUserSignInError ? dismissModal() : this.handleContinue(userProfile))}\n disabled={isCTADisabled}\n iconProps={isOpenInNewWindow ? { iconName: 'OpenInNewWindow' } : {}}\n />\n );\n }\n\n return {getTooltipSilentLogIn(ctaButton, isLoadingUserProfile)};\n }\n\n renderUserProfileModal() {\n const { isEmbedded, isContactForm } = this.props;\n const { formSubmitted, localLicenseStore, forceUserProfileCompacted, shouldSkipModal } = this.state;\n\n return (\n !shouldSkipModal && (\n \n \n \n )\n );\n }\n\n renderNotes() {\n const { notes, isNotesInvalid } = this.state;\n const { isContactForm } = this.props;\n\n return (\n isContactForm && (\n \n )\n );\n }\n\n onNotesChange = (event: React.FormEvent, newValue?: string) => {\n this.setState({ notes: newValue, isNotesInvalid: containsURLOrEmail(newValue) });\n };\n\n renderLoginInfo() {\n const { userInfo } = this.props;\n const { displayName, email } = userInfo;\n\n return (\n userInfo?.signedIn && (\n \n \n {this.context.loc('Leads_Logged_User_Info', \"You're signed in as\")}{' '}\n \n {displayName} ({email}).\n \n \n \n )\n );\n }\n\n renderConsentElement() {\n const { appInfo, serviceInfo } = this.props;\n const { isEmbeddedApp } = this.state;\n const iconSrc = appInfo ? appInfo.iconURL : serviceInfo.extraData.smallIconUri;\n const title = appInfo ? appInfo.title : serviceInfo.title;\n const publisher = appInfo ? appInfo.publisher : serviceInfo.publisher;\n\n return (\n \n \n \n \n \n \n \n {title}\n \n {this.context.locParams('Tile_By', [publisher])}\n \n \n {!isEmbeddedApp && (\n <>\n {this.renderLoginInfo()}\n {this.renderUserProfileModal()}\n {this.renderNotes()}\n \n )}\n {this.renderConsentDisclaimer()}\n {this.renderConsentBottomBar()}\n \n );\n }\n\n switchToleadGen() {\n this.setState({ showAnimation: false });\n return this.renderConsentElement();\n }\n\n renderImpl() {\n const { dismissModal, isTransactApp, isContactForm, appInfo, serviceInfo } = this.props;\n const { shouldSkipModal, showSuccessDialog, showUserSignInError, showAnimation, showContactMeError } = this.state;\n const publisher = appInfo ? appInfo.publisher : serviceInfo.publisher;\n\n if (shouldSkipModal && this.isCTAShouldSkipModal()) {\n this.setState({ shouldSkipModal: false });\n }\n\n return showSuccessDialog && (isContactForm || serviceInfo) ? (\n \n ) : (showUserSignInError || showContactMeError) && (isContactForm || serviceInfo) ? (\n \n ) : (\n \n {this.renderPromptContent()}\n \n );\n }\n\n renderPromptContent() {\n const { showAnimation } = this.state;\n const { appInfo, serviceInfo } = this.props;\n\n if (showAnimation) {\n return this.renderAnimation();\n }\n\n if (appInfo || serviceInfo) {\n return this.renderApp();\n }\n }\n\n renderAnimation() {\n const { appInfo, serviceInfo } = this.props;\n const iconSrc = appInfo ? appInfo.iconURL : serviceInfo.extraData.smallIconUri;\n const title = appInfo ? appInfo.title : serviceInfo.title;\n const { animationImg, appImg, appImgContainer } = consentModalClassNames;\n\n return (\n \n \n \n \n \n \n \n \n );\n }\n\n renderApp() {\n const { isTransactApp, userInfo } = this.props;\n\n const leadGen =\n isTransactApp && (userInfo.isMSAUser || (userInfo.tenantType && userInfo.tenantType !== Constants.TenantType.Managed));\n return leadGen ? this.switchToleadGen() : this.renderConsentElement();\n }\n}\n\n(ConsentModal as any).contextTypes = {\n loc: PropTypes.func,\n locParams: PropTypes.func,\n renderErrorModal: PropTypes.func,\n buildHref: PropTypes.func,\n};\n","import * as React from 'react';\nimport { ConsentModal as ConsentModalComponent } from '@appsource/components/modals/consentModal';\nimport {\n createInstructionsModalAction,\n createDriveModalAction,\n createUserSignOutAction,\n createUpdateUserProfileLicenseAction,\n} from '@shared/actions/actions';\nimport {\n getAppDetail,\n getUserAuthStatus,\n updatePurchaseStatus,\n sendLeadInfo,\n} from '@shared/actions/thunkActions';\nimport { IAppDataItem, ILicense, SendLeadInfoOptions } from '@shared/Models';\nimport { IState } from '@src/State';\nimport { Constants } from '@shared/utils/constants';\nimport { getTelemetryDetailContentCurrentView } from '@shared/utils/appUtils';\nimport { connect } from 'react-redux';\nimport { withRouter } from '@shared/routerHistory';\n\nexport const mapStateToProps = (state: IState, ownProps: any) => {\n return {\n userInfo: state.users,\n userProfile: state.users.profile,\n isNationalCloud: state.config.nationalCloud,\n billingCountryCode: state.config.billingCountryCode,\n flightCodes: state.config.flightCodes,\n featureFlags: state.config.featureFlags,\n query: ownProps?.query,\n embedHost: state.config.embedHost,\n currentView: getTelemetryDetailContentCurrentView(state.config.currentView, state.config.currentGalleryViewIsCurated),\n modal: state.modal,\n isLoadingUserProfile: state.users.isLoadingUserProfile,\n };\n};\n\nexport const mapDispatchToProps = (dispatch: any) => {\n return {\n updatePurchaseStatus: () => dispatch(updatePurchaseStatus()),\n openInstructionsModal: () =>\n dispatch(\n createInstructionsModalAction({\n showModal: true,\n isFromDownload: true,\n })\n ),\n openDriveModal: (driveUrl: string) =>\n dispatch(\n createDriveModalAction({\n showModal: true,\n driveUrl,\n })\n ),\n fetchAppDetails: (targetApp: IAppDataItem) => dispatch(getAppDetail(targetApp.entityId, false)),\n sendLeadInfo: ({ item, ctaType, notes, planId }: SendLeadInfoOptions) =>\n dispatch(sendLeadInfo({ item, ctaType, notes, planId })),\n getUserAuthStatus: () => dispatch(getUserAuthStatus()),\n signOutUser: () => dispatch(createUserSignOutAction(null)),\n updateLicense: (licenseType: number, license: ILicense) =>\n dispatch(createUpdateUserProfileLicenseAction({ licenseType, license })),\n };\n};\n\nexport const ConsentModal = withRouter(\n connect(mapStateToProps, mapDispatchToProps)(ConsentModalComponent) as React.StatelessComponent\n);\n","import * as React from 'react';\nimport {\n Dialog,\n DialogFooter,\n PrimaryButton,\n TextField,\n DefaultButton,\n Stack,\n RatingSize,\n Rating,\n Checkbox,\n Link,\n Text,\n} from '@fluentui/react';\nimport { ILocContext, ILocParamsContext } from '@shared/interfaces/context';\nimport { IAppReview } from '@shared/Models';\nimport { Constants } from '@shared/utils/constants';\nimport { getReviewModalDialogProps } from '@shared/utils/reviewsUtils';\nimport greyExclamationMark from '@shared/images/greyExclamationMark.svg';\n\nexport interface IReviewActionModal {\n context: ILocContext & ILocParamsContext;\n title: string;\n appTitle: string;\n appPublisher: string;\n userEmail: string;\n userDisplayName: string;\n appIcon: string;\n curRating: number;\n curReviewTitle: string;\n curReviewContent: string;\n isAnonymous: boolean;\n userReview: IAppReview;\n closeModal: () => void;\n handleRatingChange: (rating: number) => void;\n handleReviewTitleChange: (titleText: string) => void;\n handleReviewContentChange: (contentText: string) => void;\n handleSendEmailCheckbox: () => void;\n handleDisplayUserNameCheckbox: () => void;\n handleSubmitReview: () => void;\n handleDeleteReview: () => void;\n}\n\nexport const renderOfferInfoSection = (appIcon: string, appTitle: string, appPublisher: string): JSX.Element => {\n return (\n
    \n {`${appTitle}\n \n {appTitle}\n {appPublisher}\n \n
    \n );\n};\n\nexport const renderRatingSection = (\n context: ILocContext,\n IsvReplyExists: boolean,\n curRating: number,\n handleRatingChange: (rating: number) => void\n): JSX.Element => {\n return (\n \n \n {context.loc('ReviewActionModal_RatingSectionTitle', 'Rating')}{' '}\n *\n \n \n context.loc('ReviewActionModal_RatingAriaLabelReadOnly', `Rating value is ${rating} of ${max}`)\n }\n onChange={(event: React.FormEvent, rating: number) => handleRatingChange(rating)}\n />\n \n );\n};\n\nexport const renderLinksSection = (context: ILocContext): JSX.Element => {\n return (\n
    \n \n {context.loc('ReviewActionModal_Terms', 'Terms')}\n \n | \n \n {context.loc('ReviewActionModal_PrivacyStatement', 'Privacy statement')}\n \n
    \n );\n};\n\nexport const renderWriteDialogButtonsSection = (\n context: ILocContext,\n curRating: number,\n handleSubmitReview: () => void,\n closeModal: () => void\n): JSX.Element => {\n return (\n \n handleSubmitReview()}\n disabled={!(!!curRating || curRating === 0)}\n text={context.loc('ReviewActionModal_Submit', 'Submit')}\n />\n closeModal()} text={context.loc('Cancel', 'Cancel')} />\n \n );\n};\n\nexport const renderEditDialogButtonsSection = (\n context: ILocContext,\n IsvReplyExists: boolean,\n handleDeleteReview: () => void,\n handleSubmitReview: () => void,\n closeModal: () => void\n): JSX.Element => {\n return (\n \n handleDeleteReview()} text={context.loc('Rating_DeleteButton', 'Delete')} />\n handleSubmitReview()}\n disabled={IsvReplyExists}\n text={context.loc('Rating_UpdateButton', 'Update')}\n />\n closeModal()} text={context.loc('Cancel', 'Cancel')} />\n \n );\n};\n\nexport const ReviewActionModal = ({\n context,\n title,\n appTitle,\n appPublisher,\n userEmail,\n userDisplayName,\n appIcon,\n curRating,\n curReviewTitle,\n curReviewContent,\n isAnonymous,\n userReview,\n closeModal,\n handleRatingChange,\n handleReviewTitleChange,\n handleReviewContentChange,\n handleSendEmailCheckbox,\n handleDisplayUserNameCheckbox,\n handleSubmitReview,\n handleDeleteReview,\n}: IReviewActionModal) => {\n const dialogProps = getReviewModalDialogProps(title);\n const isEditMode = !!userReview;\n const IsvReplyExists: boolean = isEditMode && !!userReview.isv_reply;\n return (\n