From 55ce07ace181a447bae7cfdebec1bad344279f62 Mon Sep 17 00:00:00 2001 From: oleh Date: Fri, 7 Jul 2023 16:15:09 +0300 Subject: [PATCH 1/9] refactored BasePaymentsPage to function based component --- .../Payments/PaymentsPage/BasePaymentsPage.js | 734 +++++++++--------- 1 file changed, 372 insertions(+), 362 deletions(-) diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index 8ec4b2079a81..8a4f6e7e3a87 100644 --- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {ActivityIndicator, View, InteractionManager, LayoutAnimation} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -34,139 +34,87 @@ import Button from '../../../../components/Button'; import themeColors from '../../../../styles/themes/default'; import variables from '../../../../styles/variables'; -class BasePaymentsPage extends React.Component { - constructor(props) { - super(props); - - this.state = { - shouldShowAddPaymentMenu: false, - shouldShowDefaultDeleteMenu: false, - shouldShowPasswordPrompt: false, - shouldShowLoadingSpinner: false, - isSelectedPaymentMethodDefault: false, - selectedPaymentMethod: {}, - formattedSelectedPaymentMethod: { - title: '', - }, - selectedPaymentMethodType: null, - anchorPositionHorizontal: 0, - anchorPositionVertical: 0, - anchorPositionTop: 0, - anchorPositionRight: 0, - addPaymentMethodButton: null, - methodID: null, - showConfirmDeleteContent: false, - }; - - this.paymentMethodPressed = this.paymentMethodPressed.bind(this); - this.addPaymentMethodTypePressed = this.addPaymentMethodTypePressed.bind(this); - this.hideAddPaymentMenu = this.hideAddPaymentMenu.bind(this); - this.hideDefaultDeleteMenu = this.hideDefaultDeleteMenu.bind(this); - this.makeDefaultPaymentMethod = this.makeDefaultPaymentMethod.bind(this); - this.deletePaymentMethod = this.deletePaymentMethod.bind(this); - this.hidePasswordPrompt = this.hidePasswordPrompt.bind(this); - this.navigateToTransferBalancePage = this.navigateToTransferBalancePage.bind(this); - this.setMenuPosition = this.setMenuPosition.bind(this); - this.listHeaderComponent = this.listHeaderComponent.bind(this); - - this.debounceSetShouldShowLoadingSpinner = _.debounce(this.setShouldShowLoadingSpinner.bind(this), CONST.TIMING.SHOW_LOADING_SPINNER_DEBOUNCE_TIME); - } - - componentDidMount() { - this.fetchData(); - } - - componentDidUpdate(prevProps) { - if (this.shouldListenForResize) { - this.setMenuPosition(); - } - - // If the user was previously offline, skip debouncing showing the loader - if (prevProps.network.isOffline && !this.props.network.isOffline) { - this.setShouldShowLoadingSpinner(); - } else { - this.debounceSetShouldShowLoadingSpinner(); - } - - if (this.state.shouldShowDefaultDeleteMenu || this.state.shouldShowPasswordPrompt) { - // We should reset selected payment method state values and close corresponding modals if the selected payment method is deleted - let shouldResetPaymentMethodData = false; - - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && _.isEmpty(this.props.bankAccountList[this.state.methodID])) { - shouldResetPaymentMethodData = true; - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(this.props.cardList[this.state.methodID])) { - shouldResetPaymentMethodData = true; - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL && this.props.payPalMeData !== prevProps.payPalMeData && _.isEmpty(this.props.payPalMeData)) { - shouldResetPaymentMethodData = true; - } - if (shouldResetPaymentMethodData) { - // Close corresponding selected payment method modals which are open - if (this.state.shouldShowDefaultDeleteMenu) { - this.hideDefaultDeleteMenu(); - } else if (this.state.shouldShowPasswordPrompt) { - this.hidePasswordPrompt(); - } - } - } - - // previously online OR currently offline, skip fetch - if (!prevProps.network.isOffline || this.props.network.isOffline) { - return; +function BasePaymentsPage(props) { + const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); + const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false); + const [showPassword, setShowPassword] = useState({ + shouldShowPasswordPrompt: false, + passwordButtonText: '', + }); + const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(false); + const [paymentMethod, setPaymentMethod] = useState({ + isSelectedPaymentMethodDefault: false, + selectedPaymentMethod: {}, + formattedSelectedPaymentMethod: { + title: '', + }, + methodID: null, + selectedPaymentMethodType: null, + }); + const [anchorPosition, setAnchorPosition] = useState({ + anchorPositionHorizontal: 0, + anchorPositionVertical: 0, + anchorPositionTop: 0, + anchorPositionRight: 0, + }); + const [addPaymentMethodButton, setAddPaymentMethodButton] = useState(null); + const [showConfirmDeleteContent, setShowConfirmDeleteContent] = useState(false); + + const updateShouldShowLoadingSpinner = useCallback(() => { + // In order to prevent a loop, only update state of the spinner if there is a change + const showLoadingSpinner = props.isLoadingPaymentMethods || false; + if (showLoadingSpinner !== shouldShowLoadingSpinner) { + setShouldShowLoadingSpinner(props.isLoadingPaymentMethods && !props.network.isOffline); } + }, [props.isLoadingPaymentMethods, props.network.isOffline, shouldShowLoadingSpinner]); - this.fetchData(); - } + const debounceSetShouldShowLoadingSpinner = _.debounce(updateShouldShowLoadingSpinner, CONST.TIMING.SHOW_LOADING_SPINNER_DEBOUNCE_TIME); - setShouldShowLoadingSpinner() { - // In order to prevent a loop, only update state of the spinner if there is a change - const shouldShowLoadingSpinner = this.props.isLoadingPaymentMethods || false; - if (shouldShowLoadingSpinner !== this.state.shouldShowLoadingSpinner) { - this.setState({shouldShowLoadingSpinner: this.props.isLoadingPaymentMethods && !this.props.network.isOffline}); - } - } + /** + * Set position of the payment menu + * + * @param {Object} position + */ + const setPositionAddPaymentMenu = useCallback( + (position) => { + setAnchorPosition({ + anchorPositionTop: position.top + position.height + variables.addPaymentPopoverTopSpacing, + + // We want the position to be 13px to the right of the left border + anchorPositionRight: props.windowWidth - position.right + variables.addPaymentPopoverRightSpacing, + anchorPositionHorizontal: position.x, + anchorPositionVertical: position.y, + }); + }, + [props.windowWidth], + ); - setMenuPosition() { - if (!this.state.addPaymentMethodButton) { + const setMenuPosition = useCallback(() => { + if (!addPaymentMethodButton) { return; } - const buttonPosition = getClickedTargetLocation(this.state.addPaymentMethodButton); - this.setPositionAddPaymentMenu(buttonPosition); - } + const buttonPosition = getClickedTargetLocation(addPaymentMethodButton); + setPositionAddPaymentMenu(buttonPosition); + }, [addPaymentMethodButton, setPositionAddPaymentMenu]); - getSelectedPaymentMethodID() { - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { + const getSelectedPaymentMethodID = useCallback(() => { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { return CONST.PAYMENT_METHODS.PAYPAL; } - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - return this.state.selectedPaymentMethod.bankAccountID; + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + return paymentMethod.selectedPaymentMethod.bankAccountID; } - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - return this.state.selectedPaymentMethod.fundID; + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + return paymentMethod.selectedPaymentMethod.fundID; } - } + }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType]); - /** - * Set position of the payment menu - * - * @param {Object} position - */ - setPositionAddPaymentMenu(position) { - this.setState({ - anchorPositionTop: position.top + position.height + variables.addPaymentPopoverTopSpacing, - - // We want the position to be 13px to the right of the left border - anchorPositionRight: this.props.windowWidth - position.right + variables.addPaymentPopoverRightSpacing, - anchorPositionHorizontal: position.x, - anchorPositionVertical: position.y, - }); - } - - resetSelectedPaymentMethodData() { + const resetSelectedPaymentMethodData = useCallback(() => { // The below state values are used by payment method modals and we reset them while closing the modals. // We should only reset the values when the modal animation is completed and so using InteractionManager.runAfterInteractions which fires after all animaitons are complete InteractionManager.runAfterInteractions(() => { // Reset to same values as in the constructor - this.setState({ + setPaymentMethod({ isSelectedPaymentMethodDefault: false, selectedPaymentMethod: {}, formattedSelectedPaymentMethod: { @@ -176,7 +124,7 @@ class BasePaymentsPage extends React.Component { selectedPaymentMethodType: null, }); }); - } + }, [setPaymentMethod]); /** * Display the delete/default menu, or the add payment method menu @@ -187,11 +135,9 @@ class BasePaymentsPage extends React.Component { * @param {Boolean} isDefault * @param {String|Number} methodID */ - paymentMethodPressed(nativeEvent, accountType, account, isDefault, methodID) { + const paymentMethodPressed = (nativeEvent, accountType, account, isDefault, methodID) => { const position = getClickedTargetLocation(nativeEvent.currentTarget); - this.setState({ - addPaymentMethodButton: nativeEvent.currentTarget, - }); + setAddPaymentMethodButton(nativeEvent.currentTarget); // The delete/default menu if (accountType) { @@ -218,31 +164,35 @@ class BasePaymentsPage extends React.Component { type: CONST.PAYMENT_METHODS.DEBIT_CARD, }; } - this.setState({ + setPaymentMethod({ isSelectedPaymentMethodDefault: isDefault, - shouldShowDefaultDeleteMenu: true, selectedPaymentMethod: account, selectedPaymentMethodType: accountType, formattedSelectedPaymentMethod, methodID, }); - this.setPositionAddPaymentMenu(position); + setShouldShowDefaultDeleteMenu(true); + setPositionAddPaymentMenu(position); return; } - this.setState({ - shouldShowAddPaymentMenu: true, - }); + setShouldShowAddPaymentMenu(true); + setPositionAddPaymentMenu(position); + }; - this.setPositionAddPaymentMenu(position); - } + /** + * Hide the add payment modal + */ + const hideAddPaymentMenu = () => { + setShouldShowAddPaymentMenu(false); + }; /** * Navigate to the appropriate payment type addition screen * * @param {String} paymentType */ - addPaymentMethodTypePressed(paymentType) { - this.hideAddPaymentMenu(); + const addPaymentMethodTypePressed = (paymentType) => { + hideAddPaymentMenu(); if (paymentType === CONST.PAYMENT_METHODS.PAYPAL) { Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME); @@ -260,263 +210,323 @@ class BasePaymentsPage extends React.Component { } throw new Error('Invalid payment method type selected'); - } - - fetchData() { - PaymentMethods.openPaymentsPage(); - } - - /** - * Hide the add payment modal - */ - hideAddPaymentMenu() { - this.setState({shouldShowAddPaymentMenu: false}); - } + }; /** * Hide the default / delete modal * @param {boolean} shouldClearSelectedData - Clear selected payment method data if true */ - hideDefaultDeleteMenu(shouldClearSelectedData = true) { - this.setState({shouldShowDefaultDeleteMenu: false}); - InteractionManager.runAfterInteractions(() => { - this.setState({ - showConfirmDeleteContent: false, + const hideDefaultDeleteMenu = useCallback( + (shouldClearSelectedData = true) => { + setShouldShowDefaultDeleteMenu(false); + InteractionManager.runAfterInteractions(() => { + setShowConfirmDeleteContent(false); + if (shouldClearSelectedData) { + resetSelectedPaymentMethodData(); + } + }); + }, + [setShouldShowDefaultDeleteMenu, setShowConfirmDeleteContent, resetSelectedPaymentMethodData], + ); + + const hidePasswordPrompt = useCallback( + (shouldClearSelectedData = true) => { + setShowPassword({ + shouldShowPasswordPrompt: false, + passwordButtonText: '', }); if (shouldClearSelectedData) { - this.resetSelectedPaymentMethodData(); + resetSelectedPaymentMethodData(); } - }); - } - hidePasswordPrompt(shouldClearSelectedData = true) { - this.setState({shouldShowPasswordPrompt: false}); - if (shouldClearSelectedData) { - this.resetSelectedPaymentMethodData(); + // Due to iOS modal freeze issue, password modal freezes the app when closed. + // LayoutAnimation undoes the running animation. + LayoutAnimation.configureNext(LayoutAnimation.create(50, LayoutAnimation.Types.easeInEaseOut, LayoutAnimation.Properties.opacity)); + }, + [setShowPassword, resetSelectedPaymentMethodData], + ); + + const makeDefaultPaymentMethod = useCallback( + (password = '') => { + // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors + const paymentMethods = PaymentUtils.formatPaymentMethods(props.bankAccountList, props.cardList); + + const previousPaymentMethod = _.find(paymentMethods, (method) => method.isDefault); + const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + PaymentMethods.makeDefaultPaymentMethod(password, paymentMethod.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + PaymentMethods.makeDefaultPaymentMethod(password, null, paymentMethod.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); + } + resetSelectedPaymentMethodData(); + }, + [ + paymentMethod.methodID, + paymentMethod.selectedPaymentMethod.bankAccountID, + paymentMethod.selectedPaymentMethod.fundID, + paymentMethod.selectedPaymentMethodType, + props.bankAccountList, + props.cardList, + resetSelectedPaymentMethodData, + ], + ); + + const deletePaymentMethod = useCallback(() => { + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { + PaymentMethods.deletePayPalMe(); + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + BankAccounts.deletePaymentBankAccount(paymentMethod.selectedPaymentMethod.bankAccountID); + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + PaymentMethods.deletePaymentCard(paymentMethod.selectedPaymentMethod.fundID); } + resetSelectedPaymentMethodData(); + }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType, resetSelectedPaymentMethodData]); - // Due to iOS modal freeze issue, password modal freezes the app when closed. - // LayoutAnimation undoes the running animation. - LayoutAnimation.configureNext(LayoutAnimation.create(50, LayoutAnimation.Types.easeInEaseOut, LayoutAnimation.Properties.opacity)); - } - - makeDefaultPaymentMethod(password = '') { - // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors - const paymentMethods = PaymentUtils.formatPaymentMethods(this.props.bankAccountList, this.props.cardList); - - const previousPaymentMethod = _.find(paymentMethods, (method) => method.isDefault); - const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === this.state.methodID); - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - PaymentMethods.makeDefaultPaymentMethod(password, this.state.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(password, null, this.state.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); + const navigateToTransferBalancePage = () => { + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS_TRANSFER_BALANCE); + }; + + const listHeaderComponent = () => ( + <> + {Permissions.canUseWallet(props.betas) && ( + <> + + {shouldShowLoadingSpinner ? ( + + ) : ( + + + + )} + + {props.userWallet.currentBalance > 0 && ( + + + {(triggerKYCFlow) => ( + + )} + + + )} + + )} + {props.translate('paymentsPage.paymentMethodsTitle')} + + ); + + useEffect(() => { + PaymentMethods.openPaymentsPage(); + }, []); + + useEffect(() => { + // If the user was previously offline, skip debouncing showing the loader + if (!props.network.isOffline) { + updateShouldShowLoadingSpinner(); + } else { + debounceSetShouldShowLoadingSpinner(); } - this.resetSelectedPaymentMethodData(); - } + }, [props.network.isOffline, debounceSetShouldShowLoadingSpinner, updateShouldShowLoadingSpinner]); - deletePaymentMethod() { - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { - PaymentMethods.deletePayPalMe(); - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - BankAccounts.deletePaymentBankAccount(this.state.selectedPaymentMethod.bankAccountID); - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.deletePaymentCard(this.state.selectedPaymentMethod.fundID); + useEffect(() => { + if (props.shouldListenForResize) { + setMenuPosition(); } - this.resetSelectedPaymentMethodData(); - } - navigateToTransferBalancePage() { - Navigation.navigate(ROUTES.SETTINGS_PAYMENTS_TRANSFER_BALANCE); - } - - listHeaderComponent() { - return ( - <> - {Permissions.canUseWallet(this.props.betas) && ( - <> - - {this.state.shouldShowLoadingSpinner ? ( - - ) : ( - - - - )} - - {this.props.userWallet.currentBalance > 0 && ( - - - {(triggerKYCFlow) => ( - - )} - - - )} - - )} - {this.props.translate('paymentsPage.paymentMethodsTitle')} - - ); - } - - render() { - const isPayPalMeSelected = this.state.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PAYPAL; - const shouldShowMakeDefaultButton = - !this.state.isSelectedPaymentMethodDefault && - Permissions.canUseWallet(this.props.betas) && - !isPayPalMeSelected && - !(this.state.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT && this.state.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); - - // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens - const isPopoverBottomMount = this.state.anchorPositionTop === 0 || this.props.isSmallScreenWidth; - return ( - - Navigation.goBack(ROUTES.SETTINGS)} - /> - - PaymentMethods.clearWalletError()} - errors={this.props.userWallet.errors} - errorRowStyles={[styles.ph6]} - > - - - - this.addPaymentMethodTypePressed(method)} - /> - + Navigation.goBack(ROUTES.SETTINGS)} + /> + + PaymentMethods.clearWalletError()} + errors={props.userWallet.errors} + errorRowStyles={[styles.ph6]} > - {!this.state.showConfirmDeleteContent ? ( - - {isPopoverBottomMount && ( - - )} - {shouldShowMakeDefaultButton && ( -