Skip to content

Commit 192cb48

Browse files
chore(runway): cherry-pick c23defd (#21826)
- chore: move user from redux store to card sdk context (#21582) <!-- 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? --> ## **Changelog** <!-- 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** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [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 - [x] 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** - [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] > Move user management to the Card SDK context, update onboarding components/hooks and navigator to use it, remove user from the Redux slice, and adjust tests accordingly. > > - **SDK**: > - Add `user` and `setUser` to Card SDK context; fetch user on mount via `getRegistrationStatus(onboardingId)`; clear user on logout. > - **Onboarding UI**: > - Switch `ConfirmPhoneNumber`, `PersonalDetails`, `PhysicalAddress`, and `VerifyIdentity` to use `useCardSDK` (`user`/`setUser`) instead of Redux. > - Update error/navigation handling accordingly. > - **Navigation**: > - `OnboardingNavigator` initial route now derived from SDK `user` + `onboardingId`. > - **Hooks**: > - `useUserRegistrationStatus` writes to SDK `user` and no longer uses Redux `setUser`/`userResponse`. > - Tests for hooks updated to mock `useCardSDK` with `user`/`setUser`. > - **Redux**: > - Remove `onboarding.user`, its selectors/actions; keep `onboardingId`, `selectedCountry`, `contactVerificationId`. > - **Tests**: > - Broad test updates across components/hooks to reflect SDK-based user state. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3c428bc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [c23defd](c23defd) Co-authored-by: sophieqgu <[email protected]>
1 parent 7b432cb commit 192cb48

31 files changed

+544
-219
lines changed

app/components/UI/Card/components/Onboarding/ConfirmPhoneNumber.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ jest.mock('../../../../../../locales/i18n', () => ({
273273
// Mock hooks
274274
const mockUsePhoneVerificationVerify = jest.fn();
275275
const mockUsePhoneVerificationSend = jest.fn();
276+
const mockSetUser = jest.fn();
276277

277278
jest.mock('../../hooks/usePhoneVerificationVerify', () => ({
278279
__esModule: true,
@@ -284,6 +285,17 @@ jest.mock('../../hooks/usePhoneVerificationSend', () => ({
284285
default: () => mockUsePhoneVerificationSend(),
285286
}));
286287

288+
// Mock SDK
289+
jest.mock('../../sdk', () => ({
290+
useCardSDK: jest.fn(() => ({
291+
sdk: {},
292+
isLoading: false,
293+
user: { id: 'user-123', email: '[email protected]' },
294+
setUser: mockSetUser,
295+
logoutFromProvider: jest.fn(),
296+
})),
297+
}));
298+
287299
// Create test store
288300
const createTestStore = (initialState = {}) =>
289301
configureStore({

app/components/UI/Card/components/Onboarding/ConfirmPhoneNumber.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import usePhoneVerificationVerify from '../../hooks/usePhoneVerificationVerify';
2323
import {
2424
selectContactVerificationId,
2525
selectOnboardingId,
26-
setUser,
2726
} from '../../../../../core/redux/slices/card';
28-
import { useDispatch, useSelector } from 'react-redux';
27+
import { useSelector } from 'react-redux';
2928
import { CardError } from '../../types';
3029
import usePhoneVerificationSend from '../../hooks/usePhoneVerificationSend';
30+
import { useCardSDK } from '../../sdk';
3131

3232
const CELL_COUNT = 6;
3333

@@ -61,7 +61,7 @@ const createStyles = (params: { theme: Theme }) => {
6161

6262
const ConfirmPhoneNumber = () => {
6363
const navigation = useNavigation();
64-
const dispatch = useDispatch();
64+
const { setUser } = useCardSDK();
6565
const { styles } = useStyles(createStyles, {});
6666
const inputRef = useRef<TextInput>(null);
6767
const [resendCooldown, setResendCooldown] = useState(0);
@@ -113,7 +113,7 @@ const ConfirmPhoneNumber = () => {
113113
contactVerificationId,
114114
});
115115
if (user) {
116-
dispatch(setUser(user));
116+
setUser(user);
117117
navigation.navigate(Routes.CARD.ONBOARDING.VERIFY_IDENTITY);
118118
}
119119
} catch (error) {
@@ -139,7 +139,7 @@ const ConfirmPhoneNumber = () => {
139139
phoneCountryCode,
140140
contactVerificationId,
141141
verifyPhoneVerification,
142-
dispatch,
142+
setUser,
143143
navigation,
144144
]);
145145

app/components/UI/Card/components/Onboarding/MailingAddress.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { configureStore } from '@reduxjs/toolkit';
77
import MailingAddress from './MailingAddress';
88
import useRegisterMailingAddress from '../../hooks/useRegisterMailingAddress';
99
import useRegistrationSettings from '../../hooks/useRegistrationSettings';
10+
import { useCardSDK } from '../../sdk';
1011

1112
// Mock navigation
1213
jest.mock('@react-navigation/native', () => ({
@@ -17,6 +18,7 @@ jest.mock('@react-navigation/native', () => ({
1718
jest.mock('../../hooks/useRegisterMailingAddress');
1819
jest.mock('../../hooks/useRegisterUserConsent');
1920
jest.mock('../../hooks/useRegistrationSettings');
21+
jest.mock('../../sdk');
2022

2123
// Mock OnboardingStep component
2224
jest.mock('./OnboardingStep', () => {
@@ -358,6 +360,8 @@ const mockUseRegistrationSettings =
358360
useRegistrationSettings as jest.MockedFunction<
359361
typeof useRegistrationSettings
360362
>;
363+
const mockSetUser = jest.fn();
364+
const mockUseCardSDK = useCardSDK as jest.MockedFunction<typeof useCardSDK>;
361365

362366
describe('MailingAddress Component', () => {
363367
let store: ReturnType<typeof createTestStore>;
@@ -441,6 +445,18 @@ describe('MailingAddress Component', () => {
441445
fetchData: jest.fn(),
442446
});
443447

448+
// Mock useCardSDK
449+
mockUseCardSDK.mockReturnValue({
450+
sdk: null,
451+
isLoading: false,
452+
user: {
453+
id: 'user-id',
454+
455+
},
456+
setUser: mockSetUser,
457+
logoutFromProvider: jest.fn(),
458+
});
459+
444460
// Mock useSelector
445461
const { useSelector } = jest.requireMock('react-redux');
446462
useSelector.mockImplementation((selector: any) =>

app/components/UI/Card/components/Onboarding/MailingAddress.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ import { AddressFields } from './PhysicalAddress';
1212
import {
1313
selectOnboardingId,
1414
selectSelectedCountry,
15-
setUser,
1615
} from '../../../../../core/redux/slices/card';
17-
import { useDispatch, useSelector } from 'react-redux';
16+
import { useSelector } from 'react-redux';
1817
import useRegisterMailingAddress from '../../hooks/useRegisterMailingAddress';
1918
import { Box, Text, TextVariant } from '@metamask/design-system-react-native';
2019
import { CardError } from '../../types';
20+
import { useCardSDK } from '../../sdk';
2121

2222
const MailingAddress = () => {
2323
const navigation = useNavigation();
24-
const dispatch = useDispatch();
24+
const { setUser } = useCardSDK();
2525
const onboardingId = useSelector(selectOnboardingId);
2626
const selectedCountry = useSelector(selectSelectedCountry);
2727

@@ -122,7 +122,7 @@ const MailingAddress = () => {
122122
});
123123

124124
if (updatedUser) {
125-
dispatch(setUser(updatedUser));
125+
setUser(updatedUser);
126126
}
127127

128128
if (accessToken) {

app/components/UI/Card/components/Onboarding/PersonalDetails.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ jest.mock('../../hooks/useRegistrationSettings', () => ({
241241
default: jest.fn(),
242242
}));
243243

244+
jest.mock('../../sdk', () => ({
245+
useCardSDK: jest.fn(),
246+
}));
247+
244248
jest.mock('../../../../../../locales/i18n', () => ({
245249
strings: jest.fn((key: string) => {
246250
const mockStrings: Record<string, string> = {
@@ -272,11 +276,13 @@ import { useDispatch, useSelector } from 'react-redux';
272276
import PersonalDetails from './PersonalDetails';
273277
import useRegisterPersonalDetails from '../../hooks/useRegisterPersonalDetails';
274278
import useRegistrationSettings from '../../hooks/useRegistrationSettings';
279+
import { useCardSDK } from '../../sdk';
275280

276281
// Mock implementations
277282
const mockNavigate = jest.fn();
278283
const mockDispatch = jest.fn();
279284
const mockRegisterPersonalDetails = jest.fn();
285+
const mockSetUser = jest.fn();
280286

281287
// Mock hooks
282288
(useNavigation as jest.Mock).mockReturnValue({
@@ -314,6 +320,14 @@ const mockRegisterPersonalDetails = jest.fn();
314320
},
315321
});
316322

323+
(useCardSDK as jest.Mock).mockReturnValue({
324+
sdk: null,
325+
isLoading: false,
326+
user: null,
327+
setUser: mockSetUser,
328+
logoutFromProvider: jest.fn(),
329+
});
330+
317331
describe('PersonalDetails Component', () => {
318332
beforeEach(() => {
319333
jest.clearAllMocks();

app/components/UI/Card/components/Onboarding/PersonalDetails.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ import { useDebouncedValue } from '../../../../hooks/useDebouncedValue';
1818
import {
1919
selectOnboardingId,
2020
selectSelectedCountry,
21-
setUser,
2221
} from '../../../../../core/redux/slices/card';
23-
import { useDispatch, useSelector } from 'react-redux';
22+
import { useSelector } from 'react-redux';
2423
import SelectComponent from '../../../SelectComponent';
2524
import useRegisterPersonalDetails from '../../hooks/useRegisterPersonalDetails';
2625
import useRegistrationSettings from '../../hooks/useRegistrationSettings';
@@ -29,10 +28,11 @@ import {
2928
validateDateOfBirth,
3029
} from '../../util/validateDateOfBirth';
3130
import { CardError } from '../../types';
31+
import { useCardSDK } from '../../sdk';
3232

3333
const PersonalDetails = () => {
3434
const navigation = useNavigation();
35-
const dispatch = useDispatch();
35+
const { setUser } = useCardSDK();
3636
const onboardingId = useSelector(selectOnboardingId);
3737
const selectedCountry = useSelector(selectSelectedCountry);
3838

@@ -152,7 +152,7 @@ const PersonalDetails = () => {
152152
});
153153

154154
if (user) {
155-
dispatch(setUser(user));
155+
setUser(user);
156156
navigation.navigate(Routes.CARD.ONBOARDING.PHYSICAL_ADDRESS);
157157
}
158158
} catch (error) {

app/components/UI/Card/components/Onboarding/PhysicalAddress.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Routes from '../../../../../constants/navigation/Routes';
99
import useRegisterPhysicalAddress from '../../hooks/useRegisterPhysicalAddress';
1010
import useRegisterUserConsent from '../../hooks/useRegisterUserConsent';
1111
import useRegistrationSettings from '../../hooks/useRegistrationSettings';
12+
import { useCardSDK } from '../../sdk';
1213

1314
// Mock navigation
1415
jest.mock('@react-navigation/native', () => ({
@@ -20,6 +21,11 @@ jest.mock('../../hooks/useRegisterPhysicalAddress');
2021
jest.mock('../../hooks/useRegisterUserConsent');
2122
jest.mock('../../hooks/useRegistrationSettings');
2223

24+
// Mock SDK
25+
jest.mock('../../sdk', () => ({
26+
useCardSDK: jest.fn(),
27+
}));
28+
2329
// Mock OnboardingStep component
2430
jest.mock('./OnboardingStep', () => {
2531
const React = jest.requireActual('react');
@@ -362,6 +368,7 @@ const mockUseRegistrationSettings =
362368
useRegistrationSettings as jest.MockedFunction<
363369
typeof useRegistrationSettings
364370
>;
371+
const mockUseCardSDK = useCardSDK as jest.MockedFunction<typeof useCardSDK>;
365372

366373
describe('PhysicalAddress Component', () => {
367374
let store: ReturnType<typeof createTestStore>;
@@ -457,6 +464,18 @@ describe('PhysicalAddress Component', () => {
457464
fetchData: jest.fn(),
458465
});
459466

467+
// Mock useCardSDK
468+
mockUseCardSDK.mockReturnValue({
469+
sdk: null,
470+
isLoading: false,
471+
user: {
472+
id: 'user-id',
473+
474+
},
475+
setUser: jest.fn(),
476+
logoutFromProvider: jest.fn(),
477+
});
478+
460479
// Mock useSelector
461480
const { useSelector } = jest.requireMock('react-redux');
462481
useSelector.mockImplementation((selector: any) =>

app/components/UI/Card/components/Onboarding/PhysicalAddress.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ import OnboardingStep from './OnboardingStep';
1616
import Checkbox from '../../../../../component-library/components/Checkbox';
1717
import { useTailwind } from '@metamask/design-system-twrnc-preset';
1818
import useRegisterPhysicalAddress from '../../hooks/useRegisterPhysicalAddress';
19-
import { useDispatch, useSelector } from 'react-redux';
19+
import { useSelector } from 'react-redux';
2020
import {
2121
selectOnboardingId,
2222
selectSelectedCountry,
23-
selectUser,
24-
setUser,
2523
} from '../../../../../core/redux/slices/card';
2624
import useRegisterUserConsent from '../../hooks/useRegisterUserConsent';
2725
import { CardError } from '../../types';
2826
import useRegistrationSettings from '../../hooks/useRegistrationSettings';
2927
import SelectComponent from '../../../SelectComponent';
28+
import { useCardSDK } from '../../sdk';
3029

3130
export const AddressFields = ({
3231
addressLine1,
@@ -182,11 +181,10 @@ export const AddressFields = ({
182181

183182
const PhysicalAddress = () => {
184183
const navigation = useNavigation();
185-
const dispatch = useDispatch();
186184
const tw = useTailwind();
185+
const { user, setUser } = useCardSDK();
187186
const onboardingId = useSelector(selectOnboardingId);
188187
const selectedCountry = useSelector(selectSelectedCountry);
189-
const user = useSelector(selectUser);
190188

191189
const [addressLine1, setAddressLine1] = useState('');
192190
const [addressLine2, setAddressLine2] = useState('');
@@ -318,7 +316,7 @@ const PhysicalAddress = () => {
318316
});
319317

320318
if (updatedUser) {
321-
dispatch(setUser(updatedUser));
319+
setUser(updatedUser);
322320
}
323321

324322
if (accessToken) {

app/components/UI/Card/components/Onboarding/VerifyIdentity.test.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useNavigation } from '@react-navigation/native';
66
import VerifyIdentity from './VerifyIdentity';
77
import Routes from '../../../../../constants/navigation/Routes';
88
import useStartVerification from '../../hooks/useStartVerification';
9+
import { useCardSDK } from '../../sdk';
910

1011
// Mock dependencies
1112
jest.mock('@react-navigation/native', () => ({
@@ -15,6 +16,11 @@ jest.mock('@react-navigation/native', () => ({
1516
// Mock useStartVerification hook
1617
jest.mock('../../hooks/useStartVerification');
1718

19+
// Mock useCardSDK hook
20+
jest.mock('../../sdk', () => ({
21+
useCardSDK: jest.fn(),
22+
}));
23+
1824
// Mock OnboardingStep component
1925
jest.mock('./OnboardingStep', () => {
2026
const React = jest.requireActual('react');
@@ -191,6 +197,14 @@ describe('VerifyIdentity Component', () => {
191197
error: null,
192198
});
193199

200+
(useCardSDK as jest.Mock).mockReturnValue({
201+
sdk: null,
202+
isLoading: false,
203+
user: null,
204+
setUser: jest.fn(),
205+
logoutFromProvider: jest.fn(),
206+
});
207+
194208
store = createTestStore();
195209
});
196210

@@ -351,12 +365,16 @@ describe('VerifyIdentity Component', () => {
351365

352366
describe('User State Testing', () => {
353367
it('navigates to validating KYC when user verification state is PENDING', async () => {
354-
const storeWithPendingUser = createTestStore({
368+
(useCardSDK as jest.Mock).mockReturnValue({
369+
sdk: null,
370+
isLoading: false,
355371
user: { verificationState: 'PENDING' },
372+
setUser: jest.fn(),
373+
logoutFromProvider: jest.fn(),
356374
});
357375

358376
render(
359-
<Provider store={storeWithPendingUser}>
377+
<Provider store={store}>
360378
<VerifyIdentity />
361379
</Provider>,
362380
);
@@ -369,12 +387,16 @@ describe('VerifyIdentity Component', () => {
369387
});
370388

371389
it('does not auto-navigate when user verification state is not PENDING', () => {
372-
const storeWithNonPendingUser = createTestStore({
390+
(useCardSDK as jest.Mock).mockReturnValue({
391+
sdk: null,
392+
isLoading: false,
373393
user: { verificationState: 'VERIFIED' },
394+
setUser: jest.fn(),
395+
logoutFromProvider: jest.fn(),
374396
});
375397

376398
render(
377-
<Provider store={storeWithNonPendingUser}>
399+
<Provider store={store}>
378400
<VerifyIdentity />
379401
</Provider>,
380402
);
@@ -383,12 +405,16 @@ describe('VerifyIdentity Component', () => {
383405
});
384406

385407
it('does not auto-navigate when user is null', () => {
386-
const storeWithNullUser = createTestStore({
408+
(useCardSDK as jest.Mock).mockReturnValue({
409+
sdk: null,
410+
isLoading: false,
387411
user: null,
412+
setUser: jest.fn(),
413+
logoutFromProvider: jest.fn(),
388414
});
389415

390416
render(
391-
<Provider store={storeWithNullUser}>
417+
<Provider store={store}>
392418
<VerifyIdentity />
393419
</Provider>,
394420
);

0 commit comments

Comments
 (0)