[MM-50976] - Contact Support to redirect to Zendesk and pre-fill known information (#22640)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5215a0c30d
Коммит
865b3d75e7
@@ -5,16 +5,12 @@ import React from 'react';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
|
import {useOpenCloudZendeskSupportForm} from 'components/common/hooks/useOpenZendeskForm';
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
type Props = {
|
const CancelSubscription = () => {
|
||||||
cancelAccountLink: any;
|
const description = `I am requesting that workspace "${window.location.host}" be deleted`;
|
||||||
}
|
const [, contactSupportURL] = useOpenCloudZendeskSupportForm('Request workspace be deleted', description);
|
||||||
|
|
||||||
const CancelSubscription = (props: Props) => {
|
|
||||||
const {
|
|
||||||
cancelAccountLink,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='cancelSubscriptionSection'>
|
<div className='cancelSubscriptionSection'>
|
||||||
@@ -33,7 +29,7 @@ const CancelSubscription = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
location='cancel_subscription'
|
location='cancel_subscription'
|
||||||
href={cancelAccountLink}
|
href={contactSupportURL}
|
||||||
className='cancelSubscriptionSection__contactUs'
|
className='cancelSubscriptionSection__contactUs'
|
||||||
onClick={() => trackEvent('cloud_admin', 'click_contact_us')}
|
onClick={() => trackEvent('cloud_admin', 'click_contact_us')}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import AlertBanner from 'components/alert_banner';
|
|||||||
import UpgradeLink from 'components/widgets/links/upgrade_link';
|
import UpgradeLink from 'components/widgets/links/upgrade_link';
|
||||||
|
|
||||||
import './cloud_trial_banner.scss';
|
import './cloud_trial_banner.scss';
|
||||||
import {SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
trialEndDate: number;
|
trialEndDate: number;
|
||||||
@@ -34,7 +33,7 @@ const CloudTrialBanner = ({trialEndDate}: Props): JSX.Element | null => {
|
|||||||
const endDate = new Date(trialEndDate);
|
const endDate = new Date(trialEndDate);
|
||||||
const DISMISSED_DAYS = 10;
|
const DISMISSED_DAYS = 10;
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
const openSalesLink = useOpenSalesLink(SalesInquiryIssue.UpgradeEnterprise);
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const user = useSelector(getCurrentUser);
|
const user = useSelector(getCurrentUser);
|
||||||
const storedDismissedEndDate = useSelector((state: GlobalState) => getPreference(state, Preferences.CLOUD_TRIAL_BANNER, CloudBanners.UPGRADE_FROM_TRIAL));
|
const storedDismissedEndDate = useSelector((state: GlobalState) => getPreference(state, Preferences.CLOUD_TRIAL_BANNER, CloudBanners.UPGRADE_FROM_TRIAL));
|
||||||
|
|||||||
@@ -10,21 +10,19 @@ import {CloudLinks, CloudProducts} from 'utils/constants';
|
|||||||
import PrivateCloudSvg from 'components/common/svg_images_components/private_cloud_svg';
|
import PrivateCloudSvg from 'components/common/svg_images_components/private_cloud_svg';
|
||||||
import CloudTrialSvg from 'components/common/svg_images_components/cloud_trial_svg';
|
import CloudTrialSvg from 'components/common/svg_images_components/cloud_trial_svg';
|
||||||
import {TelemetryProps} from 'components/common/hooks/useOpenPricingModal';
|
import {TelemetryProps} from 'components/common/hooks/useOpenPricingModal';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
contactSalesLink: any;
|
|
||||||
isFreeTrial: boolean;
|
isFreeTrial: boolean;
|
||||||
trialQuestionsLink: any;
|
|
||||||
subscriptionPlan: string | undefined;
|
subscriptionPlan: string | undefined;
|
||||||
onUpgradeMattermostCloud: (telemetryProps?: TelemetryProps | undefined) => void;
|
onUpgradeMattermostCloud: (telemetryProps?: TelemetryProps | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContactSalesCard = (props: Props) => {
|
const ContactSalesCard = (props: Props) => {
|
||||||
|
const [openSalesLink, contactSalesLink] = useOpenSalesLink();
|
||||||
const {
|
const {
|
||||||
contactSalesLink,
|
|
||||||
isFreeTrial,
|
isFreeTrial,
|
||||||
trialQuestionsLink,
|
|
||||||
subscriptionPlan,
|
subscriptionPlan,
|
||||||
onUpgradeMattermostCloud,
|
onUpgradeMattermostCloud,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -145,7 +143,7 @@ const ContactSalesCard = (props: Props) => {
|
|||||||
{(isFreeTrial || subscriptionPlan === CloudProducts.ENTERPRISE || isCloudLegacyPlan) &&
|
{(isFreeTrial || subscriptionPlan === CloudProducts.ENTERPRISE || isCloudLegacyPlan) &&
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
location='contact_sales_card'
|
location='contact_sales_card'
|
||||||
href={isFreeTrial ? trialQuestionsLink : contactSalesLink}
|
href={contactSalesLink}
|
||||||
className='PrivateCloudCard__actionButton'
|
className='PrivateCloudCard__actionButton'
|
||||||
onClick={() => trackEvent('cloud_admin', 'click_contact_sales')}
|
onClick={() => trackEvent('cloud_admin', 'click_contact_sales')}
|
||||||
>
|
>
|
||||||
@@ -163,7 +161,7 @@ const ContactSalesCard = (props: Props) => {
|
|||||||
if (subscriptionPlan === CloudProducts.STARTER) {
|
if (subscriptionPlan === CloudProducts.STARTER) {
|
||||||
onUpgradeMattermostCloud({trackingLocation: 'admin_console_subscription_card_upgrade_now_button'});
|
onUpgradeMattermostCloud({trackingLocation: 'admin_console_subscription_card_upgrade_now_button'});
|
||||||
} else {
|
} else {
|
||||||
window.open(contactSalesLink, '_blank');
|
openSalesLink();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className='PrivateCloudCard__actionButton'
|
className='PrivateCloudCard__actionButton'
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import FormattedAdminHeader from 'components/widgets/admin_console/formatted_adm
|
|||||||
import CloudTrialBanner from 'components/admin_console/billing/billing_subscriptions/cloud_trial_banner';
|
import CloudTrialBanner from 'components/admin_console/billing/billing_subscriptions/cloud_trial_banner';
|
||||||
import CloudFetchError from 'components/cloud_fetch_error';
|
import CloudFetchError from 'components/cloud_fetch_error';
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType, SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
import {
|
import {
|
||||||
getSubscriptionProduct,
|
getSubscriptionProduct,
|
||||||
getCloudSubscription as selectCloudSubscription,
|
getCloudSubscription as selectCloudSubscription,
|
||||||
@@ -63,9 +62,6 @@ const BillingSubscriptions = () => {
|
|||||||
|
|
||||||
const isCardExpired = isCustomerCardExpired(useSelector(selectCloudCustomer));
|
const isCardExpired = isCustomerCardExpired(useSelector(selectCloudCustomer));
|
||||||
|
|
||||||
const contactSalesLink = useSelector(getCloudContactUsLink)(InquiryType.Sales);
|
|
||||||
const cancelAccountLink = useSelector(getCloudContactUsLink)(InquiryType.Sales, SalesInquiryIssue.CancelAccount);
|
|
||||||
const trialQuestionsLink = useSelector(getCloudContactUsLink)(InquiryType.Sales, SalesInquiryIssue.TrialQuestions);
|
|
||||||
const trialEndDate = subscription?.trial_end_at || 0;
|
const trialEndDate = subscription?.trial_end_at || 0;
|
||||||
|
|
||||||
const [showCreditCardBanner, setShowCreditCardBanner] = useState(true);
|
const [showCreditCardBanner, setShowCreditCardBanner] = useState(true);
|
||||||
@@ -159,19 +155,12 @@ const BillingSubscriptions = () => {
|
|||||||
<Limits/>
|
<Limits/>
|
||||||
) : (
|
) : (
|
||||||
<ContactSalesCard
|
<ContactSalesCard
|
||||||
contactSalesLink={contactSalesLink}
|
|
||||||
isFreeTrial={isFreeTrial}
|
isFreeTrial={isFreeTrial}
|
||||||
trialQuestionsLink={trialQuestionsLink}
|
|
||||||
subscriptionPlan={product?.sku}
|
subscriptionPlan={product?.sku}
|
||||||
onUpgradeMattermostCloud={openPricingModal}
|
onUpgradeMattermostCloud={openPricingModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAnnualProfessionalOrEnterprise && !isFreeTrial ?
|
{isAnnualProfessionalOrEnterprise && !isFreeTrial ? <CancelSubscription/> : <DeleteWorkspaceCTA/>}
|
||||||
<CancelSubscription
|
|
||||||
cancelAccountLink={cancelAccountLink}
|
|
||||||
/> :
|
|
||||||
<DeleteWorkspaceCTA/>
|
|
||||||
}
|
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ describe('limits_reached_banner', () => {
|
|||||||
const store = mockStore(state);
|
const store = mockStore(state);
|
||||||
const spies = makeSpies();
|
const spies = makeSpies();
|
||||||
const mockOpenSalesLink = jest.fn();
|
const mockOpenSalesLink = jest.fn();
|
||||||
spies.useOpenSalesLink.mockReturnValue(mockOpenSalesLink);
|
spies.useOpenSalesLink.mockReturnValue([mockOpenSalesLink, '']);
|
||||||
spies.useGetUsageDeltas.mockReturnValue(someLimitReached);
|
spies.useGetUsageDeltas.mockReturnValue(someLimitReached);
|
||||||
renderWithIntl(<Provider store={store}><LimitReachedBanner product={free}/></Provider>);
|
renderWithIntl(<Provider store={store}><LimitReachedBanner product={free}/></Provider>);
|
||||||
screen.getByText(titleFree);
|
screen.getByText(titleFree);
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import React from 'react';
|
|||||||
import {useIntl, FormattedMessage} from 'react-intl';
|
import {useIntl, FormattedMessage} from 'react-intl';
|
||||||
import {useSelector} from 'react-redux';
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
import {SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
|
|
||||||
import {CloudProducts} from 'utils/constants';
|
import {CloudProducts} from 'utils/constants';
|
||||||
import {anyUsageDeltaExceededLimit} from 'utils/limits';
|
import {anyUsageDeltaExceededLimit} from 'utils/limits';
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ const LimitReachedBanner = (props: Props) => {
|
|||||||
|
|
||||||
const hasDismissedBanner = useSelector(getHasDismissedSystemConsoleLimitReached);
|
const hasDismissedBanner = useSelector(getHasDismissedSystemConsoleLimitReached);
|
||||||
|
|
||||||
const openSalesLink = useOpenSalesLink(props.product?.sku === CloudProducts.PROFESSIONAL ? SalesInquiryIssue.UpgradeEnterprise : undefined);
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
const openPricingModal = useOpenPricingModal();
|
const openPricingModal = useOpenPricingModal();
|
||||||
const saveBool = useSaveBool();
|
const saveBool = useSaveBool();
|
||||||
if (hasDismissedBanner || !someLimitExceeded || !props.product || (props.product.sku !== CloudProducts.STARTER)) {
|
if (hasDismissedBanner || !someLimitExceeded || !props.product || (props.product.sku !== CloudProducts.STARTER)) {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
getSubscriptionProduct,
|
getSubscriptionProduct,
|
||||||
} from 'mattermost-redux/selectors/entities/cloud';
|
} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
|
|
||||||
import {SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
|
|
||||||
import {CloudProducts} from 'utils/constants';
|
import {CloudProducts} from 'utils/constants';
|
||||||
import {asGBString, fallbackStarterLimits, hasSomeLimits} from 'utils/limits';
|
import {asGBString, fallbackStarterLimits, hasSomeLimits} from 'utils/limits';
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ const Limits = (): JSX.Element | null => {
|
|||||||
const subscriptionProduct = useSelector(getSubscriptionProduct);
|
const subscriptionProduct = useSelector(getSubscriptionProduct);
|
||||||
const [cloudLimits, limitsLoaded] = useGetLimits();
|
const [cloudLimits, limitsLoaded] = useGetLimits();
|
||||||
const usage = useGetUsage();
|
const usage = useGetUsage();
|
||||||
const openSalesLink = useOpenSalesLink(SalesInquiryIssue.UpgradeEnterprise);
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
const openPricingModal = useOpenPricingModal();
|
const openPricingModal = useOpenPricingModal();
|
||||||
|
|
||||||
if (!subscriptionProduct || !limitsLoaded || !hasSomeLimits(cloudLimits)) {
|
if (!subscriptionProduct || !limitsLoaded || !hasSomeLimits(cloudLimits)) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import useOpenCloudPurchaseModal from 'components/common/hooks/useOpenCloudPurch
|
|||||||
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
|
import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
|
||||||
|
|
||||||
import {SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
import {getSubscriptionProduct as selectSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
import {getSubscriptionProduct as selectSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
@@ -79,7 +78,7 @@ const ToYearlyNudgeBannerDismissable = () => {
|
|||||||
const ToYearlyNudgeBanner = () => {
|
const ToYearlyNudgeBanner = () => {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
const openSalesLink = useOpenSalesLink(SalesInquiryIssue.AboutPurchasing);
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
const openPurchaseModal = useOpenCloudPurchaseModal({});
|
const openPurchaseModal = useOpenCloudPurchaseModal({});
|
||||||
|
|
||||||
const product = useSelector(selectSubscriptionProduct);
|
const product = useSelector(selectSubscriptionProduct);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {useDispatch, useSelector} from 'react-redux';
|
|||||||
|
|
||||||
import IconMessage from 'components/purchase_modal/icon_message';
|
import IconMessage from 'components/purchase_modal/icon_message';
|
||||||
import FullScreenModal from 'components/widgets/modals/full_screen_modal';
|
import FullScreenModal from 'components/widgets/modals/full_screen_modal';
|
||||||
|
import {useOpenCloudZendeskSupportForm} from 'components/common/hooks/useOpenZendeskForm';
|
||||||
|
|
||||||
import {closeModal} from 'actions/views/modals';
|
import {closeModal} from 'actions/views/modals';
|
||||||
import {isModalOpen} from 'selectors/views/modals';
|
import {isModalOpen} from 'selectors/views/modals';
|
||||||
@@ -14,9 +15,6 @@ import {GlobalState} from 'types/store';
|
|||||||
|
|
||||||
import './result_modal.scss';
|
import './result_modal.scss';
|
||||||
|
|
||||||
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
|
||||||
import {InquiryType} from 'selectors/cloud';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onHide?: () => void;
|
onHide?: () => void;
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
@@ -33,7 +31,7 @@ type Props = {
|
|||||||
export default function ResultModal(props: Props) {
|
export default function ResultModal(props: Props) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const openContactUs = useOpenSalesLink(undefined, InquiryType.Technical);
|
const [openContactSupport] = useOpenCloudZendeskSupportForm('Delete workspace', '');
|
||||||
|
|
||||||
const isResultModalOpen = useSelector((state: GlobalState) =>
|
const isResultModalOpen = useSelector((state: GlobalState) =>
|
||||||
isModalOpen(state, props.identifier),
|
isModalOpen(state, props.identifier),
|
||||||
@@ -64,14 +62,13 @@ export default function ResultModal(props: Props) {
|
|||||||
buttonHandler={props.primaryButtonHandler}
|
buttonHandler={props.primaryButtonHandler}
|
||||||
className={'success'}
|
className={'success'}
|
||||||
formattedTertiaryButonText={
|
formattedTertiaryButonText={
|
||||||
props.contactSupportButtonVisible ?
|
props.contactSupportButtonVisible ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={'admin.billing.deleteWorkspace.resultModal.ContactSupport'}
|
id={'admin.billing.deleteWorkspace.resultModal.ContactSupport'}
|
||||||
defaultMessage={'Contact Support'}
|
defaultMessage={'Contact Support'}
|
||||||
/> :
|
/>) : undefined
|
||||||
undefined
|
|
||||||
}
|
}
|
||||||
tertiaryButtonHandler={props.contactSupportButtonVisible ? openContactUs : undefined}
|
tertiaryButtonHandler={props.contactSupportButtonVisible ? openContactSupport : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ describe('components/feature_discovery', () => {
|
|||||||
<FeatureDiscovery
|
<FeatureDiscovery
|
||||||
featureName='test'
|
featureName='test'
|
||||||
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
||||||
contactSalesLink='/sales'
|
|
||||||
titleID='translation.test.title'
|
titleID='translation.test.title'
|
||||||
titleDefault='Foo'
|
titleDefault='Foo'
|
||||||
copyID='translation.test.copy'
|
copyID='translation.test.copy'
|
||||||
@@ -46,7 +45,6 @@ describe('components/feature_discovery', () => {
|
|||||||
<FeatureDiscovery
|
<FeatureDiscovery
|
||||||
featureName='test'
|
featureName='test'
|
||||||
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
||||||
contactSalesLink='/sales'
|
|
||||||
titleID='translation.test.title'
|
titleID='translation.test.title'
|
||||||
titleDefault='Foo'
|
titleDefault='Foo'
|
||||||
copyID='translation.test.copy'
|
copyID='translation.test.copy'
|
||||||
@@ -76,7 +74,6 @@ describe('components/feature_discovery', () => {
|
|||||||
<FeatureDiscovery
|
<FeatureDiscovery
|
||||||
featureName='test'
|
featureName='test'
|
||||||
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
minimumSKURequiredForFeature={LicenseSkus.Professional}
|
||||||
contactSalesLink='/sales'
|
|
||||||
titleID='translation.test.title'
|
titleID='translation.test.title'
|
||||||
titleDefault='Foo'
|
titleDefault='Foo'
|
||||||
copyID='translation.test.copy'
|
copyID='translation.test.copy'
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
|
|
||||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||||
|
|
||||||
import {AnalyticsRow} from '@mattermost/types/admin';
|
|
||||||
import {ClientLicense} from '@mattermost/types/config';
|
|
||||||
|
|
||||||
import {EmbargoedEntityTrialError} from 'components/admin_console/license_settings/trial_banner/trial_banner';
|
import {EmbargoedEntityTrialError} from 'components/admin_console/license_settings/trial_banner/trial_banner';
|
||||||
import AlertBanner from 'components/alert_banner';
|
import AlertBanner from 'components/alert_banner';
|
||||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||||
@@ -17,17 +14,22 @@ import PurchaseLink from 'components/announcement_bar/purchase_link/purchase_lin
|
|||||||
import ContactUsButton from 'components/announcement_bar/contact_sales/contact_us';
|
import ContactUsButton from 'components/announcement_bar/contact_sales/contact_us';
|
||||||
import PurchaseModal from 'components/purchase_modal';
|
import PurchaseModal from 'components/purchase_modal';
|
||||||
import CloudStartTrialButton from 'components/cloud_start_trial/cloud_start_trial_btn';
|
import CloudStartTrialButton from 'components/cloud_start_trial/cloud_start_trial_btn';
|
||||||
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
import {ModalIdentifiers, TELEMETRY_CATEGORIES, AboutLinks, LicenseLinks, LicenseSkus} from 'utils/constants';
|
import {ModalIdentifiers, TELEMETRY_CATEGORIES, AboutLinks, LicenseLinks, LicenseSkus} from 'utils/constants';
|
||||||
import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils';
|
import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils';
|
||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
|
import {goToMattermostContactSalesForm} from 'utils/contact_support_sales';
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
|
|
||||||
import {ModalData} from 'types/actions';
|
import {ModalData} from 'types/actions';
|
||||||
|
|
||||||
|
import {ClientLicense} from '@mattermost/types/config';
|
||||||
|
import {AnalyticsRow} from '@mattermost/types/admin';
|
||||||
|
import {CloudCustomer} from '@mattermost/types/cloud';
|
||||||
|
|
||||||
import './feature_discovery.scss';
|
import './feature_discovery.scss';
|
||||||
import ExternalLink from 'components/external_link';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
featureName: string;
|
featureName: string;
|
||||||
@@ -56,7 +58,7 @@ type Props = {
|
|||||||
hadPrevCloudTrial: boolean;
|
hadPrevCloudTrial: boolean;
|
||||||
isSubscriptionLoaded: boolean;
|
isSubscriptionLoaded: boolean;
|
||||||
isPaidSubscription: boolean;
|
isPaidSubscription: boolean;
|
||||||
contactSalesLink: string;
|
customer?: CloudCustomer;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -97,6 +99,16 @@ export default class FeatureDiscovery extends React.PureComponent<Props, State>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contactSalesFunc = () => {
|
||||||
|
const {customer, isCloud} = this.props;
|
||||||
|
const customerEmail = customer?.email || '';
|
||||||
|
const firstName = customer?.contact_first_name || '';
|
||||||
|
const lastName = customer?.contact_last_name || '';
|
||||||
|
const companyName = customer?.name || '';
|
||||||
|
const utmMedium = isCloud ? 'in-product-cloud' : 'in-product';
|
||||||
|
goToMattermostContactSalesForm(firstName, lastName, companyName, customerEmail, 'mattermost', utmMedium);
|
||||||
|
}
|
||||||
|
|
||||||
renderPostTrialCta = () => {
|
renderPostTrialCta = () => {
|
||||||
const {
|
const {
|
||||||
minimumSKURequiredForFeature,
|
minimumSKURequiredForFeature,
|
||||||
@@ -110,7 +122,7 @@ export default class FeatureDiscovery extends React.PureComponent<Props, State>
|
|||||||
data-testid='featureDiscovery_primaryCallToAction'
|
data-testid='featureDiscovery_primaryCallToAction'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
trackEvent(TELEMETRY_CATEGORIES.SELF_HOSTED_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
trackEvent(TELEMETRY_CATEGORIES.SELF_HOSTED_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
this.contactSalesFunc();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -161,7 +173,6 @@ export default class FeatureDiscovery extends React.PureComponent<Props, State>
|
|||||||
hadPrevCloudTrial,
|
hadPrevCloudTrial,
|
||||||
isPaidSubscription,
|
isPaidSubscription,
|
||||||
minimumSKURequiredForFeature,
|
minimumSKURequiredForFeature,
|
||||||
contactSalesLink,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const canRequestCloudFreeTrial = isCloud && !isCloudTrial && !hadPrevCloudTrial && !isPaidSubscription;
|
const canRequestCloudFreeTrial = isCloud && !isCloudTrial && !hadPrevCloudTrial && !isPaidSubscription;
|
||||||
@@ -217,11 +228,10 @@ export default class FeatureDiscovery extends React.PureComponent<Props, State>
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
trackEvent(TELEMETRY_CATEGORIES.CLOUD_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
trackEvent(TELEMETRY_CATEGORIES.CLOUD_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
||||||
window.open(contactSalesLink, '_blank');
|
|
||||||
} else {
|
} else {
|
||||||
trackEvent(TELEMETRY_CATEGORIES.SELF_HOSTED_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
trackEvent(TELEMETRY_CATEGORIES.SELF_HOSTED_ADMIN, 'click_enterprise_contact_sales_feature_discovery');
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
|
||||||
}
|
}
|
||||||
|
this.contactSalesFunc();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import {bindActionCreators, Dispatch, ActionCreatorsMapObject} from 'redux';
|
|||||||
import {getPrevTrialLicense} from 'mattermost-redux/actions/admin';
|
import {getPrevTrialLicense} from 'mattermost-redux/actions/admin';
|
||||||
import {getCloudSubscription} from 'mattermost-redux/actions/cloud';
|
import {getCloudSubscription} from 'mattermost-redux/actions/cloud';
|
||||||
import {Action, GenericAction} from 'mattermost-redux/types/actions';
|
import {Action, GenericAction} from 'mattermost-redux/types/actions';
|
||||||
import {checkHadPriorTrial} from 'mattermost-redux/selectors/entities/cloud';
|
import {checkHadPriorTrial, getCloudCustomer} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType} from 'selectors/cloud';
|
|
||||||
|
|
||||||
import {ModalData} from 'types/actions';
|
import {ModalData} from 'types/actions';
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
const isCloud = isCloudLicense(license);
|
const isCloud = isCloudLicense(license);
|
||||||
const hasPriorTrial = checkHadPriorTrial(state);
|
const hasPriorTrial = checkHadPriorTrial(state);
|
||||||
const isCloudTrial = subscription?.is_free_trial === 'true';
|
const isCloudTrial = subscription?.is_free_trial === 'true';
|
||||||
const contactSalesLink = getCloudContactUsLink(state)(InquiryType.Sales);
|
const customer = getCloudCustomer(state);
|
||||||
return {
|
return {
|
||||||
stats: state.entities.admin.analytics,
|
stats: state.entities.admin.analytics,
|
||||||
prevTrialLicense: state.entities.admin.prevTrialLicense,
|
prevTrialLicense: state.entities.admin.prevTrialLicense,
|
||||||
@@ -39,7 +37,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
isSubscriptionLoaded: subscription !== undefined && subscription !== null,
|
isSubscriptionLoaded: subscription !== undefined && subscription !== null,
|
||||||
hadPrevCloudTrial: hasPriorTrial,
|
hadPrevCloudTrial: hasPriorTrial,
|
||||||
isPaidSubscription: isCloud && license?.SkuShortName !== LicenseSkus.Starter && !isCloudTrial,
|
isPaidSubscription: isCloud && license?.SkuShortName !== LicenseSkus.Starter && !isCloudTrial,
|
||||||
contactSalesLink,
|
customer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,46 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
import {LicenseSkus} from 'utils/constants';
|
import {LicenseSkus} from 'utils/constants';
|
||||||
|
|
||||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||||
|
import mockStore from 'tests/test_store';
|
||||||
|
|
||||||
import EnterpriseEditionRightPanel, {EnterpriseEditionProps} from './enterprise_edition_right_panel';
|
import EnterpriseEditionRightPanel, {EnterpriseEditionProps} from './enterprise_edition_right_panel';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
views: {
|
||||||
|
announcementBar: {
|
||||||
|
announcementBarState: {
|
||||||
|
announcementBarCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entities: {
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
CWSURL: '',
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
IsLicensed: 'true',
|
||||||
|
Cloud: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'current_user_id',
|
||||||
|
profiles: {
|
||||||
|
current_user_id: {roles: 'system_user'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
myPreferences: {},
|
||||||
|
},
|
||||||
|
cloud: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('components/admin_console/license_settings/enterprise_edition/enterprise_edition_right_panel', () => {
|
describe('components/admin_console/license_settings/enterprise_edition/enterprise_edition_right_panel', () => {
|
||||||
const license = {
|
const license = {
|
||||||
IsLicensed: 'true',
|
IsLicensed: 'true',
|
||||||
@@ -28,8 +61,11 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
} as EnterpriseEditionProps;
|
} as EnterpriseEditionProps;
|
||||||
|
|
||||||
test('should render for no Gov no Trial no Enterprise', () => {
|
test('should render for no Gov no Trial no Enterprise', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
<EnterpriseEditionRightPanel {...props}/>,
|
<Provider store={store}>
|
||||||
|
<EnterpriseEditionRightPanel {...props}/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Upgrade to the Enterprise Plan');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Upgrade to the Enterprise Plan');
|
||||||
@@ -43,11 +79,14 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render for Gov no Trial no Enterprise', () => {
|
test('should render for Gov no Trial no Enterprise', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
|
<Provider store={store}>
|
||||||
<EnterpriseEditionRightPanel
|
<EnterpriseEditionRightPanel
|
||||||
license={{...props.license, IsGovSku: 'true'}}
|
license={{...props.license, IsGovSku: 'true'}}
|
||||||
isTrialLicense={props.isTrialLicense}
|
isTrialLicense={props.isTrialLicense}
|
||||||
/>,
|
/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Upgrade to the Enterprise Gov Plan');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Upgrade to the Enterprise Gov Plan');
|
||||||
@@ -61,11 +100,14 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render for Enterprise no Trial', () => {
|
test('should render for Enterprise no Trial', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
|
<Provider store={store}>
|
||||||
<EnterpriseEditionRightPanel
|
<EnterpriseEditionRightPanel
|
||||||
license={{...props.license, SkuShortName: LicenseSkus.Enterprise}}
|
license={{...props.license, SkuShortName: LicenseSkus.Enterprise}}
|
||||||
isTrialLicense={props.isTrialLicense}
|
isTrialLicense={props.isTrialLicense}
|
||||||
/>,
|
/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Need to increase your headcount?');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Need to increase your headcount?');
|
||||||
@@ -73,11 +115,14 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render for E20 no Trial', () => {
|
test('should render for E20 no Trial', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
|
<Provider store={store}>
|
||||||
<EnterpriseEditionRightPanel
|
<EnterpriseEditionRightPanel
|
||||||
license={{...props.license, SkuShortName: LicenseSkus.E20}}
|
license={{...props.license, SkuShortName: LicenseSkus.E20}}
|
||||||
isTrialLicense={props.isTrialLicense}
|
isTrialLicense={props.isTrialLicense}
|
||||||
/>,
|
/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Need to increase your headcount?');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Need to increase your headcount?');
|
||||||
@@ -85,11 +130,14 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render for Trial no Gov', () => {
|
test('should render for Trial no Gov', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
|
<Provider store={store}>
|
||||||
<EnterpriseEditionRightPanel
|
<EnterpriseEditionRightPanel
|
||||||
license={props.license}
|
license={props.license}
|
||||||
isTrialLicense={true}
|
isTrialLicense={true}
|
||||||
/>,
|
/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Purchase the Enterprise Plan');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Purchase the Enterprise Plan');
|
||||||
@@ -97,11 +145,14 @@ describe('components/admin_console/license_settings/enterprise_edition/enterpris
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render for Trial Gov', () => {
|
test('should render for Trial Gov', () => {
|
||||||
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
|
<Provider store={store}>
|
||||||
<EnterpriseEditionRightPanel
|
<EnterpriseEditionRightPanel
|
||||||
license={{...props.license, IsGovSku: 'true'}}
|
license={{...props.license, IsGovSku: 'true'}}
|
||||||
isTrialLicense={true}
|
isTrialLicense={true}
|
||||||
/>,
|
/>
|
||||||
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.find('.upgrade-title').text()).toEqual('Purchase the Enterprise Gov Plan');
|
expect(wrapper.find('.upgrade-title').text()).toEqual('Purchase the Enterprise Gov Plan');
|
||||||
|
|||||||
@@ -12,6 +12,37 @@ import mockStore from 'tests/test_store';
|
|||||||
|
|
||||||
import RenewalLicenseCard from './renew_license_card';
|
import RenewalLicenseCard from './renew_license_card';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
views: {
|
||||||
|
announcementBar: {
|
||||||
|
announcementBarState: {
|
||||||
|
announcementBarCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entities: {
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
CWSURL: '',
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
IsLicensed: 'true',
|
||||||
|
Cloud: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'current_user_id',
|
||||||
|
profiles: {
|
||||||
|
current_user_id: {roles: 'system_user'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
myPreferences: {},
|
||||||
|
},
|
||||||
|
cloud: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const actImmediate = (wrapper: ReactWrapper) =>
|
const actImmediate = (wrapper: ReactWrapper) =>
|
||||||
act(
|
act(
|
||||||
() =>
|
() =>
|
||||||
@@ -47,7 +78,7 @@ describe('components/RenewalLicenseCard', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
getRenewalLinkSpy.mockImplementation(() => promise);
|
getRenewalLinkSpy.mockImplementation(() => promise);
|
||||||
const store = mockStore({});
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(<Provider store={store}><RenewalLicenseCard {...props}/></Provider>);
|
const wrapper = mountWithIntl(<Provider store={store}><RenewalLicenseCard {...props}/></Provider>);
|
||||||
|
|
||||||
// wait for the promise to resolve and component to update
|
// wait for the promise to resolve and component to update
|
||||||
@@ -64,7 +95,7 @@ describe('components/RenewalLicenseCard', () => {
|
|||||||
reject(new Error('License cannot be renewed from portal'));
|
reject(new Error('License cannot be renewed from portal'));
|
||||||
});
|
});
|
||||||
getRenewalLinkSpy.mockImplementation(() => promise);
|
getRenewalLinkSpy.mockImplementation(() => promise);
|
||||||
const store = mockStore({});
|
const store = mockStore(initialState);
|
||||||
const wrapper = mountWithIntl(<Provider store={store}><RenewalLicenseCard {...props}/></Provider>);
|
const wrapper = mountWithIntl(<Provider store={store}><RenewalLicenseCard {...props}/></Provider>);
|
||||||
|
|
||||||
// wait for the promise to resolve and component to update
|
// wait for the promise to resolve and component to update
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
|||||||
|
|
||||||
import {GlobalState} from '@mattermost/types/store';
|
import {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
import {CloudLinks, ConsolePages, DocLinks, LicenseLinks} from 'utils/constants';
|
import {CloudLinks, ConsolePages, DocLinks} from 'utils/constants';
|
||||||
import {daysToLicenseExpire, isEnterpriseOrE20License, getIsStarterLicense} from '../../../utils/license_utils';
|
import {daysToLicenseExpire, isEnterpriseOrE20License, getIsStarterLicense} from '../../../utils/license_utils';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
|
||||||
export type DataModel = {
|
export type DataModel = {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
@@ -84,8 +85,10 @@ const useMetricsData = () => {
|
|||||||
const isEnterpriseLicense = isEnterpriseOrE20License(license);
|
const isEnterpriseLicense = isEnterpriseOrE20License(license);
|
||||||
const isStarterLicense = getIsStarterLicense(license);
|
const isStarterLicense = getIsStarterLicense(license);
|
||||||
|
|
||||||
|
const [, contactSalesLink] = useOpenSalesLink();
|
||||||
|
|
||||||
const trialOrEnterpriseCtaConfig = {
|
const trialOrEnterpriseCtaConfig = {
|
||||||
configUrl: canStartTrial ? ConsolePages.LICENSE : LicenseLinks.CONTACT_SALES,
|
configUrl: canStartTrial ? ConsolePages.LICENSE : contactSalesLink,
|
||||||
configText: canStartTrial ? formatMessage({id: 'admin.reporting.workspace_optimization.cta.startTrial', defaultMessage: 'Start trial'}) : formatMessage({id: 'admin.reporting.workspace_optimization.cta.upgradeLicense', defaultMessage: 'Contact sales'}),
|
configText: canStartTrial ? formatMessage({id: 'admin.reporting.workspace_optimization.cta.startTrial', defaultMessage: 'Start trial'}) : formatMessage({id: 'admin.reporting.workspace_optimization.cta.upgradeLicense', defaultMessage: 'Contact sales'}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import React from 'react';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
|
||||||
import './contact_us.scss';
|
import './contact_us.scss';
|
||||||
import {LicenseLinks} from '../../../utils/constants';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
buttonTextElement?: JSX.Element;
|
buttonTextElement?: JSX.Element;
|
||||||
@@ -16,10 +17,12 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ContactUsButton: React.FC<Props> = (props: Props) => {
|
const ContactUsButton: React.FC<Props> = (props: Props) => {
|
||||||
|
const [openContactSales] = useOpenSalesLink();
|
||||||
|
|
||||||
const handleContactUsLinkClick = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleContactUsLinkClick = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
trackEvent('admin', props.eventID || 'in_trial_contact_sales');
|
trackEvent('admin', props.eventID || 'in_trial_contact_sales');
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
openContactSales();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {savePreferences} from 'mattermost-redux/actions/preferences';
|
|||||||
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {PreferenceType} from '@mattermost/types/preferences';
|
import {PreferenceType} from '@mattermost/types/preferences';
|
||||||
import {useExpandOverageUsersCheck} from 'components/common/hooks/useExpandOverageUsersCheck';
|
import {useExpandOverageUsersCheck} from 'components/common/hooks/useExpandOverageUsersCheck';
|
||||||
import {LicenseLinks, StatTypes, Preferences, AnnouncementBarTypes} from 'utils/constants';
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
import {StatTypes, Preferences, AnnouncementBarTypes} from 'utils/constants';
|
||||||
|
|
||||||
import './overage_users_banner.scss';
|
import './overage_users_banner.scss';
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ const adminHasDismissed = ({preferenceName, overagePreferences, isWarningBanner}
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OverageUsersBanner = () => {
|
const OverageUsersBanner = () => {
|
||||||
|
const [openContactSales] = useOpenSalesLink();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const stats = useSelector((state: GlobalState) => state.entities.admin.analytics) || {};
|
const stats = useSelector((state: GlobalState) => state.entities.admin.analytics) || {};
|
||||||
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
||||||
@@ -90,7 +92,7 @@ const OverageUsersBanner = () => {
|
|||||||
const handleContactSalesClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleContactSalesClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
trackEventFn('Contact Sales');
|
trackEventFn('Contact Sales');
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
openContactSales();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = isExpandable ? handleUpdateSeatsSelfServeClick : handleContactSalesClick;
|
const handleClick = isExpandable ? handleUpdateSeatsSelfServeClick : handleContactSalesClick;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {fireEvent, screen} from '@testing-library/react';
|
|||||||
import {DeepPartial} from '@mattermost/types/utilities';
|
import {DeepPartial} from '@mattermost/types/utilities';
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
import {General} from 'mattermost-redux/constants';
|
import {General} from 'mattermost-redux/constants';
|
||||||
import {LicenseLinks, OverActiveUserLimits, Preferences, StatTypes} from 'utils/constants';
|
import {OverActiveUserLimits, Preferences, StatTypes} from 'utils/constants';
|
||||||
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
|
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
@@ -249,7 +249,10 @@ describe('components/overage_users_banner', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText(contactSalesTextLink));
|
fireEvent.click(screen.getByText(contactSalesTextLink));
|
||||||
expect(windowSpy).toBeCalledTimes(1);
|
expect(windowSpy).toBeCalledTimes(1);
|
||||||
expect(windowSpy).toBeCalledWith(LicenseLinks.CONTACT_SALES, '_blank');
|
|
||||||
|
// only the email is encoded and other params are empty. See logic for useOpenSalesLink hook
|
||||||
|
const salesLinkWithEncodedParams = 'https://mattermost.com/contact-sales/?qk=&qp=&qw=&qx=dGVzdEBtYXR0ZXJtb3N0LmNvbQ==&utm_source=mattermost&utm_medium=in-product';
|
||||||
|
expect(windowSpy).toBeCalledWith(salesLinkWithEncodedParams, '_blank');
|
||||||
expect(trackEvent).toBeCalledTimes(1);
|
expect(trackEvent).toBeCalledTimes(1);
|
||||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_warning', {
|
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_warning', {
|
||||||
cta: 'Contact Sales',
|
cta: 'Contact Sales',
|
||||||
@@ -368,7 +371,10 @@ describe('components/overage_users_banner', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText(contactSalesTextLink));
|
fireEvent.click(screen.getByText(contactSalesTextLink));
|
||||||
expect(windowSpy).toBeCalledTimes(1);
|
expect(windowSpy).toBeCalledTimes(1);
|
||||||
expect(windowSpy).toBeCalledWith(LicenseLinks.CONTACT_SALES, '_blank');
|
|
||||||
|
// only the email is encoded and other params are empty. See logic for useOpenSalesLink hook
|
||||||
|
const salesLinkWithEncodedParams = 'https://mattermost.com/contact-sales/?qk=&qp=&qw=&qx=dGVzdEBtYXR0ZXJtb3N0LmNvbQ==&utm_source=mattermost&utm_medium=in-product';
|
||||||
|
expect(windowSpy).toBeCalledWith(salesLinkWithEncodedParams, '_blank');
|
||||||
expect(trackEvent).toBeCalledTimes(1);
|
expect(trackEvent).toBeCalledTimes(1);
|
||||||
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_error', {
|
expect(trackEvent).toBeCalledWith('insights', 'click_true_up_error', {
|
||||||
cta: 'Contact Sales',
|
cta: 'Contact Sales',
|
||||||
|
|||||||
@@ -3,13 +3,46 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {ReactWrapper} from 'enzyme';
|
import {ReactWrapper} from 'enzyme';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
import {act} from 'react-dom/test-utils';
|
import {act} from 'react-dom/test-utils';
|
||||||
|
|
||||||
import {Client4} from 'mattermost-redux/client';
|
import {Client4} from 'mattermost-redux/client';
|
||||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||||
|
import mockStore from 'tests/test_store';
|
||||||
|
|
||||||
import RenewalLink from './renewal_link';
|
import RenewalLink from './renewal_link';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
views: {
|
||||||
|
announcementBar: {
|
||||||
|
announcementBarState: {
|
||||||
|
announcementBarCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entities: {
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
CWSURL: '',
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
IsLicensed: 'true',
|
||||||
|
Cloud: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'current_user_id',
|
||||||
|
profiles: {
|
||||||
|
current_user_id: {roles: 'system_user'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
myPreferences: {},
|
||||||
|
},
|
||||||
|
cloud: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const actImmediate = (wrapper: ReactWrapper) =>
|
const actImmediate = (wrapper: ReactWrapper) =>
|
||||||
act(
|
act(
|
||||||
() =>
|
() =>
|
||||||
@@ -40,7 +73,8 @@ describe('components/RenewalLink', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
getRenewalLinkSpy.mockImplementation(() => promise);
|
getRenewalLinkSpy.mockImplementation(() => promise);
|
||||||
const wrapper = mountWithIntl(<RenewalLink {...props}/>);
|
const store = mockStore(initialState);
|
||||||
|
const wrapper = mountWithIntl(<Provider store={store}><RenewalLink {...props}/></Provider>);
|
||||||
|
|
||||||
// wait for the promise to resolve and component to update
|
// wait for the promise to resolve and component to update
|
||||||
await actImmediate(wrapper);
|
await actImmediate(wrapper);
|
||||||
@@ -54,7 +88,8 @@ describe('components/RenewalLink', () => {
|
|||||||
reject(new Error('License cannot be renewed from portal'));
|
reject(new Error('License cannot be renewed from portal'));
|
||||||
});
|
});
|
||||||
getRenewalLinkSpy.mockImplementation(() => promise);
|
getRenewalLinkSpy.mockImplementation(() => promise);
|
||||||
const wrapper = mountWithIntl(<RenewalLink {...props}/>);
|
const store = mockStore(initialState);
|
||||||
|
const wrapper = mountWithIntl(<Provider store={store}><RenewalLink {...props}/></Provider>);
|
||||||
|
|
||||||
// wait for the promise to resolve and component to update
|
// wait for the promise to resolve and component to update
|
||||||
await actImmediate(wrapper);
|
await actImmediate(wrapper);
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import {trackEvent} from 'actions/telemetry_actions';
|
|||||||
import {ModalData} from 'types/actions';
|
import {ModalData} from 'types/actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LicenseLinks,
|
|
||||||
ModalIdentifiers,
|
ModalIdentifiers,
|
||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
|
||||||
import NoInternetConnection from '../no_internet_connection/no_internet_connection';
|
import NoInternetConnection from '../no_internet_connection/no_internet_connection';
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ export interface RenewalLinkProps {
|
|||||||
const RenewalLink = (props: RenewalLinkProps) => {
|
const RenewalLink = (props: RenewalLinkProps) => {
|
||||||
const [renewalLink, setRenewalLink] = useState('');
|
const [renewalLink, setRenewalLink] = useState('');
|
||||||
const [manualInterventionRequired, setManualInterventionRequired] = useState(false);
|
const [manualInterventionRequired, setManualInterventionRequired] = useState(false);
|
||||||
|
|
||||||
|
const [openContactSales] = useOpenSalesLink();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Client4.getRenewalLink().then(({renewal_link: renewalLinkParam}) => {
|
Client4.getRenewalLink().then(({renewal_link: renewalLinkParam}) => {
|
||||||
try {
|
try {
|
||||||
@@ -55,7 +58,7 @@ const RenewalLink = (props: RenewalLinkProps) => {
|
|||||||
}
|
}
|
||||||
window.open(renewalLink, '_blank');
|
window.open(renewalLink, '_blank');
|
||||||
} else if (manualInterventionRequired) {
|
} else if (manualInterventionRequired) {
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
openContactSales();
|
||||||
} else {
|
} else {
|
||||||
showConnectionErrorModal();
|
showConnectionErrorModal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import PaymentFailedSvg from 'components/common/svg_images_components/payment_fa
|
|||||||
import IconMessage from 'components/purchase_modal/icon_message';
|
import IconMessage from 'components/purchase_modal/icon_message';
|
||||||
|
|
||||||
import FullScreenModal from 'components/widgets/modals/full_screen_modal';
|
import FullScreenModal from 'components/widgets/modals/full_screen_modal';
|
||||||
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
import {useOpenCloudZendeskSupportForm} from 'components/common/hooks/useOpenZendeskForm';
|
||||||
|
|
||||||
import {InquiryType} from 'selectors/cloud';
|
|
||||||
import {closeModal} from 'actions/views/modals';
|
import {closeModal} from 'actions/views/modals';
|
||||||
import {ModalIdentifiers} from 'utils/constants';
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
import {isModalOpen} from 'selectors/views/modals';
|
import {isModalOpen} from 'selectors/views/modals';
|
||||||
@@ -31,7 +30,8 @@ type Props = {
|
|||||||
function ErrorModal(props: Props) {
|
function ErrorModal(props: Props) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const subscriptionProduct = useSelector(getSubscriptionProduct);
|
const subscriptionProduct = useSelector(getSubscriptionProduct);
|
||||||
const openContactUs = useOpenSalesLink(undefined, InquiryType.Technical);
|
|
||||||
|
const [openContactSupport] = useOpenCloudZendeskSupportForm('Cloud Subscription', '');
|
||||||
|
|
||||||
const isSuccessModalOpen = useSelector((state: GlobalState) =>
|
const isSuccessModalOpen = useSelector((state: GlobalState) =>
|
||||||
isModalOpen(state, ModalIdentifiers.ERROR_MODAL),
|
isModalOpen(state, ModalIdentifiers.ERROR_MODAL),
|
||||||
@@ -97,7 +97,7 @@ function ErrorModal(props: Props) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
tertiaryButtonHandler={openContactUs}
|
tertiaryButtonHandler={openContactSupport}
|
||||||
buttonHandler={onBackButtonPress}
|
buttonHandler={onBackButtonPress}
|
||||||
className={'success'}
|
className={'success'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,11 +3,35 @@
|
|||||||
|
|
||||||
import {useSelector} from 'react-redux';
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType, SalesInquiryIssue} from 'selectors/cloud';
|
import {getCloudCustomer, isCurrentLicenseCloud} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
|
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
import {buildMMURL, goToMattermostContactSalesForm} from 'utils/contact_support_sales';
|
||||||
|
import {LicenseLinks} from 'utils/constants';
|
||||||
|
|
||||||
export default function useOpenSalesLink(issue?: SalesInquiryIssue, inquireType: InquiryType = InquiryType.Sales) {
|
export default function useOpenSalesLink(): [() => void, string] {
|
||||||
const contactSalesLink = useSelector(getCloudContactUsLink)(inquireType, issue);
|
const isCloud = useSelector(isCurrentLicenseCloud);
|
||||||
|
const customer = useSelector(getCloudCustomer);
|
||||||
|
const currentUser = useSelector(getCurrentUser);
|
||||||
|
let customerEmail = '';
|
||||||
|
let firstName = '';
|
||||||
|
let lastName = '';
|
||||||
|
let companyName = '';
|
||||||
|
const utmSource = 'mattermost';
|
||||||
|
let utmMedium = 'in-product';
|
||||||
|
|
||||||
return () => window.open(contactSalesLink, '_blank');
|
if (isCloud && customer) {
|
||||||
|
customerEmail = customer.email || '';
|
||||||
|
firstName = customer.contact_first_name || '';
|
||||||
|
lastName = customer.contact_last_name || '';
|
||||||
|
companyName = customer.name || '';
|
||||||
|
utmMedium = 'in-product-cloud';
|
||||||
|
} else {
|
||||||
|
customerEmail = currentUser.email || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contactSalesLink = buildMMURL(LicenseLinks.CONTACT_SALES, firstName, lastName, companyName, customerEmail, utmSource, utmMedium);
|
||||||
|
const goToSalesLinkFunc = () => {
|
||||||
|
goToMattermostContactSalesForm(firstName, lastName, companyName, customerEmail, utmSource, utmMedium);
|
||||||
|
};
|
||||||
|
return [goToSalesLinkFunc, contactSalesLink];
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import {getCloudCustomer} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
|
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
import {getCloudSupportLink, getSelfHostedSupportLink, goToCloudSupportForm, goToSelfHostedSupportForm} from 'utils/contact_support_sales';
|
||||||
|
|
||||||
|
export function useOpenCloudZendeskSupportForm(subject: string, description: string): [() => void, string] {
|
||||||
|
const customer = useSelector(getCloudCustomer);
|
||||||
|
const customerEmail = customer?.email || '';
|
||||||
|
|
||||||
|
const url = getCloudSupportLink(customerEmail, subject, description, window.location.host);
|
||||||
|
|
||||||
|
return [() => goToCloudSupportForm(customerEmail, subject, description, window.location.host), url];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOpenSelfHostedZendeskSupportForm(subject: string): [() => void, string] {
|
||||||
|
const currentUser = useSelector(getCurrentUser);
|
||||||
|
const customerEmail = currentUser.email || '';
|
||||||
|
|
||||||
|
const url = getSelfHostedSupportLink(customerEmail, subject);
|
||||||
|
|
||||||
|
return [() => goToSelfHostedSupportForm(customerEmail, subject), url];
|
||||||
|
}
|
||||||
@@ -11,8 +11,7 @@ import {trackEvent} from 'actions/telemetry_actions';
|
|||||||
|
|
||||||
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
|
||||||
import {LicenseLinks, TELEMETRY_CATEGORIES} from 'utils/constants';
|
import {TELEMETRY_CATEGORIES} from 'utils/constants';
|
||||||
import {SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
|
|
||||||
const StyledA = styled.a`
|
const StyledA = styled.a`
|
||||||
color: var(--denim-button-bg);
|
color: var(--denim-button-bg);
|
||||||
@@ -27,11 +26,7 @@ text-align: center;
|
|||||||
|
|
||||||
function ContactSalesCTA() {
|
function ContactSalesCTA() {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
const openSalesLink = useOpenSalesLink(SalesInquiryIssue.UpgradeEnterprise);
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
|
|
||||||
const openSelfHostedLink = () => {
|
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCloud = useSelector(isCurrentLicenseCloud);
|
const isCloud = useSelector(isCurrentLicenseCloud);
|
||||||
|
|
||||||
@@ -42,11 +37,10 @@ function ContactSalesCTA() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
trackEvent(TELEMETRY_CATEGORIES.CLOUD_PRICING, 'click_enterprise_contact_sales');
|
trackEvent(TELEMETRY_CATEGORIES.CLOUD_PRICING, 'click_enterprise_contact_sales');
|
||||||
openSalesLink();
|
|
||||||
} else {
|
} else {
|
||||||
trackEvent('self_hosted_pricing', 'click_enterprise_contact_sales');
|
trackEvent('self_hosted_pricing', 'click_enterprise_contact_sales');
|
||||||
openSelfHostedLink();
|
|
||||||
}
|
}
|
||||||
|
openSalesLink();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatMessage({id: 'pricing_modal.btn.contactSalesForQuote', defaultMessage: 'Contact Sales'})}
|
{formatMessage({id: 'pricing_modal.btn.contactSalesForQuote', defaultMessage: 'Contact Sales'})}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import {CloudLinks, CloudProducts, LicenseSkus, ModalIdentifiers, MattermostFeat
|
|||||||
import {fallbackStarterLimits, asGBString, hasSomeLimits} from 'utils/limits';
|
import {fallbackStarterLimits, asGBString, hasSomeLimits} from 'utils/limits';
|
||||||
import {findOnlyYearlyProducts, findProductBySku} from 'utils/products';
|
import {findOnlyYearlyProducts, findProductBySku} from 'utils/products';
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType, SalesInquiryIssue} from 'selectors/cloud';
|
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
import {closeModal, openModal} from 'actions/views/modals';
|
import {closeModal, openModal} from 'actions/views/modals';
|
||||||
import {subscribeCloudSubscription} from 'actions/cloud';
|
import {subscribeCloudSubscription} from 'actions/cloud';
|
||||||
@@ -38,6 +36,8 @@ import useOpenCloudPurchaseModal from 'components/common/hooks/useOpenCloudPurch
|
|||||||
|
|
||||||
import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
||||||
import useOpenDowngradeModal from 'components/common/hooks/useOpenDowngradeModal';
|
import useOpenDowngradeModal from 'components/common/hooks/useOpenDowngradeModal';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
import {useOpenCloudZendeskSupportForm} from 'components/common/hooks/useOpenZendeskForm';
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
import DowngradeTeamRemovalModal from './downgrade_team_removal_modal';
|
import DowngradeTeamRemovalModal from './downgrade_team_removal_modal';
|
||||||
@@ -64,15 +64,12 @@ function Content(props: ContentProps) {
|
|||||||
const openPricingModalBackAction = useOpenPricingModal();
|
const openPricingModalBackAction = useOpenPricingModal();
|
||||||
|
|
||||||
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
||||||
const contactSalesLink = useSelector(getCloudContactUsLink)(InquiryType.Sales, SalesInquiryIssue.UpgradeEnterprise);
|
|
||||||
|
|
||||||
const subscription = useSelector(selectCloudSubscription);
|
const subscription = useSelector(selectCloudSubscription);
|
||||||
const currentProduct = useSelector(selectSubscriptionProduct);
|
const currentProduct = useSelector(selectSubscriptionProduct);
|
||||||
const products = useSelector(selectCloudProducts);
|
const products = useSelector(selectCloudProducts);
|
||||||
const yearlyProducts = findOnlyYearlyProducts(products || {}); // pricing modal should now only show yearly products
|
const yearlyProducts = findOnlyYearlyProducts(products || {}); // pricing modal should now only show yearly products
|
||||||
|
|
||||||
const contactSupportLink = useSelector(getCloudContactUsLink)(InquiryType.Technical);
|
|
||||||
|
|
||||||
const currentSubscriptionIsMonthly = currentProduct?.recurring_interval === RecurringIntervals.MONTH;
|
const currentSubscriptionIsMonthly = currentProduct?.recurring_interval === RecurringIntervals.MONTH;
|
||||||
const isEnterprise = currentProduct?.sku === CloudProducts.ENTERPRISE;
|
const isEnterprise = currentProduct?.sku === CloudProducts.ENTERPRISE;
|
||||||
const isEnterpriseTrial = subscription?.is_free_trial === 'true';
|
const isEnterpriseTrial = subscription?.is_free_trial === 'true';
|
||||||
@@ -124,6 +121,8 @@ function Content(props: ContentProps) {
|
|||||||
const freeTierText = (!isStarter && !currentSubscriptionIsMonthly) ? formatMessage({id: 'pricing_modal.btn.contactSupport', defaultMessage: 'Contact Support'}) : formatMessage({id: 'pricing_modal.btn.downgrade', defaultMessage: 'Downgrade'});
|
const freeTierText = (!isStarter && !currentSubscriptionIsMonthly) ? formatMessage({id: 'pricing_modal.btn.contactSupport', defaultMessage: 'Contact Support'}) : formatMessage({id: 'pricing_modal.btn.downgrade', defaultMessage: 'Downgrade'});
|
||||||
const adminProfessionalTierText = currentSubscriptionIsMonthlyProfessional ? formatMessage({id: 'pricing_modal.btn.switch_to_annual', defaultMessage: 'Switch to annual billing'}) : formatMessage({id: 'pricing_modal.btn.upgrade', defaultMessage: 'Upgrade'});
|
const adminProfessionalTierText = currentSubscriptionIsMonthlyProfessional ? formatMessage({id: 'pricing_modal.btn.switch_to_annual', defaultMessage: 'Switch to annual billing'}) : formatMessage({id: 'pricing_modal.btn.upgrade', defaultMessage: 'Upgrade'});
|
||||||
|
|
||||||
|
const [openContactSales] = useOpenSalesLink();
|
||||||
|
const [openContactSupport] = useOpenCloudZendeskSupportForm('Workspace downgrade', '');
|
||||||
const openCloudPurchaseModal = useOpenCloudPurchaseModal({});
|
const openCloudPurchaseModal = useOpenCloudPurchaseModal({});
|
||||||
const openCloudDelinquencyModal = useOpenCloudPurchaseModal({
|
const openCloudDelinquencyModal = useOpenCloudPurchaseModal({
|
||||||
isDelinquencyModal: true,
|
isDelinquencyModal: true,
|
||||||
@@ -239,7 +238,7 @@ function Content(props: ContentProps) {
|
|||||||
return {
|
return {
|
||||||
action: () => {
|
action: () => {
|
||||||
trackEvent(TELEMETRY_CATEGORIES.CLOUD_PRICING, 'click_enterprise_contact_sales');
|
trackEvent(TELEMETRY_CATEGORIES.CLOUD_PRICING, 'click_enterprise_contact_sales');
|
||||||
window.open(contactSalesLink, '_blank');
|
openContactSales();
|
||||||
},
|
},
|
||||||
text: formatMessage({id: 'pricing_modal.btn.contactSales', defaultMessage: 'Contact Sales'}),
|
text: formatMessage({id: 'pricing_modal.btn.contactSales', defaultMessage: 'Contact Sales'}),
|
||||||
customClass: ButtonCustomiserClasses.active,
|
customClass: ButtonCustomiserClasses.active,
|
||||||
@@ -350,7 +349,7 @@ function Content(props: ContentProps) {
|
|||||||
buttonDetails={{
|
buttonDetails={{
|
||||||
action: () => {
|
action: () => {
|
||||||
if (!isStarter && !currentSubscriptionIsMonthly) {
|
if (!isStarter && !currentSubscriptionIsMonthly) {
|
||||||
window.open(contactSupportLink, '_blank');
|
openContactSupport();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {Modal} from 'react-bootstrap';
|
|||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
import {CloudLinks, LicenseLinks, ModalIdentifiers, SelfHostedProducts, LicenseSkus, TELEMETRY_CATEGORIES, RecurringIntervals} from 'utils/constants';
|
import {CloudLinks, ModalIdentifiers, SelfHostedProducts, LicenseSkus, TELEMETRY_CATEGORIES, RecurringIntervals} from 'utils/constants';
|
||||||
import {findSelfHostedProductBySku} from 'utils/hosted_customer';
|
import {findSelfHostedProductBySku} from 'utils/hosted_customer';
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
@@ -27,6 +27,7 @@ import StartTrialBtn from 'components/learn_more_trial_modal/start_trial_btn';
|
|||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
import useCanSelfHostedSignup from 'components/common/hooks/useCanSelfHostedSignup';
|
import useCanSelfHostedSignup from 'components/common/hooks/useCanSelfHostedSignup';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useControlAirGappedSelfHostedPurchaseModal,
|
useControlAirGappedSelfHostedPurchaseModal,
|
||||||
@@ -89,6 +90,7 @@ function SelfHostedContent(props: ContentProps) {
|
|||||||
const isEnterprise = license.SkuShortName === LicenseSkus.Enterprise;
|
const isEnterprise = license.SkuShortName === LicenseSkus.Enterprise;
|
||||||
const isPostSelfHostedEnterpriseTrial = prevSelfHostedTrialLicense.IsLicensed === 'true';
|
const isPostSelfHostedEnterpriseTrial = prevSelfHostedTrialLicense.IsLicensed === 'true';
|
||||||
|
|
||||||
|
const [openContactSales] = useOpenSalesLink();
|
||||||
const controlScreeningInProgressModal = useControlScreeningInProgressModal();
|
const controlScreeningInProgressModal = useControlScreeningInProgressModal();
|
||||||
const controlAirgappedModal = useControlAirGappedSelfHostedPurchaseModal();
|
const controlAirgappedModal = useControlAirGappedSelfHostedPurchaseModal();
|
||||||
|
|
||||||
@@ -287,7 +289,7 @@ function SelfHostedContent(props: ContentProps) {
|
|||||||
buttonDetails={(isPostSelfHostedEnterpriseTrial || !isAdmin) ? {
|
buttonDetails={(isPostSelfHostedEnterpriseTrial || !isAdmin) ? {
|
||||||
action: () => {
|
action: () => {
|
||||||
trackEvent('self_hosted_pricing', 'click_enterprise_contact_sales');
|
trackEvent('self_hosted_pricing', 'click_enterprise_contact_sales');
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
openContactSales();
|
||||||
},
|
},
|
||||||
text: formatMessage({id: 'pricing_modal.btn.contactSales', defaultMessage: 'Contact Sales'}),
|
text: formatMessage({id: 'pricing_modal.btn.contactSales', defaultMessage: 'Contact Sales'}),
|
||||||
customClass: ButtonCustomiserClasses.active,
|
customClass: ButtonCustomiserClasses.active,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {GlobalState} from 'types/store';
|
|||||||
import {BillingDetails} from 'types/cloud/sku';
|
import {BillingDetails} from 'types/cloud/sku';
|
||||||
|
|
||||||
import {isModalOpen} from 'selectors/views/modals';
|
import {isModalOpen} from 'selectors/views/modals';
|
||||||
import {getCloudContactUsLink, InquiryType, getCloudDelinquentInvoices, isCloudDelinquencyGreaterThan90Days} from 'selectors/cloud';
|
import {getCloudDelinquentInvoices, isCloudDelinquencyGreaterThan90Days} from 'selectors/cloud';
|
||||||
import {isDevModeEnabled} from 'selectors/general';
|
import {isDevModeEnabled} from 'selectors/general';
|
||||||
|
|
||||||
import {ModalIdentifiers} from 'utils/constants';
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
@@ -29,6 +29,7 @@ import {completeStripeAddPaymentMethod, subscribeCloudSubscription} from 'action
|
|||||||
import {ModalData} from 'types/actions';
|
import {ModalData} from 'types/actions';
|
||||||
import withGetCloudSubscription from 'components/common/hocs/cloud/with_get_cloud_subscription';
|
import withGetCloudSubscription from 'components/common/hocs/cloud/with_get_cloud_subscription';
|
||||||
import {findOnlyYearlyProducts} from 'utils/products';
|
import {findOnlyYearlyProducts} from 'utils/products';
|
||||||
|
import {getCloudContactSalesLink, getCloudSupportLink} from 'utils/contact_support_sales';
|
||||||
|
|
||||||
const PurchaseModal = makeAsyncComponent('PurchaseModal', React.lazy(() => import('./purchase_modal')));
|
const PurchaseModal = makeAsyncComponent('PurchaseModal', React.lazy(() => import('./purchase_modal')));
|
||||||
|
|
||||||
@@ -39,19 +40,27 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
const products = state.entities.cloud!.products;
|
const products = state.entities.cloud!.products;
|
||||||
const yearlyProducts = findOnlyYearlyProducts(products || {});
|
const yearlyProducts = findOnlyYearlyProducts(products || {});
|
||||||
|
|
||||||
|
const customer = state.entities.cloud.customer;
|
||||||
|
const customerEmail = customer?.email || '';
|
||||||
|
const firstName = customer?.contact_first_name || '';
|
||||||
|
const lastName = customer?.contact_last_name || '';
|
||||||
|
const companyName = customer?.name || '';
|
||||||
|
const contactSalesLink = getCloudContactSalesLink(firstName, lastName, companyName, customerEmail, 'mattermost', 'in-product-cloud');
|
||||||
|
const contactSupportLink = getCloudSupportLink(customerEmail, 'Cloud purchase', '', window.location.host);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show: isModalOpen(state, ModalIdentifiers.CLOUD_PURCHASE),
|
show: isModalOpen(state, ModalIdentifiers.CLOUD_PURCHASE),
|
||||||
products,
|
products,
|
||||||
yearlyProducts,
|
yearlyProducts,
|
||||||
isDevMode: isDevModeEnabled(state),
|
isDevMode: isDevModeEnabled(state),
|
||||||
contactSupportLink: getCloudContactUsLink(state)(InquiryType.Technical),
|
contactSupportLink,
|
||||||
invoices: getCloudDelinquentInvoices(state),
|
invoices: getCloudDelinquentInvoices(state),
|
||||||
isCloudDelinquencyGreaterThan90Days: isCloudDelinquencyGreaterThan90Days(state),
|
isCloudDelinquencyGreaterThan90Days: isCloudDelinquencyGreaterThan90Days(state),
|
||||||
isFreeTrial: subscription?.is_free_trial === 'true',
|
isFreeTrial: subscription?.is_free_trial === 'true',
|
||||||
isComplianceBlocked: subscription?.compliance_blocked === 'true',
|
isComplianceBlocked: subscription?.compliance_blocked === 'true',
|
||||||
contactSalesLink: getCloudContactUsLink(state)(InquiryType.Sales),
|
contactSalesLink,
|
||||||
productId: subscription?.product_id,
|
productId: subscription?.product_id,
|
||||||
customer: state.entities.cloud.customer,
|
customer,
|
||||||
currentTeam: getCurrentTeam(state),
|
currentTeam: getCurrentTeam(state),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
isDelinquencyModal,
|
isDelinquencyModal,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import ComplianceScreenFailedSvg from 'components/common/svg_images_components/a
|
|||||||
import AddressForm from 'components/payment_form/address_form';
|
import AddressForm from 'components/payment_form/address_form';
|
||||||
|
|
||||||
import {t} from 'utils/i18n';
|
import {t} from 'utils/i18n';
|
||||||
import {Address, CloudCustomer, Product, Invoice, areShippingDetailsValid, Feedback} from '@mattermost/types/cloud';
|
|
||||||
import {ActionResult} from 'mattermost-redux/types/actions';
|
import {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import {localizeMessage, getNextBillingDate, getBlankAddressWithCountry} from 'utils/utils';
|
import {localizeMessage, getNextBillingDate, getBlankAddressWithCountry} from 'utils/utils';
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
ModalIdentifiers,
|
ModalIdentifiers,
|
||||||
RecurringIntervals,
|
RecurringIntervals,
|
||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
|
import {goToMattermostContactSalesForm} from 'utils/contact_support_sales';
|
||||||
|
|
||||||
import PaymentDetails from 'components/admin_console/billing/payment_details';
|
import PaymentDetails from 'components/admin_console/billing/payment_details';
|
||||||
import {STRIPE_CSS_SRC, STRIPE_PUBLIC_KEY} from 'components/payment_form/stripe';
|
import {STRIPE_CSS_SRC, STRIPE_PUBLIC_KEY} from 'components/payment_form/stripe';
|
||||||
@@ -54,6 +54,8 @@ import {ModalData} from 'types/actions';
|
|||||||
|
|
||||||
import {Theme} from 'mattermost-redux/selectors/entities/preferences';
|
import {Theme} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
|
||||||
|
import {Address, CloudCustomer, Product, Invoice, areShippingDetailsValid, Feedback} from '@mattermost/types/cloud';
|
||||||
|
|
||||||
import {areBillingDetailsValid, BillingDetails} from '../../types/cloud/sku';
|
import {areBillingDetailsValid, BillingDetails} from '../../types/cloud/sku';
|
||||||
|
|
||||||
import {Team} from '@mattermost/types/teams';
|
import {Team} from '@mattermost/types/teams';
|
||||||
@@ -463,6 +465,7 @@ class PurchaseModal extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmSwitchToAnnual = () => {
|
confirmSwitchToAnnual = () => {
|
||||||
|
const {customer} = this.props;
|
||||||
this.props.actions.openModal({
|
this.props.actions.openModal({
|
||||||
modalId: ModalIdentifiers.CONFIRM_SWITCH_TO_YEARLY,
|
modalId: ModalIdentifiers.CONFIRM_SWITCH_TO_YEARLY,
|
||||||
dialogType: SwitchToYearlyPlanConfirmModal,
|
dialogType: SwitchToYearlyPlanConfirmModal,
|
||||||
@@ -476,7 +479,11 @@ class PurchaseModal extends React.PureComponent<Props, State> {
|
|||||||
TELEMETRY_CATEGORIES.CLOUD_ADMIN,
|
TELEMETRY_CATEGORIES.CLOUD_ADMIN,
|
||||||
'confirm_switch_to_annual_click_contact_sales',
|
'confirm_switch_to_annual_click_contact_sales',
|
||||||
);
|
);
|
||||||
window.open(this.props.contactSalesLink, '_blank');
|
const customerEmail = customer?.email || '';
|
||||||
|
const firstName = customer?.contact_first_name || '';
|
||||||
|
const lastName = customer?.contact_last_name || '';
|
||||||
|
const companyName = customer?.name || '';
|
||||||
|
goToMattermostContactSalesForm(firstName, lastName, companyName, customerEmail, 'mattermost', 'in-product-cloud');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1013,7 +1020,7 @@ class PurchaseModal extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
contactSupportLink={
|
contactSupportLink={
|
||||||
this.props.contactSalesLink
|
this.props.contactSupportLink
|
||||||
}
|
}
|
||||||
currentTeam={this.props.currentTeam}
|
currentTeam={this.props.currentTeam}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
|
|||||||
@@ -4,18 +4,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
import {useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType} from 'selectors/cloud';
|
|
||||||
import {
|
import {
|
||||||
TELEMETRY_CATEGORIES,
|
TELEMETRY_CATEGORIES,
|
||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
|
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
export default function ContactSalesLink() {
|
export default function ContactSalesLink() {
|
||||||
const contactSupportLink = useSelector(getCloudContactUsLink)(InquiryType.Technical);
|
const [, contactSalesLink] = useOpenSalesLink();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
@@ -26,7 +25,7 @@ export default function ContactSalesLink() {
|
|||||||
'click_contact_sales',
|
'click_contact_sales',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
href={contactSupportLink}
|
href={contactSalesLink}
|
||||||
location='contact_sales_link'
|
location='contact_sales_link'
|
||||||
>
|
>
|
||||||
{intl.formatMessage({id: 'self_hosted_signup.contact_sales', defaultMessage: 'Contact Sales'})}
|
{intl.formatMessage({id: 'self_hosted_signup.contact_sales', defaultMessage: 'Contact Sales'})}
|
||||||
|
|||||||
@@ -4,13 +4,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {getCloudContactUsLink, InquiryType} from 'selectors/cloud';
|
|
||||||
|
|
||||||
import PaymentFailedSvg from 'components/common/svg_images_components/payment_failed_svg';
|
import PaymentFailedSvg from 'components/common/svg_images_components/payment_failed_svg';
|
||||||
import AccessDeniedHappySvg from 'components/common/svg_images_components/access_denied_happy_svg';
|
import AccessDeniedHappySvg from 'components/common/svg_images_components/access_denied_happy_svg';
|
||||||
import IconMessage from 'components/purchase_modal/icon_message';
|
import IconMessage from 'components/purchase_modal/icon_message';
|
||||||
|
import {useOpenSelfHostedZendeskSupportForm} from 'components/common/hooks/useOpenZendeskForm';
|
||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -20,7 +18,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ErrorPage(props: Props) {
|
export default function ErrorPage(props: Props) {
|
||||||
const contactSupportLink = useSelector(getCloudContactUsLink)(InquiryType.Technical);
|
const [, contactSupportLink] = useOpenSelfHostedZendeskSupportForm('Purchase error');
|
||||||
let formattedTitle = (
|
let formattedTitle = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.billing.subscription.paymentVerificationFailed'
|
id='admin.billing.subscription.paymentVerificationFailed'
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
|||||||
import {checkHadPriorTrial, isCurrentLicenseCloud, getSubscriptionProduct as selectSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
import {checkHadPriorTrial, isCurrentLicenseCloud, getSubscriptionProduct as selectSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||||
import {getBrowserTimezone} from 'utils/timezone';
|
import {getBrowserTimezone} from 'utils/timezone';
|
||||||
|
|
||||||
import {CloudProducts, LicenseLinks, LicenseSkus} from 'utils/constants';
|
import {CloudProducts, LicenseSkus} from 'utils/constants';
|
||||||
|
|
||||||
import CloudStartTrialButton from 'components/cloud_start_trial/cloud_start_trial_btn';
|
import CloudStartTrialButton from 'components/cloud_start_trial/cloud_start_trial_btn';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ function ADLDAPUpsellBanner() {
|
|||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
const openSalesLink = useOpenSalesLink();
|
const [openSalesLink] = useOpenSalesLink();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getPrevTrialLicense());
|
dispatch(getPrevTrialLicense());
|
||||||
@@ -54,14 +54,6 @@ function ADLDAPUpsellBanner() {
|
|||||||
|
|
||||||
const currentLicenseEndDate = new Date(parseInt(currentLicense?.ExpiresAt, 10));
|
const currentLicenseEndDate = new Date(parseInt(currentLicense?.ExpiresAt, 10));
|
||||||
|
|
||||||
const openLink = () => {
|
|
||||||
if (isCloud) {
|
|
||||||
openSalesLink();
|
|
||||||
} else {
|
|
||||||
window.open(LicenseLinks.CONTACT_SALES, '_blank');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmBanner = (
|
const confirmBanner = (
|
||||||
<div className='ad_ldap_upsell_confirm'>
|
<div className='ad_ldap_upsell_confirm'>
|
||||||
<div className='upsell-confirm-backdrop'/>
|
<div className='upsell-confirm-backdrop'/>
|
||||||
@@ -71,7 +63,7 @@ function ADLDAPUpsellBanner() {
|
|||||||
<div className='btns-container'>
|
<div className='btns-container'>
|
||||||
<button
|
<button
|
||||||
className='confrim-btn learn-more'
|
className='confrim-btn learn-more'
|
||||||
onClick={openLink}
|
onClick={openSalesLink}
|
||||||
>
|
>
|
||||||
{formatMessage({id: 'adldap_upsell_banner.confirm.learn_more', defaultMessage: 'Learn more'})}
|
{formatMessage({id: 'adldap_upsell_banner.confirm.learn_more', defaultMessage: 'Learn more'})}
|
||||||
</button>
|
</button>
|
||||||
@@ -122,7 +114,7 @@ function ADLDAPUpsellBanner() {
|
|||||||
btn = (
|
btn = (
|
||||||
<button
|
<button
|
||||||
className='ad-ldap-banner-btn'
|
className='ad-ldap-banner-btn'
|
||||||
onClick={openLink}
|
onClick={openSalesLink}
|
||||||
>
|
>
|
||||||
{formatMessage({id: 'adldap_upsell_banner.sales_btn', defaultMessage: 'Contact sales to use'})}
|
{formatMessage({id: 'adldap_upsell_banner.sales_btn', defaultMessage: 'Contact sales to use'})}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,51 +4,10 @@
|
|||||||
import {Invoice, Subscription} from '@mattermost/types/cloud';
|
import {Invoice, Subscription} from '@mattermost/types/cloud';
|
||||||
|
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
|
||||||
import {createSelector} from 'reselect';
|
import {createSelector} from 'reselect';
|
||||||
|
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
export enum InquiryType {
|
|
||||||
Technical = 'technical',
|
|
||||||
Sales = 'sales',
|
|
||||||
Billing = 'billing',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TechnicalInquiryIssue {
|
|
||||||
AdminConsole = 'admin_console',
|
|
||||||
MattermostMessaging = 'mm_messaging',
|
|
||||||
DataExport = 'data_export',
|
|
||||||
Other = 'other',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SalesInquiryIssue {
|
|
||||||
AboutPurchasing = 'about_purchasing',
|
|
||||||
CancelAccount = 'cancel_account',
|
|
||||||
PurchaseNonprofit = 'purchase_nonprofit',
|
|
||||||
TrialQuestions = 'trial_questions',
|
|
||||||
UpgradeEnterprise = 'upgrade_enterprise',
|
|
||||||
SomethingElse = 'something_else',
|
|
||||||
}
|
|
||||||
|
|
||||||
type Issue = SalesInquiryIssue | TechnicalInquiryIssue
|
|
||||||
|
|
||||||
export const getCloudContactUsLink: (state: GlobalState) => (inquiry: InquiryType, inquiryIssue?: Issue) => string = createSelector(
|
|
||||||
'getCloudContactUsLink',
|
|
||||||
getConfig,
|
|
||||||
getCurrentUser,
|
|
||||||
(config, user) => {
|
|
||||||
// cloud/contact-us with query params for name, email and inquiry
|
|
||||||
const cwsUrl = config.CWSURL;
|
|
||||||
const fullName = `${user.first_name} ${user.last_name}`;
|
|
||||||
return (inquiry: InquiryType, inquiryIssue?: Issue) => {
|
|
||||||
const inquiryIssueQuery = inquiryIssue ? `&inquiry-issue=${inquiryIssue}` : '';
|
|
||||||
|
|
||||||
return `${cwsUrl}/cloud/contact-us?email=${encodeURIComponent(user.email)}&name=${encodeURIComponent(fullName)}&inquiry=${inquiry}${inquiryIssueQuery}`;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getExpandSeatsLink: (state: GlobalState) => (licenseId: string) => string = createSelector(
|
export const getExpandSeatsLink: (state: GlobalState) => (licenseId: string) => string = createSelector(
|
||||||
'getExpandSeatsLink',
|
'getExpandSeatsLink',
|
||||||
getConfig,
|
getConfig,
|
||||||
|
|||||||
100
webapp/channels/src/utils/contact_support_sales.ts
Обычный файл
100
webapp/channels/src/utils/contact_support_sales.ts
Обычный файл
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {Buffer} from 'buffer';
|
||||||
|
|
||||||
|
import {LicenseLinks} from './constants';
|
||||||
|
|
||||||
|
const baseZendeskFormURL = 'https://support.mattermost.com/hc/en-us/requests/new';
|
||||||
|
|
||||||
|
export enum ZendeskSupportForm {
|
||||||
|
SELF_HOSTED_SUPPORT_FORM = '11184911962004',
|
||||||
|
CLOUD_SUPPORT_FORM = '11184929555092',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ZendeskFormFieldIDs {
|
||||||
|
CLOUD_WORKSPACE_URL = '5245314479252',
|
||||||
|
SELF_HOSTED_ENVIRONMENT = '360026980452',
|
||||||
|
BILLING_SALES_CATEGORY = '360031056451',
|
||||||
|
EMAIL = 'anonymous_requester_email',
|
||||||
|
SUBJECT = 'subject',
|
||||||
|
DESCRIPTION = 'description'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PrefillFieldFormFieldIDs = {
|
||||||
|
id: ZendeskFormFieldIDs;
|
||||||
|
val: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildZendeskSupportForm = (form: ZendeskSupportForm, formFieldIDs: PrefillFieldFormFieldIDs[]): string => {
|
||||||
|
let formUrl = `${baseZendeskFormURL}?ticket_form_id=${form}`;
|
||||||
|
|
||||||
|
formFieldIDs.forEach((formPrefill) => {
|
||||||
|
formUrl = formUrl.concat(`&tf_${formPrefill.id}=${formPrefill.val}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (form === ZendeskSupportForm.SELF_HOSTED_SUPPORT_FORM) {
|
||||||
|
formUrl = formUrl.concat(`&tf_${ZendeskFormFieldIDs.SELF_HOSTED_ENVIRONMENT}=production`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const goToSelfHostedSupportForm = (email: string, subject: string) => {
|
||||||
|
const form = ZendeskSupportForm.SELF_HOSTED_SUPPORT_FORM;
|
||||||
|
const url = buildZendeskSupportForm(form, [
|
||||||
|
{id: ZendeskFormFieldIDs.EMAIL, val: email},
|
||||||
|
{id: ZendeskFormFieldIDs.SUBJECT, val: subject},
|
||||||
|
]);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSelfHostedSupportLink = (email: string, subject: string) => {
|
||||||
|
const form = ZendeskSupportForm.SELF_HOSTED_SUPPORT_FORM;
|
||||||
|
const url = buildZendeskSupportForm(form, [
|
||||||
|
{id: ZendeskFormFieldIDs.EMAIL, val: email},
|
||||||
|
{id: ZendeskFormFieldIDs.SUBJECT, val: subject},
|
||||||
|
]);
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const goToCloudSupportForm = (email: string, subject: string, description: string, workspaceURL: string) => {
|
||||||
|
const form = ZendeskSupportForm.CLOUD_SUPPORT_FORM;
|
||||||
|
let url = buildZendeskSupportForm(form, [
|
||||||
|
{id: ZendeskFormFieldIDs.EMAIL, val: email},
|
||||||
|
{id: ZendeskFormFieldIDs.SUBJECT, val: subject},
|
||||||
|
{id: ZendeskFormFieldIDs.DESCRIPTION, val: description},
|
||||||
|
]);
|
||||||
|
url = url.concat(`&tf_${ZendeskFormFieldIDs.CLOUD_WORKSPACE_URL}=${workspaceURL}`);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCloudSupportLink = (email: string, subject: string, description: string, workspaceURL: string) => {
|
||||||
|
const form = ZendeskSupportForm.CLOUD_SUPPORT_FORM;
|
||||||
|
let url = buildZendeskSupportForm(form, [
|
||||||
|
{id: ZendeskFormFieldIDs.EMAIL, val: email},
|
||||||
|
{id: ZendeskFormFieldIDs.SUBJECT, val: subject},
|
||||||
|
{id: ZendeskFormFieldIDs.DESCRIPTION, val: description},
|
||||||
|
]);
|
||||||
|
url = url.concat(`&tf_${ZendeskFormFieldIDs.CLOUD_WORKSPACE_URL}=${workspaceURL}`);
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeString = (s: string) => {
|
||||||
|
return Buffer.from(s).toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildMMURL = (baseURL: string, firstName: string, lastName: string, companyName: string, businessEmail: string, source: string, medium: string) => {
|
||||||
|
const mmURL = `${baseURL}?qk=${encodeString(firstName)}&qp=${encodeString(lastName)}&qw=${encodeString(companyName)}&qx=${encodeString(businessEmail)}&utm_source=${source}&utm_medium=${medium}`;
|
||||||
|
return mmURL;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const goToMattermostContactSalesForm = (firstName: string, lastName: string, companyName: string, businessEmail: string, source: string, medium: string) => {
|
||||||
|
const url = buildMMURL(LicenseLinks.CONTACT_SALES, firstName, lastName, companyName, businessEmail, source, medium);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCloudContactSalesLink = (firstName: string, lastName: string, companyName: string, businessEmail: string, source: string, medium: string) => {
|
||||||
|
const url = buildMMURL(LicenseLinks.CONTACT_SALES, firstName, lastName, companyName, businessEmail, source, medium);
|
||||||
|
return url;
|
||||||
|
};
|
||||||
Ссылка в новой задаче
Block a user