Skip to content

Commit 0eff38e

Browse files
authored
Merge pull request #18299 from mozilla/FXA-10983
feat(settings): Show banner when unverified accounts signin
2 parents c2bd7b8 + 00c25fe commit 0eff38e

11 files changed

Lines changed: 136 additions & 6 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# Link to delete account on main Settings page
22
delete-account-link = Delete account
3+
# Success message displayed in alert bar after the user has successfully confirmed their account is not inactive.
4+
inactive-update-status-success-alert = Signed in successfully. Your { -product-mozilla-account } and data will stay active.

packages/fxa-settings/src/components/Settings/PageSettings/index.test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PageSettings from '.';
99
import {
1010
MOCK_ACCOUNT,
1111
mockAppContext,
12+
mockSettingsContext,
1213
renderWithRouter,
1314
} from '../../../models/mocks';
1415
import * as Metrics from '../../../lib/metrics';
@@ -19,6 +20,10 @@ import {
1920
ALL_PRODUCT_PROMO_SUBSCRIPTIONS,
2021
} from '../../../pages/mocks';
2122
import { MOCK_SERVICES } from '../ConnectedServices/mocks';
23+
import { mockWebIntegration } from '../../../pages/Signin/SigninRecoveryCode/mocks';
24+
import { SettingsContext } from '../../../models/contexts/SettingsContext';
25+
26+
jest.mock('../../../models/AlertBarInfo');
2227

2328
jest.mock('../../../lib/metrics', () => ({
2429
setProperties: jest.fn(),
@@ -35,6 +40,9 @@ jest.mock('../../../lib/glean', () => ({
3540
deleteAccount: {
3641
settingsSubmit: jest.fn(),
3742
},
43+
accountBanner: {
44+
reactivationSuccessView: jest.fn(),
45+
},
3846
},
3947
}));
4048

@@ -68,6 +76,25 @@ describe('PageSettings', () => {
6876
});
6977
});
7078

79+
it('renders without imploding when passing an integration', async () => {
80+
renderWithRouter(<PageSettings integration={mockWebIntegration} />);
81+
82+
// assert all typical PageSetting elements
83+
expect(screen.getByTestId('settings-profile')).toBeInTheDocument();
84+
expect(screen.getByTestId('settings-security')).toBeInTheDocument();
85+
expect(
86+
screen.getByTestId('settings-connected-services')
87+
).toBeInTheDocument();
88+
expect(screen.getByTestId('settings-delete-account')).toBeInTheDocument();
89+
expect(
90+
screen.queryByTestId('settings-data-collection')
91+
).toBeInTheDocument();
92+
expect(Metrics.setProperties).toHaveBeenCalledWith({
93+
lang: null,
94+
uid: 'abc123',
95+
});
96+
});
97+
7198
describe('glean metrics', () => {
7299
it('emits the expected event on render', async () => {
73100
renderWithRouter(<PageSettings />);
@@ -118,5 +145,32 @@ describe('PageSettings', () => {
118145
expect(GleanMetrics.accountPref.promoMonitorView).not.toBeCalled();
119146
});
120147
});
148+
describe('inactive account verified', () => {
149+
const alertBarInfo = {
150+
success: jest.fn(),
151+
} as any;
152+
const settingsContext = mockSettingsContext({ alertBarInfo });
153+
154+
it('user has seen the reactivation banner', async () => {
155+
mockWebIntegration.data.utmCampaign =
156+
'fx-account-inactive-reminder-third';
157+
mockWebIntegration.data.utmMedium = 'email';
158+
mockWebIntegration.data.utmContent = 'fx-account-deletion';
159+
renderWithRouter(
160+
<AppContext.Provider value={mockAppContext()}>
161+
<SettingsContext.Provider value={settingsContext}>
162+
<PageSettings integration={mockWebIntegration} />
163+
</SettingsContext.Provider>
164+
</AppContext.Provider>
165+
);
166+
167+
expect(alertBarInfo.success).toHaveBeenCalledWith(
168+
'Signed in successfully. Your Mozilla account and data will stay active.'
169+
);
170+
expect(
171+
GleanMetrics.accountBanner.reactivationSuccessView
172+
).toBeCalledTimes(1);
173+
});
174+
});
121175
});
122176
});

packages/fxa-settings/src/components/Settings/PageSettings/index.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ConnectedServices from '../ConnectedServices';
1010
import LinkedAccounts from '../LinkedAccounts';
1111

1212
import * as Metrics from '../../../lib/metrics';
13-
import { useAccount, useFtlMsgResolver } from '../../../models';
13+
import { useAccount, useAlertBar, useFtlMsgResolver } from '../../../models';
1414
import { SETTINGS_PATH } from 'fxa-settings/src/constants';
1515
import { Localized } from '@fluent/react';
1616
import DataCollection from '../DataCollection';
@@ -23,10 +23,14 @@ import SideBar from '../Sidebar';
2323
import NotificationPromoBanner from '../../NotificationPromoBanner';
2424
import keyImage from '../../NotificationPromoBanner/key.svg';
2525
import Head from 'fxa-react/components/Head';
26+
import { SettingsIntegration } from '../interfaces';
2627

