diff --git a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx
index af797c5f92..e9061333a5 100644
--- a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx
+++ b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx
@@ -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(
-
-
- ,
- );
+ renderWithIntlAndStore(, 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(
-
-
- ,
- );
+ renderWithIntlAndStore(, 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(
-
-
- ,
- );
+ renderWithIntlAndStore(, 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(
-
-
- ,
- );
+ renderWithIntlAndStore(, 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(, state);
- const store = mockStore(state);
- const wrapper = mountWithIntl(
-
-
- ,
- );
-
- 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(, state);
- const store = mockStore(state);
- const wrapper = mountWithIntl(
-
-
- ,
- );
-
- 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(
-
-
- ,
- );
+ renderWithIntlAndStore(, 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(
-
-
- ,
- );
+ renderWithIntlAndStore(, state);
- expect(wrapper.find('AlertBanner').exists()).toBe(false);
+ expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner')).toThrow();
});
});
+
diff --git a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx
index 3cc1e8aa75..f2eac86e69 100644
--- a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx
+++ b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx
@@ -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 = (
+
+ );
+
+ const announcementType = (daysToProMonthlyEnd <= 10) ? AnnouncementBarTypes.CRITICAL : AnnouncementBarTypes.ANNOUNCEMENT;
return (
10}
onButtonClick={() => openPurchaseModal({trackingLocation: 'to_yearly_nudge_annoucement_bar'})}
modalButtonText={t('cloud_billing.nudge_to_yearly.learn_more')}
modalButtonDefaultText='Learn more'
- 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 = (
);
const description = (
);
@@ -122,9 +219,12 @@ const ToYearlyNudgeBanner = () => {
);
+ const bannerMode = (daysToProMonthlyEnd <= 10) ? 'danger' : 'info';
+
return (