Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Payments Page to show wallet balance, empty state, and more! #3890

Merged
merged 17 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from 13 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
58 changes: 58 additions & 0 deletions src/components/CurrentWalletBalance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import {ActivityIndicator, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import styles from '../styles/styles';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import compose from '../libs/compose';
import themeColors from '../styles/themes/default';
import ONYXKEYS from '../ONYXKEYS';

const propTypes = {
/** The user's wallet account */
userWallet: PropTypes.shape({
/** The user's current wallet balance */
availableBalance: PropTypes.number,
}),

...withLocalizePropTypes,
};

const defaultProps = {
userWallet: {},
};

const CurrentWalletBalance = (props) => {
if (_.isEmpty(props.userWallet)) {
return (
<ActivityIndicator
color={themeColors.text}
size="large"
style={styles.pv5}
/>
);
}

const formattedBalance = Number(props.userWallet.availableBalance).toFixed(2);

return (
<Text
style={[styles.textXXXLarge, styles.pv5, styles.alignSelfCenter]}
>
{`$${formattedBalance}`}
</Text>
);
};

CurrentWalletBalance.propTypes = propTypes;
CurrentWalletBalance.defaultProps = defaultProps;
CurrentWalletBalance.displayName = 'CurrentWalletBalance';
export default compose(
withLocalize,
withOnyx({
userWallet: {
key: ONYXKEYS.USER_WALLET,
},
}),
)(CurrentWalletBalance);
4 changes: 4 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,14 @@ export default {
addPayPalAccount: 'Add PayPal Account',
growlMessageOnSave: 'Your PayPal username was successfully added',
},
paymentsPage: {
paymentMethodsTitle: 'Payment Methods',
},
paymentMethodList: {
addPaymentMethod: 'Add Payment Method',
accountLastFour: 'Account ending in',
cardLastFour: 'Card ending in',
addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app',
},
preferencesPage: {
mostRecent: 'Most Recent',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,14 @@ export default {
yourPayPalUsername: 'Tu usuario de PayPal',
addPayPalAccount: 'Agregar Cuenta de Paypal',
},
paymentsPage: {
paymentMethodsTitle: 'Métodos de pago',
},
paymentMethodList: {
addPaymentMethod: 'Agrega método de pago',
accountLastFour: 'Cuenta con terminación',
cardLastFour: 'Tarjeta con terminacíon',
addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación',
},
preferencesPage: {
mostRecent: 'Más Recientes',
Expand Down
6 changes: 4 additions & 2 deletions src/libs/actions/PaymentMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import CONST from '../../CONST';

/**
* Calls the API to get the user's bankAccountList, cardList, wallet, and payPalMe
*
* @returns {Promise}
*/
function getPaymentMethods() {
API.Get({
return API.Get({
returnValueList: 'bankAccountList, cardList, userWallet, nameValuePairs',
name: 'paypalMeAddress',
})
.then((response) => {
Onyx.multiSet({
[ONYXKEYS.USER_WALLET]: lodashGet(response, 'userWallet', null),
[ONYXKEYS.USER_WALLET]: lodashGet(response, 'userWallet', {}),
jasperhuangg marked this conversation as resolved.
Show resolved Hide resolved
[ONYXKEYS.BANK_ACCOUNT_LIST]: lodashGet(response, 'bankAccountList', []),
[ONYXKEYS.CARD_LIST]: lodashGet(response, 'cardList', []),
[ONYXKEYS.NVP_PAYPAL_ME_ADDRESS]:
Expand Down
70 changes: 50 additions & 20 deletions src/pages/settings/Payments/PaymentMethodList.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import _ from 'underscore';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {FlatList} from 'react-native';
import {FlatList, Text} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import styles from '../../../styles/styles';
import MenuItem from '../../../components/MenuItem';
import compose from '../../../libs/compose';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
Expand All @@ -13,13 +14,17 @@ import {
CreditCard,
PayPal,
Plus,
Wallet,
} from '../../../components/Icon/Expensicons';

const MENU_ITEM = 'menuItem';

const propTypes = {
/** What to do when a menu item is pressed */
onPress: PropTypes.func.isRequired,

/** Are we loading payments from the server? */
isLoadingPayments: PropTypes.bool,

/** User's paypal.me username if they have one */
payPalMeUsername: PropTypes.string,

Expand Down Expand Up @@ -57,6 +62,7 @@ const defaultProps = {
payPalMeUsername: '',
bankAccountList: [],
cardList: [],
isLoadingPayments: false,
};

class PaymentMethodList extends Component {
Expand All @@ -75,20 +81,26 @@ class PaymentMethodList extends Component {
const combinedPaymentMethods = [];

_.each(this.props.bankAccountList, (bankAccount) => {
combinedPaymentMethods.push({
title: bankAccount.addressName,
// Add all bank accounts besides the wallet
if (bankAccount.type !== CONST.BANK_ACCOUNT_TYPES.WALLET) {
combinedPaymentMethods.push({
type: MENU_ITEM,
title: bankAccount.addressName,

// eslint-disable-next-line
description: `${this.props.translate('paymentMethodList.accountLastFour')} ${bankAccount.accountNumber.slice(-4)}`,
icon: bankAccount.type === CONST.BANK_ACCOUNT_TYPES.WALLET ? Wallet : Bank,
onPress: e => this.props.onPress(e, bankAccount.bankAccountID),
key: `bankAccount-${bankAccount.bankAccountID}`,
});
// eslint-disable-next-line
description: `${this.props.translate('paymentMethodList.accountLastFour')} ${bankAccount.accountNumber.slice(-4)}`,
icon: Bank,
onPress: e => this.props.onPress(e, bankAccount.bankAccountID),
key: `bankAccount-${bankAccount.bankAccountID}`,
});
}
});

_.each(this.props.cardList, (card) => {
// Add all cards besides the "cash" card
if (card.cardName !== CONST.CARD_TYPES.DEFAULT_CASH) {
combinedPaymentMethods.push({
type: MENU_ITEM,
title: card.cardName,

// eslint-disable-next-line
Expand All @@ -102,6 +114,7 @@ class PaymentMethodList extends Component {

if (this.props.payPalMeUsername) {
combinedPaymentMethods.push({
type: MENU_ITEM,
title: 'PayPal.me',
description: this.props.payPalMeUsername,
icon: PayPal,
Expand All @@ -110,11 +123,20 @@ class PaymentMethodList extends Component {
});
}

// If we have not added any payment methods, show a default empty state
if (_.isEmpty(combinedPaymentMethods)) {
combinedPaymentMethods.push({
text: this.props.translate('paymentMethodList.addFirstPaymentMethod'),
});
}

combinedPaymentMethods.push({
type: MENU_ITEM,
title: this.props.translate('paymentMethodList.addPaymentMethod'),
icon: Plus,
onPress: e => this.props.onPress(e),
key: 'addPaymentMethodButton',
disabled: this.props.isLoadingPayments,
});

return combinedPaymentMethods;
Expand All @@ -129,14 +151,25 @@ class PaymentMethodList extends Component {
* @return {React.Component}
*/
renderItem({item}) {
if (item.type === MENU_ITEM) {
return (
<MenuItem
onPress={item.onPress}
title={item.title}
description={item.description}
icon={item.icon}
key={item.key}
disabled={item.disabled}
/>
);
}

return (
<MenuItem
onPress={item.onPress}
title={item.title}
description={item.description}
icon={item.icon}
key={item.key}
/>
<Text
style={[styles.popoverMenuItem]}
>
{item.text}
</Text>
);
}

Expand All @@ -163,9 +196,6 @@ export default compose(
cardList: {
key: ONYXKEYS.CARD_LIST,
},
userWallet: {
key: ONYXKEYS.USER_WALLET,
},
payPalMeUsername: {
key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS,
},
Expand Down
26 changes: 23 additions & 3 deletions src/pages/settings/Payments/PaymentsPage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {View} from 'react-native';
import {View, ActivityIndicator} from 'react-native';
import PaymentMethodList from './PaymentMethodList';
import ROUTES from '../../../ROUTES';
import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton';
Expand All @@ -9,11 +9,14 @@ import styles from '../../../styles/styles';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import compose from '../../../libs/compose';
import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView/index';
import Text from '../../../components/Text';
import getPaymentMethods from '../../../libs/actions/PaymentMethods';
import Popover from '../../../components/Popover';
import {PayPal} from '../../../components/Icon/Expensicons';
import MenuItem from '../../../components/MenuItem';
import getClickedElementLocation from '../../../libs/getClickedElementLocation';
import CurrentWalletBalance from '../../../components/CurrentWalletBalance';
import themeColors from '../../../styles/themes/default';

const PAYPAL = 'payPalMe';

Expand All @@ -29,6 +32,7 @@ class PaymentsPage extends React.Component {
shouldShowAddPaymentMenu: false,
anchorPositionTop: 0,
anchorPositionLeft: 0,
isLoadingPaymentMethods: true,
};

this.paymentMethodPressed = this.paymentMethodPressed.bind(this);
Expand All @@ -37,7 +41,9 @@ class PaymentsPage extends React.Component {
}

componentDidMount() {
getPaymentMethods();
getPaymentMethods().then(() => {
this.setState({isLoadingPaymentMethods: false});
});
}

/**
Expand Down Expand Up @@ -91,9 +97,23 @@ class PaymentsPage extends React.Component {
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<View style={[styles.flex1]}>
<View>
<CurrentWalletBalance />
<View style={[styles.flex1, styles.flexRow, styles.mb5, styles.alignItemsCenter]}>
<Text
style={[styles.ph5, styles.textStrong, styles.justifyContentStart, styles.flex1]}
>
{this.props.translate('paymentsPage.paymentMethodsTitle')}
</Text>
{
this.state.isLoadingPaymentMethods
&& <ActivityIndicator color={themeColors.spinner} style={[styles.mr5]} />
}
</View>
<PaymentMethodList
onPress={this.paymentMethodPressed}
style={[styles.flex4]}
isLoadingPayments={this.state.isLoadingPaymentMethods}
/>
</View>
<Popover
Expand Down
7 changes: 7 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ const styles = {
fontSize: variables.fontSizeLarge,
},

textXXXLarge: {
color: themeColors.heading,
fontFamily: fontFamily.GTA_BOLD,
fontSize: variables.fontSizeXXXLarge,
fontWeight: fontWeightBold,
},

textStrong: {
fontFamily: fontFamily.GTA_BOLD,
fontWeight: fontWeightBold,
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utilities/spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export default {
marginRight: 16,
},

mr5: {
marginRight: 20,
},

ml1: {
marginLeft: 4,
},
Expand Down