Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3feccf9
added pna-25 banner
NidhiKJha Nov 21, 2025
85cc83d
added pna25 banner to use boolean logic
NidhiKJha Nov 24, 2025
26e9ed2
updated selector
NidhiKJha Nov 24, 2025
1bfb72b
show banner
NidhiKJha Nov 24, 2025
82adc6d
nit fix
NidhiKJha Nov 24, 2025
330dfdb
locale update
NidhiKJha Nov 24, 2025
d270e72
updated metametrics link
NidhiKJha Nov 24, 2025
3ff5436
nit fix
NidhiKJha Nov 25, 2025
8e6603e
jest update
NidhiKJha Nov 25, 2025
82a81c8
added sentry state
NidhiKJha Nov 25, 2025
26821c2
sentry update
NidhiKJha Nov 25, 2025
c787954
sentry update
NidhiKJha Nov 25, 2025
ba6bbab
lint fix
NidhiKJha Nov 25, 2025
79d3892
nit fix
NidhiKJha Nov 25, 2025
cf8d1dd
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 25, 2025
9aa85c1
metrics update
NidhiKJha Nov 25, 2025
eed9b2a
lint fix
NidhiKJha Nov 25, 2025
cc388b3
lint fix
NidhiKJha Nov 25, 2025
f3a4bda
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 26, 2025
8450372
lint fix
NidhiKJha Nov 26, 2025
2386bc4
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 26, 2025
58a3457
updated state to be boolea
NidhiKJha Nov 26, 2025
4bfd97d
lint fix
NidhiKJha Nov 26, 2025
e6758fc
lint fix
NidhiKJha Nov 26, 2025
971c1d0
added onboarding
NidhiKJha Nov 26, 2025
7b4cf07
added build flag
NidhiKJha Nov 26, 2025
a23ac5d
added pna banner
NidhiKJha Nov 26, 2025
af8f50e
removed unused code
NidhiKJha Nov 27, 2025
395951e
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 27, 2025
1252cb7
nit fix
NidhiKJha Nov 27, 2025
b70dd38
updated welcome logic
NidhiKJha Nov 27, 2025
7af809a
lint fix
NidhiKJha Nov 27, 2025
5236ca2
added metrics logic to settings
NidhiKJha Nov 27, 2025
ca876f7
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 27, 2025
7a157ea
updated sentry state
NidhiKJha Nov 27, 2025
bf7d334
Merge branch 'main' into CEUX-713-pna-25
NidhiKJha Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions app/_locales/en_GB/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions app/scripts/controllers/app-state-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ describe('AppStateController', () => {
"outdatedBrowserWarningLastShown": null,
"pendingShieldCohort": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"popupGasPollTokens": [],
"productTour": "accountIcon",
"recoveryPhraseReminderHasBeenShown": false,
Expand Down Expand Up @@ -883,6 +884,7 @@ describe('AppStateController', () => {
"outdatedBrowserWarningLastShown": null,
"pendingShieldCohort": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"popupGasPollTokens": [],
"productTour": "accountIcon",
"recoveryPhraseReminderHasBeenShown": false,
Expand Down Expand Up @@ -963,6 +965,7 @@ describe('AppStateController', () => {
"onboardingDate": null,
"outdatedBrowserWarningLastShown": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"productTour": "accountIcon",
"recoveryPhraseReminderHasBeenShown": false,
"recoveryPhraseReminderLastShown": 1000,
Expand Down Expand Up @@ -1053,6 +1056,7 @@ describe('AppStateController', () => {
"outdatedBrowserWarningLastShown": null,
"pendingShieldCohort": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"popupGasPollTokens": [],
"productTour": "accountIcon",
"recoveryPhraseReminderHasBeenShown": false,
Expand Down
14 changes: 14 additions & 0 deletions app/scripts/controllers/app-state-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export type AppStateControllerState = {
networkConnectionBanner: NetworkConnectionBanner;
newPrivacyPolicyToastClickedOrClosed: boolean | null;
newPrivacyPolicyToastShownDate: number | null;
pna25Acknowledged: boolean | null;
nftsDetectionNoticeDismissed: boolean;
nftsDropdownState: Json;
notificationGasPollTokens: string[];
Expand Down Expand Up @@ -269,6 +270,7 @@ const getDefaultAppStateControllerState = (): AppStateControllerState => ({
lastViewedUserSurvey: null,
newPrivacyPolicyToastClickedOrClosed: null,
newPrivacyPolicyToastShownDate: null,
pna25Acknowledged: null,
nftsDetectionNoticeDismissed: false,
notificationGasPollTokens: [],
onboardingDate: null,
Expand Down Expand Up @@ -466,6 +468,12 @@ const controllerMetadata: StateMetadata<AppStateControllerState> = {
includeInDebugSnapshot: true,
usedInUi: true,
},
pna25Acknowledged: {
includeInStateLogs: true,
persist: true,
includeInDebugSnapshot: true,
usedInUi: true,
},
nftsDetectionNoticeDismissed: {
includeInStateLogs: true,
persist: true,
Expand Down Expand Up @@ -874,6 +882,12 @@ export class AppStateController extends BaseController<
});
}

setPna25Acknowledged(acknowledged: boolean): void {
this.update((state) => {
state.pna25Acknowledged = acknowledged;
});
}

setShieldPausedToastLastClickedOrClosed(time: number): void {
this.update((state) => {
state.shieldPausedToastLastClickedOrClosed = time;
Expand Down
4 changes: 4 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,10 @@
appStateController.setShieldEndingToastLastClickedOrClosed.bind(
appStateController,
),
setPna25Acknowledged:
appStateController.setPna25Acknowledged.bind(

Check failure on line 2959 in app/scripts/metamask-controller.js

View workflow job for this annotation

GitHub Actions / test-lint / Test lint

Replace `⏎··········appStateController,⏎········` with `appStateController`
appStateController,
),
setAppActiveTab:
appStateController.setAppActiveTab.bind(appStateController),
setDefaultSubscriptionPaymentOptions:
Expand Down
2 changes: 2 additions & 0 deletions shared/lib/ui-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const SUPPORT_LINK = process.env.SUPPORT_LINK;
export const COINGECKO_LINK = 'https://www.coingecko.com/';
export const CRYPTOCOMPARE_LINK = 'https://www.cryptocompare.com/';
export const PRIVACY_POLICY_LINK = 'https://consensys.io/privacy-policy/';
export const METAMETRICS_SETTINGS_LINK =
'https://support.metamask.io/configure/privacy/how-to-manage-your-metametrics-settings/';
export const SURVEY_LINK = 'https://www.getfeedback.com/r/Oczu1vP0';

// TODO make sure these links are correct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"outdatedBrowserWarningLastShown": "object",
"pendingShieldCohort": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"popupGasPollTokens": "object",
"productTour": "accountIcon",
"recoveryPhraseReminderHasBeenShown": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"showShieldEntryModalOnce": "boolean",
"pendingShieldCohort": null,
"pendingShieldCohortTxType": null,
"pna25Acknowledged": null,
"appActiveTab": "object"
},
"BridgeController": {},
Expand Down
48 changes: 48 additions & 0 deletions ui/components/app/toast-master/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type State = {
| 'surveyLinkLastClickedOrClosed'
| 'shieldEndingToastLastClickedOrClosed'
| 'shieldPausedToastLastClickedOrClosed'
| 'participateInMetaMetrics'
| 'remoteFeatureFlags'
| 'pna25Acknowledged'
| 'completedOnboarding'
>
>;
};
Expand Down Expand Up @@ -234,3 +238,47 @@ export function selectShowShieldEndingToast(
): boolean {
return !state.metamask.shieldEndingToastLastClickedOrClosed;
}

/**
* Determines if the PNA25 banner should be shown based on:
* - User has completed onboarding (completedOnboarding === true)
* - LaunchDarkly feature flag (extension-ux-pna25) is enabled (boolean)
* - User has opted into metrics (participateInMetaMetrics === true)
* - User is an EXISTING user (pna25Acknowledged === null)
* New users will have pna25Acknowledged = true (opted in) or false (opted out)
* Existing users will have pna25Acknowledged = null (state didn't exist when they onboarded)
*
* @param state - The application state containing the banner data.
* @returns Boolean indicating whether to show the banner
*/
export function selectShowPna25Banner(state: Pick<State, 'metamask'>): boolean {
const {
completedOnboarding,
participateInMetaMetrics,
pna25Acknowledged,
remoteFeatureFlags,
} = state.metamask || {};

// Only show to users who have completed onboarding
if (!completedOnboarding) {
return false; // User hasn't completed onboarding yet
}

const isPna25Enabled = remoteFeatureFlags?.['extension-ux-pna25'];

// Check all conditions
if (!isPna25Enabled) {
return false; // LD flag not enabled
}

if (participateInMetaMetrics !== true) {
return false; // User hasn't opted into metrics
}

if (pna25Acknowledged === true) {
return false; // User already acknowledged
}

// Show banner only for existing users who opted in before this feature
return true;
}
40 changes: 40 additions & 0 deletions ui/components/app/toast-master/toast-master.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MILLISECOND, SECOND } from '../../../../shared/constants/time';
import {
PRIVACY_POLICY_LINK,
SURVEY_LINK,
METAMETRICS_SETTINGS_LINK,
} from '../../../../shared/lib/ui-utils';
import {
BorderColor,
Expand Down Expand Up @@ -94,6 +95,7 @@ import {
selectClaimSubmitToast,
selectShowShieldPausedToast,
selectShowShieldEndingToast,
selectShowPna25Banner,
} from './selectors';
import {
setNewPrivacyPolicyToastClickedOrClosed,
Expand All @@ -106,6 +108,7 @@ import {
setShowClaimSubmitToast,
setShieldPausedToastLastClickedOrClosed,
setShieldEndingToastLastClickedOrClosed,
setPna25Acknowledged,
} from './utils';

export function ToastMaster({ location } = {}) {
Expand All @@ -129,6 +132,7 @@ export function ToastMaster({ location } = {}) {
)}
<SurveyToastMayDelete />
<PrivacyPolicyToast />
<Pna25Banner />
<NftEnablementToast />
<PermittedNetworkToast />
<NewSrpAddedToast />
Expand Down Expand Up @@ -749,3 +753,39 @@ function ShieldEndingToast() {
)
);
}

function Pna25Banner() {
const t = useI18nContext();

const showPna25Banner = useSelector(selectShowPna25Banner);

const handleLearnMore = () => {
// Open MetaMetrics settings help page and acknowledge
global.platform.openTab({
url: METAMETRICS_SETTINGS_LINK,
});
setPna25Acknowledged(true);
};

const handleClose = () => {
// Just acknowledge without opening link
setPna25Acknowledged(true);
};

return (
showPna25Banner && (
<Toast
key="pna25-banner"
dataTestId="pna25-banner"
startAdornment={
<Icon name={IconName.Info} color={IconColor.infoDefault} />
}
text={t('pna25BannerTitle')}
textVariant={TextVariant.bodySm}
actionText={t('learnMoreUpperCase')}
onActionClick={handleLearnMore}
onClose={handleClose}
/>
)
);
}
4 changes: 4 additions & 0 deletions ui/components/app/toast-master/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,7 @@ export function setShieldEndingToastLastClickedOrClosed(time: number) {
time,
]);
}

export function setPna25Acknowledged(acknowledged: boolean) {
submitRequestToBackgroundAndCatch('setPna25Acknowledged', [acknowledged]);
}
18 changes: 17 additions & 1 deletion ui/pages/onboarding-flow/metametrics/metametrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getIsParticipateInMetaMetricsSet,
getParticipateInMetaMetrics,
} from '../../../selectors';
import { getRemoteFeatureFlags } from '../../../selectors/remote-feature-flags';

import {
MetaMetricsEventCategory,
Expand All @@ -50,6 +51,7 @@ import {
} from '../../../components/component-library';
import { FirstTimeFlowType } from '../../../../shared/constants/onboarding';
import { getBrowserName } from '../../../../shared/modules/browser-runtime.utils';
import { submitRequestToBackgroundAndCatch } from '../../../components/app/toast-master/utils';

const isFirefox = getBrowserName() === PLATFORM_FIREFOX;

Expand All @@ -65,6 +67,11 @@ export default function OnboardingMetametrics() {
);
const participateInMetaMetrics = useSelector(getParticipateInMetaMetrics);
const dataCollectionForMarketing = useSelector(getDataCollectionForMarketing);
const remoteFeatureFlags = useSelector(getRemoteFeatureFlags);

// Check if the PNA25 feature is enabled
// extension-ux-pna25 is a boolean LaunchDarkly flag
const isPna25Enabled = remoteFeatureFlags?.extensionUxPna25;

const [
isParticipateInMetaMetricsChecked,
Expand Down Expand Up @@ -135,6 +142,13 @@ export default function OnboardingMetametrics() {
dispatch(setParticipateInMetaMetrics(false));
dispatch(setDataCollectionForMarketing(false));
}

// If LD flag is enabled, set pna25Acknowledged to true
// This means they saw the updated policy during onboarding (whether they opted in or out)
// No need to show banner to new users who already saw the updated message
if (isPna25Enabled) {
await submitRequestToBackgroundAndCatch('setPna25Acknowledged', [true]);
}
} catch (error) {
log.error('onConfirm::error', error);
} finally {
Expand Down Expand Up @@ -229,7 +243,9 @@ export default function OnboardingMetametrics() {
color={TextColor.textAlternative}
textAlign={TextAlign.Left}
>
{t('onboardingMetametricCheckboxDescriptionOne')}
{isPna25Enabled
? t('onboardingMetametricCheckboxDescriptionOneUpdated')
: t('onboardingMetametricCheckboxDescriptionOne')}
</Text>
</Box>

Expand Down
Loading