From 3766251f5e6110c195fa7bf18c3642951ca2f591 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 2 Oct 2019 18:53:28 +0200 Subject: [PATCH 1/3] Start on new import flow with empty wallet handling --- packages/mobile/locales/en-US/global.json | 2 +- .../locales/en-US/nuxRestoreWallet3.json | 23 +- packages/mobile/locales/es-419/global.json | 2 +- .../locales/es-419/nuxRestoreWallet3.json | 23 +- packages/mobile/package.json | 2 +- packages/mobile/src/app/ErrorMessages.ts | 3 +- .../mobile/src/import/ImportWallet.test.tsx | 6 +- packages/mobile/src/import/ImportWallet.tsx | 225 ++++++++---------- packages/mobile/src/import/actions.ts | 40 +++- packages/mobile/src/import/saga.ts | 70 +++++- packages/mobile/src/invite/saga.ts | 18 +- packages/mobile/src/redux/reducers.ts | 3 + packages/mobile/src/redux/store.ts | 2 +- packages/mobile/src/tokens/saga.ts | 27 ++- packages/mobile/src/web3/actions.ts | 2 +- packages/react-components/styles/styles.ts | 2 +- packages/utils/src/signatureUtils.ts | 8 + 17 files changed, 275 insertions(+), 183 deletions(-) diff --git a/packages/mobile/locales/en-US/global.json b/packages/mobile/locales/en-US/global.json index 075a019c67e..db9a3014954 100644 --- a/packages/mobile/locales/en-US/global.json +++ b/packages/mobile/locales/en-US/global.json @@ -62,7 +62,7 @@ "canNotRequestFromUnverified": "Can not request from unverified users", "restartApp": "Restart App", "loading": "Loading…", - "invalidBackup": "Invalid Backup Key", + "invalidBackupPhrase": "Invalid Backup Key", "importBackupFailed": "Importing Wallet Failed", "inviteFailed": "Failure sending invite", "importContactsFailed": "Failed to import contacts", diff --git a/packages/mobile/locales/en-US/nuxRestoreWallet3.json b/packages/mobile/locales/en-US/nuxRestoreWallet3.json index 5d7b2eb0f34..bb39338c84b 100644 --- a/packages/mobile/locales/en-US/nuxRestoreWallet3.json +++ b/packages/mobile/locales/en-US/nuxRestoreWallet3.json @@ -4,19 +4,16 @@ "Enter your invitation code. If you do not have one, ask someone in the Celo Community to invite you. By joining this application, you agree to share your name & phone number with us. Learn more at celo.org", "fullName": "Full Name", "invitationCode": "Invitation Code", - "submit": "Submit", "alreadyHaveWallet": "Already have a Celo Wallet? Restore it", - "restoreYourWallet": { - "title": "Restore your Celo Wallet", - "userYourBackupKey": - "Already have a Celo Wallet? Use your secure Backup Key to restore your wallet to this phone.", - "warning": "Warning ", - "restoreInPrivate": "Restore your Wallet in private " - }, - "backupKeyPrompt": "Backup Key", + "title": "Restore your Celo Wallet", + "userYourBackupKey": + "Already have a Celo Wallet? Enter your Backup Key to restore it to this phone.", + "backupKeyPrompt": + "horse leopard dog monkey shark tiger lemur whale squid wolf squirrel mouse lion elephant cat shrimp bear penguin deer turtle fox zebra goat giraffe", "tip": "Tip: ", - "backupKeyTip": "Backup Key words are separated by a space", - "continue": "Continue", - "cancel": "Cancel", - "restoreWallet": "Restore Celo Wallet" + "backupKeyTip": "Backup Keys are 24 words long, separated by a space.", + "restoreWallet": "Restore", + "emptyWalletWarning": "This wallet is empty. Would you like to use it anyway?", + "useEmptyWallet": "Use Empty Wallet", + "tryAnotherKey": "Try Another Backup Key" } diff --git a/packages/mobile/locales/es-419/global.json b/packages/mobile/locales/es-419/global.json index 2aab3fe4c2b..ac19d046e6b 100755 --- a/packages/mobile/locales/es-419/global.json +++ b/packages/mobile/locales/es-419/global.json @@ -62,7 +62,7 @@ "canNotRequestFromUnverified": "No se puede solicitar a usuarios no verificados.", "restartApp": "Reiniciar la aplicación", "loading": "Cargando…", - "invalidBackup": "Clave de respaldo inválida", + "invalidBackupPhrase": "Clave de respaldo inválida", "importBackupFailed": "No se pudo importar el monedero", "inviteFailed": "Falló el envío de la invitación", "importContactsFailed": "Error al importar contactos", diff --git a/packages/mobile/locales/es-419/nuxRestoreWallet3.json b/packages/mobile/locales/es-419/nuxRestoreWallet3.json index f9046d0fb25..e0e8d7959b7 100755 --- a/packages/mobile/locales/es-419/nuxRestoreWallet3.json +++ b/packages/mobile/locales/es-419/nuxRestoreWallet3.json @@ -4,19 +4,16 @@ "Ingresegcódigo de invitación. Si no tiene uno, pídale a alguien de la comunidad de Celo que lo invite.\n\nAl unirse a esta aplicación, acuerda compartir tu nombre y número telefónico con nosotros. Obtenga más información en celo.org", "fullName": "Nombre completo", "invitationCode": "Código de invitación", - "submit": "Enviar", "alreadyHaveWallet": "¿Ya tiene un Monedero Celo? Restáurelo", - "restoreWallet": "Restaurar el Monedero Celo", - "restoreYourWallet": { - "title": "Restaurar tu Monedero Celo", - "userYourBackupKey": - "¿Ya tiene un Monedero Celo? Use tu clave de respaldo segura para restaurar tu monedero a este teléfono.", - "warning": "¡Cuidado! ", - "restoreInPrivate": "Hágalo en privado" - }, - "backupKeyPrompt": "Clave de respaldo", + "title": "Restaurar tu Monedero Celo", + "userYourBackupKey": + "¿Ya tiene un Monedero Celo? Ingrese su Clave de Respaldo para restaurarla a este teléfono.", + "backupKeyPrompt": + "caballo leopardo perro mono tiburón tigre lémur ballena calamar lobo ardilla ratón león elefante gato camarones oso pingüino...", "tip": "Consejo: ", - "backupKeyTip": "las palabras de la clave de respaldo están separadas por un espacio", - "continue": "Continuar", - "cancel": "Cancelar" + "backupKeyTip": "Las Claves de Respaldo son de 24 palabras, separadas por un espacio.", + "restoreWallet": "Restaurar", + "emptyWalletWarning": "~This wallet is empty. Would you like to use it anyway?", + "useEmptyWallet": "~Use Empty Wallet", + "tryAnotherKey": "~Try Another Backup Key" } diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 6d81a05cddd..b04e355141c 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -48,7 +48,7 @@ "dependencies": { "@celo/client": "ae27fd3", "@celo/react-native-sms-retriever": "git+https://github.com/celo-org/react-native-sms-retriever#d3a2fdb", - "@celo/utils": "^0.1.0", + "@celo/utils": "^0.1.1", "@celo/walletkit": "^0.0.14", "@react-native-community/netinfo": "^2.0.4", "@segment/analytics-react-native": "^1.1.0-beta.1", diff --git a/packages/mobile/src/app/ErrorMessages.ts b/packages/mobile/src/app/ErrorMessages.ts index e74d1716448..6eebd9c425a 100644 --- a/packages/mobile/src/app/ErrorMessages.ts +++ b/packages/mobile/src/app/ErrorMessages.ts @@ -8,7 +8,8 @@ export enum ErrorMessages { NSF_DOLLARS = 'notEnoughDollarsError', NSF_TO_SEND = 'needMoreFundsToSend', INVALID_AMOUNT = 'invalidAmount', - INVALID_BACKUP = 'invalidBackup', + INVALID_BACKUP_PHRASE = 'invalidBackupPhrase', + EMPTY_BACKUP_PHRASE = 'emptyBackupPhrase', IMPORT_BACKUP_FAILED = 'importBackupFailed', INVALID_PHONE_NUMBER = 'nuxVerification2:invalidPhone', NOT_READY_FOR_CODE = 'nuxVerification2:notReadyForCode', diff --git a/packages/mobile/src/import/ImportWallet.test.tsx b/packages/mobile/src/import/ImportWallet.test.tsx index 9c54bbc201b..7bcc4b6c33c 100644 --- a/packages/mobile/src/import/ImportWallet.test.tsx +++ b/packages/mobile/src/import/ImportWallet.test.tsx @@ -61,7 +61,9 @@ describe('ImportWallet', () => { }) it('renders with an error', () => { - const store = createMockStore({ alert: { underlyingError: ErrorMessages.INVALID_BACKUP } }) + const store = createMockStore({ + alert: { underlyingError: ErrorMessages.INVALID_BACKUP_PHRASE }, + }) const tree = renderer.create( @@ -100,7 +102,7 @@ describe('ImportWallet', () => { BAD_ENGLISH_MNEMONIC ) fireEvent.press(wrapper.getByTestId('ImportWalletButton')) - expect(error).toHaveBeenCalledWith(ErrorMessages.INVALID_BACKUP) + expect(error).toHaveBeenCalledWith(ErrorMessages.INVALID_BACKUP_PHRASE) }) it('calls assign account with the proper private key', () => { diff --git a/packages/mobile/src/import/ImportWallet.tsx b/packages/mobile/src/import/ImportWallet.tsx index 33e37a35a57..0a105972988 100644 --- a/packages/mobile/src/import/ImportWallet.tsx +++ b/packages/mobile/src/import/ImportWallet.tsx @@ -1,27 +1,22 @@ -import { BtnTypes } from '@celo/react-components/components/Button' +import Button, { BtnTypes } from '@celo/react-components/components/Button' import colors from '@celo/react-components/styles/colors' import { fontStyles } from '@celo/react-components/styles/fonts' import { componentStyles } from '@celo/react-components/styles/styles' import * as React from 'react' import { WithNamespaces, withNamespaces } from 'react-i18next' import { ActivityIndicator, Keyboard, StyleSheet, Text, TextInput, View } from 'react-native' -import { validateMnemonic } from 'react-native-bip39' import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' import { connect } from 'react-redux' import { hideAlert, showError } from 'src/alert/actions' -import { errorSelector } from 'src/alert/reducer' import CeloAnalytics from 'src/analytics/CeloAnalytics' import { CustomEventNames } from 'src/analytics/constants' -import { ErrorMessages } from 'src/app/ErrorMessages' import GethAwareButton from 'src/geth/GethAwareButton' import { Namespaces } from 'src/i18n' import NuxLogo from 'src/icons/NuxLogo' import { importBackupPhrase } from 'src/import/actions' import { nuxNavigationOptions } from 'src/navigator/Headers' import { RootState } from 'src/redux/reducers' -import Logger from 'src/utils/Logger' - -const TAG = 'ImportWallet' +import { getMoneyDisplayValue } from 'src/utils/formatting' // Because of a RN bug, we can't fully clean the text as the user types // https://github.com/facebook/react-native/issues/11068 @@ -36,7 +31,6 @@ export const formatBackupPhraseOnSubmit = (phrase: string) => interface State { backupPhrase: string - isSubmitting: boolean } interface DispatchProps { @@ -46,39 +40,24 @@ interface DispatchProps { } interface StateProps { - error: ErrorMessages | null + isImportingWallet: boolean + isWalletEmpty: boolean } type Props = StateProps & DispatchProps & WithNamespaces const mapStateToProps = (state: RootState): StateProps => { return { - error: errorSelector(state), + isImportingWallet: state.imports.isImportingWallet, + isWalletEmpty: state.imports.isWalletEmpty, } } -const displayedErrors = [ErrorMessages.INVALID_BACKUP, ErrorMessages.IMPORT_BACKUP_FAILED] - -const hasDisplayedError = (error: ErrorMessages | null) => { - return error && displayedErrors.includes(error) -} - export class ImportWallet extends React.Component { static navigationOptions = nuxNavigationOptions - static getDerivedStateFromProps(props: Props, state: State): State | null { - if (hasDisplayedError(props.error) && state.isSubmitting) { - return { - ...state, - isSubmitting: false, - } - } - return null - } - state = { backupPhrase: '', - isSubmitting: false, } setBackupPhrase = (input: string) => { @@ -92,47 +71,41 @@ export class ImportWallet extends React.Component { CeloAnalytics.track(CustomEventNames.import_phrase_input) } - onSubmit = () => { - try { - this.setState({ - isSubmitting: true, - }) - - Keyboard.dismiss() - this.props.hideAlert() - CeloAnalytics.track(CustomEventNames.import_wallet_submit) - - const formattedPhrase = formatBackupPhraseOnSubmit(this.state.backupPhrase) - this.setState({ - backupPhrase: formattedPhrase, - }) - - if (!validateMnemonic(formattedPhrase)) { - Logger.warn(TAG, 'Invalid mnemonic') - this.props.showError(ErrorMessages.INVALID_BACKUP) - this.setState({ - isSubmitting: false, - }) - return - } - - this.props.importBackupPhrase(formattedPhrase) - } catch (error) { - this.setState({ - isSubmitting: false, - }) - Logger.error(TAG, 'Error importing wallet', error) - this.props.showError(ErrorMessages.IMPORT_BACKUP_FAILED) - } + onPressRestore = () => { + Keyboard.dismiss() + this.props.hideAlert() + CeloAnalytics.track(CustomEventNames.import_wallet_submit) + + const formattedPhrase = formatBackupPhraseOnSubmit(this.state.backupPhrase) + this.setState({ + backupPhrase: formattedPhrase, + }) + + this.props.importBackupPhrase(formattedPhrase, false) + } + + onPressUseEmpty = () => { + this.props.importBackupPhrase(this.state.backupPhrase, true) + } + + onPressTryAnotherKey = () => { + // TODO } isBackupPhraseValid() { - return this.state.backupPhrase.trim().split(/\s+/g).length >= 12 + return ( + formatBackupPhraseOnEdit(this.state.backupPhrase) + .trim() + .split(/\s+/g).length >= 12 + ) } render() { - const { backupPhrase, isSubmitting } = this.state - const { t, error } = this.props + const { backupPhrase } = this.state + //TODO + let { t, isImportingWallet, isWalletEmpty } = this.props + + isWalletEmpty = true return ( @@ -141,53 +114,72 @@ export class ImportWallet extends React.Component { keyboardShouldPersistTaps="always" > - {t('restoreYourWallet.title')} - - {t('restoreYourWallet.userYourBackupKey')} - - {t('backupKeyTip')} - - - {t('restoreYourWallet.warning')} - - {t('restoreYourWallet.restoreInPrivate')} - - - - + {!isWalletEmpty && ( + <> + {t('title')} + {t('userYourBackupKey')} + + + + + {t('tip')} + {t('backupKeyTip')} + + + )} + {isWalletEmpty && ( + <> + {getMoneyDisplayValue(0)} + {t('emptyWalletWarning')} + + )} - {isSubmitting && ( + {isImportingWallet && ( )} - + {!isWalletEmpty && ( + + )} + {isWalletEmpty && ( + <> + +