Skip to content

Commit f21cd1f

Browse files
runway-github[bot]Brunonascdevsophieqgu
authored
chore(runway): cherry-pick a172652 (#21831)
- feat(card): Card Onboarding edge cases and Onboarding token management (#21594) <!-- 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? --> This PR improves the Onboarding flow by adding coverage for several edge cases and refining the token management strategy for the onboarding token. These updates ensure more reliable handling of onboarding sessions and reduce potential authentication inconsistencies. ## **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: Added coverage for edge cases in the Onboarding flow CHANGELOG entry: Improved onboarding token management strategy CHANGELOG entry: Enhanced session reliability and authentication consistency ## **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** - [ ] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Improves card onboarding/login flows, adds robust auth error handling and access-token storage, and introduces utilities/tests for onboarding token management. > > - **Card Onboarding/Auth (UI + Flow)**: > - Redirect login to onboarding when verification is pending; store `onboardingId` in Redux. > - Confirm Email: navigates to auth when account exists and shows toast; resend cooldown; stricter validation. > - Physical/Mailing Address: on success store short-lived access token, set authenticated state/location, then continue; improved field handling and validation; minor UI tweak to area code selector width. > - **Card Home (Error Handling)**: > - Detect authentication errors, clear stored tokens and Redux auth state, and redirect to welcome; hide "Try again" on auth errors. > - **Auth/Session Management**: > - `cardTokenVault`: support access-only tokens (optional refresh token), validate accordingly. > - `handleLocalAuthentication`: handle access-only tokens (5‑min buffer), clear on expiry, refresh and persist full tokens; clearer logs. > - **SDK/Hook Updates**: > - `CardSDK.getCardDetails` throws `INVALID_CREDENTIALS` on 401; email/phone verification endpoints and auth flow refinements. > - `useCardProviderAuthentication`: treat OTP/pending/phase states as intermediate; proceed only after token exchange; set auth state/location. > - `useRegisterPersonalDetails`: cleanup and naming consistency. > - **Utilities**: > - Add `extractTokenExpiration` (default 5 hours) and `mapCountryToLocation` helpers. > - Add `isAuthenticationError` helper to classify auth failures. > - **Tests & i18n**: > - Comprehensive tests for Confirm Email, Mailing Address, auth handling, and token expiration/auth flows. > - Add/update locale strings for onboarding and KYC states. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e94d8c1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: sophieqgu <[email protected]> [a172652](a172652) Co-authored-by: Bruno Nascimento <[email protected]> Co-authored-by: sophieqgu <[email protected]>
1 parent 192cb48 commit f21cd1f

21 files changed

+2204
-102
lines changed

app/components/UI/Card/Views/CardAuthentication/CardAuthentication.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import {
5050
} from 'react-native-confirmation-code-field';
5151
import { useStyles } from '../../../../../component-library/hooks';
5252
import { Theme } from '../../../../../util/theme/models';
53+
import { useDispatch } from 'react-redux';
54+
import { setOnboardingId } from '../../../../../core/redux/slices/card';
5355

5456
const CELL_COUNT = 6;
5557

@@ -98,7 +100,7 @@ const CardAuthentication = () => {
98100
>(null);
99101
const [resendCountdown, setResendCountdown] = useState(60);
100102
const otpInputRef = useRef<TextInput>(null);
101-
103+
const dispatch = useDispatch();
102104
const theme = useTheme();
103105
const {
104106
login,
@@ -197,6 +199,16 @@ const CardAuthentication = () => {
197199
return;
198200
}
199201

202+
if (
203+
loginResponse?.verificationState === 'PENDING' ||
204+
loginResponse?.phase
205+
) {
206+
// Switch to OTP step instead of navigating
207+
dispatch(setOnboardingId(loginResponse.userId));
208+
navigation.navigate(Routes.CARD.ONBOARDING.ROOT);
209+
return;
210+
}
211+
200212
// Successful login - navigate to home
201213
navigation.reset({
202214
index: 0,
@@ -208,7 +220,7 @@ const CardAuthentication = () => {
208220
setLoading(false);
209221
}
210222
},
211-
[email, location, login, password, navigation],
223+
[email, location, login, password, navigation, dispatch],
212224
);
213225

214226
// Auto-submit when all OTP digits are entered

app/components/UI/Card/Views/CardHome/CardHome.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Text, {
1616
TextVariant,
1717
} from '../../../../../component-library/components/Texts/Text';
1818
import { useNavigation } from '@react-navigation/native';
19-
import { useSelector } from 'react-redux';
19+
import { useDispatch, useSelector } from 'react-redux';
2020
import SensitiveText, {
2121
SensitiveTextLength,
2222
} from '../../../../../component-library/components/Texts/SensitiveText';
@@ -54,10 +54,19 @@ import { useCardSDK } from '../../sdk';
5454
import Routes from '../../../../../constants/navigation/Routes';
5555
import useIsBaanxLoginEnabled from '../../hooks/isBaanxLoginEnabled';
5656
import useCardDetails from '../../hooks/useCardDetails';
57-
import { selectIsAuthenticatedCard } from '../../../../../core/redux/slices/card';
57+
import {
58+
selectIsAuthenticatedCard,
59+
setIsAuthenticatedCard,
60+
setAuthenticatedPriorityToken,
61+
setAuthenticatedPriorityTokenLastFetched,
62+
setUserCardLocation,
63+
} from '../../../../../core/redux/slices/card';
5864
import { useCardProvision } from '../../hooks/useCardProvision';
5965
import CardWarningBox from '../../components/CardWarningBox/CardWarningBox';
6066
import { useIsSwapEnabledForPriorityToken } from '../../hooks/useIsSwapEnabledForPriorityToken';
67+
import { isAuthenticationError } from '../../util/isAuthenticationError';
68+
import { removeCardBaanxToken } from '../../util/cardTokenVault';
69+
import Logger from '../../../../../util/Logger';
6170

6271
/**
6372
* CardHome Component
@@ -80,6 +89,7 @@ const CardHome = () => {
8089

8190
const { trackEvent, createEventBuilder } = useMetrics();
8291
const navigation = useNavigation();
92+
const dispatch = useDispatch();
8393
const theme = useTheme();
8494

8595
const styles = createStyles(theme);
@@ -389,6 +399,34 @@ const CardHome = () => {
389399
[priorityTokenError, cardDetailsError],
390400
);
391401

402+
// Handle authentication errors (expired token, invalid credentials, etc.)
403+
useEffect(() => {
404+
const handleAuthenticationError = async () => {
405+
if (!error) {
406+
return;
407+
}
408+
409+
// Check if the error is authentication-related
410+
if (isAuthenticated && isAuthenticationError(cardDetailsError)) {
411+
Logger.log(
412+
'CardHome: Authentication error detected, clearing auth state and redirecting',
413+
);
414+
415+
// Clear authentication state
416+
await removeCardBaanxToken();
417+
dispatch(setIsAuthenticatedCard(false));
418+
dispatch(setAuthenticatedPriorityToken(null));
419+
dispatch(setAuthenticatedPriorityTokenLastFetched(null));
420+
dispatch(setUserCardLocation(null));
421+
422+
// Redirect to welcome screen for re-authentication
423+
navigation.navigate(Routes.CARD.WELCOME);
424+
}
425+
};
426+
427+
handleAuthenticationError();
428+
}, [error, isAuthenticated, dispatch, navigation, cardDetailsError]);
429+
392430
if (error) {
393431
return (
394432
<View style={styles.errorContainer}>
@@ -410,7 +448,7 @@ const CardHome = () => {
410448
>
411449
{strings('card.card_home.error_description')}
412450
</Text>
413-
{retries < 3 && (
451+
{retries < 3 && !isAuthenticationError(error) && (
414452
<View style={styles.tryAgainButtonContainer}>
415453
<Button
416454
variant={ButtonVariants.Primary}

0 commit comments

Comments
 (0)