Skip to content

Commit

Permalink
feat: adds "data collection for marketing" toggles (#9687)
Browse files Browse the repository at this point in the history
Adds data collection for marketing toggles (and toasts/warnings) on:
- Onboarding
- Toast in Wallet
- Settings page

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

## **Related issues**

Fixes:
MetaMask/MetaMask-planning#2525
MetaMask/MetaMask-planning#2439
MetaMask/MetaMask-planning#2440

## **Manual testing steps**


Onboarding checkbox:
Set `isPastPrivacyPolicyDate` to true in OptInMetrics
1. Start a new account
2. There should be a new checkbox that asks for marketing consent
3. Checking it should set the marketing consent to true (check at
Settings, Securty tab page)

Security tab:
1. Go to Security tab
2. When checking the "Data collection for marketing" to `true`, the
"Participate in MetaMetrics" toggle should turn to `true`
3. When checking "Participate in MetaMetrics" to `false`, "Data
collection for marketing" should be set to `false`
4. When "Participate in Metametrics" is `true` and "Data collection for
marketing" is `true`, and the latter is set to `false`, a warning
message should appear.

Toast:
An already onboarded user will have the "dataCollectionForMarketing"
value as `null` (neither `true` or `false`). This will trigger the
toast.
1. By clicking on "I accept", it should set the "Data collection for
marketing" to `true`.
2. By closing the toast or clicking on "No thanks", it should set the
"Data collection for marketing" to `false`.

All of these actions should trigger subsequent Segment events.


## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**



https://github.com/MetaMask/metamask-mobile/assets/11148144/d0ebee90-38e1-4362-92ad-73728c82df72

<img width="573" alt="Screenshot 2024-05-20 at 19 18 19"
src="https://github.com/MetaMask/metamask-mobile/assets/11148144/04538fdb-564e-4c75-9922-f0224bcbeec6">

<img width="573" alt="Screenshot 2024-05-20 at 19 19 30"
src="https://github.com/MetaMask/metamask-mobile/assets/11148144/347bf76a-e075-4647-b015-b1770814556b">



## **Pre-merge author checklist**

- [ ] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: EtherWizard33 <eric.lamontagne@consensys.net>
Co-authored-by: sethkfman <10342624+sethkfman@users.noreply.github.com>
Co-authored-by: Curtis David <Curtis.David7@gmail.com>
Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com>
Co-authored-by: salimtb <salim.toubal@outlook.com>
  • Loading branch information
6 people authored Jun 4, 2024
1 parent be29b05 commit da288b5
Show file tree
Hide file tree
Showing 25 changed files with 1,083 additions and 8 deletions.
14 changes: 13 additions & 1 deletion app/actions/security/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum ActionType {
SET_AUTOMATIC_SECURITY_CHECKS = 'SET_AUTOMATIC_SECURITY_CHECKS',
USER_SELECTED_AUTOMATIC_SECURITY_CHECKS_OPTION = 'USER_SELECTED_AUTOMATIC_SECURITY_CHECKS_OPTION',
SET_AUTOMATIC_SECURITY_CHECKS_MODAL_OPEN = 'SET_AUTOMATIC_SECURITY_CHECKS_MODAL_OPEN',
SET_DATA_COLLECTION_FOR_MARKETING = 'SET_DATA_COLLECTION_FOR_MARKETING',
}

export interface AllowLoginWithRememberMeUpdated
Expand All @@ -28,11 +29,17 @@ export interface SetAutomaticSecurityChecksModalOpen
open: boolean;
}

export interface SetDataCollectionForMarketing
extends ReduxAction<ActionType.SET_DATA_COLLECTION_FOR_MARKETING> {
enabled: boolean;
}

export type Action =
| AllowLoginWithRememberMeUpdated
| AutomaticSecurityChecks
| UserSelectedAutomaticSecurityChecksOptions
| SetAutomaticSecurityChecksModalOpen;
| SetAutomaticSecurityChecksModalOpen
| SetDataCollectionForMarketing;

export const setAllowLoginWithRememberMe = (
enabled: boolean,
Expand Down Expand Up @@ -60,3 +67,8 @@ export const setAutomaticSecurityChecksModalOpen = (
type: ActionType.SET_AUTOMATIC_SECURITY_CHECKS_MODAL_OPEN,
open,
});

