[MM-51475] - Monthly Subscriptions: Notifications to move to annual (#22842)
* [MM-51475] - Monthly Subscriptions: Notifications to move to annual * change wording * change banners impl * fix translation * rm env var * fix tests * rm unused consts --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
48cd0f884e
Коммит
ea44b6bd38
@@ -2,11 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {screen} from '@testing-library/react';
|
||||
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
|
||||
import {CloudProducts, RecurringIntervals} from 'utils/constants';
|
||||
|
||||
import {ToYearlyNudgeBanner, ToYearlyNudgeBannerDismissable} from './to_yearly_nudge_banner';
|
||||
@@ -42,7 +40,7 @@ const initialState = {
|
||||
},
|
||||
};
|
||||
|
||||
describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () => {
|
||||
describe('ToYearlyNudgeBannerDismissable', () => {
|
||||
test('should show for admins cloud professional monthly', () => {
|
||||
const state = JSON.parse(JSON.stringify(initialState));
|
||||
state.entities.users.profiles = {
|
||||
@@ -63,14 +61,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(true);
|
||||
screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar');
|
||||
});
|
||||
|
||||
test('should NOT show for NON admins', () => {
|
||||
@@ -93,14 +86,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow();
|
||||
});
|
||||
|
||||
test('should NOT show for admins on cloud free', () => {
|
||||
@@ -123,14 +111,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow();
|
||||
});
|
||||
|
||||
test('should NOT show for admins on cloud enterprise', () => {
|
||||
@@ -153,14 +136,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow();
|
||||
});
|
||||
|
||||
test('should NOT show for admins on cloud pro annual', () => {
|
||||
@@ -182,15 +160,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow();
|
||||
});
|
||||
|
||||
test('should NOT show for admins when banner was dismissed in preferences', () => {
|
||||
@@ -200,10 +172,10 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
};
|
||||
state.entities.preferences = {
|
||||
myPreferences: {
|
||||
'cloud_yearly_nudge_banner--nudge_to_yearly_banner_dismissed': {
|
||||
category: 'cloud_yearly_nudge_banner',
|
||||
name: 'nudge_to_yearly_banner_dismissed',
|
||||
value: 'true',
|
||||
'to_cloud_yearly_plan_nudge--nudge_to_cloud_yearly_plan_snoozed': {
|
||||
category: 'to_cloud_yearly_plan_nudge',
|
||||
name: 'nudge_to_cloud_yearly_plan_snoozed',
|
||||
value: '{"range": 0, "show": false}',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -221,19 +193,13 @@ describe('components/admin_console/billing/ToYearlyNudgeBannerDismissable', () =
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBannerDismissable/>, state);
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBannerDismissable/>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('AnnouncementBar').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('components/admin_console/billing/ToYearlyNudgeBanner', () => {
|
||||
describe('ToYearlyNudgeBanner', () => {
|
||||
test('should show for cloud professional monthly', () => {
|
||||
const state = JSON.parse(JSON.stringify(initialState));
|
||||
state.entities.cloud = {
|
||||
@@ -251,14 +217,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBanner', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBanner/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBanner/>, state);
|
||||
|
||||
expect(wrapper.find('AlertBanner').exists()).toBe(true);
|
||||
screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner');
|
||||
});
|
||||
|
||||
test('should NOT show for non cloud professional monthly', () => {
|
||||
@@ -278,13 +239,9 @@ describe('components/admin_console/billing/ToYearlyNudgeBanner', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<ToYearlyNudgeBanner/>
|
||||
</Provider>,
|
||||
);
|
||||
renderWithIntlAndStore(<ToYearlyNudgeBanner/>, state);
|
||||
|
||||
expect(wrapper.find('AlertBanner').exists()).toBe(false);
|
||||
expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {useIntl, FormattedMessage} from 'react-intl';
|
||||
import moment from 'moment';
|
||||
|
||||
import AlertBanner from 'components/alert_banner';
|
||||
import useOpenCloudPurchaseModal from 'components/common/hooks/useOpenCloudPurchaseModal';
|
||||
@@ -22,12 +23,35 @@ import {GlobalState} from '@mattermost/types/store';
|
||||
|
||||
import './to_yearly_nudge_banner.scss';
|
||||
|
||||
enum DismissShowRange {
|
||||
GreaterThanEqual90 = '>=90',
|
||||
BetweenNinetyAnd60 = '89-61',
|
||||
SixtyTo31 = '60-31',
|
||||
ThirtyTo11 = '30-11',
|
||||
TenTo1 = '10-1',
|
||||
Zero = '0'
|
||||
}
|
||||
|
||||
const cloudProMonthlyCloseMoment = '20230727';
|
||||
|
||||
interface ToYearlyPlanDismissPreference {
|
||||
|
||||
// range represents the range for the days to the deprecation of cloud free e.g. in 30 to 10 days to deprecate cloud free
|
||||
// Incase of dismissing the banner, range represents the time (days) period when this banner was dismissed.
|
||||
// This is important because in case the banner was dismissed for a certain period, it helps us know that we should not show it again for that period.
|
||||
range: DismissShowRange;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
const ToYearlyNudgeBannerDismissable = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const openPurchaseModal = useOpenCloudPurchaseModal({});
|
||||
|
||||
const nudgeDismissed = useSelector((state: GlobalState) => getPreference(state, Preferences.CLOUD_YEARLY_NUDGE_BANNER, CloudBanners.NUDGE_TO_YEARLY_BANNER_DISMISSED)) === 'true';
|
||||
const snoozePreferenceVal = useSelector((state: GlobalState) => getPreference(state, Preferences.TO_CLOUD_YEARLY_PLAN_NUDGE, CloudBanners.NUDGE_TO_CLOUD_YEARLY_PLAN_SNOOZED, '{"range": 0, "show": true}'));
|
||||
const snoozeInfo = JSON.parse(snoozePreferenceVal) as ToYearlyPlanDismissPreference;
|
||||
const show = snoozeInfo.show;
|
||||
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
||||
const product = useSelector(selectSubscriptionProduct);
|
||||
@@ -35,16 +59,75 @@ const ToYearlyNudgeBannerDismissable = () => {
|
||||
const currentProductIsMonthly = product?.recurring_interval === RecurringIntervals.MONTH;
|
||||
const currentProductProMonthly = currentProductProfessional && currentProductIsMonthly;
|
||||
|
||||
const savedDismissedPref = () => {
|
||||
const now = moment(Date.now());
|
||||
const proMonthlyEndDate = moment(cloudProMonthlyCloseMoment, 'YYYYMMDD');
|
||||
const daysToProMonthlyEnd = proMonthlyEndDate.diff(now, 'days');
|
||||
|
||||
const snoozedForRange = (range: DismissShowRange) => {
|
||||
return snoozeInfo.range === range;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!snoozeInfo.show) {
|
||||
if (daysToProMonthlyEnd >= 90 && !snoozedForRange(DismissShowRange.GreaterThanEqual90)) {
|
||||
showBanner(true);
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd < 90 && daysToProMonthlyEnd > 60 && !snoozedForRange(DismissShowRange.BetweenNinetyAnd60)) {
|
||||
showBanner(true);
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd <= 60 && daysToProMonthlyEnd > 30 && !snoozedForRange(DismissShowRange.SixtyTo31)) {
|
||||
showBanner(true);
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd <= 30 && daysToProMonthlyEnd > 10 && !snoozedForRange(DismissShowRange.ThirtyTo11)) {
|
||||
showBanner(true);
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd <= 10) {
|
||||
showBanner(true);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showBanner = (show = false) => {
|
||||
let dRange = DismissShowRange.Zero;
|
||||
if (daysToProMonthlyEnd >= 90) {
|
||||
dRange = DismissShowRange.GreaterThanEqual90;
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd < 90 && daysToProMonthlyEnd > 60) {
|
||||
dRange = DismissShowRange.BetweenNinetyAnd60;
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd <= 60 && daysToProMonthlyEnd > 30) {
|
||||
dRange = DismissShowRange.SixtyTo31;
|
||||
}
|
||||
|
||||
if (daysToProMonthlyEnd <= 30 && daysToProMonthlyEnd > 10) {
|
||||
dRange = DismissShowRange.ThirtyTo11;
|
||||
}
|
||||
|
||||
// ideally this case should not happen because snooze button is not shown when TenTo1 days are remaining
|
||||
if (daysToProMonthlyEnd <= 10 && daysToProMonthlyEnd > 0) {
|
||||
dRange = DismissShowRange.TenTo1;
|
||||
}
|
||||
|
||||
const snoozeInfo: ToYearlyPlanDismissPreference = {
|
||||
range: dRange,
|
||||
show,
|
||||
};
|
||||
|
||||
dispatch(savePreferences(currentUser.id, [{
|
||||
category: Preferences.CLOUD_YEARLY_NUDGE_BANNER,
|
||||
name: CloudBanners.NUDGE_TO_YEARLY_BANNER_DISMISSED,
|
||||
category: Preferences.TO_CLOUD_YEARLY_PLAN_NUDGE,
|
||||
name: CloudBanners.NUDGE_TO_CLOUD_YEARLY_PLAN_SNOOZED,
|
||||
user_id: currentUser.id,
|
||||
value: 'true',
|
||||
value: JSON.stringify(snoozeInfo),
|
||||
}]));
|
||||
};
|
||||
|
||||
if (nudgeDismissed) {
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -56,21 +139,30 @@ const ToYearlyNudgeBannerDismissable = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = {
|
||||
id: 'cloud_billing.nudge_to_yearly.announcement_bar',
|
||||
defaultMessage: 'Simplify your billing and switch to an annual plan today',
|
||||
};
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id='cloud_billing.nudge_to_yearly.announcement_bar'
|
||||
defaultMessage='Monthly billing will be discontinued in {days} days . Switch to annual billing'
|
||||
values={{
|
||||
days: daysToProMonthlyEnd,
|
||||
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const announcementType = (daysToProMonthlyEnd <= 10) ? AnnouncementBarTypes.CRITICAL : AnnouncementBarTypes.ANNOUNCEMENT;
|
||||
|
||||
return (
|
||||
<AnnouncementBar
|
||||
type={AnnouncementBarTypes.ANNOUNCEMENT}
|
||||
showCloseButton={true}
|
||||
id='cloud-pro-monthly-deprecation-announcement-bar'
|
||||
type={announcementType}
|
||||
showCloseButton={daysToProMonthlyEnd > 10}
|
||||
onButtonClick={() => openPurchaseModal({trackingLocation: 'to_yearly_nudge_annoucement_bar'})}
|
||||
modalButtonText={t('cloud_billing.nudge_to_yearly.learn_more')}
|
||||
modalButtonDefaultText='Learn more'
|
||||
message={<FormattedMessage {...message}/>}
|
||||
message={message}
|
||||
showLinkAsButton={true}
|
||||
handleClose={savedDismissedPref}
|
||||
handleClose={showBanner}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -90,17 +182,22 @@ const ToYearlyNudgeBanner = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = moment(Date.now());
|
||||
const proMonthlyEndDate = moment(cloudProMonthlyCloseMoment, 'YYYYMMDD');
|
||||
const daysToProMonthlyEnd = proMonthlyEndDate.diff(now, 'days');
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='cloud_billing.nudge_to_yearly.title'
|
||||
defaultMessage='Switch to an annual plan today'
|
||||
defaultMessage='Action required: Switch to annual billing to keep your workspace.'
|
||||
/>
|
||||
);
|
||||
|
||||
const description = (
|
||||
<FormattedMessage
|
||||
id='cloud_billing.nudge_to_yearly.description'
|
||||
defaultMessage='Simplify your billing by switching to an annual subscription.'
|
||||
defaultMessage='Monthly billing will be discontinued on {date}. To keep your workspace, switch to annual billing.'
|
||||
values={{date: moment(cloudProMonthlyCloseMoment, 'YYYYMMDD').format('MMMM DD, YYYY')}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -122,9 +219,12 @@ const ToYearlyNudgeBanner = () => {
|
||||
</button>
|
||||
);
|
||||
|
||||
const bannerMode = (daysToProMonthlyEnd <= 10) ? 'danger' : 'info';
|
||||
|
||||
return (
|
||||
<AlertBanner
|
||||
mode='info'
|
||||
id='cloud-pro-monthly-deprecation-alert-banner'
|
||||
mode={bannerMode}
|
||||
title={title}
|
||||
message={description}
|
||||
className='ToYearlyNudgeBanner'
|
||||
|
||||
@@ -3039,10 +3039,11 @@
|
||||
"cloud_billing.nudge_to_paid.learn_more": "Upgrade",
|
||||
"cloud_billing.nudge_to_paid.title": "Upgrade to paid plan to keep your workspace",
|
||||
"cloud_billing.nudge_to_paid.view_plans": "View plans",
|
||||
"cloud_billing.nudge_to_yearly.announcement_bar": "Monthly billing will be discontinued in {days} days . Switch to annual billing",
|
||||
"cloud_billing.nudge_to_yearly.contact_sales": "Contact sales",
|
||||
"cloud_billing.nudge_to_yearly.description": "Simplify your billing by switching to an annual subscription.",
|
||||
"cloud_billing.nudge_to_yearly.description": "Monthly billing will be discontinued on {date}. To keep your workspace, switch to annual billing.",
|
||||
"cloud_billing.nudge_to_yearly.learn_more": "Learn more",
|
||||
"cloud_billing.nudge_to_yearly.title": "Switch to an annual plan today",
|
||||
"cloud_billing.nudge_to_yearly.title": "Action required: Switch to annual billing to keep your workspace.",
|
||||
"cloud_delinquency.banner.buttonText": "Update billing now",
|
||||
"cloud_delinquency.banner.end_user_notify_admin_button": "Notify admin",
|
||||
"cloud_delinquency.banner.end_user_notify_admin_title": "Your workspace has been downgraded. Notify your admin to fix billing issues",
|
||||
|
||||
@@ -157,7 +157,7 @@ export const Preferences = {
|
||||
CONFIGURATION_BANNERS: 'configuration_banners',
|
||||
NOTIFY_ADMIN_REVOKE_DOWNGRADED_WORKSPACE: 'admin_revoke_downgraded_instance',
|
||||
OVERAGE_USERS_BANNER: 'overage_users_banner',
|
||||
CLOUD_YEARLY_NUDGE_BANNER: 'cloud_yearly_nudge_banner',
|
||||
TO_CLOUD_YEARLY_PLAN_NUDGE: 'to_cloud_yearly_plan_nudge',
|
||||
TO_PAID_PLAN_NUDGE: 'to_paid_plan_nudge',
|
||||
};
|
||||
|
||||
@@ -726,7 +726,7 @@ export const CloudBanners = {
|
||||
TRIAL: 'trial',
|
||||
UPGRADE_FROM_TRIAL: 'upgrade_from_trial',
|
||||
THREE_DAYS_LEFT_TRIAL_MODAL_DISMISSED: 'dismiss_3_days_left_trial_modal',
|
||||
NUDGE_TO_YEARLY_BANNER_DISMISSED: 'nudge_to_yearly_banner_dismissed',
|
||||
NUDGE_TO_CLOUD_YEARLY_PLAN_SNOOZED: 'nudge_to_cloud_yearly_plan_snoozed',
|
||||
NUDGE_TO_PAID_PLAN_SNOOZED: 'nudge_to_paid_plan_snoozed',
|
||||
};
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user