From 8e712ab70524a1c6182cd53b1fc39de35e0e369d Mon Sep 17 00:00:00 2001 From: Gaurav Goel Date: Fri, 28 Nov 2025 12:45:01 +0700 Subject: [PATCH 1/7] feat: convert onboarding page to tsx (#22912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** * Convert onboarding component code from javascript to typescript. * Jira: https://consensyssoftware.atlassian.net/browse/SL-315 ## **Changelog** 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** ### **Before** ### **After** https://github.com/user-attachments/assets/7652f646-cd17-4e6b-a5b3-abf8f256437a ## **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. --- > [!NOTE] > Converts the Onboarding screen from a JS class to a typed TSX functional component using hooks, with styles extracted and minor refactors to networking, metrics, and tracing. > > - **Onboarding (`app/components/Views/Onboarding`)**: > - **Migration to TSX + Hooks**: Rewrites `index.js` class component to `index.tsx` functional component using `useState`, `useEffect`, `useRef`, `useCallback`, and `useContext`. > - Replaces `connect`/HOCs with `useSelector`/`useDispatch` and `useMetrics`. > - Switches navigation to `useNavigation`/`useRoute` with typed params. > - Adds TypeScript interfaces for state, route params, and OAuth results; uses `AuthConnection` enum. > - Refactors animated values and trace contexts to `useRef`. > - Adjusts NetInfo usage to `fetch` alias and tightens error handling. > - **Styles Extraction**: Moves inline `StyleSheet.create` to `styles.ts` with typed theme colors via `createStyles`. > - **Logic Parity**: Preserves social login/import/create flows, migration recovery check (`MIGRATION_ERROR_HAPPENED` + `getVaultFromBackup`), metrics/tracing, notifications, and loaders. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 87513c6892c878bda2e8731341a7af125493d8f8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: Aslau Mario-Daniel Co-authored-by: metamaskbot --- app/components/Views/Onboarding/index.js | 986 ---------------------- app/components/Views/Onboarding/index.tsx | 903 ++++++++++++++++++++ app/components/Views/Onboarding/styles.ts | 144 ++++ 3 files changed, 1047 insertions(+), 986 deletions(-) delete mode 100644 app/components/Views/Onboarding/index.js create mode 100644 app/components/Views/Onboarding/index.tsx create mode 100644 app/components/Views/Onboarding/styles.ts diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js deleted file mode 100644 index d9ea53f8609..00000000000 --- a/app/components/Views/Onboarding/index.js +++ /dev/null @@ -1,986 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { - ActivityIndicator, - BackHandler, - View, - ScrollView, - StyleSheet, - InteractionManager, - Animated, - Easing, - Platform, -} from 'react-native'; -import { captureException } from '@sentry/react-native'; -import Text, { - TextVariant, -} from '../../../component-library/components/Texts/Text'; -import { baseStyles, colors as importedColors } from '../../../styles/common'; -import { strings } from '../../../../locales/i18n'; -import { connect } from 'react-redux'; -import FadeOutOverlay from '../../UI/FadeOutOverlay'; -import Device from '../../../util/device'; -import BaseNotification from '../../UI/Notification/BaseNotification'; -import ElevatedView from 'react-native-elevated-view'; -import { loadingSet, loadingUnset } from '../../../actions/user'; -import { saveOnboardingEvent as saveEvent } from '../../../actions/onboarding'; -import { storePrivacyPolicyClickedOrClosed as storePrivacyPolicyClickedOrClosedAction } from '../../../reducers/legalNotices'; -import PreventScreenshot from '../../../core/PreventScreenshot'; -import { PREVIOUS_SCREEN, ONBOARDING } from '../../../constants/navigation'; -import { MetaMetricsEvents } from '../../../core/Analytics'; -import { Authentication } from '../../../core'; -import { getVaultFromBackup } from '../../../core/BackupVault'; -import Logger from '../../../util/Logger'; -import FilesystemStorage from 'redux-persist-filesystem-storage'; -import { MIGRATION_ERROR_HAPPENED } from '../../../constants/storage'; -import { ThemeContext, mockTheme } from '../../../util/theme'; -import { isE2E } from '../../../util/test/utils'; -import { OnboardingSelectorIDs } from '../../../../e2e/selectors/Onboarding/Onboarding.selectors'; -import Routes from '../../../constants/navigation/Routes'; -import { selectAccounts } from '../../../selectors/accountTrackerController'; -import { selectExistingUser } from '../../../reducers/user/selectors'; -import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; -import NetInfo from '@react-native-community/netinfo'; -import { - TraceName, - TraceOperation, - endTrace, - trace, - hasMetricsConsent, - discardBufferedTraces, -} from '../../../util/trace'; -import { getTraceTags } from '../../../util/sentry/tags'; -import { store } from '../../../store'; -import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; -import Button, { - ButtonVariants, - ButtonWidthTypes, - ButtonSize, -} from '../../../component-library/components/Buttons/Button'; -import OAuthLoginService from '../../../core/OAuthService/OAuthService'; -import { OAuthError, OAuthErrorType } from '../../../core/OAuthService/error'; -import { createLoginHandler } from '../../../core/OAuthService/OAuthLoginHandlers'; -import { SEEDLESS_ONBOARDING_ENABLED } from '../../../core/OAuthService/OAuthLoginHandlers/constants'; -import { withMetricsAwareness } from '../../hooks/useMetrics'; -import { setupSentry } from '../../../util/sentry/utils'; -import ErrorBoundary from '../ErrorBoundary'; -import FastOnboarding from './FastOnboarding'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import FoxAnimation from '../../UI/FoxAnimation/FoxAnimation'; -import OnboardingAnimation from '../../UI/OnboardingAnimation/OnboardingAnimation'; - -const createStyles = (colors) => - StyleSheet.create({ - scroll: { - flex: 1, - }, - wrapper: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 16, - }, - loaderWrapper: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - rowGap: 32, - marginBottom: 160, - }, - loaderOverlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - zIndex: 1000, - alignItems: 'center', - justifyContent: 'center', - }, - image: { - alignSelf: 'center', - width: Device.isMediumDevice() ? 180 : 240, - height: Device.isMediumDevice() ? 180 : 240, - }, - largeFoxWrapper: { - width: Device.isMediumDevice() ? 180 : 240, - height: Device.isMediumDevice() ? 180 : 240, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginHorizontal: 'auto', - padding: Device.isMediumDevice() ? 30 : 40, - position: 'absolute', - top: '50%', - left: '50%', - marginLeft: -(Device.isMediumDevice() ? 90 : 120), - marginTop: -(Device.isMediumDevice() ? 90 : 120), - }, - foxImage: { - width: Device.isMediumDevice() ? 110 : 145, - height: Device.isMediumDevice() ? 110 : 145, - resizeMode: 'contain', - }, - title: { - fontSize: Device.isMediumDevice() ? 30 : 40, - lineHeight: Device.isMediumDevice() ? 30 : 40, - textAlign: 'center', - paddingHorizontal: Device.isMediumDevice() ? 40 : 60, - fontFamily: - Platform.OS === 'android' ? 'MM Sans Regular' : 'MMSans-Regular', - color: importedColors.gettingStartedTextColor, - width: '100%', - marginVertical: 16, - }, - ctas: { - flex: 1, - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - paddingHorizontal: 20, - rowGap: Device.isMediumDevice() ? 16 : 24, - }, - titleWrapper: { - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - flex: 1, - rowGap: Device.isMediumDevice() ? 24 : 32, - }, - footer: { - marginBottom: 40, - marginTop: -40, - }, - createWrapper: { - flexDirection: 'column', - rowGap: Device.isMediumDevice() ? 12 : 16, - marginBottom: 16, - position: 'absolute', - top: '50%', - left: Device.isMediumDevice() ? 26 : 36, - right: Device.isMediumDevice() ? 26 : 36, - marginTop: 180, - alignItems: 'stretch', - }, - buttonWrapper: { - flexDirection: 'column', - justifyContent: 'flex-end', - gap: Device.isMediumDevice() ? 12 : 16, - width: '100%', - }, - buttonLabel: { - flexDirection: 'row', - alignItems: 'center', - columnGap: 8, - }, - loader: { - justifyContent: 'center', - textAlign: 'center', - }, - loadingText: { - marginTop: 30, - textAlign: 'center', - color: colors.text.default, - }, - modalTypeView: { - position: 'absolute', - bottom: 0, - paddingBottom: Device.isIphoneX() ? 20 : 10, - left: 0, - right: 0, - backgroundColor: importedColors.transparent, - }, - notificationContainer: { - flex: 0.1, - flexDirection: 'row', - alignItems: 'flex-end', - }, - blackButton: { - backgroundColor: importedColors.white, - }, - inverseBlackButton: { - backgroundColor: importedColors.applePayBlack, - }, - }); - -/** - * View that is displayed to first time (new) users - */ -class Onboarding extends PureComponent { - static propTypes = { - disableNewPrivacyPolicyToast: PropTypes.func, - /** - * The navigator object - */ - navigation: PropTypes.object, - /** - * redux flag that indicates if the user set a password - */ - passwordSet: PropTypes.bool, - /** - * loading status - */ - loading: PropTypes.bool, - /** - * set loading status - */ - setLoading: PropTypes.func, - /** - * unset loading status - */ - unsetLoading: PropTypes.func, - /** - * redux flag that indicates if the user is existing - */ - existingUser: PropTypes.bool, - /** - * Action to save onboarding event - */ - saveOnboardingEvent: PropTypes.func, - /** - * loadings msg - */ - loadingMsg: PropTypes.string, - /** - * Object that represents the current route info like params passed to it - */ - route: PropTypes.object, - /** - * Metrics injected by withMetricsAwareness HOC - */ - metrics: PropTypes.object, - }; - notificationAnimated = new Animated.Value(100); - detailsYAnimated = new Animated.Value(0); - actionXAnimated = new Animated.Value(0); - detailsAnimated = new Animated.Value(0); - - onboardingTraceCtx = null; - socialLoginTraceCtx = null; - - animatedTimingStart = (animatedRef, toValue) => { - Animated.timing(animatedRef, { - toValue, - duration: 500, - easing: Easing.linear, - useNativeDriver: true, - }).start(); - }; - - state = { - warningModalVisible: false, - loading: false, - existingUser: false, - createWallet: false, - existingWallet: false, - errorSheetVisible: false, - errorToThrow: null, - startOnboardingAnimation: false, - startFoxAnimation: false, - }; - - seedwords = null; - importedAccounts = null; - channelName = null; - incomingDataStr = ''; - dataToSync = null; - mounted = false; - hasCheckedVaultBackup = false; // Prevent multiple vault backup checks - - warningCallback = () => true; - - showNotification = () => { - // show notification - this.animatedTimingStart(this.notificationAnimated, 0); - // hide notification - setTimeout(() => { - this.animatedTimingStart(this.notificationAnimated, 200); - }, 4000); - this.disableBackPress(); - }; - - disableBackPress = () => { - // Disable back press - const hardwareBackPress = () => true; - BackHandler.addEventListener('hardwareBackPress', hardwareBackPress); - }; - - updateNavBar = () => { - const { navigation } = this.props; - navigation.setOptions({ - headerShown: false, - }); - }; - - componentDidMount() { - this.onboardingTraceCtx = trace({ - name: TraceName.OnboardingJourneyOverall, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - }); - - this.props.unsetLoading(); - this.updateNavBar(); - this.mounted = true; - this.checkIfExistingUser(); - this.props.disableNewPrivacyPolicyToast(); - - InteractionManager.runAfterInteractions(() => { - this.checkForMigrationFailureAndVaultBackup(); - PreventScreenshot.forbid(); - if (this.props.route.params?.delete) { - this.showNotification(); - } - this.setState({ startOnboardingAnimation: true }); - }); - } - - componentWillUnmount() { - this.mounted = false; - this.props.unsetLoading(); - InteractionManager.runAfterInteractions(PreventScreenshot.allow); - } - - componentDidUpdate = () => { - this.updateNavBar(); - }; - - async checkIfExistingUser() { - // Read from Redux state instead of MMKV storage - const { existingUser } = this.props; - if (existingUser) { - this.setState({ existingUser: true }); - } - } - - /** - * Check for migration failure scenario and vault backup availability - * This detects when a migration has failed and left the user with a corrupted state - * but a valid vault backup still exists in the secure keychain - */ - async checkForMigrationFailureAndVaultBackup() { - // Prevent multiple checks - only run once per instance - if (this.hasCheckedVaultBackup) { - return; - } - - this.hasCheckedVaultBackup = true; - - // Skip check in E2E test environment - // E2E tests start with fresh state but may have vault backups from fixtures/previous runs - // This would trigger false positive vault recovery redirects and break onboarding tests - if (isE2E) { - return; - } - - // Skip check if this is an intentional wallet reset - // (route.params.delete is set when user explicitly resets their wallet) - if (this.props.route?.params?.delete) { - return; - } - - try { - // Check for migration error flag - // Using FilesystemStorage (excluded from iCloud backup) for reliability - const migrationErrorFlag = await FilesystemStorage.getItem( - MIGRATION_ERROR_HAPPENED, - ); - - if (migrationErrorFlag === 'true') { - // Migration failed, check if vault backup exists - const vaultBackupResult = await getVaultFromBackup(); - - if (vaultBackupResult.success && vaultBackupResult.vault) { - // Both migration error and vault backup exist - trigger recovery - this.props.navigation.reset({ - routes: [{ name: Routes.VAULT_RECOVERY.RESTORE_WALLET }], - }); - } - } - } catch (error) { - Logger.error( - error, - 'Failed to check for migration failure and vault backup', - ); - } - } - - onLogin = async () => { - const { passwordSet } = this.props; - if (!passwordSet) { - await Authentication.resetVault(); - this.props.navigation.replace(Routes.ONBOARDING.HOME_NAV); - } else { - await Authentication.lockApp(); - this.props.navigation.replace(Routes.ONBOARDING.LOGIN); - } - }; - - handleExistingUser = (action) => { - if (this.state.existingUser) { - this.alertExistingUser(action); - } else { - action(); - } - }; - - onPressCreate = async () => { - if (SEEDLESS_ONBOARDING_ENABLED) { - OAuthLoginService.resetOauthState(); - } - await this.props.metrics.enableSocialLogin(false); - // need to call hasMetricConset to update the cached consent state - await hasMetricsConsent(); - - trace({ name: TraceName.OnboardingCreateWallet }); - const action = () => { - trace({ - name: TraceName.OnboardingNewSrpCreateWallet, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - parentContext: this.onboardingTraceCtx, - }); - this.props.navigation.navigate('ChoosePassword', { - [PREVIOUS_SCREEN]: ONBOARDING, - onboardingTraceCtx: this.onboardingTraceCtx, - }); - this.track(MetaMetricsEvents.WALLET_SETUP_STARTED, { - account_type: 'metamask', - }); - }; - - this.handleExistingUser(action); - endTrace({ name: TraceName.OnboardingCreateWallet }); - }; - - onPressImport = async () => { - if (SEEDLESS_ONBOARDING_ENABLED) { - OAuthLoginService.resetOauthState(); - } - await this.props.metrics.enableSocialLogin(false); - await hasMetricsConsent(); - - const action = async () => { - trace({ - name: TraceName.OnboardingExistingSrpImport, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - parentContext: this.onboardingTraceCtx, - }); - this.props.navigation.navigate( - Routes.ONBOARDING.IMPORT_FROM_SECRET_RECOVERY_PHRASE, - { - [PREVIOUS_SCREEN]: ONBOARDING, - onboardingTraceCtx: this.onboardingTraceCtx, - }, - ); - this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED, { - account_type: 'imported', - }); - }; - this.handleExistingUser(action); - }; - - handlePostSocialLogin = (result, createWallet, provider) => { - const isIOS = Platform.OS === 'ios'; - if (this.socialLoginTraceCtx) { - endTrace({ name: TraceName.OnboardingSocialLoginAttempt }); - this.socialLoginTraceCtx = null; - } - - if (result.type === 'success') { - // Track social login completed - this.track(MetaMetricsEvents.SOCIAL_LOGIN_COMPLETED, { - account_type: provider, - }); - if (createWallet) { - if (result.existingUser) { - this.props.navigation.navigate('AccountAlreadyExists', { - accountName: result.accountName, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - provider, - }); - } else { - trace({ - name: TraceName.OnboardingNewSocialCreateWallet, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - parentContext: this.onboardingTraceCtx, - }); - - if (isIOS) { - // Navigate to SocialLoginSuccess screen first, then ChoosePassword - this.props.navigation.navigate( - Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_NEW_USER, - { - accountName: result.accountName, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - provider, - }, - ); - } else { - // Direct navigation to ChoosePassword for Android - this.props.navigation.navigate('ChoosePassword', { - [PREVIOUS_SCREEN]: ONBOARDING, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - provider, - }); - } - } - } else if (!createWallet) { - if (result.existingUser) { - trace({ - name: TraceName.OnboardingExistingSocialLogin, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - parentContext: this.onboardingTraceCtx, - }); - isIOS - ? this.props.navigation.navigate( - Routes.ONBOARDING.SOCIAL_LOGIN_SUCCESS_EXISTING_USER, - { - [PREVIOUS_SCREEN]: ONBOARDING, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - }, - ) - : this.props.navigation.navigate('Rehydrate', { - [PREVIOUS_SCREEN]: ONBOARDING, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - }); - } else { - this.props.navigation.navigate('AccountNotFound', { - accountName: result.accountName, - oauthLoginSuccess: true, - onboardingTraceCtx: this.onboardingTraceCtx, - provider, - }); - } - } - } else { - // handle error: show error message in the UI - } - }; - - onPressContinueWithSocialLogin = async (createWallet, provider) => { - // check for internet connection - try { - const netState = await NetInfo.fetch(); - if (!netState.isConnected || netState.isInternetReachable === false) { - this.props.navigation.replace(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.SUCCESS_ERROR_SHEET, - params: { - title: strings(`error_sheet.no_internet_connection_title`), - description: strings( - `error_sheet.no_internet_connection_description`, - ), - descriptionAlign: 'left', - buttonLabel: strings(`error_sheet.no_internet_connection_button`), - primaryButtonLabel: strings( - `error_sheet.no_internet_connection_button`, - ), - closeOnPrimaryButtonPress: true, - type: 'error', - }, - }); - return; - } - } catch (error) { - console.warn('Network check failed:', error); - } - - // Continue with the social login flow - this.props.navigation.navigate('Onboarding'); - - // Enable metrics for OAuth users - await this.props.metrics.enableSocialLogin(true); - discardBufferedTraces(); - await setupSentry(); - - // use new trace instead of buffered trace for social login - this.onboardingTraceCtx = trace({ - name: TraceName.OnboardingJourneyOverall, - op: TraceOperation.OnboardingUserJourney, - tags: getTraceTags(store.getState()), - }); - - if (createWallet) { - this.track(MetaMetricsEvents.WALLET_SETUP_STARTED, { - account_type: `metamask_${provider}`, - }); - } else { - this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED, { - account_type: `imported_${provider}`, - }); - } - - this.socialLoginTraceCtx = trace({ - name: TraceName.OnboardingSocialLoginAttempt, - op: TraceOperation.OnboardingUserJourney, - tags: { ...getTraceTags(store.getState()), provider }, - parentContext: this.onboardingTraceCtx, - }); - - const action = async () => { - this.props.setLoading(); - const loginHandler = createLoginHandler(Platform.OS, provider); - const result = await OAuthLoginService.handleOAuthLogin( - loginHandler, - !createWallet, - ).catch((error) => { - this.props.unsetLoading(); - this.handleLoginError(error, provider); - return { type: 'error', error, existingUser: false }; - }); - this.handlePostSocialLogin(result, createWallet, provider); - - // delay unset loading to avoid flash of loading state - setTimeout(() => { - this.props.unsetLoading(); - }, 1000); - }; - this.handleExistingUser(action); - }; - - onPressContinueWithApple = async (createWallet) => - this.onPressContinueWithSocialLogin(createWallet, 'apple'); - - onPressContinueWithGoogle = async (createWallet) => - this.onPressContinueWithSocialLogin(createWallet, 'google'); - - handleLoginError = (error, socialConnectionType) => { - if (error instanceof OAuthError) { - // For OAuth API failures (excluding user cancellation/dismissal), handle based on analytics consent - if ( - error.code === OAuthErrorType.UserCancelled || - error.code === OAuthErrorType.UserDismissed || - error.code === OAuthErrorType.GoogleLoginError || - error.code === OAuthErrorType.AppleLoginError - ) { - // QA: do not show error sheet if user cancelled - return; - } else if ( - error.code === OAuthErrorType.GoogleLoginNoCredential || - error.code === OAuthErrorType.GoogleLoginNoMatchingCredential - ) { - // de-escalate google no credential error - const errorMessage = 'google_login_no_credential'; - this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.SUCCESS_ERROR_SHEET, - params: { - title: strings(`error_sheet.${errorMessage}_title`), - description: strings(`error_sheet.${errorMessage}_description`), - descriptionAlign: 'center', - buttonLabel: strings(`error_sheet.${errorMessage}_button`), - type: 'error', - }, - }); - return; - } - // unexpected oauth login error - this.handleOAuthLoginError(error); - return; - } - - const errorMessage = 'oauth_error'; - - trace({ - name: TraceName.OnboardingSocialLoginError, - op: TraceOperation.OnboardingError, - tags: { provider: socialConnectionType, errorMessage }, - parentContext: this.onboardingTraceCtx, - }); - endTrace({ name: TraceName.OnboardingSocialLoginError }); - - if (this.socialLoginTraceCtx) { - endTrace({ - name: TraceName.OnboardingSocialLoginAttempt, - data: { success: false }, - }); - this.socialLoginTraceCtx = null; - } - - this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.SUCCESS_ERROR_SHEET, - params: { - title: strings(`error_sheet.${errorMessage}_title`), - description: strings(`error_sheet.${errorMessage}_description`), - descriptionAlign: 'center', - buttonLabel: strings(`error_sheet.${errorMessage}_button`), - type: 'error', - }, - }); - }; - - handleOAuthLoginError = (error) => { - // If user has already consented to analytics, report error using regular Sentry - if (this.props.metrics.isEnabled()) { - captureException(error, { - tags: { - view: 'Onboarding', - context: 'OAuth login failed - user consented to analytics', - }, - }); - } else { - // User hasn't consented to analytics yet, use ErrorBoundary onboarding flow - this.setState({ - loading: false, - errorToThrow: new Error(`OAuth login failed: ${error.message}`), - }); - } - }; - track = (event, properties) => { - trackOnboarding( - MetricsEventBuilder.createEventBuilder(event) - .addProperties(properties) - .build(), - this.props.saveOnboardingEvent, - ); - }; - - alertExistingUser = (callback) => { - this.warningCallback = () => { - callback(); - this.toggleWarningModal(); - }; - this.toggleWarningModal(); - }; - - toggleWarningModal = () => { - const warningModalVisible = this.state.warningModalVisible; - this.setState({ warningModalVisible: !warningModalVisible }); - }; - - handleCtaActions = async (actionType) => { - if (SEEDLESS_ONBOARDING_ENABLED) { - this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ONBOARDING_SHEET, - params: { - onPressCreate: this.onPressCreate, - onPressImport: this.onPressImport, - onPressContinueWithGoogle: this.onPressContinueWithGoogle, - onPressContinueWithApple: this.onPressContinueWithApple, - createWallet: actionType === 'create', - }, - }); - // else - } else if (actionType === 'create') { - await this.onPressCreate(); - } else { - await this.onPressImport(); - } - }; - - setStartFoxAnimation = () => { - this.setState({ startFoxAnimation: 'Start' }); - }; - - renderLoader = () => { - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - return ( - - - - {this.props.loadingMsg} - - - ); - }; - - renderContent() { - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - return ( - - -