From 4e8d8dd7945f8aaf3d2f5052a2ad0b219909ff24 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Tue, 25 May 2021 19:00:56 +0100 Subject: [PATCH 001/115] first iteration of component --- src/languages/en.js | 6 +++ src/pages/settings/PasswordPage.js | 6 ++- src/pages/settings/TestComp.js | 81 ++++++++++++++++++++++++++++++ src/styles/styles.js | 8 +++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/pages/settings/TestComp.js diff --git a/src/languages/en.js b/src/languages/en.js index f373d51bdd27..06852b3a4550 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -210,7 +210,13 @@ export default { setPasswordPage: { passwordCannotBeBlank: 'Password cannot be blank', 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.', + + }, attachmentView: { unknownFilename: 'Unknown Filename', diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js index 1685dcc11caf..8a51ebf5a908 100755 --- a/src/pages/settings/PasswordPage.js +++ b/src/pages/settings/PasswordPage.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import {View, TextInput} from 'react-native'; +import {TextInput, View} from 'react-native'; import Onyx, {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import {isEmpty} from 'underscore'; @@ -16,6 +16,7 @@ import ButtonWithLoader from '../../components/ButtonWithLoader'; import {changePassword} from '../../libs/actions/User'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; +import TestComp from './TestComp'; const propTypes = { /* Onyx Props */ @@ -38,6 +39,7 @@ const propTypes = { const defaultProps = { account: {}, }; + class PasswordPage extends Component { constructor(props) { super(props); @@ -144,8 +146,10 @@ class PasswordPage extends Component { onClick={this.handleChangePassword} /> + + ); } } diff --git a/src/pages/settings/TestComp.js b/src/pages/settings/TestComp.js new file mode 100644 index 000000000000..b02ddf20e45f --- /dev/null +++ b/src/pages/settings/TestComp.js @@ -0,0 +1,81 @@ +import {withOnyx} from 'react-native-onyx'; +import React from 'react'; +import {TextInput, View} from 'react-native'; +import ONYXKEYS from '../../ONYXKEYS'; +import compose from '../../libs/compose'; +import withLocalize from '../../components/withLocalize'; +import styles from '../../styles/styles'; +import Text from '../../components/Text'; +import CONST from '../../CONST'; + +class NewPassword extends React.Component { + constructor(props) { + super(props); + + this.state = { + newPassword: '', + confirmNewPassword: '', + focusIsOnNewPassword: false, + focusIsOnConfirmNewPassword: false, + }; + } + + render() { + const meetsPasswordRules = this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); + const shouldShowPasswordHint = this.state.focusIsOnNewPassword || (this.state.newPassword.length > 0 && !meetsPasswordRules); + const passwordsMatch = this.state.newPassword !== this.state.confirmNewPassword; + + + return ( + <> + + + {`${this.props.translate('setPasswordPage.enterPassword')}*`} + + this.setState({newPassword})} + onFocus={() => this.setState({focusIsOnNewPassword: true})} + onBlur={() => this.setState({focusIsOnNewPassword: false})} + /> + {shouldShowPasswordHint && ( + + {this.props.translate('setPasswordPage.newPasswordPrompt')} + + )} + + + + {`${this.props.translate('setPasswordPage.confirmNewPassword')}*`} + + this.setState({confirmNewPassword})} + onSubmitEditing={this.handleChangePassword} + onFocus={() => this.setState({focusIsOnConfirmNewPassword: true})} + onBlur={() => this.setState({focusIsOnConfirmNewPassword: false})} + /> + {(passwordsMatch && this.state.focusIsOnConfirmNewPassword) && ( + + {this.props.translate('setPasswordPage.passwordsDontMatch')} + + )} + + + ); + } +} +export default compose( + withLocalize, + withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + }), +)(NewPassword); diff --git a/src/styles/styles.js b/src/styles/styles.js index 432b0ca1dd90..ecd0ff074636 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -360,6 +360,14 @@ const styles = { lineHeight: 18, }, + textSuccess: { + color: themeColors.textSuccess, + }, + + textError: { + color: themeColors.textError, + }, + signInPage: { backgroundColor: themeColors.sidebar, padding: 20, From cefc4c22b58de869c6b34df0bdb9f211bdd0474e Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Thu, 27 May 2021 19:53:42 +0100 Subject: [PATCH 002/115] change user error UX and wire component into setPassword page --- src/pages/SetPasswordPage.js | 35 ++------ .../settings/{TestComp.js => NewPassword.js} | 90 +++++++++++++------ src/pages/settings/PasswordPage.js | 2 - src/styles/styles.js | 8 -- 4 files changed, 72 insertions(+), 63 deletions(-) rename src/pages/settings/{TestComp.js => NewPassword.js} (50%) diff --git a/src/pages/SetPasswordPage.js b/src/pages/SetPasswordPage.js index 9f07d33c801d..2ffbf4a2fc71 100755 --- a/src/pages/SetPasswordPage.js +++ b/src/pages/SetPasswordPage.js @@ -19,6 +19,7 @@ import SignInPageLayout from './signin/SignInPageLayout'; import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; +import NewPassword from './settings/NewPassword'; const propTypes = { /* Onyx Props */ @@ -63,7 +64,7 @@ class SetPasswordPage extends Component { this.state = { password: '', - formError: null, + passwordIsValid: false, }; } @@ -71,16 +72,9 @@ class SetPasswordPage extends Component { * Validate the form and then submit it */ validateAndSubmitForm() { - if (!this.state.password.trim()) { - this.setState({ - formError: this.props.translate('setPasswordPage.passwordCannotBeBlank'), - }); + if (!this.state.passwordIsValid) { return; } - - this.setState({ - formError: null, - }); setPassword( this.state.password, lodashGet(this.props.route, 'params.validateCode', ''), @@ -93,20 +87,11 @@ class SetPasswordPage extends Component { - - {this.props.translate('setPasswordPage.enterPassword')} - - this.setState({password: text})} + this.setState({password})} + passwordIsValid={isValid => this.setState({passwordIsValid: isValid})} onSubmitEditing={this.validateAndSubmitForm} - autoCapitalize="none" - placeholderTextColor={themeColors.placeholderText} - autoFocus={canFocusInputOnScreenFocus()} /> @@ -117,12 +102,6 @@ class SetPasswordPage extends Component { /> - {this.state.formError && ( - - {this.state.formError} - - )} - {!_.isEmpty(this.props.account.error) && ( {this.props.account.error} diff --git a/src/pages/settings/TestComp.js b/src/pages/settings/NewPassword.js similarity index 50% rename from src/pages/settings/TestComp.js rename to src/pages/settings/NewPassword.js index b02ddf20e45f..c775464601a6 100644 --- a/src/pages/settings/TestComp.js +++ b/src/pages/settings/NewPassword.js @@ -1,31 +1,69 @@ -import {withOnyx} from 'react-native-onyx'; import React from 'react'; +import PropTypes from 'prop-types'; import {TextInput, View} from 'react-native'; -import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; -import withLocalize from '../../components/withLocalize'; -import styles from '../../styles/styles'; import Text from '../../components/Text'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import CONST from '../../CONST'; +import compose from '../../libs/compose'; +import styles from '../../styles/styles'; + +const propTypes = { + password: PropTypes.string.isRequired, + setPassword: PropTypes.func.isRequired, + passwordIsValid: PropTypes.func.isRequired, + onSubmitEditing: PropTypes.func.isRequired, + ...withLocalizePropTypes, +}; class NewPassword extends React.Component { constructor(props) { super(props); this.state = { - newPassword: '', confirmNewPassword: '', - focusIsOnNewPassword: false, focusIsOnConfirmNewPassword: false, + passwordHintError: false, }; } - render() { - const meetsPasswordRules = this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); - const shouldShowPasswordHint = this.state.focusIsOnNewPassword || (this.state.newPassword.length > 0 && !meetsPasswordRules); - const passwordsMatch = this.state.newPassword !== this.state.confirmNewPassword; + componentDidUpdate(prevProps, prevState) { + if ( + this.props.password !== prevProps.password + // eslint-disable-next-line max-len + || this.state.confirmNewPassword !== prevState.confirmNewPassword) { this.props.passwordIsValid(this.formIsValid()); } + } + onBlur() { + if (this.state.passwordHintError) { + return; + } + if (this.props.password && !this.meetsPasswordRules()) { + this.setState({passwordHintError: true}); + } + } + + meetsPasswordRules() { + return this.props.password.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); + } + + passwordsMatch() { + return this.props.password === this.state.confirmNewPassword; + } + + formIsValid() { + return this.meetsPasswordRules() && this.passwordsMatch(); + } + + showPasswordMatchError() { + return !this.passwordsMatch() && !this.state.focusIsOnConfirmNewPassword && this.state.confirmNewPassword; + } + + render() { + const passwordHintStyle = this.state.passwordHintError + ? styles.formError + : undefined; + return ( <> @@ -33,49 +71,51 @@ class NewPassword extends React.Component { {`${this.props.translate('setPasswordPage.enterPassword')}*`} this.setState({newPassword})} + value={this.state.password} + onChangeText={password => this.props.setPassword(password)} onFocus={() => this.setState({focusIsOnNewPassword: true})} - onBlur={() => this.setState({focusIsOnNewPassword: false})} + onBlur={() => this.onBlur()} /> - {shouldShowPasswordHint && ( - + {this.props.translate('setPasswordPage.newPasswordPrompt')} - )} {`${this.props.translate('setPasswordPage.confirmNewPassword')}*`} this.setState({confirmNewPassword})} - onSubmitEditing={this.handleChangePassword} + onSubmitEditing={() => this.props.onSubmitEditing()} onFocus={() => this.setState({focusIsOnConfirmNewPassword: true})} onBlur={() => this.setState({focusIsOnConfirmNewPassword: false})} /> - {(passwordsMatch && this.state.focusIsOnConfirmNewPassword) && ( - - {this.props.translate('setPasswordPage.passwordsDontMatch')} - + {this.showPasswordMatchError() && ( + + {this.props.translate('setPasswordPage.passwordsDontMatch')} + )} ); } } + +NewPassword.propTypes = propTypes; + export default compose( withLocalize, - withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - }), )(NewPassword); diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js index 8a51ebf5a908..dc045966092e 100755 --- a/src/pages/settings/PasswordPage.js +++ b/src/pages/settings/PasswordPage.js @@ -16,7 +16,6 @@ import ButtonWithLoader from '../../components/ButtonWithLoader'; import {changePassword} from '../../libs/actions/User'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; -import TestComp from './TestComp'; const propTypes = { /* Onyx Props */ @@ -146,7 +145,6 @@ class PasswordPage extends Component { onClick={this.handleChangePassword} /> - diff --git a/src/styles/styles.js b/src/styles/styles.js index ecd0ff074636..432b0ca1dd90 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -360,14 +360,6 @@ const styles = { lineHeight: 18, }, - textSuccess: { - color: themeColors.textSuccess, - }, - - textError: { - color: themeColors.textError, - }, - signInPage: { backgroundColor: themeColors.sidebar, padding: 20, From 486571add5222838073514f6457b1da20e8ff487 Mon Sep 17 00:00:00 2001 From: Anthony Hull Date: Thu, 27 May 2021 20:10:15 +0100 Subject: [PATCH 003/115] clean up imports --- src/pages/SetPasswordPage.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/SetPasswordPage.js b/src/pages/SetPasswordPage.js index 2ffbf4a2fc71..779074e0922e 100755 --- a/src/pages/SetPasswordPage.js +++ b/src/pages/SetPasswordPage.js @@ -2,7 +2,6 @@ import React, {Component} from 'react'; import { SafeAreaView, Text, - TextInput, View, } from 'react-native'; import PropTypes from 'prop-types'; @@ -14,9 +13,7 @@ import styles from '../styles/styles'; import {setPassword} from '../libs/actions/Session'; import ONYXKEYS from '../ONYXKEYS'; import ButtonWithLoader from '../components/ButtonWithLoader'; -import themeColors from '../styles/themes/default'; import SignInPageLayout from './signin/SignInPageLayout'; -import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; import NewPassword from './settings/NewPassword'; From 58901ecb26c60dbe60afbffe7d2c1a702a737045 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 28 May 2021 02:57:22 +0530 Subject: [PATCH 004/115] button styling fixed --- src/components/FixedFooter.js | 28 +++ src/libs/KeyboardAvoidingView/index.js | 16 +- src/pages/settings/AddSecondaryLoginPage.js | 99 ++++----- src/pages/settings/PasswordPage.js | 28 +-- src/pages/settings/Profile/ProfilePage.js | 234 ++++++++++---------- 5 files changed, 213 insertions(+), 192 deletions(-) create mode 100644 src/components/FixedFooter.js diff --git a/src/components/FixedFooter.js b/src/components/FixedFooter.js new file mode 100644 index 000000000000..976134fa1e31 --- /dev/null +++ b/src/components/FixedFooter.js @@ -0,0 +1,28 @@ +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; + +const propTypes = { + /** Children to wrap in FixedFooter. */ + children: PropTypes.node.isRequired, + + /** Styles to be assigned to Container */ + style: PropTypes.arrayOf(PropTypes.object), + +}; + +const defaultProps = { + style: [], +}; + +const FixedFooter = props => ( + + {props.children} + +); + +FixedFooter.propTypes = propTypes; +FixedFooter.defaultProps = defaultProps; +FixedFooter.displayName = 'FixedFooter'; +export default FixedFooter; diff --git a/src/libs/KeyboardAvoidingView/index.js b/src/libs/KeyboardAvoidingView/index.js index 894cbd36e6b5..985240a00777 100644 --- a/src/libs/KeyboardAvoidingView/index.js +++ b/src/libs/KeyboardAvoidingView/index.js @@ -2,10 +2,7 @@ * This is a KeyboardAvoidingView only enabled for ios && disabled for all other platforms * @param {Node} */ -import React from 'react'; -import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; import PropTypes from 'prop-types'; -import styles from '../../styles/styles'; const propTypes = { children: PropTypes.node, @@ -14,18 +11,9 @@ const defaultProps = { children: null, }; -function KeyboardAvoidingView({children}) { - return ( - - {children} - - ); -} +const KeyboardAvoidingView = props => props.children; KeyboardAvoidingView.propTypes = propTypes; KeyboardAvoidingView.defaultProps = defaultProps; +KeyboardAvoidingView.displayName = 'KeyboardAvoidingView'; export default KeyboardAvoidingView; diff --git a/src/pages/settings/AddSecondaryLoginPage.js b/src/pages/settings/AddSecondaryLoginPage.js index 7eb0d3e4a491..bde28d5d04b5 100755 --- a/src/pages/settings/AddSecondaryLoginPage.js +++ b/src/pages/settings/AddSecondaryLoginPage.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import Onyx, {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; -import {View, TextInput} from 'react-native'; +import {View, TextInput, ScrollView} from 'react-native'; import _ from 'underscore'; import Str from 'expensify-common/lib/str'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; @@ -17,6 +17,7 @@ import CONST from '../../CONST'; import KeyboardAvoidingView from '../../libs/KeyboardAvoidingView'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; +import FixedFooter from '../../components/FixedFooter'; const propTypes = { /* Onyx Props */ @@ -111,60 +112,58 @@ class AddSecondaryLoginPage extends Component { onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PROFILE)} onCloseButtonPress={() => Navigation.dismissModal()} /> - - - + + + {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE + ? 'addSecondaryLoginPage.enterPreferredPhoneNumberToSendValidationLink' + : 'addSecondaryLoginPage.enterPreferredEmailToSendValidationLink')} + + + {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE - ? 'addSecondaryLoginPage.enterPreferredPhoneNumberToSendValidationLink' - : 'addSecondaryLoginPage.enterPreferredEmailToSendValidationLink')} + ? 'common.phoneNumber' + : 'profilePage.emailAddress')} - - - {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE - ? 'common.phoneNumber' - : 'profilePage.emailAddress')} - - this.setState({login})} - autoFocus - keyboardType={this.formType === CONST.LOGIN_TYPE.PHONE - ? CONST.KEYBOARD_TYPE.PHONE_PAD : undefined} - returnKeyType="done" - /> - - - - {this.props.translate('common.password')} - - this.setState({password})} - secureTextEntry - autoCompleteType="password" - textContentType="password" - onSubmitEditing={this.submitForm} - /> - - {!_.isEmpty(this.props.user.error) && ( - - {this.props.user.error} - - )} + this.setState({login})} + autoFocus + keyboardType={this.formType === CONST.LOGIN_TYPE.PHONE + ? CONST.KEYBOARD_TYPE.PHONE_PAD : undefined} + returnKeyType="done" + /> - -