Skip to content

Commit

Permalink
Merge pull request #1475 from Emurgo/textinput
Browse files Browse the repository at this point in the history
update WalletForm
  • Loading branch information
vsubhuman committed Jul 24, 2021
2 parents f88cfed + 2ec3d06 commit 17d6534
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 180 deletions.
287 changes: 112 additions & 175 deletions src/components/WalletInit/WalletForm.js
@@ -1,24 +1,24 @@
// @flow

import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {compose} from 'redux'
import React from 'react'
import {useSelector} from 'react-redux'
import {View, ScrollView} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import _ from 'lodash'
import {withHandlers} from 'recompose'
import {injectIntl, defineMessages, type IntlShape} from 'react-intl'

import {Button, ValidatedTextInput, StatusBar} from '../UiKit'
import {validatePassword, getWalletNameError, validateWalletName} from '../../utils/validators'
import {Button, TextInput} from '../UiKit'
import {
validatePassword,
getWalletNameError,
validateWalletName,
REQUIRED_PASSWORD_LENGTH,
} from '../../utils/validators'
import {CONFIG} from '../../config/config'
import PasswordStrengthIndicator from './PasswordStrengthIndicator'
import styles from './styles/WalletForm.style'
import {walletNamesSelector} from '../../selectors'
import globalMessages from '../../i18n/global-messages'

import type {PasswordValidationErrors, WalletNameValidationErrors} from '../../utils/validators'
import type {Navigation} from '../../types/navigation'
import {Checkmark} from '../UiKit/TextInput'

