Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 138 additions & 141 deletions src/pages/settings/Payments/TransferBalancePage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'underscore';
import React from 'react';
import React, {useEffect} from 'react';
import {View, ScrollView} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -65,65 +65,50 @@ const defaultProps = {
walletTransfer: {},
};

class TransferBalancePage extends React.Component {
constructor(props) {
super(props);

this.paymentTypes = [
{
key: CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT,
title: this.props.translate('transferAmountPage.instant'),
description: this.props.translate('transferAmountPage.instantSummary', {
rate: this.props.numberFormat(CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT.RATE),
minAmount: CurrencyUtils.convertToDisplayString(CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT.MINIMUM_FEE),
}),
icon: Expensicons.Bolt,
type: CONST.PAYMENT_METHODS.DEBIT_CARD,
},
{
key: CONST.WALLET.TRANSFER_METHOD_TYPE.ACH,
title: this.props.translate('transferAmountPage.ach'),
description: this.props.translate('transferAmountPage.achSummary'),
icon: Expensicons.Bank,
type: CONST.PAYMENT_METHODS.BANK_ACCOUNT,
},
];

PaymentMethods.resetWalletTransferData();
const selectedAccount = this.getSelectedPaymentMethodAccount();

// Select the default payment account when page is opened,
// so that user can see that preselected on choose transfer account page
if (!selectedAccount || !selectedAccount.isDefault) {
return;
}

PaymentMethods.saveWalletTransferAccountTypeAndID(selectedAccount.accountType, selectedAccount.methodID);
}
function TransferBalancePage(props) {
const paymentTypes = [
{
key: CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT,
title: props.translate('transferAmountPage.instant'),
description: props.translate('transferAmountPage.instantSummary', {
rate: props.numberFormat(CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT.RATE),
minAmount: CurrencyUtils.convertToDisplayString(CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT.MINIMUM_FEE),
}),
icon: Expensicons.Bolt,
type: CONST.PAYMENT_METHODS.DEBIT_CARD,
},
{
key: CONST.WALLET.TRANSFER_METHOD_TYPE.ACH,
title: props.translate('transferAmountPage.ach'),
description: props.translate('transferAmountPage.achSummary'),
icon: Expensicons.Bank,
type: CONST.PAYMENT_METHODS.BANK_ACCOUNT,
},
];

/**
* Get the selected/default payment method account for wallet transfer
* @returns {Object|undefined}
*/
getSelectedPaymentMethodAccount() {
const paymentMethods = PaymentUtils.formatPaymentMethods(this.props.bankAccountList, this.props.cardList);
function getSelectedPaymentMethodAccount() {
const paymentMethods = PaymentUtils.formatPaymentMethods(props.bankAccountList, props.cardList);

const defaultAccount = _.find(paymentMethods, (method) => method.isDefault);
const selectedAccount = _.find(
paymentMethods,
(method) => method.accountType === this.props.walletTransfer.selectedAccountType && method.methodID === this.props.walletTransfer.selectedAccountID,
(method) => method.accountType === props.walletTransfer.selectedAccountType && method.methodID === props.walletTransfer.selectedAccountID,
);
return selectedAccount || defaultAccount;
}

/**
* @param {String} filterPaymentMethodType
*/
navigateToChooseTransferAccount(filterPaymentMethodType) {
function navigateToChooseTransferAccount(filterPaymentMethodType) {
PaymentMethods.saveWalletTransferMethodType(filterPaymentMethodType);

// If we only have a single option for the given paymentMethodType do not force the user to make a selection
const combinedPaymentMethods = PaymentUtils.formatPaymentMethods(this.props.bankAccountList, this.props.cardList);
const combinedPaymentMethods = PaymentUtils.formatPaymentMethods(props.bankAccountList, props.cardList);

const filteredMethods = _.filter(combinedPaymentMethods, (paymentMethod) => paymentMethod.accountType === filterPaymentMethodType);
if (filteredMethods.length === 1) {
Expand All @@ -135,120 +120,132 @@ class TransferBalancePage extends React.Component {
Navigation.navigate(ROUTES.SETTINGS_PAYMENTS_CHOOSE_TRANSFER_ACCOUNT);
}

render() {
if (this.props.walletTransfer.shouldShowSuccess && !this.props.walletTransfer.loading) {
return (
<ScreenWrapper>
<HeaderWithBackButton
title={this.props.translate('common.transferBalance')}
onBackButtonPress={PaymentMethods.dismissSuccessfulTransferBalancePage}
/>
<ConfirmationPage
heading={this.props.translate('transferAmountPage.transferSuccess')}
description={
this.props.walletTransfer.paymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT
? this.props.translate('transferAmountPage.transferDetailBankAccount')
: this.props.translate('transferAmountPage.transferDetailDebitCard')
}
shouldShowButton
buttonText={this.props.translate('common.done')}
onButtonPress={PaymentMethods.dismissSuccessfulTransferBalancePage}
/>
</ScreenWrapper>
);
}
const selectedAccount = this.getSelectedPaymentMethodAccount();
const selectedPaymentType =
selectedAccount && selectedAccount.accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? CONST.WALLET.TRANSFER_METHOD_TYPE.ACH : CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT;
useEffect(() => {
// Reset to the default account when the page is opened
PaymentMethods.resetWalletTransferData();

const calculatedFee = PaymentUtils.calculateWalletTransferBalanceFee(this.props.userWallet.currentBalance, selectedPaymentType);
const transferAmount = this.props.userWallet.currentBalance - calculatedFee;
const isTransferable = transferAmount > 0;
const isButtonDisabled = !isTransferable || !selectedAccount;
const errorMessage = !_.isEmpty(this.props.walletTransfer.errors) ? _.chain(this.props.walletTransfer.errors).values().first().value() : '';
const selectedAccount = getSelectedPaymentMethodAccount();
if (!selectedAccount) {
return;
}
Comment on lines +127 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        // Select the default payment account when page is opened,
        // so that user can see that preselected on choose transfer account page
        if (!selectedAccount || !selectedAccount.isDefault) {
            return;
        }

This was original code.
Any concerns in keeping original comment and also adding || !selectedAccount.isDefault condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think this was a bug, if we return early here and you have a non-default account selected, then we can't properly pre-select the account in the ChooseTransferAccountPage.

Here's what it looks like if we add the || !selectedAccount.isDefault (also happening on main):

Screen.Recording.2023-06-16.at.2.22.21.PM.mov


const shouldShowTransferView =
PaymentUtils.hasExpensifyPaymentMethod(this.props.cardList, this.props.bankAccountList) && this.props.userWallet.tierName === CONST.WALLET.TIER_NAME.GOLD;
PaymentMethods.saveWalletTransferAccountTypeAndID(selectedAccount.accountType, selectedAccount.methodID);
// eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this effect to run on initial render
}, []);

if (props.walletTransfer.shouldShowSuccess && !props.walletTransfer.loading) {
return (
<ScreenWrapper>
<FullPageNotFoundView
shouldShow={!shouldShowTransferView}
titleKey="notFound.pageNotFound"
subtitleKey="transferAmountPage.notHereSubTitle"
shouldShowLink
linkKey="transferAmountPage.goToPayment"
onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_PAYMENTS)}
<HeaderWithBackButton
title={props.translate('common.transferBalance')}
onBackButtonPress={PaymentMethods.dismissSuccessfulTransferBalancePage}
/>
<ConfirmationPage
heading={props.translate('transferAmountPage.transferSuccess')}
description={
props.walletTransfer.paymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT
? props.translate('transferAmountPage.transferDetailBankAccount')
: props.translate('transferAmountPage.transferDetailDebitCard')
}
shouldShowButton
buttonText={props.translate('common.done')}
onButtonPress={PaymentMethods.dismissSuccessfulTransferBalancePage}
/>
</ScreenWrapper>
);
}

const selectedAccount = getSelectedPaymentMethodAccount();
const selectedPaymentType =
selectedAccount && selectedAccount.accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? CONST.WALLET.TRANSFER_METHOD_TYPE.ACH : CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT;

const calculatedFee = PaymentUtils.calculateWalletTransferBalanceFee(props.userWallet.currentBalance, selectedPaymentType);
const transferAmount = props.userWallet.currentBalance - calculatedFee;
const isTransferable = transferAmount > 0;
const isButtonDisabled = !isTransferable || !selectedAccount;
const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? _.chain(props.walletTransfer.errors).values().first().value() : '';

const shouldShowTransferView = PaymentUtils.hasExpensifyPaymentMethod(props.cardList, props.bankAccountList) && props.userWallet.tierName === CONST.WALLET.TIER_NAME.GOLD;

return (
<ScreenWrapper>
<FullPageNotFoundView
shouldShow={!shouldShowTransferView}
titleKey="notFound.pageNotFound"
subtitleKey="transferAmountPage.notHereSubTitle"
shouldShowLink
linkKey="transferAmountPage.goToPayment"
onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_PAYMENTS)}
>
<HeaderWithBackButton
title={props.translate('common.transferBalance')}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_PAYMENTS)}
/>
<View style={[styles.flexGrow1, styles.flexShrink1, styles.flexBasisAuto, styles.justifyContentCenter]}>
<CurrentWalletBalance balanceStyles={[styles.transferBalanceBalance]} />
</View>
<ScrollView
style={styles.flexGrow0}
contentContainerStyle={styles.pv5}
>
<HeaderWithBackButton
title={this.props.translate('common.transferBalance')}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_PAYMENTS)}
/>
<View style={[styles.flexGrow1, styles.flexShrink1, styles.flexBasisAuto, styles.justifyContentCenter]}>
<CurrentWalletBalance balanceStyles={[styles.transferBalanceBalance]} />
</View>
<ScrollView
style={styles.flexGrow0}
contentContainerStyle={styles.pv5}
>
<View style={styles.ph5}>
{_.map(this.paymentTypes, (paymentType) => (
<MenuItem
key={paymentType.key}
title={paymentType.title}
description={paymentType.description}
iconWidth={variables.iconSizeXLarge}
iconHeight={variables.iconSizeXLarge}
icon={paymentType.icon}
success={selectedPaymentType === paymentType.key}
wrapperStyle={{
...styles.mt3,
...styles.pv4,
...styles.transferBalancePayment,
...(selectedPaymentType === paymentType.key && styles.transferBalanceSelectedPayment),
}}
onPress={() => this.navigateToChooseTransferAccount(paymentType.type)}
/>
))}
</View>
<Text style={[styles.p5, styles.textLabelSupporting, styles.justifyContentStart]}>{this.props.translate('transferAmountPage.whichAccount')}</Text>
{Boolean(selectedAccount) && (
<View style={styles.ph5}>
{_.map(paymentTypes, (paymentType) => (
<MenuItem
title={selectedAccount.title}
description={selectedAccount.description}
shouldShowRightIcon
iconWidth={selectedAccount.iconSize}
iconHeight={selectedAccount.iconSize}
icon={selectedAccount.icon}
onPress={() => this.navigateToChooseTransferAccount(selectedAccount.accountType)}
key={paymentType.key}
title={paymentType.title}
description={paymentType.description}
iconWidth={variables.iconSizeXLarge}
iconHeight={variables.iconSizeXLarge}
icon={paymentType.icon}
success={selectedPaymentType === paymentType.key}
wrapperStyle={{
...styles.mt3,
...styles.pv4,
...styles.transferBalancePayment,
...(selectedPaymentType === paymentType.key && styles.transferBalanceSelectedPayment),
}}
onPress={() => navigateToChooseTransferAccount(paymentType.type)}
/>
)}
<View style={styles.ph5}>
<Text style={[styles.mt5, styles.mb3, styles.textLabelSupporting, styles.justifyContentStart]}>{this.props.translate('transferAmountPage.fee')}</Text>
<Text style={[styles.justifyContentStart]}>{CurrencyUtils.convertToDisplayString(calculatedFee)}</Text>
</View>
</ScrollView>
<View>
<FormAlertWithSubmitButton
buttonText={this.props.translate('transferAmountPage.transfer', {
amount: isTransferable ? CurrencyUtils.convertToDisplayString(transferAmount) : '',
})}
isLoading={this.props.walletTransfer.loading}
onSubmit={() => PaymentMethods.transferWalletBalance(selectedAccount)}
isDisabled={isButtonDisabled || this.props.network.isOffline}
message={errorMessage}
isAlertVisible={!_.isEmpty(errorMessage)}
))}
</View>
<Text style={[styles.p5, styles.textLabelSupporting, styles.justifyContentStart]}>{props.translate('transferAmountPage.whichAccount')}</Text>
{Boolean(selectedAccount) && (
<MenuItem
title={selectedAccount.title}
description={selectedAccount.description}
shouldShowRightIcon
iconWidth={selectedAccount.iconSize}
iconHeight={selectedAccount.iconSize}
icon={selectedAccount.icon}
onPress={() => navigateToChooseTransferAccount(selectedAccount.accountType)}
/>
)}
<View style={styles.ph5}>
<Text style={[styles.mt5, styles.mb3, styles.textLabelSupporting, styles.justifyContentStart]}>{props.translate('transferAmountPage.fee')}</Text>
<Text style={[styles.justifyContentStart]}>{CurrencyUtils.convertToDisplayString(calculatedFee)}</Text>
</View>
</FullPageNotFoundView>
</ScreenWrapper>
);
}
</ScrollView>
<View>
<FormAlertWithSubmitButton
buttonText={props.translate('transferAmountPage.transfer', {
amount: isTransferable ? CurrencyUtils.convertToDisplayString(transferAmount) : '',
})}
isLoading={props.walletTransfer.loading}
onSubmit={() => PaymentMethods.transferWalletBalance(selectedAccount)}
isDisabled={isButtonDisabled || props.network.isOffline}
message={errorMessage}
isAlertVisible={!_.isEmpty(errorMessage)}
/>
</View>
</FullPageNotFoundView>
</ScreenWrapper>
);
}

TransferBalancePage.propTypes = propTypes;
TransferBalancePage.defaultProps = defaultProps;
TransferBalancePage.displayName = 'TransferBalancePage';

export default compose(
withLocalize,
Expand Down