diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index 9a7d3b6bc415..9a32e45a4981 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} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -8,7 +8,6 @@ import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; import ScreenWrapper from '../../../../components/ScreenWrapper'; import Navigation from '../../../../libs/Navigation/Navigation'; import styles from '../../../../styles/styles'; -import withLocalize from '../../../../components/withLocalize'; import compose from '../../../../libs/compose'; import * as BankAccounts from '../../../../libs/actions/BankAccounts'; import Popover from '../../../../components/Popover'; @@ -16,7 +15,6 @@ import MenuItem from '../../../../components/MenuItem'; import Text from '../../../../components/Text'; import * as PaymentMethods from '../../../../libs/actions/PaymentMethods'; import getClickedTargetLocation from '../../../../libs/getClickedTargetLocation'; -import withWindowDimensions from '../../../../components/withWindowDimensions'; import CurrentWalletBalance from '../../../../components/CurrentWalletBalance'; import ONYXKEYS from '../../../../ONYXKEYS'; import Permissions from '../../../../libs/Permissions'; @@ -32,138 +30,92 @@ import ConfirmContent from '../../../../components/ConfirmContent'; 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.navigateToTransferBalancePage = this.navigateToTransferBalancePage.bind(this); - this.setMenuPosition = this.setMenuPosition.bind(this); - this.listHeaderComponent = this.listHeaderComponent.bind(this); - this.navigateToAddPaypalRoute = this.navigateToAddPaypalRoute.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(); - } - } - } - - // previously online OR currently offline, skip fetch - if (!prevProps.network.isOffline || this.props.network.isOffline) { - return; +import useLocalize from '../../../../hooks/useLocalize'; +import useWindowDimensions from '../../../../hooks/useWindowDimensions'; + +function BasePaymentsPage(props) { + const {translate} = useLocalize(); + const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); + 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: windowWidth - position.right + variables.addPaymentPopoverRightSpacing, + anchorPositionHorizontal: position.x, + anchorPositionVertical: position.y, + }); + }, + [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: { @@ -173,7 +125,7 @@ class BasePaymentsPage extends React.Component { selectedPaymentMethodType: null, }); }); - } + }, [setPaymentMethod]); /** * Display the delete/default menu, or the add payment method menu @@ -184,11 +136,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) { @@ -215,31 +165,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); @@ -257,250 +211,305 @@ 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(); + } }); - if (shouldClearSelectedData) { - this.resetSelectedPaymentMethodData(); - } - }); - } + }, + [setShouldShowDefaultDeleteMenu, setShowConfirmDeleteContent, resetSelectedPaymentMethodData], + ); - makeDefaultPaymentMethod() { + const makeDefaultPaymentMethod = useCallback(() => { // 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 paymentMethods = PaymentUtils.formatPaymentMethods(props.bankAccountList, 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(this.state.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); - } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(null, this.state.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); + const currentPaymentMethod = _.find(paymentMethods, (method) => method.methodID === paymentMethod.methodID); + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID, null, previousPaymentMethod, currentPaymentMethod); + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + PaymentMethods.makeDefaultPaymentMethod(null, paymentMethod.selectedPaymentMethod.fundID, previousPaymentMethod, currentPaymentMethod); } - this.resetSelectedPaymentMethodData(); - } - - deletePaymentMethod() { - if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { + 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 (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); + } 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); } - this.resetSelectedPaymentMethodData(); - } + resetSelectedPaymentMethodData(); + }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType, resetSelectedPaymentMethodData]); - navigateToTransferBalancePage() { + const navigateToTransferBalancePage = () => { Navigation.navigate(ROUTES.SETTINGS_PAYMENTS_TRANSFER_BALANCE); - } + }; - navigateToAddPaypalRoute() { + const navigateToAddPaypalRoute = () => { Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME); - this.setState({ - shouldShowDefaultDeleteMenu: false, - }); - } - - listHeaderComponent() { - return ( - <> - {Permissions.canUseWallet(this.props.betas) && ( - <> - - {this.state.shouldShowLoadingSpinner ? ( - - ) : ( - - - - )} - - {this.props.userWallet.currentBalance > 0 && ( - - - {(triggerKYCFlow) => ( - - )} - - + setShouldShowDefaultDeleteMenu(false); + }; + + const listHeaderComponent = () => ( + <> + {Permissions.canUseWallet(props.betas) && ( + <> + + {shouldShowLoadingSpinner ? ( + + ) : ( + + + )} - - )} - {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)} - /> - + {props.userWallet.currentBalance > 0 && ( + + + {(triggerKYCFlow) => ( + + )} + + + )} + + )} + {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(); + } + }, [props.network.isOffline, debounceSetShouldShowLoadingSpinner, updateShouldShowLoadingSpinner]); + + useEffect(() => { + if (props.network.isOffline) { + return; + } + PaymentMethods.openPaymentsPage(); + }, [props.network.isOffline]); + + useEffect(() => { + if (!props.shouldListenForResize) { + return; + } + setMenuPosition(); + }, [props.shouldListenForResize, setMenuPosition]); + + useEffect(() => { + if (!shouldShowDefaultDeleteMenu && !showPassword.shouldShowPasswordPrompt) { + return; + } + + // We should reset selected payment method state values and close corresponding modals if the selected payment method is deleted + let shouldResetPaymentMethodData = false; + + if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && _.isEmpty(props.bankAccountList[paymentMethod.methodID])) { + shouldResetPaymentMethodData = true; + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && _.isEmpty(props.cardList[paymentMethod.methodID])) { + shouldResetPaymentMethodData = true; + } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL && _.isEmpty(props.payPalMeData)) { + shouldResetPaymentMethodData = true; + } + if (shouldResetPaymentMethodData) { + // Close corresponding selected payment method modals which are open + if (shouldShowDefaultDeleteMenu) { + hideDefaultDeleteMenu(); + } + } + }, [ + hideDefaultDeleteMenu, + paymentMethod.methodID, + paymentMethod.selectedPaymentMethodType, + props.bankAccountList, + props.cardList, + props.payPalMeData, + shouldShowDefaultDeleteMenu, + showPassword.shouldShowPasswordPrompt, + ]); + + const isPayPalMeSelected = paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PAYPAL; + const shouldShowMakeDefaultButton = + !paymentMethod.isSelectedPaymentMethodDefault && + Permissions.canUseWallet(props.betas) && + !isPayPalMeSelected && + !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT && paymentMethod.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 = anchorPosition.anchorPositionTop === 0 || isSmallScreenWidth; + + return ( + + Navigation.goBack(ROUTES.SETTINGS)} + /> + + PaymentMethods.clearWalletError()} + errors={props.userWallet.errors} + errorRowStyles={[styles.ph6]} > - {!this.state.showConfirmDeleteContent ? ( - - {isPopoverBottomMount && ( - - )} - {shouldShowMakeDefaultButton && ( -