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

feat(Wallet): add empty state and redesign wallet page #26406

Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions assets/animations/FastMoney.json

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default {
WORKSPACES: 'Settings_Workspaces',
SECURITY: 'Settings_Security',
STATUS: 'Settings_Status',
WALLET: 'Settings_Wallet',
pac-guerreiro marked this conversation as resolved.
Show resolved Hide resolved
WALLET_DOMAIN_CARDS: 'Settings_Wallet_DomainCards',
},
SAVE_THE_WORLD: {
ROOT: 'SaveTheWorld_Root',
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import MoneyBadge from '../../../assets/images/simple-illustrations/simple-illus
import TreasureChest from '../../../assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
import ThumbsUpStars from '../../../assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
import Hands from '../../../assets/images/product-illustrations/home-illustration-hands.svg';
import HandEarth from '../../../assets/images/simple-illustrations/simple-illustration__handearth.svg';

export {
Abracadabra,
Expand Down Expand Up @@ -94,4 +95,5 @@ export {
TreasureChest,
ThumbsUpStars,
Hands,
HandEarth,
};
3 changes: 2 additions & 1 deletion src/components/LottieAnimations.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const ExpensifyLounge = require('../../assets/animations/ExpensifyLounge.json');
const FastMoney = require('../../assets/animations/FastMoney.json');
const Fireworks = require('../../assets/animations/Fireworks.json');
const Hands = require('../../assets/animations/Hands.json');
const PreferencesDJ = require('../../assets/animations/PreferencesDJ.json');
Expand All @@ -8,4 +9,4 @@ const SaveTheWorld = require('../../assets/animations/SaveTheWorld.json');
const Safe = require('../../assets/animations/Safe.json');
const Magician = require('../../assets/animations/Magician.json');

export {ExpensifyLounge, Fireworks, Hands, PreferencesDJ, ReviewingBankInfo, SaveTheWorld, WorkspacePlanet, Safe, Magician};
export {ExpensifyLounge, FastMoney, Fireworks, Hands, PreferencesDJ, ReviewingBankInfo, SaveTheWorld, WorkspacePlanet, Safe, Magician};
44 changes: 34 additions & 10 deletions src/components/Section.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const propTypes = {
/** The text to display in the title of the section */
title: PropTypes.string.isRequired,

/** The text to display in the subtitle of the section */
subtitle: PropTypes.string,

/** The icon to display along with the title */
icon: PropTypes.func,

Expand All @@ -27,6 +30,18 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
containerStyles: PropTypes.arrayOf(PropTypes.object),

/** Customize the Section container */
// eslint-disable-next-line react/forbid-prop-types
titleStyles: PropTypes.arrayOf(PropTypes.object),

/** Customize the Section container */
// eslint-disable-next-line react/forbid-prop-types
subtitleStyles: PropTypes.arrayOf(PropTypes.object),

/** Customize the Section container */
// eslint-disable-next-line react/forbid-prop-types
childrenStyles: PropTypes.arrayOf(PropTypes.object),

/** Customize the Icon container */
// eslint-disable-next-line react/forbid-prop-types
iconContainerStyles: PropTypes.arrayOf(PropTypes.object),
Expand All @@ -39,21 +54,24 @@ const defaultProps = {
IconComponent: null,
containerStyles: [],
iconContainerStyles: [],
titleStyles: [],
subtitleStyles: [],
childrenStyles: [],
subtitle: null,
};

function Section(props) {
const IconComponent = props.IconComponent;
function Section({children, childrenStyles, containerStyles, icon, IconComponent, iconContainerStyles, menuItems, subtitle, subtitleStyles, title, titleStyles}) {
return (
<>
<View style={[styles.pageWrapper, styles.cardSection, ...props.containerStyles]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.w100]}>
<View style={[styles.pageWrapper, styles.cardSection, ...containerStyles]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, ...titleStyles]}>
<View style={[styles.flexShrink1]}>
<Text style={[styles.textHeadline, styles.cardSectionTitle]}>{props.title}</Text>
<Text style={[styles.textHeadline, styles.cardSectionTitle]}>{title}</Text>
</View>
<View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd, ...props.iconContainerStyles]}>
{Boolean(props.icon) && (
<View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd, ...iconContainerStyles]}>
{Boolean(icon) && (
<Icon
src={props.icon}
src={icon}
height={68}
width={68}
/>
Expand All @@ -62,9 +80,15 @@ function Section(props) {
</View>
</View>

<View style={[styles.w100]}>{props.children}</View>
{Boolean(subtitle) && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, styles.mt4, ...subtitleStyles]}>
<Text style={styles.textNormal}>{subtitle}</Text>
</View>
)}

<View style={[styles.w100, ...childrenStyles]}>{children}</View>

<View style={[styles.w100]}>{Boolean(props.menuItems) && <MenuItemList menuItems={props.menuItems} />}</View>
<View style={[styles.w100]}>{Boolean(menuItems) && <MenuItemList menuItems={menuItems} />}</View>
</View>
</>
);
Expand Down
45 changes: 45 additions & 0 deletions src/components/WalletSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
import React from 'react';
import Section from './Section';
import styles from '../styles/styles';

const propTypes = {
/** Contents to display inside the section */
children: PropTypes.node,

/** The icon to display along with the title */
icon: PropTypes.func,

/** The text to display in the subtitle of the section */
subtitle: PropTypes.string,

/** The text to display in the title of the section */
title: PropTypes.string.isRequired,
};

const defaultProps = {
children: null,
icon: null,
subtitle: null,
};

function WalletSection({children, icon, subtitle, title}) {
return (
<Section
icon={icon}
subtitle={subtitle}
title={title}
containerStyles={[styles.p0, styles.pv5]}
titleStyles={[styles.ph5]}
subtitleStyles={[styles.ph5]}
>
{children}
</Section>
);
}

WalletSection.defaultProps = defaultProps;
WalletSection.displayName = 'WalletSection';
WalletSection.propTypes = propTypes;

export default WalletSection;
10 changes: 10 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,16 @@ export default {
setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.',
},
addBankAccountFailure: 'An unexpected error occurred while trying to add your bank account. Please try again.',
getPaidFaster: 'Get paid faster',
addPaymentMethod: 'Add a payment method to send and receive payments directly in the app.',
getPaidBackFaster: 'Get paid back faster',
secureAccessToYourMoney: 'Secure access to your money',
receiveMoney: 'Receive money in your local currency',
expensifyWallet: 'Expensify Wallet',
sendAndReceiveMoney: 'Send and receive money from your Expensify Wallet.',
bankAccounts: 'Bank accounts',
addBankAccountToSendAndReceive: 'Add a bank account to send and receive payments directly in the app.',
addBankAccount: 'Add bank account',
},
cardPage: {
expensifyCard: 'Expensify Card',
Expand Down
10 changes: 10 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,16 @@ export default {
setDefaultFailure: 'No se ha podido configurar el método de pago.',
},
addBankAccountFailure: 'Ocurrió un error inesperado al intentar añadir la cuenta bancaria. Inténtalo de nuevo.',
getPaidFaster: 'Cobra más rápido',
addPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente en la aplicación.',
getPaidBackFaster: 'Recibe tus pagos más rápido',
secureAccessToYourMoney: 'Acceso seguro a tu dinero',
receiveMoney: 'Recibe dinero en tu moneda local',
expensifyWallet: 'Billetera Expensify',
sendAndReceiveMoney: 'Envía y recibe dinero desde tu Billetera Expensify.',
bankAccounts: 'Cuentas bancarias',
addBankAccountToSendAndReceive: 'Añade una cuenta bancaria para enviar y recibir pagos directamente en la aplicación.',
addBankAccount: 'Agregar cuenta bancaria',
},
cardPage: {
expensifyCard: 'Tarjeta Expensify',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/NavigationRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function NavigationRoot(props) {

const animateStatusBarBackgroundColor = () => {
const currentRoute = navigationRef.getCurrentRoute();
const currentScreenBackgroundColor = themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG;
const currentScreenBackgroundColor = (currentRoute.params && currentRoute.params.backgroundColor) || themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG;

prevStatusBarBackgroundColor.current = statusBarBackgroundColor.current;
statusBarBackgroundColor.current = currentScreenBackgroundColor;
Expand Down
69 changes: 57 additions & 12 deletions src/pages/settings/Wallet/PaymentMethodList.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import FormAlertWrapper from '../../../components/FormAlertWrapper';
import OfflineWithFeedback from '../../../components/OfflineWithFeedback';
import * as PaymentMethods from '../../../libs/actions/PaymentMethods';
import Log from '../../../libs/Log';
import stylePropTypes from '../../../styles/stylePropTypes';

const propTypes = {
/** What to do when a menu item is pressed */
Expand All @@ -36,6 +37,9 @@ const propTypes = {
/** Whether the add Payment button be shown on the list */
shouldShowAddPaymentMethodButton: PropTypes.bool,

/** Whether the empty list message should be shown when the list is empty */
shouldShowEmptyListMessage: PropTypes.bool,

/** Are we loading payment methods? */
isLoadingPaymentMethods: PropTypes.bool,

Expand Down Expand Up @@ -69,6 +73,12 @@ const propTypes = {
/** React ref being forwarded to the PaymentMethodList Button */
buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

/** To enable/disable scrolling */
shouldEnableScroll: PropTypes.bool,

/** List container style */
style: stylePropTypes,

...withLocalizePropTypes,
};

Expand All @@ -81,13 +91,16 @@ const defaultProps = {
},
isLoadingPaymentMethods: true,
shouldShowAddPaymentMethodButton: true,
shouldShowEmptyListMessage: true,
filterType: '',
actionPaymentMethodType: '',
activePaymentMethodID: '',
selectedMethodID: '',
listHeaderComponent: null,
buttonRef: () => {},
onListContentSizeChange: () => {},
shouldEnableScroll: true,
style: {},
};

/**
Expand Down Expand Up @@ -143,9 +156,26 @@ function shouldShowDefaultBadge(filteredPaymentMethods, isDefault = false) {
function isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod) {
return paymentMethod.accountType === actionPaymentMethodType && paymentMethod.methodID === activePaymentMethodID;
}
function PaymentMethodList(props) {
const {actionPaymentMethodType, activePaymentMethodID, bankAccountList, fundList, filterType, network, onPress, shouldShowSelectedState, selectedMethodID, translate} = props;

function PaymentMethodList({
actionPaymentMethodType,
activePaymentMethodID,
bankAccountList,
buttonRef,
fundList,
filterType,
isLoadingPaymentMethods,
listHeaderComponent,
network,
onListContentSizeChange,
onPress,
shouldEnableScroll,
shouldShowSelectedState,
shouldShowAddPaymentMethodButton,
shouldShowEmptyListMessage,
selectedMethodID,
style,
translate,
}) {
const filteredPaymentMethods = useMemo(() => {
const paymentCardList = fundList || {};
// Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them
Expand Down Expand Up @@ -183,6 +213,18 @@ function PaymentMethodList(props) {
*/
const renderListEmptyComponent = useCallback(() => <Text style={[styles.popoverMenuItem]}>{translate('paymentMethodList.addFirstPaymentMethod')}</Text>, [translate]);

const renderListFooterComponent = useCallback(
() => (
<MenuItem
onPress={onPress}
title={translate('walletPage.addBankAccount')}
icon={Expensicons.Plus}
wrapperStyle={styles.paymentMethod}
/>
),
[onPress, translate],
);

/**
* Create a menuItem for each passed paymentMethod
*
Expand All @@ -209,13 +251,13 @@ function PaymentMethodList(props) {
iconHeight={item.iconSize}
iconWidth={item.iconSize}
badgeText={shouldShowDefaultBadge(filteredPaymentMethods, item.isDefault) ? translate('paymentMethodList.defaultPaymentMethod') : null}
wrapperStyle={item.wrapperStyle}
wrapperStyle={styles.paymentMethod}
shouldShowSelectedState={shouldShowSelectedState}
isSelected={selectedMethodID === item.methodID}
/>
</OfflineWithFeedback>
),
[shouldShowSelectedState, selectedMethodID, filteredPaymentMethods, translate],
[filteredPaymentMethods, translate, shouldShowSelectedState, selectedMethodID],
);

return (
Expand All @@ -224,25 +266,28 @@ function PaymentMethodList(props) {
data={filteredPaymentMethods}
renderItem={renderItem}
keyExtractor={(item) => item.key}
ListEmptyComponent={renderListEmptyComponent(translate)}
ListHeaderComponent={props.listHeaderComponent}
onContentSizeChange={props.onListContentSizeChange}
ListEmptyComponent={shouldShowEmptyListMessage ? renderListEmptyComponent(translate) : null}
ListHeaderComponent={listHeaderComponent}
ListFooterComponent={renderListFooterComponent}
pac-guerreiro marked this conversation as resolved.
Show resolved Hide resolved
onContentSizeChange={onListContentSizeChange}
scrollEnabled={shouldEnableScroll}
style={style}
/>
{props.shouldShowAddPaymentMethodButton && (
{shouldShowAddPaymentMethodButton && (
<FormAlertWrapper>
{(isOffline) => (
<Button
text={translate('paymentMethodList.addPaymentMethod')}
icon={Expensicons.CreditCard}
onPress={props.onPress}
isDisabled={props.isLoadingPaymentMethods || isOffline}
onPress={onPress}
isDisabled={isLoadingPaymentMethods || isOffline}
style={[styles.mh4, styles.buttonCTA]}
iconStyles={[styles.buttonCTAIcon]}
key="addPaymentMethodButton"
success
shouldShowRightIcon
large
ref={props.buttonRef}
ref={buttonRef}
/>
)}
</FormAlertWrapper>
Expand Down
Loading
Loading