export const setDataCollectionForMarketing = (enabled: boolean) => ({
type: ActionType.SET_DATA_COLLECTION_FOR_MARKETING,
enabled,
});
2 changes: 2 additions & 0 deletions app/actions/security/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export interface SecuritySettingsState {
automaticSecurityChecksEnabled: boolean;
hasUserSelectedAutomaticSecurityCheckOption: boolean;
isAutomaticSecurityChecksModalOpen: boolean;
// 'null' represents the user not having set his preference over dataCollectionForMarketing yet
dataCollectionForMarketing: boolean | null;
}
10 changes: 10 additions & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Animated, Linking } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import Login from '../../Views/Login';
import QRScanner from '../../Views/QRScanner';
import DataCollectionModal from '../../Views/DataCollectionModal';
import Onboarding from '../../Views/Onboarding';
import OnboardingCarousel from '../../Views/OnboardingCarousel';
import ChoosePassword from '../../Views/ChoosePassword';
Expand Down Expand Up @@ -100,6 +101,7 @@ import ShowDisplayNftMediaSheet from '../../Views/ShowDisplayMediaNFTSheet/ShowD
import AmbiguousAddressSheet from '../../../../app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet';
import SDKDisconnectModal from '../../../../app/components/Views/SDKDisconnectModal/SDKDisconnectModal';
import SDKSessionModal from '../../../../app/components/Views/SDKSessionModal/SDKSessionModal';
import ExperienceEnhancerModal from '../../../../app/components/Views/ExperienceEnhancerModal';
import { MetaMetrics } from '../../../core/Analytics';
import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';
import generateDeviceAnalyticsMetaData from '../../../util/metrics/DeviceAnalyticsMetaData/generateDeviceAnalyticsMetaData';
Expand Down Expand Up @@ -612,6 +614,14 @@ const App = ({ userLoggedIn }) => {
name={Routes.SHEET.SDK_MANAGE_CONNECTIONS}
component={SDKSessionModal}
/>
<Stack.Screen
name={Routes.SHEET.EXPERIENCE_ENHANCER}
component={ExperienceEnhancerModal}
/>
<Stack.Screen
name={Routes.SHEET.DATA_COLLECTION}
component={DataCollectionModal}
/>
<Stack.Screen
name={Routes.SHEET.SDK_DISCONNECT}
component={SDKDisconnectModal}
Expand Down
57 changes: 53 additions & 4 deletions app/components/UI/OptinMetrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { strings } from '../../../../locales/i18n';
import setOnboardingWizardStep from '../../../actions/wizard';
import { connect } from 'react-redux';
import { clearOnboardingEvents } from '../../../actions/onboarding';
import { setDataCollectionForMarketing } from '../../../actions/security';
import { ONBOARDING_WIZARD } from '../../../constants/storage';
import AppConstants from '../../../core/AppConstants';
import {
Expand All @@ -26,20 +27,18 @@ import {
import DefaultPreference from 'react-native-default-preference';
import { ThemeContext } from '../../../util/theme';
import { MetaMetricsOptInSelectorsIDs } from '../../../../e2e/selectors/Onboarding/MetaMetricsOptIn.selectors';
import Checkbox from '../../../component-library/components/Checkbox';
import Button, {
ButtonVariants,
ButtonSize,
} from '../../../component-library/components/Buttons/Button';
import { MAINNET } from '../../../constants/network';
import { newPrivacyPolicyDate } from '../../../reducers/legalNotices';
import { isPastPrivacyPolicyDate } from '../../../reducers/legalNotices';
import Routes from '../../../constants/navigation/Routes';
import generateDeviceAnalyticsMetaData, {
UserSettingsAnalyticsMetaData as generateUserSettingsAnalyticsMetaData,
} from '../../../util/metrics';

const currentDate = new Date(Date.now());
const isPastPrivacyPolicyDate = currentDate >= newPrivacyPolicyDate;

const createStyles = ({ colors }) =>
StyleSheet.create({
root: {
Expand All @@ -52,6 +51,13 @@ const createStyles = ({ colors }) =>
crossIcon: {
color: colors.error.default,
},
checkbox: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 15,
marginRight: 25,
},
icon: {
marginRight: 5,
},
Expand Down Expand Up @@ -118,6 +124,8 @@ const createStyles = ({ colors }) =>
*/
class OptinMetrics extends PureComponent {
static propTypes = {
isDataCollectionForMarketingEnabled: PropTypes.bool,
setDataCollectionForMarketing: PropTypes.func,
/**
/* navigation object required to push and pop other views
*/
Expand Down Expand Up @@ -337,6 +345,21 @@ class OptinMetrics extends PureComponent {

this.props.clearOnboardingEvents();

if (this.props.isDataCollectionForMarketingEnabled) {
const traits = {
is_metrics_opted_in: true,
has_marketing_consent: Boolean(
this.props.setDataCollectionForMarketing,
),
};

metrics.addTraitsToUser(traits);
metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, {
...traits,
location: 'onboarding_metametrics',
});
}

// track event for user opting in
metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, {
analytics_option_selected: 'Metrics Opt In',
Expand Down Expand Up @@ -524,6 +547,11 @@ class OptinMetrics extends PureComponent {
};

render() {
const {
isDataCollectionForMarketingEnabled,
setDataCollectionForMarketing,
} = this.props;

const styles = this.getStyles();

return (
Expand Down Expand Up @@ -569,6 +597,23 @@ class OptinMetrics extends PureComponent {
? this.renderAction(action, i)
: this.renderLegacyAction(action, i),
)}
{isPastPrivacyPolicyDate ? (
<View style={styles.checkbox}>
<Checkbox
isChecked={isDataCollectionForMarketingEnabled}
accessibilityRole={'checkbox'}
accessible
onPress={() =>
setDataCollectionForMarketing(
!isDataCollectionForMarketingEnabled,
)
}
/>
<Text style={styles.content}>
{strings('privacy_policy.checkbox')}
</Text>
</View>
) : null}
{this.renderPrivacyPolicy()}
</View>
</ScrollView>
Expand All @@ -582,11 +627,15 @@ OptinMetrics.contextType = ThemeContext;

const mapStateToProps = (state) => ({
events: state.onboarding.events,
isDataCollectionForMarketingEnabled:
state.security.dataCollectionForMarketing,
});

const mapDispatchToProps = (dispatch) => ({
setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
clearOnboardingEvents: () => dispatch(clearOnboardingEvents()),
setDataCollectionForMarketing: (value) =>
dispatch(setDataCollectionForMarketing(value)),
});

export default connect(
Expand Down
Loading

0 comments on commit da288b5

Please sign in to comment.