diff --git a/src/CONST.js b/src/CONST.js index 47fb10ec576a..988ded81a73d 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -559,6 +559,7 @@ const CONST = { }, STATE: { SUBMITTED: 'SUBMITTED', + PROCESSING: 'PROCESSING', }, STATE_NUM: { OPEN: 0, @@ -960,6 +961,7 @@ const CONST = { ELSEWHERE: 'Elsewhere', EXPENSIFY: 'Expensify', PAYPAL_ME: 'PayPal.me', + VBBA: 'ACH', }, MONEY_REQUEST_TYPE: { SEND: 'send', diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index a5e3c97740da..b901e1f2ea8a 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -91,6 +91,9 @@ export default { // A unique identifier that each user has that's used to send notifications NVP_PRIVATE_PUSH_NOTIFICATION_ID: 'private_pushNotificationID', + // The NVP with the last payment method used per policy + NVP_LAST_PAYMENT_METHOD: 'nvp_lastPaymentMethod', + // Does this user have push notifications enabled for this device? PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', diff --git a/src/ROUTES.js b/src/ROUTES.js index ec4730289e06..c67bef713fa1 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -19,6 +19,7 @@ const SETTINGS_CONTACT_METHODS = 'settings/profile/contact-methods'; export default { BANK_ACCOUNT: 'bank-account', + BANK_ACCOUNT_NEW: 'bank-account/new', BANK_ACCOUNT_WITH_STEP_TO_OPEN: 'bank-account/:stepToOpen?', BANK_ACCOUNT_PERSONAL: 'bank-account/personal', getBankAccountRoute: (stepToOpen = '', policyID = '') => `bank-account/${stepToOpen}?policyID=${policyID}`, diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 61927aefb868..2581bde85b4e 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -12,11 +12,16 @@ import PopoverMenu from './PopoverMenu'; import paypalMeDataPropTypes from './paypalMeDataPropTypes'; const propTypes = { + /** Should the component be visible? */ isVisible: PropTypes.bool.isRequired, + + /** Callback to execute when the component closes. */ onClose: PropTypes.func.isRequired, + + /** Anchor position for the AddPaymentMenu. */ anchorPosition: PropTypes.shape({ - top: PropTypes.number, - left: PropTypes.number, + horizontal: PropTypes.number, + vertical: PropTypes.number, }), /** Account details for PayPal.Me */ @@ -43,7 +48,7 @@ const AddPaymentMethodMenu = (props) => ( isVisible={props.isVisible} onClose={props.onClose} anchorPosition={props.anchorPosition} - onItemSelected={() => props.onClose()} + onItemSelected={props.onClose} menuItems={[ { text: props.translate('common.bankAccount'), diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index f65696ec3541..29424233891d 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -294,6 +294,7 @@ class AvatarWithImagePicker extends React.Component { onItemSelected={() => this.setState({isMenuVisible: false})} menuItems={this.createMenuItems(openPicker)} anchorPosition={this.props.anchorPosition} + anchorAlignment={this.props.anchorAlignment} /> )} diff --git a/src/components/Button/index.js b/src/components/Button/index.js index 65107cfe9392..f9200d085b28 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -109,6 +109,9 @@ const propTypes = { /** Accessibility label for the component */ accessibilityLabel: PropTypes.string, + + /** A ref to forward the button */ + forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.oneOfType([PropTypes.instanceOf(React.Component), PropTypes.func])})]), }; const defaultProps = { @@ -141,6 +144,7 @@ const defaultProps = { shouldEnableHapticFeedback: false, nativeID: '', accessibilityLabel: '', + forwardedRef: undefined, }; class Button extends Component { @@ -240,6 +244,7 @@ class Button extends Component { render() { return ( { if (e && e.type === 'click') { e.currentTarget.blur(); @@ -303,4 +308,15 @@ class Button extends Component { Button.propTypes = propTypes; Button.defaultProps = defaultProps; -export default compose(withNavigationFallback, withNavigationFocus)(Button); +export default compose( + withNavigationFallback, + withNavigationFocus, +)( + React.forwardRef((props, ref) => ( + - -); - -ButtonWithDropdown.propTypes = propTypes; -ButtonWithDropdown.defaultProps = defaultProps; -ButtonWithDropdown.displayName = 'ButtonWithDropdown'; -export default ButtonWithDropdown; diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js new file mode 100644 index 000000000000..a518ce647c75 --- /dev/null +++ b/src/components/ButtonWithDropdownMenu.js @@ -0,0 +1,139 @@ +import React, {useState, useRef, useEffect} from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import _ from 'underscore'; +import useWindowDimensions from '../hooks/useWindowDimensions'; +import styles from '../styles/styles'; +import Button from './Button'; +import PopoverMenu from './PopoverMenu'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import themeColors from '../styles/themes/default'; +import CONST from '../CONST'; + +const propTypes = { + /** Text to display for the menu header */ + menuHeaderText: PropTypes.string, + + /** Callback to execute when the main button is pressed */ + onPress: PropTypes.func.isRequired, + + /** Whether we should show a loading state for the main button */ + isLoading: PropTypes.bool, + + /** Should the confirmation button be disabled? */ + isDisabled: PropTypes.bool, + + /** Additional styles to add to the component */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + + /** Menu options to display */ + /** e.g. [{text: 'Pay with Expensify', icon: Wallet}, {text: 'PayPal', icon: PayPal}] */ + options: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + icon: PropTypes.elementType, + iconWidth: PropTypes.number, + iconHeight: PropTypes.number, + iconDescription: PropTypes.string, + }), + ).isRequired, +}; + +const defaultProps = { + isLoading: false, + isDisabled: false, + menuHeaderText: '', + style: [], +}; + +const ButtonWithDropdownMenu = (props) => { + const [selectedItemIndex, setSelectedItemIndex] = useState(0); + const [isMenuVisible, setIsMenuVisible] = useState(false); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); + const {width: windowWidth, height: windowHeight} = useWindowDimensions(); + const caretButton = useRef(null); + useEffect(() => { + if (!caretButton.current) { + return; + } + caretButton.current.measureInWindow((x, y, w, h) => { + setPopoverAnchorPosition({ + horizontal: x + w, + vertical: y + h, + }); + }); + }, [windowWidth, windowHeight]); + + const selectedItem = props.options[selectedItemIndex]; + return ( + + {props.options.length > 1 ? ( + + + + ) : ( +