Skip to content

Commit e2db41a

Browse files
authored
chore(nav): type Rewards nested navigator with NavigatorScreenParams (#33231)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> <!-- mms-check directive vocabulary — read by .github/scripts/shared/pr-template-checks.ts at module load to build the validation plan. Directives are invisible in rendered markdown and must NOT be removed or edited without updating the validator registry. type=text Section must contain non-placeholder prose. type=changelog Section must have a valid CHANGELOG entry: line. type=issue-link Section must have a Fixes:/Closes:/Refs: line with a value. type=manual-testing Section must have real testing steps or an explicit N/A. type=screenshot Section must have evidence (image/URL) or an explicit N/A. type=checklist Section must have all checkboxes consciously checked. required=true|false Whether a missing/invalid section runs the validator at all. blocking=true|false Whether a failure of this check fails the CI workflow. Default: false — failures are shown as warnings in the sticky comment but do not block the PR. Sections without a directive are checked for structural presence only. --> ## **Description** <!-- mms-check: type=text required=true --> <!-- 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? --> - Introduce `RewardsStackParamList` for screens inside `RewardsNavigator`. - Keep `RewardsNavigationParamList` as the feature-level type (stack + `RewardsSelectSheet` + root `RewardsFlow` entry). - Update `RootStackParamList.RewardsFlow` to `NavigatorScreenParams<RewardsStackParamList> | undefined`. - Wire `RewardsNavigator` to `RewardsStackParamList` and tighten `navigateToRewardsRoute` generics. ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry:null ## **Related issues** <!-- mms-check: type=issue-link required=true --> Fixes: https://consensyssoftware.atlassian.net/browse/MCWP-674 ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> N/A ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> N/A ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] 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. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Type-only navigation refactor with no intended runtime behavior change; main follow-up is fixing any new TypeScript errors at call sites that pass invalid route names or params. > > **Overview** > Adds **compile-time typing** for Rewards nested navigation so stack screens, params, and the root `RewardsFlow` entry align with React Navigation’s nested navigator pattern. > > **`RewardsStackParamList`** is introduced for screens registered in `RewardsNavigator`, and **`RewardsNavigator`** is wired to `createNativeStackNavigator<RewardsStackParamList>()`. **`RewardsNavigationParamList`** becomes the feature-level map: stack routes plus `RewardsSelectSheet` and a typed **`RewardsFlow`** entry using `NavigatorScreenParams<RewardsStackParamList>`. > > On the app root, **`RootStackParamList.RewardsFlow`** is updated from loose `NestedNavigationParams` to **`NavigatorScreenParams<RewardsStackParamList>`**. **`navigateToRewardsRoute`** is generic over `keyof RewardsStackParamList` so `screen` and `params` are checked per route; campaign outcome toast navigation uses narrow casts where routes still come from config objects. The **`navigateToRewardsRoute`** unit test now asserts a typed campaign screen and `campaignId` params. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f267111. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 42e2c2b commit e2db41a

6 files changed

Lines changed: 44 additions & 17 deletions

File tree

app/components/UI/Rewards/RewardsNavigator.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ import useRewardsToast from './hooks/useRewardsToast';
4242
import { strings } from '../../../../locales/i18n';
4343
import PerpsTradingCampaignWinningView from './Views/PerpsTradingCampaignWinningView';
4444
import { getActiveRouteNameFromNavigationState } from './utils';
45+
import type { RewardsStackParamList } from './types/navigation';
4546

4647
let sessionNotificationsNudgeShown = false;
47-
const Stack = createNativeStackNavigator();
48+
const Stack = createNativeStackNavigator<RewardsStackParamList>();
4849

4950
const RewardsNavigator: React.FC = () => {
5051
const subscriptionId = useSelector(selectRewardsSubscriptionId);

app/components/UI/Rewards/hooks/useCampaignOutcomeToast.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { buildCampaignOutcomeToastCompositeKey } from '../../../../reducers/rewa
2323
import { selectRewardsSubscriptionId } from '../../../../selectors/rewards';
2424
import { navigateToRewardsRoute } from '../utils';
2525
import useRewardsToast from './useRewardsToast';
26+
import type { RewardsStackParamList } from '../types/navigation';
2627

2728
export interface CampaignOutcomeToastConfig {
2829
campaignType: CampaignType;
@@ -120,8 +121,8 @@ export function useCampaignOutcomeToast(
120121
: getNonWinnerNavigation(targetCampaign);
121122
navigateToRewardsRoute(
122123
navigation,
123-
nav.route,
124-
nav.params as Record<string, unknown>,
124+
nav.route as keyof RewardsStackParamList,
125+
nav.params as never,
125126
);
126127
}, [
127128
variant,

app/components/UI/Rewards/types/navigation.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { NavigatorScreenParams } from '@react-navigation/native';
12
import type { RewardsSelectSheetParams } from '../components/RewardsSelectSheet';
23

34
export interface RewardsOndoCampaignDetailsParams {
@@ -81,8 +82,12 @@ export interface RewardsPredictThePitchCampaignStatsParams {
8182
campaignId: string;
8283
}
8384

84-
/** Param list for screens registered in RewardsNavigator. */
85-
export interface RewardsNavigationParamList {
85+
/**
86+
* Param list for screens registered in `RewardsNavigator`.
87+
*/
88+
// ParamListBase requires `type`; `interface` cannot satisfy it.
89+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
90+
export type RewardsStackParamList = {
8691
ReferralRewardsView: undefined;
8792
RewardsSettingsView: undefined;
8893
RewardsVipSplashView: undefined;
@@ -116,5 +121,15 @@ export interface RewardsNavigationParamList {
116121
RewardsPredictThePitchCampaignPortfolioView: RewardsPredictThePitchCampaignPortfolioParams;
117122
RewardsPredictThePitchCampaignWinning: RewardsPredictThePitchCampaignWinningParams;
118123
RewardsPredictThePitchCampaignStats: RewardsPredictThePitchCampaignStatsParams;
124+
};
125+
126+
/**
127+
* Feature-level Rewards navigation params: stack screens, flat modal sheet,
128+
* and the typed `RewardsFlow` entry for cross-stack navigation.
129+
*/
130+
// Intersection (`&`) requires `type`; `interface` cannot express this.
131+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
132+
export type RewardsNavigationParamList = RewardsStackParamList & {
119133
RewardsSelectSheet: RewardsSelectSheetParams;
120-
}
134+
RewardsFlow: NavigatorScreenParams<RewardsStackParamList> | undefined;
135+
};

app/components/UI/Rewards/utils.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,15 @@ describe('Rewards Utils', () => {
255255

256256
navigateToRewardsRoute(
257257
{ navigate: mockNavigate },
258-
'RewardsSettingsView',
258+
'RewardsCampaignMechanics',
259259
{
260-
foo: 'bar',
260+
campaignId: 'campaign-1',
261261
},
262262
);
263263

264264
expect(mockNavigate).toHaveBeenCalledWith(Routes.REWARDS_FLOW, {
265-
screen: 'RewardsSettingsView',
266-
params: { foo: 'bar' },
265+
screen: 'RewardsCampaignMechanics',
266+
params: { campaignId: 'campaign-1' },
267267
});
268268
});
269269
});

app/components/UI/Rewards/utils.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import { InternalAccount } from '@metamask/keyring-internal-api';
99
import Logger from '../../../util/Logger';
1010
import { strings } from '../../../../locales/i18n';
1111
import { isEvmAccountType } from '@metamask/keyring-api';
12-
import type { NavigationProp } from '@react-navigation/native';
12+
import type {
13+
NavigationProp,
14+
NavigatorScreenParams,
15+
} from '@react-navigation/native';
1316
import { isSolanaAccount } from '../../../core/Multichain/utils';
1417
import { getAddressAccountType } from '../../../util/address';
1518
import Routes from '../../../constants/navigation/Routes';
19+
import type { RewardsStackParamList } from './types/navigation';
1620

1721
// Initialize dayjs with relativeTime plugin
1822
dayjs.extend(relativeTime);
@@ -178,12 +182,15 @@ export const getActiveRouteNameFromNavigationState = (
178182
* @param screen - The destination route name inside the rewards flow.
179183
* @param params - Optional params forwarded to the destination screen.
180184
*/
181-
export const navigateToRewardsRoute = (
185+
export const navigateToRewardsRoute = <S extends keyof RewardsStackParamList>(
182186
navigation: Pick<NavigationProp<ReactNavigation.RootParamList>, 'navigate'>,
183-
screen: string,
184-
params?: Record<string, unknown>,
187+
screen: S,
188+
params?: RewardsStackParamList[S],
185189
): void => {
186-
navigation.navigate(Routes.REWARDS_FLOW, { screen, params });
190+
navigation.navigate(Routes.REWARDS_FLOW, {
191+
screen,
192+
params,
193+
} as NavigatorScreenParams<RewardsStackParamList>);
187194
};
188195

189196
type RewardsFlowExitNavigation = Pick<

app/core/NavigationService/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,10 @@ import type {
261261

262262
// Rewards params
263263
import { BenefitFullViewRouteParams } from '../../components/UI/Rewards/Views/BenefitFullView.types.ts';
264-
import type { RewardsNavigationParamList } from '../../components/UI/Rewards/types/navigation';
264+
import type {
265+
RewardsNavigationParamList,
266+
RewardsStackParamList,
267+
} from '../../components/UI/Rewards/types/navigation';
265268

266269
// Webview params
267270
import type {
@@ -480,7 +483,7 @@ export type RootStackParamList = {
480483
TransactionDetails: TransactionDetailsParams | undefined;
481484
ActivityDetails: ActivityDetailsParams;
482485
RewardsView: undefined;
483-
RewardsFlow: NestedNavigationParams | undefined;
486+
RewardsFlow: NavigatorScreenParams<RewardsStackParamList> | undefined;
484487
ReferralRewardsView: undefined;
485488
RewardsSettingsView: undefined;
486489
RewardsDashboard: undefined;

0 commit comments

Comments
 (0)