const messages = defineMessages({
walletNameInputLabel: {
Expand All @@ -33,6 +33,10 @@ const messages = defineMessages({
id: 'components.walletinit.walletform.continueButton',
defaultMessage: '!!!Continue',
},
passwordStrengthRequirement: {
id: 'components.walletinit.createwallet.createwalletscreen.passwordLengthRequirement',
defaultMessage: '!!!Minimum characters',
},
repeatPasswordInputLabel: {
id: 'components.walletinit.walletform.repeatPasswordInputLabel',
defaultMessage: '!!!Repeat password',
Expand All @@ -43,175 +47,108 @@ const messages = defineMessages({
},
})

type FormValidationErrors = {
nameErrors: WalletNameValidationErrors,
passwordErrors: PasswordValidationErrors,
}

type ComponentState = {
name: string,
password: string,
passwordConfirmation: string,
showPasswordsDoNotMatchError: boolean,
}

type Props = {
intl: IntlShape,
walletNames: Array<string>,
onSubmit: ({
name: string,
password: string,
}) => mixed,
validateWalletName: (walletName: string) => WalletNameValidationErrors,
navigation: Navigation,
onSubmit: ({name: string, password: string}) => mixed,
}

class WalletForm extends PureComponent<Props, ComponentState> {
/* prettier-ignore */
state = CONFIG.DEBUG.PREFILL_FORMS
? {
name: CONFIG.DEBUG.WALLET_NAME,
password: CONFIG.DEBUG.PASSWORD,
passwordConfirmation: CONFIG.DEBUG.PASSWORD,
showPasswordsDoNotMatchError: false,
}
: {
name: '',
password: '',
passwordConfirmation: '',
showPasswordsDoNotMatchError: false,
}

_unsubscribe: void | (() => mixed) = undefined

debouncedHandlePasswordMatchValidation = _.debounce(() => {
this.setState(({password, passwordConfirmation}) => ({
showPasswordsDoNotMatchError: !!passwordConfirmation && password !== passwordConfirmation,
}))
}, 300)

componentDidMount = () => {
this._unsubscribe = this.props.navigation.addListener('blur', () => this.handleOnWillBlur())
}

componentWillUnmount = () => {
if (this._unsubscribe != null) this._unsubscribe()
}

handleOnWillBlur = () =>
this.setState({
password: '',
passwordConfirmation: '',
})

handleSubmit = () => {
const {name, password} = this.state

this.props.onSubmit({name, password})
}

handleSetName = (name) => this.setState({name})

handleSetPassword = (password) => {
this.debouncedHandlePasswordMatchValidation()
this.setState({password})
}

handleSetPasswordConfirmation = (passwordConfirmation) => {
this.debouncedHandlePasswordMatchValidation()
this.setState({passwordConfirmation})
}

validateForm = (): FormValidationErrors => {
const {name, password, passwordConfirmation} = this.state

const nameErrors = this.props.validateWalletName(name)
const passwordErrors = validatePassword(password, passwordConfirmation)

return {
const WalletForm = ({intl, onSubmit}: Props) => {
const walletNames = useSelector(walletNamesSelector)
const [name, setName] = React.useState(CONFIG.DEBUG.PREFILL_FORMS ? CONFIG.DEBUG.WALLET_NAME : '')
const nameErrors = validateWalletName(name, null, walletNames)
const walletNameErrorText =
getWalletNameError(
{
tooLong: intl.formatMessage(globalMessages.walletNameErrorTooLong),
nameAlreadyTaken: intl.formatMessage(globalMessages.walletNameErrorNameAlreadyTaken),
mustBeFilled: intl.formatMessage(globalMessages.walletNameErrorMustBeFilled),
},
nameErrors,
passwordErrors,
}
}

render() {
const {intl} = this.props
const {name, password, passwordConfirmation, showPasswordsDoNotMatchError} = this.state

const validationErrors = this.validateForm()

return (
<SafeAreaView style={styles.safeAreaView}>
<StatusBar type="dark" />

<ScrollView keyboardDismissMode="on-drag">
<View style={styles.content}>
<ValidatedTextInput
label={intl.formatMessage(messages.walletNameInputLabel)}
value={name}
onChangeText={this.handleSetName}
error={getWalletNameError(
{
tooLong: intl.formatMessage(globalMessages.walletNameErrorTooLong),
nameAlreadyTaken: intl.formatMessage(globalMessages.walletNameErrorNameAlreadyTaken),
},
validationErrors.nameErrors,
)}
testID="walletNameInput"
/>

<ValidatedTextInput
secureTextEntry
label={intl.formatMessage(messages.newPasswordInput)}
value={password}
onChangeText={this.handleSetPassword}
testID="walletPasswordInput"
/>

<ValidatedTextInput
secureTextEntry
label={intl.formatMessage(messages.repeatPasswordInputLabel)}
value={passwordConfirmation}
onChangeText={this.handleSetPasswordConfirmation}
error={showPasswordsDoNotMatchError && intl.formatMessage(messages.repeatPasswordInputError)}
testID="walletRepeatPasswordInput"
/>

<PasswordStrengthIndicator password={password} />
</View>
</ScrollView>

<View style={styles.action}>
<Button
onPress={this.handleSubmit}
disabled={
!_.isEmpty({
...validationErrors.nameErrors,
...validationErrors.passwordErrors,
})
}
title={intl.formatMessage(messages.continueButton)}
testID="walletFormContinueButton"
/>
</View>
</SafeAreaView>
)
}
) || undefined

const passwordRef = React.useRef<{focus: () => void} | null>(null)
const [password, setPassword] = React.useState(CONFIG.DEBUG.PREFILL_FORMS ? CONFIG.DEBUG.PASSWORD : '')

const passwordConfirmationRef = React.useRef<{focus: () => void} | null>(null)
const [passwordConfirmation, setPasswordConfirmation] = React.useState(
CONFIG.DEBUG.PREFILL_FORMS ? CONFIG.DEBUG.PASSWORD : '',
)
const passwordErrors = validatePassword(password, passwordConfirmation)
const passwordErrorText = passwordErrors.passwordIsWeak
? intl.formatMessage(messages.passwordStrengthRequirement, {requiredPasswordLength: REQUIRED_PASSWORD_LENGTH})
: undefined
const passwordConfirmationErrorText = passwordErrors.matchesConfirmation
? intl.formatMessage(messages.repeatPasswordInputError)
: undefined

return (
<SafeAreaView edges={['left', 'right', 'bottom']} style={styles.safeAreaView}>
<ScrollView keyboardShouldPersistTaps={'always'} contentContainerStyle={styles.scrollContentContainer}>
<WalletNameInput
enablesReturnKeyAutomatically
autoFocus
label={intl.formatMessage(messages.walletNameInputLabel)}
value={name}
onChangeText={setName}
errorText={walletNameErrorText}
errorDelay={0}
returnKeyType={'next'}
onSubmitEditing={() => passwordRef.current?.focus()}
testID="walletNameInput"
/>

<Spacer />

<PasswordInput
enablesReturnKeyAutomatically
ref={passwordRef}
secureTextEntry
label={intl.formatMessage(messages.newPasswordInput)}
value={password}
onChangeText={setPassword}
errorText={passwordErrorText}
returnKeyType={'next'}
helperText={intl.formatMessage(messages.passwordStrengthRequirement, {
requiredPasswordLength: REQUIRED_PASSWORD_LENGTH,
})}
right={!passwordErrors.passwordIsWeak ? <Checkmark /> : undefined}
onSubmitEditing={() => passwordConfirmationRef.current?.focus()}
testID="walletPasswordInput"
/>

<Spacer />

<PasswordConfirmationInput
enablesReturnKeyAutomatically
ref={passwordConfirmationRef}
secureTextEntry
returnKeyType={'done'}
label={intl.formatMessage(messages.repeatPasswordInputLabel)}
value={passwordConfirmation}
onChangeText={setPasswordConfirmation}
errorText={passwordConfirmationErrorText}
right={
!passwordErrors.matchesConfirmation && !passwordErrors.passwordConfirmationReq ? <Checkmark /> : undefined
}
testID="walletRepeatPasswordInput"
/>
</ScrollView>

<View style={styles.action}>
<Button
onPress={() => onSubmit({name, password})}
disabled={Object.keys(passwordErrors).length > 0 || Object.keys(nameErrors).length > 0}
title={intl.formatMessage(messages.continueButton)}
testID="walletFormContinueButton"
/>
</View>
</SafeAreaView>
)
}

export default injectIntl(
compose(
connect(
(state) => ({
walletNames: walletNamesSelector(state),
}),
{validateWalletName},
),
withHandlers({
validateWalletName:
({walletNames}) =>
(walletName) =>
validateWalletName(walletName, null, walletNames),
}),
)(WalletForm),
)
export default injectIntl(WalletForm)

const Spacer = () => <View style={styles.spacer} />
const WalletNameInput = TextInput
const PasswordInput = TextInput
const PasswordConfirmationInput = TextInput
13 changes: 9 additions & 4 deletions src/components/WalletInit/styles/WalletForm.style.js
@@ -1,18 +1,23 @@
// @flow

import {StyleSheet} from 'react-native'
import {COLORS} from '../../../styles/config'

export default StyleSheet.create({
safeAreaView: {
backgroundColor: '#fff',
backgroundColor: COLORS.BACKGROUND,
flex: 1,
},
content: {
padding: 16,
scrollContentContainer: {
flex: 1,
paddingHorizontal: 16,
paddingTop: 40,
},
action: {
padding: 16,
backgroundColor: '#fff',
backgroundColor: COLORS.BACKGROUND,
},
spacer: {
height: 16,
},
})
1 change: 1 addition & 0 deletions src/i18n/locales/en-US.json
Expand Up @@ -280,6 +280,7 @@
"components.walletinit.connectnanox.savenanoxscreen.save": "Save",
"components.walletinit.connectnanox.savenanoxscreen.title": "Save wallet",
"components.walletinit.createwallet.createwalletscreen.title": "Create a new wallet",
"components.walletinit.createwallet.createwalletscreen.passwordLengthRequirement": "Minimum {requiredPasswordLength} characters",
"components.walletinit.createwallet.mnemonicbackupimportancemodal.confirmationButton": "I understand",
"components.walletinit.createwallet.mnemonicbackupimportancemodal.keysStorageCheckbox": "I understand that my secret keys are held securely on this device only, not on the company`s servers",
"components.walletinit.createwallet.mnemonicbackupimportancemodal.newDeviceRecoveryCheckbox": "I understand that if this application is moved to another device or deleted, my funds can be only recovered with the backup phrase that I have written down and saved in a secure place.",
Expand Down
4 changes: 3 additions & 1 deletion src/utils/validators.js
Expand Up @@ -67,12 +67,14 @@ export type PasswordStrength = {|

const pickOnlyFailingValidations = (validation: Object) => _.pickBy(validation)

export const REQUIRED_PASSWORD_LENGTH = 10

export const getPasswordStrength = (password: string): PasswordStrength => {
if (!password) {
return {isStrong: false}
}

if (password.length >= 10) {
if (password.length >= REQUIRED_PASSWORD_LENGTH) {
return {isStrong: true, satisfiesPasswordRequirement: true}
}

Expand Down

0 comments on commit 17d6534

Please sign in to comment.