From 49db63000eb410ef46ecb0a3489a8c8218c00936 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Mon, 16 Aug 2021 19:12:36 +0100 Subject: [PATCH 01/30] added icon button --- assets/images/eye-disabled.svg | 15 +++++++++ .../ExpensiTextInput/BaseExpensiTextInput.js | 33 +++++++++++++++++-- src/components/Icon/Expensicons.js | 2 ++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 assets/images/eye-disabled.svg diff --git a/assets/images/eye-disabled.svg b/assets/images/eye-disabled.svg new file mode 100644 index 000000000000..804a36c63777 --- /dev/null +++ b/assets/images/eye-disabled.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 6c911d4fde85..334c573a56bb 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -1,12 +1,18 @@ import React, {Component} from 'react'; import { - Animated, TextInput, View, TouchableWithoutFeedback, + Animated, TextInput, View, TouchableWithoutFeedback, Pressable, } from 'react-native'; import Str from 'expensify-common/lib/str'; import ExpensiTextInputLabel from './ExpensiTextInputLabel'; import {propTypes, defaultProps} from './propTypes'; import themeColors from '../../styles/themes/default'; -import styles from '../../styles/styles'; +import styles, {getIconFillColor} from '../../styles/styles'; +import Icon from '../Icon'; +import { + Eye, + EyeDisabled, +} from '../Icon/Expensicons'; +import getButtonState from '../../libs/getButtonState'; const ACTIVE_LABEL_TRANSLATE_Y = -10; const ACTIVE_LABEL_TRANSLATE_X = (translateX = -22) => translateX; @@ -36,6 +42,7 @@ class BaseExpensiTextInput extends Component { this.onFocus = this.onFocus.bind(this); this.onBlur = this.onBlur.bind(this); this.setValue = this.setValue.bind(this); + this.secureTextEntry = props.secureTextEntry; } componentDidMount() { @@ -118,6 +125,7 @@ class BaseExpensiTextInput extends Component { ignoreLabelTranslateX, innerRef, autoFocus, + secureTextEntry, ...inputProps } = this.props; @@ -145,6 +153,25 @@ class BaseExpensiTextInput extends Component { labelScale={this.state.labelScale} /> ) : null} + {secureTextEntry && ( + + {({hovered, pressed}) => ( + + )} + + )} { if (typeof innerRef === 'function') { innerRef(ref); } @@ -156,7 +183,7 @@ class BaseExpensiTextInput extends Component { placeholder={(this.state.isFocused || !label) ? placeholder : null} placeholderTextColor={themeColors.placeholderText} underlineColorAndroid="transparent" - style={inputStyle} + style={[...inputStyle]} onFocus={this.onFocus} onBlur={this.onBlur} onChangeText={this.setValue} diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index 8555edc73957..1d69e85e04ff 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -17,6 +17,7 @@ import Download from '../../../assets/images/download.svg'; import Emoji from '../../../assets/images/emoji.svg'; import Exclamation from '../../../assets/images/exclamation.svg'; import Eye from '../../../assets/images/eye.svg'; +import EyeDisabled from '../../../assets/images/eye-disabled.svg'; import ExpensifyCard from '../../../assets/images/expensifycard.svg'; import Gallery from '../../../assets/images/gallery.svg'; import Gear from '../../../assets/images/gear.svg'; @@ -74,6 +75,7 @@ export { Emoji, Exclamation, Eye, + EyeDisabled, ExpensifyCard, Gallery, Gear, From e0e5e7b85911a9dfd147eeaf2e0d38a5d774e625 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Tue, 17 Aug 2021 11:57:00 +0100 Subject: [PATCH 02/30] secure input toggleable --- .../ExpensiTextInput/BaseExpensiTextInput.js | 14 ++++++++++---- src/styles/styles.js | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 334c573a56bb..a81d99285819 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -34,6 +34,7 @@ class BaseExpensiTextInput extends Component { labelTranslateX: new Animated.Value(hasValue ? ACTIVE_LABEL_TRANSLATE_X(props.translateX) : INACTIVE_LABEL_TRANSLATE_X), labelScale: new Animated.Value(hasValue ? ACTIVE_LABEL_SCALE : INACTIVE_LABEL_SCALE), + passwordHidden: true, }; this.input = null; @@ -114,6 +115,11 @@ class BaseExpensiTextInput extends Component { ]).start(); } + togglePasswordVisibility() { + this.setState(prevState => ({passwordHidden: !prevState.passwordHidden})); + } + + render() { const { label, @@ -156,17 +162,16 @@ class BaseExpensiTextInput extends Component { {secureTextEntry && ( this.togglePasswordVisibility()} > {({hovered, pressed}) => ( )} @@ -187,6 +192,7 @@ class BaseExpensiTextInput extends Component { onFocus={this.onFocus} onBlur={this.onBlur} onChangeText={this.setValue} + secureTextEntry={this.state.passwordHidden} /> diff --git a/src/styles/styles.js b/src/styles/styles.js index db54e91847e8..040708921378 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -540,6 +540,13 @@ const styles = { padding: 0, left, }), + secureInputEyeButton: { + alignSelf: 'flex-end', + borderRadius: 6, + height: 32, + margin: 3, + justifyContent: 'center', + }, textInput: { backgroundColor: themeColors.componentBG, borderRadius: variables.componentBorderRadiusNormal, From 19c8bf41760033071204e7bd976922278c78bf2b Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Tue, 17 Aug 2021 12:35:07 +0100 Subject: [PATCH 03/30] defaults password to be visible on native apps --- src/components/ExpensiTextInput/BaseExpensiTextInput.js | 2 +- src/components/ExpensiTextInput/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index a81d99285819..1cbad6bfb9d7 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -34,7 +34,7 @@ class BaseExpensiTextInput extends Component { labelTranslateX: new Animated.Value(hasValue ? ACTIVE_LABEL_TRANSLATE_X(props.translateX) : INACTIVE_LABEL_TRANSLATE_X), labelScale: new Animated.Value(hasValue ? ACTIVE_LABEL_SCALE : INACTIVE_LABEL_SCALE), - passwordHidden: true, + passwordHidden: props.defaultHidePassword, }; this.input = null; diff --git a/src/components/ExpensiTextInput/index.js b/src/components/ExpensiTextInput/index.js index c56aaee213ef..62e41cc67f85 100644 --- a/src/components/ExpensiTextInput/index.js +++ b/src/components/ExpensiTextInput/index.js @@ -10,6 +10,7 @@ const ExpensiTextInput = forwardRef((props, ref) => ( innerRef={ref} inputStyle={[styles.expensiTextInput, styles.expensiTextInputDesktop]} ignoreLabelTranslateX + defaultHidePassword /> )); From 1a55353a3a0581e1d4e99a59c5e47a73d81f41d3 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Wed, 18 Aug 2021 17:05:54 +0100 Subject: [PATCH 04/30] styling of icon --- .../ExpensiTextInput/BaseExpensiTextInput.js | 69 ++++++++++--------- src/styles/styles.js | 5 +- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 1cbad6bfb9d7..77f83fdb05e0 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -13,6 +13,7 @@ import { EyeDisabled, } from '../Icon/Expensicons'; import getButtonState from '../../libs/getButtonState'; +import display from '../../styles/utilities/display'; const ACTIVE_LABEL_TRANSLATE_Y = -10; const ACTIVE_LABEL_TRANSLATE_X = (translateX = -22) => translateX; @@ -34,7 +35,7 @@ class BaseExpensiTextInput extends Component { labelTranslateX: new Animated.Value(hasValue ? ACTIVE_LABEL_TRANSLATE_X(props.translateX) : INACTIVE_LABEL_TRANSLATE_X), labelScale: new Animated.Value(hasValue ? ACTIVE_LABEL_SCALE : INACTIVE_LABEL_SCALE), - passwordHidden: props.defaultHidePassword, + passwordHidden: props.defaultHidePassword && props.secureTextEntry, }; this.input = null; @@ -159,41 +160,43 @@ class BaseExpensiTextInput extends Component { labelScale={this.state.labelScale} /> ) : null} - {secureTextEntry && ( - + { + if (typeof innerRef === 'function') { innerRef(ref); } + this.input = ref; + }} + // eslint-disable-next-line + {...inputProps} + value={value} + placeholder={(this.state.isFocused || !label) ? placeholder : null} + placeholderTextColor={themeColors.placeholderText} + underlineColorAndroid="transparent" + style={[...inputStyle, {flex: 1, paddingTop: 13}]} + onFocus={this.onFocus} + onBlur={this.onBlur} + onChangeText={this.setValue} + secureTextEntry={this.state.passwordHidden} + /> + {secureTextEntry && ( + this.togglePasswordVisibility()} - > - {({hovered, pressed}) => ( - + style={[ + styles.secureInputEyeButton, + ]} + onPress={() => this.togglePasswordVisibility()} + > + {({hovered, pressed}) => ( + + )} + )} - - )} - { - if (typeof innerRef === 'function') { innerRef(ref); } - this.input = ref; - }} - // eslint-disable-next-line - {...inputProps} - value={value} - placeholder={(this.state.isFocused || !label) ? placeholder : null} - placeholderTextColor={themeColors.placeholderText} - underlineColorAndroid="transparent" - style={[...inputStyle]} - onFocus={this.onFocus} - onBlur={this.onBlur} - onChangeText={this.setValue} - secureTextEntry={this.state.passwordHidden} - /> + diff --git a/src/styles/styles.js b/src/styles/styles.js index 040708921378..7eb775ac3143 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -505,7 +505,7 @@ const styles = { borderWidth: 1, borderRadius: variables.componentBorderRadiusNormal, borderColor: themeColors.border, - paddingTop: 25, + paddingTop: 12, paddingBottom: 8, paddingHorizontal: 12, justifyContent: 'center', @@ -541,7 +541,8 @@ const styles = { left, }), secureInputEyeButton: { - alignSelf: 'flex-end', + right: 0, + top: 0, borderRadius: 6, height: 32, margin: 3, From b3bf88b694e568196dd3747e6c0b3103f3712761 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Wed, 18 Aug 2021 17:16:24 +0100 Subject: [PATCH 05/30] lintfix --- src/components/ExpensiTextInput/BaseExpensiTextInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index 77f83fdb05e0..b73c6309ba6d 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -13,7 +13,6 @@ import { EyeDisabled, } from '../Icon/Expensicons'; import getButtonState from '../../libs/getButtonState'; -import display from '../../styles/utilities/display'; const ACTIVE_LABEL_TRANSLATE_Y = -10; const ACTIVE_LABEL_TRANSLATE_X = (translateX = -22) => translateX; From ce21ba9ba7a226c759395144d515ae20d26c5348 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Wed, 18 Aug 2021 17:30:21 +0100 Subject: [PATCH 06/30] fix padding where no label convert inline styles to utilities --- src/components/ExpensiTextInput/BaseExpensiTextInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index b73c6309ba6d..711fba3ae687 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -159,7 +159,7 @@ class BaseExpensiTextInput extends Component { labelScale={this.state.labelScale} /> ) : null} - + { if (typeof innerRef === 'function') { innerRef(ref); } @@ -171,7 +171,7 @@ class BaseExpensiTextInput extends Component { placeholder={(this.state.isFocused || !label) ? placeholder : null} placeholderTextColor={themeColors.placeholderText} underlineColorAndroid="transparent" - style={[...inputStyle, {flex: 1, paddingTop: 13}]} + style={[...inputStyle, styles.flex1, styles.pt3, !hasLabel && styles.pv0]} onFocus={this.onFocus} onBlur={this.onBlur} onChangeText={this.setValue} From e6e57adfb54238eb3b4b4e69f63653b0ef0afc96 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Wed, 18 Aug 2021 19:53:33 +0100 Subject: [PATCH 07/30] remove confirm password input --- src/languages/en.js | 3 -- src/languages/es.js | 3 -- src/pages/settings/NewPasswordForm.js | 52 +++------------------------ src/pages/settings/PasswordPage.js | 48 +++---------------------- 4 files changed, 9 insertions(+), 97 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 3f7caa694858..c4cb25420702 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -248,7 +248,6 @@ export default { currentPassword: 'Current Password', newPassword: 'New Password', newPasswordPrompt: 'New password must be different than your old password, have at least 8 characters,\n1 capital letter, 1 lowercase letter, 1 number.', - confirmNewPassword: 'Confirm New Password', }, addPayPalMePage: { enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.', @@ -341,9 +340,7 @@ export default { }, setPasswordPage: { enterPassword: 'Enter a password', - confirmNewPassword: 'Confirm the password', setPassword: 'Set Password', - passwordsDontMatch: 'Passwords must match', newPasswordPrompt: 'Your password must have at least 8 characters,\n1 capital letter, 1 lowercase letter, 1 number.', passwordFormTitle: 'Welcome back to the New Expensify! Please set your password.', }, diff --git a/src/languages/es.js b/src/languages/es.js index c343397d1722..c8646dd3ec9b 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -248,7 +248,6 @@ export default { currentPassword: 'Contraseña Actual', newPassword: 'Nueva contraseña', newPasswordPrompt: 'La nueva contraseña tiene que ser diferente de la antigua, tener al menos 8 letras,\n1 letra mayúscula, 1 letra minúscula y 1 número.', - confirmNewPassword: 'Confirma la Nueva Contraseña', }, addPayPalMePage: { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', @@ -341,9 +340,7 @@ export default { }, setPasswordPage: { enterPassword: 'Escribe una contraseña', - confirmNewPassword: 'Confirma la contraseña', setPassword: 'Configura tu Contraseña', - passwordsDontMatch: 'Las contraseñas deben coincidir', newPasswordPrompt: 'Su contraseña debe tener al menos 8 caracteres, \n1 letra mayúscula, 1 letra minúscula, 1 número.', passwordFormTitle: '¡Bienvenido de vuelta al Nuevo Expensify! Por favor, elige una contraseña.', }, diff --git a/src/pages/settings/NewPasswordForm.js b/src/pages/settings/NewPasswordForm.js index a17d1bc7d560..eb84525fb9b6 100644 --- a/src/pages/settings/NewPasswordForm.js +++ b/src/pages/settings/NewPasswordForm.js @@ -29,17 +29,14 @@ class NewPasswordForm extends React.Component { super(props); this.state = { - confirmNewPassword: '', passwordHintError: false, - shouldShowPasswordConfirmError: false, }; } - componentDidUpdate(prevProps, prevState) { - const eitherPasswordChanged = (this.props.password !== prevProps.password) - || this.state.confirmNewPassword !== prevState.confirmNewPassword; - if (eitherPasswordChanged) { - this.props.updateIsFormValid(this.isValidForm()); + componentDidUpdate(prevProps) { + const passwordChanged = (this.props.password !== prevProps.password); + if (passwordChanged) { + this.props.updateIsFormValid(this.isValidPassword()); } } @@ -52,34 +49,11 @@ class NewPasswordForm extends React.Component { } } - onBlurConfirmPassword() { - if (this.state.shouldShowPasswordConfirmError) { - return; - } - if (this.state.confirmNewPassword && !this.doPasswordsMatch()) { - this.setState({shouldShowPasswordConfirmError: true}); - } - } isValidPassword() { return this.props.password.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); } - doPasswordsMatch() { - return this.props.password === this.state.confirmNewPassword; - } - - isValidForm() { - return this.isValidPassword() && this.doPasswordsMatch(); - } - - showPasswordMatchError() { - return Boolean( - !this.doPasswordsMatch() - && this.state.shouldShowPasswordConfirmError - && this.state.confirmNewPassword, - ); - } render() { let passwordHintStyle; @@ -100,29 +74,13 @@ class NewPasswordForm extends React.Component { textContentType="password" value={this.state.password} onChangeText={password => this.props.updatePassword(password)} + onSubmitEditing={() => this.props.onSubmitEditing()} onBlur={() => this.onBlurNewPassword()} /> {this.props.translate('setPasswordPage.newPasswordPrompt')} - - this.setState({confirmNewPassword})} - onSubmitEditing={() => this.props.onSubmitEditing()} - onBlur={() => this.onBlurConfirmPassword()} - /> - {this.showPasswordMatchError() && ( - - {this.props.translate('setPasswordPage.passwordsDontMatch')} - - )} - ); } diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js index d959d39a5c7b..6fa889184965 100755 --- a/src/pages/settings/PasswordPage.js +++ b/src/pages/settings/PasswordPage.js @@ -48,7 +48,6 @@ class PasswordPage extends Component { this.state = { currentPassword: '', newPassword: '', - confirmNewPassword: '', isPasswordRequirementsVisible: false, shouldShowPasswordConfirmError: false, }; @@ -62,29 +61,11 @@ class PasswordPage extends Component { } onBlurNewPassword() { - const stateToUpdate = {}; - if (!this.state.newPassword || !this.isValidPassword()) { - stateToUpdate.isPasswordRequirementsVisible = true; - } else { - stateToUpdate.isPasswordRequirementsVisible = false; - } - - if (this.state.newPassword && this.state.confirmNewPassword && !this.doPasswordsMatch()) { - stateToUpdate.shouldShowPasswordConfirmError = true; - } - - if (!isEmpty(stateToUpdate)) { - this.setState(stateToUpdate); - } + this.setState(prevState => ( + {isPasswordRequirementsVisible: !prevState.newPassword || !this.isValidPassword()} + )); } - onBlurConfirmPassword() { - if (!this.state.confirmNewPassword || !this.doPasswordsMatch()) { - this.setState({shouldShowPasswordConfirmError: true}); - } else { - this.setState({shouldShowPasswordConfirmError: false}); - } - } isValidPassword() { return this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); @@ -100,9 +81,6 @@ class PasswordPage extends Component { }); } - doPasswordsMatch() { - return this.state.newPassword === this.state.confirmNewPassword; - } render() { return ( @@ -143,6 +121,7 @@ class PasswordPage extends Component { textContentType="password" value={this.state.newPassword} onChangeText={newPassword => this.setState({newPassword})} + onSubmitEditing={this.handleChangePassword} onFocus={() => this.setState({isPasswordRequirementsVisible: true})} onBlur={() => this.onBlurNewPassword()} /> @@ -152,36 +131,17 @@ class PasswordPage extends Component { )} - - this.setState({confirmNewPassword})} - onSubmitEditing={this.handleChangePassword} - onBlur={() => this.onBlurConfirmPassword()} - /> - {!this.state.shouldShowPasswordConfirmError && !isEmpty(this.props.account.error) && ( {this.props.account.error} )} - {this.state.shouldShowPasswordConfirmError && ( - - {this.props.translate('setPasswordPage.passwordsDontMatch')} - - )}