diff --git a/app/src/screens/account/recovery/RecoverWithPhraseScreen.tsx b/app/src/screens/account/recovery/RecoverWithPhraseScreen.tsx index 45760c2e1..0d399d5c3 100644 --- a/app/src/screens/account/recovery/RecoverWithPhraseScreen.tsx +++ b/app/src/screens/account/recovery/RecoverWithPhraseScreen.tsx @@ -4,7 +4,7 @@ import { ethers } from 'ethers'; import React, { useCallback, useState } from 'react'; -import { Keyboard, StyleSheet } from 'react-native'; +import { Keyboard, Pressable, StyleSheet } from 'react-native'; import { Text, TextArea, View, XStack, YStack } from 'tamagui'; import Clipboard from '@react-native-clipboard/clipboard'; import { useNavigation } from '@react-navigation/native'; @@ -22,6 +22,7 @@ import { import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { black, + red500, slate300, slate400, slate600, @@ -38,6 +39,22 @@ import { } from '@/providers/passportDataProvider'; import { recoveryCopy } from '@/screens/account/recovery/recoveryCopy'; +type RecoveryError = + | 'invalid_mnemonic' + | 'restore_failed' + | 'not_registered' + | 'unexpected_error'; + +const ERROR_MESSAGES: Record = { + invalid_mnemonic: + 'That doesn’t look like a valid recovery phrase. Make sure all 24 words are correct and in the right order.', + restore_failed: + 'We couldn’t restore your account with this phrase. Please double-check and try again.', + not_registered: + 'This recovery phrase doesn’t match a registered ID. If you registered with a different phrase, try that one instead.', + unexpected_error: 'Something went wrong. Please try again.', +}; + const RecoverWithPhraseScreen: React.FC = () => { const navigation = useNavigation>(); @@ -47,20 +64,31 @@ const RecoverWithPhraseScreen: React.FC = () => { const { trackEvent } = useSelfClient(); const [mnemonic, setMnemonic] = useState(); const [restoring, setRestoring] = useState(false); + const [error, setError] = useState(null); + const onPaste = useCallback(async () => { const clipboard = (await Clipboard.getString()).trim(); // bugfix: perform a simple clipboard check; ethers.Mnemonic.isValidMnemonic doesn't work if (clipboard) { setMnemonic(clipboard); + setError(null); Keyboard.dismiss(); } }, []); + const onChangeText = useCallback((text: string) => { + setMnemonic(text); + setError(null); + }, []); + const restoreAccount = useCallback(async () => { + Keyboard.dismiss(); + setError(null); try { setRestoring(true); const slimMnemonic = mnemonic?.trim(); if (!slimMnemonic || !ethers.Mnemonic.isValidMnemonic(slimMnemonic)) { + setError('invalid_mnemonic'); setRestoring(false); return; } @@ -71,6 +99,7 @@ const RecoverWithPhraseScreen: React.FC = () => { trackEvent(BackupEvents.CLOUD_RESTORE_FAILED_AUTH, { mnemonicLength: slimMnemonic.split(' ').length, }); + setError('restore_failed'); setRestoring(false); return; } @@ -121,6 +150,7 @@ const RecoverWithPhraseScreen: React.FC = () => { reason: 'document_not_registered', hasCSCA: !!csca, }); + setError('not_registered'); setRestoring(false); return; } @@ -138,6 +168,7 @@ const RecoverWithPhraseScreen: React.FC = () => { reason: 'unexpected_error', error: error instanceof Error ? error.message : 'unknown', }); + setError('unexpected_error'); setRestoring(false); } }, [ @@ -161,7 +192,7 @@ const RecoverWithPhraseScreen: React.FC = () => {