27-
export const PageSettings = (_: RouteComponentProps) => {
28+
export const PageSettings = ({
29+
integration,
30+
}: RouteComponentProps & { integration?: SettingsIntegration }) => {
2831
const { uid, recoveryKey, attachedClients, subscriptions } = useAccount();
2932
const ftlMsgResolver = useFtlMsgResolver();
33+
const alertBar = useAlertBar();
3034

3135
Metrics.setProperties({
3236
lang: document.querySelector('html')?.getAttribute('lang'),
@@ -41,6 +45,37 @@ export const PageSettings = (_: RouteComponentProps) => {
4145
const [productPromoGleanEventSent, setProductPromoGleanEventSent] =
4246
useState(false);
4347

48+
useEffect(() => {
49+
function showInactiveVerifiedBanner() {
50+
const emailCampaigns = [
51+
'fx-account-inactive-reminder-first',
52+
'fx-account-inactive-reminder-second',
53+
'fx-account-inactive-reminder-third',
54+
];
55+
if (!emailCampaigns.some((e) => integration?.data?.utmCampaign === e)) {
56+
return false;
57+
}
58+
if (
59+
integration?.data?.utmContent !== 'fx-account-deletion' ||
60+
integration?.data?.utmMedium !== 'email'
61+
) {
62+
return false;
63+
}
64+
return true;
65+
}
66+
67+
if (showInactiveVerifiedBanner()) {
68+
GleanMetrics.accountBanner.reactivationSuccessView();
69+
alertBar.success(
70+
ftlMsgResolver.getMsg(
71+
'inactive-update-status-success-alert',
72+
'Signed in successfully. Your Mozilla account and data will stay active.'
73+
)
74+
);
75+
}
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
}, []);
78+
4479
useEffect(() => {
4580
// We want this view event to fire whenever the account settings page view
4681
// event fires, if the user is shown the promo.

packages/fxa-settings/src/components/Settings/PageSettings/mocks.tsx

Whitespace-only changes.

packages/fxa-settings/src/components/Settings/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ import PageAvatar from './PageAvatar';
3636
import PageRecentActivity from './PageRecentActivity';
3737
import PageRecoveryKeyCreate from './PageRecoveryKeyCreate';
3838
import { hardNavigate } from 'fxa-react/lib/utils';
39-
import { SettingsIntegration } from './interfaces';
4039
import { currentAccount } from '../../lib/cache';
4140
import { hasAccount, setCurrentAccount } from '../../lib/storage-utils';
4241
import GleanMetrics from '../../lib/glean';
4342
import Head from 'fxa-react/components/Head';
4443
import PageRecoveryPhoneRemove from './PageRecoveryPhoneRemove';
44+
import { SettingsIntegration } from './interfaces';
4545

4646
export const Settings = ({
4747
integration,
@@ -157,7 +157,7 @@ export const Settings = ({
157157
<Head />
158158
<Router basepath={SETTINGS_PATH}>
159159
<ScrollToTop default>
160-
<PageSettings path="/" />
160+
<PageSettings path="/" {...{ integration }} />
161161
<PageDisplayName path="/display_name" />
162162
<PageAvatar path="/avatar" />
163163
{account.hasPassword ? (

packages/fxa-settings/src/components/Settings/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
import { Integration } from '../../models';
66

7-
export type SettingsIntegration = Pick<Integration, 'type' | 'isSync'>;
7+
export type SettingsIntegration = Pick<Integration, 'type' | 'data'>;

packages/fxa-settings/src/components/Settings/mocks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function createMockSettingsIntegration({
1515
} = {}): SettingsIntegration {
1616
return {
1717
type,
18-
isSync: () => isSync,
18+
data: {},
1919
};
2020
}
2121

packages/fxa-settings/src/lib/glean/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ const recordEventMetric = (
518518
case 'account_banner_create_recovery_key_view':
519519
accountBanner.createRecoveryKeyView.record();
520520
break;
521+
case 'account_banner_reactivation_success_view':
522+
accountBanner.reactivationSuccessView.record();
523+
break;
521524
case 'error_view':
522525
error.view.record({
523526
reason: gleanPingMetrics?.event?.['reason'] || '',

packages/fxa-shared/metrics/glean/fxa-ui-metrics.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,24 @@ account_banner:
24602460
expires: never
24612461
data_sensitivity:
24622462
- interaction
2463+
reactivation_success_view:
2464+
type: event
2465+
description: |
2466+
User sees the reactivation banner after receiving the inactive
2467+
account email and clicking the provided link inside it.
2468+
send_in_pings:
2469+
- events
2470+
notification_emails:
2471+
2472+
2473+
bugs:
2474+
- https://mozilla-hub.atlassian.net/browse/FXA-10983
2475+
data_reviews:
2476+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1830504
2477+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1844121
2478+
expires: never
2479+
data_sensitivity:
2480+
- interaction
24632481

24642482
delete_account:
24652483
settings_submit:

packages/fxa-shared/metrics/glean/web/accountBanner.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@ export const createRecoveryKeyView = new EventMetricType(
2121
},
2222
[]
2323
);
24+
25+
/**
26+
* User sees the reactivation banner after receiving the inactive
27+
* account email and clicking the provided link inside it.
28+
*
29+
* Generated from `account_banner.reactivation_success_view`.
30+
*/
31+
export const reactivationSuccessView = new EventMetricType(
32+
{
33+
category: 'account_banner',
34+
name: 'reactivation_success_view',
35+
sendInPings: ['events'],
36+
lifetime: 'ping',
37+
disabled: false,
38+
},
39+
[]
40+
);

0 commit comments

Comments
 (0)