From b195e00ca248594ff6ce282903bd854f489f3817 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 22 Aug 2023 12:08:44 +0200 Subject: [PATCH 01/34] Migrate ValidationStep.js to function component --- .../ReimbursementAccount/ValidationStep.js | 274 +++++++++--------- 1 file changed, 132 insertions(+), 142 deletions(-) diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js index 0afa855f1ea6..64cdc82ecff0 100644 --- a/src/pages/ReimbursementAccount/ValidationStep.js +++ b/src/pages/ReimbursementAccount/ValidationStep.js @@ -51,23 +51,38 @@ const defaultProps = { }, }; -class ValidationStep extends React.Component { - constructor(props) { - super(props); +function ValidationStep({reimbursementAccount, translate, onBackButtonPress, account}) { + /** + * Filter input for validation amount + * Anything that isn't a number is returned as an empty string + * Any dollar amount (e.g. 1.12) will be returned as 112 + * + * @param {String} amount field input + * @returns {String} + */ + const filterInput = (amount) => { + let value = amount ? amount.toString().trim() : ''; + if (value === '' || !Math.abs(Str.fromUSDToNumber(value)) || _.isNaN(Number(value))) { + return ''; + } - this.submit = this.submit.bind(this); - this.validate = this.validate.bind(this); - } + // If the user enters the values in dollars, convert it to the respective cents amount + if (_.contains(value, '.')) { + value = Str.fromUSDToNumber(value); + } + + return value; + }; /** * @param {Object} values - form input values passed by the Form component * @returns {Object} */ - validate(values) { + const validate = (values) => { const errors = {}; _.each(values, (value, key) => { - const filteredValue = this.filterInput(value); + const filteredValue = filterInput(value); if (ValidationUtils.isRequiredFulfilled(filteredValue)) { return; } @@ -75,155 +90,130 @@ class ValidationStep extends React.Component { }); return errors; - } + }; /** * @param {Object} values - form input values passed by the Form component */ - submit(values) { - const amount1 = this.filterInput(values.amount1); - const amount2 = this.filterInput(values.amount2); - const amount3 = this.filterInput(values.amount3); + const submit = (values) => { + const amount1 = filterInput(values.amount1); + const amount2 = filterInput(values.amount2); + const amount3 = filterInput(values.amount3); const validateCode = [amount1, amount2, amount3].join(','); // Send valid amounts to BankAccountAPI::validateBankAccount in Web-Expensify - const bankaccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID'); + const bankaccountID = lodashGet(reimbursementAccount, 'achData.bankAccountID'); BankAccounts.validateBankAccount(bankaccountID, validateCode); - } - - /** - * Filter input for validation amount - * Anything that isn't a number is returned as an empty string - * Any dollar amount (e.g. 1.12) will be returned as 112 - * - * @param {String} amount field input - * - * @returns {String} - */ - filterInput(amount) { - let value = amount ? amount.toString().trim() : ''; - if (value === '' || !Math.abs(Str.fromUSDToNumber(value)) || _.isNaN(Number(value))) { - return ''; - } + }; - // If the user enters the values in dollars, convert it to the respective cents amount - if (_.contains(value, '.')) { - value = Str.fromUSDToNumber(value); - } + const state = lodashGet(reimbursementAccount, 'achData.state'); - return value; + // If a user tries to navigate directly to the validate page we'll show them the EnableStep + if (state === BankAccount.STATE.OPEN) { + return ; } - render() { - const state = lodashGet(this.props.reimbursementAccount, 'achData.state'); - - // If a user tries to navigate directly to the validate page we'll show them the EnableStep - if (state === BankAccount.STATE.OPEN) { - return ; - } - - const maxAttemptsReached = lodashGet(this.props.reimbursementAccount, 'maxAttemptsReached'); - const isVerifying = !maxAttemptsReached && state === BankAccount.STATE.VERIFYING; - const requiresTwoFactorAuth = lodashGet(this.props, 'account.requiresTwoFactorAuth'); - - return ( - - - {maxAttemptsReached && ( - - - {this.props.translate('validationStep.maxAttemptsReached')} {this.props.translate('common.please')}{' '} - {this.props.translate('common.contactUs')}. - + const maxAttemptsReached = lodashGet(reimbursementAccount, 'maxAttemptsReached'); + const isVerifying = !maxAttemptsReached && state === BankAccount.STATE.VERIFYING; + const requiresTwoFactorAuth = lodashGet(account, 'requiresTwoFactorAuth'); + + return ( + + + {maxAttemptsReached && ( + + + {translate('validationStep.maxAttemptsReached')} {translate('common.please')}{' '} + {translate('common.contactUs')}. + + + )} + {!maxAttemptsReached && state === BankAccount.STATE.PENDING && ( +
+ + {translate('validationStep.description')} + {translate('validationStep.descriptionCTA')} - )} - {!maxAttemptsReached && state === BankAccount.STATE.PENDING && ( - - - {this.props.translate('validationStep.description')} - {this.props.translate('validationStep.descriptionCTA')} - - - - - + + + + + + {!requiresTwoFactorAuth && ( + + - {!requiresTwoFactorAuth && ( - - - - )} - - )} - {isVerifying && ( - -
- {this.props.translate('validationStep.letsChatText')} -
- {this.props.reimbursementAccount.shouldShowResetModal && } - {!requiresTwoFactorAuth && } -
- )} -
- ); - } + )} + + )} + {isVerifying && ( + +
+ {translate('validationStep.letsChatText')} +
+ {reimbursementAccount.shouldShowResetModal && } + {!requiresTwoFactorAuth && } +
+ )} +
+ ); } ValidationStep.propTypes = propTypes; From cd2c219bfbba9e83797a82b600e935ad9fd2828a Mon Sep 17 00:00:00 2001 From: s-alves10 Date: Thu, 5 Oct 2023 20:26:38 -0500 Subject: [PATCH 02/34] migrate: OptionRow to function component --- src/components/OptionRow.js | 407 +++++++++++++++++------------------- 1 file changed, 196 insertions(+), 211 deletions(-) diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 70415ab03a13..849a002d5de8 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; -import React, {Component} from 'react'; +import React, {useState, useEffect} from 'react'; import PropTypes from 'prop-types'; import {View, StyleSheet, InteractionManager} from 'react-native'; import styles from '../styles/styles'; @@ -97,231 +97,216 @@ const defaultProps = { shouldDisableRowInnerPadding: false, }; -class OptionRow extends Component { - constructor(props) { - super(props); - this.state = { - isDisabled: this.props.isDisabled, - }; - } +function OptionRow(props) { + const [isDisabled, setIsDisabled] = useState(props.isDisabled); - // It is very important to use shouldComponentUpdate here so SectionList items will not unnecessarily re-render - shouldComponentUpdate(nextProps, nextState) { - return ( - this.state.isDisabled !== nextState.isDisabled || - this.props.isDisabled !== nextProps.isDisabled || - this.props.isMultilineSupported !== nextProps.isMultilineSupported || - this.props.isSelected !== nextProps.isSelected || - this.props.shouldHaveOptionSeparator !== nextProps.shouldHaveOptionSeparator || - this.props.selectedStateButtonText !== nextProps.selectedStateButtonText || - this.props.showSelectedState !== nextProps.showSelectedState || - this.props.highlightSelected !== nextProps.highlightSelected || - this.props.showTitleTooltip !== nextProps.showTitleTooltip || - !_.isEqual(this.props.option.icons, nextProps.option.icons) || - this.props.optionIsFocused !== nextProps.optionIsFocused || - this.props.option.text !== nextProps.option.text || - this.props.option.alternateText !== nextProps.option.alternateText || - this.props.option.descriptiveText !== nextProps.option.descriptiveText || - this.props.option.brickRoadIndicator !== nextProps.option.brickRoadIndicator || - this.props.option.shouldShowSubscript !== nextProps.option.shouldShowSubscript || - this.props.option.ownerAccountID !== nextProps.option.ownerAccountID || - this.props.option.subtitle !== nextProps.option.subtitle || - this.props.option.pendingAction !== nextProps.option.pendingAction || - this.props.option.customIcon !== nextProps.option.customIcon - ); - } + useEffect(() => { + setIsDisabled(props.isDisabled); + }, [props.isDisabled]); - componentDidUpdate(prevProps) { - if (this.props.isDisabled === prevProps.isDisabled) { - return; - } + let pressableRef = null; + const textStyle = props.optionIsFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; + const textUnreadStyle = props.boldStyle || props.option.boldStyle ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; + const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, textUnreadStyle, props.style, styles.pre, isDisabled ? styles.optionRowDisabled : {}); + const alternateTextStyle = StyleUtils.combineStyles( + textStyle, + styles.optionAlternateText, + styles.textLabelSupporting, + props.style, + lodashGet(props.option, 'alternateTextMaxLines', 1) === 1 ? styles.pre : styles.preWrap, + ); + const contentContainerStyles = [styles.flex1]; + const sidebarInnerRowStyle = StyleSheet.flatten([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter]); + const hoveredBackgroundColor = props.hoverStyle && props.hoverStyle.backgroundColor ? props.hoverStyle.backgroundColor : props.backgroundColor; + const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; + const isMultipleParticipant = lodashGet(props.option, 'participantsList.length', 0) > 1; + const defaultSubscriptSize = props.option.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - this.setState({isDisabled: this.props.isDisabled}); + // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( + (props.option.participantsList || (props.option.accountID ? [props.option] : [])).slice(0, 10), + isMultipleParticipant, + ); + let subscriptColor = themeColors.appBG; + if (props.optionIsFocused) { + subscriptColor = focusedBackgroundColor; } - render() { - let pressableRef = null; - const textStyle = this.props.optionIsFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; - const textUnreadStyle = this.props.boldStyle || this.props.option.boldStyle ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, textUnreadStyle, this.props.style, styles.pre, this.state.isDisabled ? styles.optionRowDisabled : {}); - const alternateTextStyle = StyleUtils.combineStyles( - textStyle, - styles.optionAlternateText, - styles.textLabelSupporting, - this.props.style, - lodashGet(this.props.option, 'alternateTextMaxLines', 1) === 1 ? styles.pre : styles.preWrap, - ); - const contentContainerStyles = [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter]); - const hoveredBackgroundColor = this.props.hoverStyle && this.props.hoverStyle.backgroundColor ? this.props.hoverStyle.backgroundColor : this.props.backgroundColor; - const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; - const isMultipleParticipant = lodashGet(this.props.option, 'participantsList.length', 0) > 1; - const defaultSubscriptSize = this.props.option.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; - - // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( - (this.props.option.participantsList || (this.props.option.accountID ? [this.props.option] : [])).slice(0, 10), - isMultipleParticipant, - ); - let subscriptColor = themeColors.appBG; - if (this.props.optionIsFocused) { - subscriptColor = focusedBackgroundColor; - } + return ( + + + {(hovered) => ( + (pressableRef = el)} + onPress={(e) => { + if (!props.onSelectRow) { + return; + } - return ( - - - {(hovered) => ( - (pressableRef = el)} - onPress={(e) => { - if (!this.props.onSelectRow) { - return; - } - - this.setState({isDisabled: true}); - if (e) { - e.preventDefault(); - } - let result = this.props.onSelectRow(this.props.option, pressableRef); - if (!(result instanceof Promise)) { - result = Promise.resolve(); - } - InteractionManager.runAfterInteractions(() => { - result.finally(() => this.setState({isDisabled: this.props.isDisabled})); - }); - }} - disabled={this.state.isDisabled} - style={[ - styles.flexRow, - styles.alignItemsCenter, - styles.justifyContentBetween, - styles.sidebarLink, - this.props.shouldDisableRowInnerPadding ? null : styles.sidebarLinkInner, - this.props.optionIsFocused ? styles.sidebarLinkActive : null, - this.props.shouldHaveOptionSeparator && styles.borderTop, - !this.props.onSelectRow && !this.props.isDisabled ? styles.cursorDefault : null, - this.props.isSelected && this.props.highlightSelected && styles.optionRowSelected, - ]} - accessibilityLabel={this.props.option.text} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} - hoverDimmingValue={1} - hoverStyle={this.props.hoverStyle} - needsOffscreenAlphaCompositing={lodashGet(this.props.option, 'icons.length', 0) >= 2} - > - - - {!_.isEmpty(this.props.option.icons) && - (this.props.option.shouldShowSubscript ? ( - - ) : ( - - ))} - - { + result.finally(() => setIsDisabled(props.isDisabled)); + }); + }} + disabled={isDisabled} + style={[ + styles.flexRow, + styles.alignItemsCenter, + styles.justifyContentBetween, + styles.sidebarLink, + props.shouldDisableRowInnerPadding ? null : styles.sidebarLinkInner, + props.optionIsFocused ? styles.sidebarLinkActive : null, + props.shouldHaveOptionSeparator && styles.borderTop, + !props.onSelectRow && !props.isDisabled ? styles.cursorDefault : null, + props.isSelected && props.highlightSelected && styles.optionRowSelected, + ]} + accessibilityLabel={props.option.text} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + hoverDimmingValue={1} + hoverStyle={props.hoverStyle} + needsOffscreenAlphaCompositing={lodashGet(props.option, 'icons.length', 0) >= 2} + > + + + {!_.isEmpty(props.option.icons) && + (props.option.shouldShowSubscript ? ( + - {this.props.option.alternateText ? ( - - {this.props.option.alternateText} - - ) : null} - - {this.props.option.descriptiveText ? ( - - {this.props.option.descriptiveText} - + ) : ( + + ))} + + + {props.option.alternateText ? ( + + {props.option.alternateText} + ) : null} - {this.props.option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && ( - - - - )} - {this.props.showSelectedState && ( - <> - {this.props.shouldShowSelectedStateAsButton && !this.props.isSelected ? ( -