diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index cd4114be508c..6fe8a0625cd8 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -30,7 +30,7 @@ export default {
// Credentials to authenticate the user
CREDENTIALS: 'credentials',
- // Contains loading data for the IOU feature (IOUModal, IOUDetail, & IOUPreview Components)
+ // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & IOUPreview Components)
IOU: 'iou',
// Keeps track if there is modal currently visible or not
diff --git a/src/ROUTES.js b/src/ROUTES.js
index 2412e7e42550..d5ba2406e322 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -6,9 +6,9 @@ import * as Url from './libs/Url';
*/
const REPORT = 'r';
-const IOU_REQUEST = 'iou/request';
-const IOU_BILL = 'iou/split';
-const IOU_SEND = 'iou/send';
+const IOU_REQUEST = 'request/new';
+const IOU_BILL = 'split/new';
+const IOU_SEND = 'send/new';
const IOU_DETAILS = 'iou/details';
const IOU_REQUEST_CURRENCY = `${IOU_REQUEST}/currency`;
const IOU_BILL_CURRENCY = `${IOU_BILL}/currency`;
diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js
index eae3f48d84ca..40439bfd39f6 100755
--- a/src/components/IOUConfirmationList.js
+++ b/src/components/IOUConfirmationList.js
@@ -25,10 +25,10 @@ const propTypes = {
/** Callback to parent modal to send money */
onSendMoney: PropTypes.func.isRequired,
- /** Callback to update comment from IOUModal */
+ /** Callback to update comment from MoneyRequestModal */
onUpdateComment: PropTypes.func,
- /** Comment value from IOUModal */
+ /** Comment value from MoneyRequestModal */
comment: PropTypes.string,
/** Should we request a single or multiple participant selection from user */
@@ -40,7 +40,7 @@ const propTypes = {
/** IOU type */
iouType: PropTypes.string,
- /** Selected participants from IOUModal with login */
+ /** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(PropTypes.shape({
login: PropTypes.string.isRequired,
alternateText: PropTypes.string,
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index ce3151da9c66..c6e1be4f6f41 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -258,7 +258,7 @@ function updateSelectedTimezone(selectedTimezone) {
/**
* Fetches the local currency based on location and sets currency code/symbol to Onyx
*/
-function openIOUModalPage() {
+function openMoneyRequestModalPage() {
API.read('OpenIOUModalPage');
}
@@ -368,7 +368,7 @@ export {
getDisplayName,
updateAvatar,
deleteAvatar,
- openIOUModalPage,
+ openMoneyRequestModalPage,
openPersonalDetailsPage,
extractFirstAndLastNameFromAvailableDetails,
updateDisplayName,
diff --git a/src/pages/iou/IOUBillPage.js b/src/pages/iou/IOUBillPage.js
index d1938ec41a15..6ca3bc6ec916 100644
--- a/src/pages/iou/IOUBillPage.js
+++ b/src/pages/iou/IOUBillPage.js
@@ -1,7 +1,7 @@
import React from 'react';
-import IOUModal from './IOUModal';
+import MoneyRequestModal from './MoneyRequestModal';
// eslint-disable-next-line react/jsx-props-no-spreading
-const IOUBillPage = props => ;
+const IOUBillPage = props => ;
IOUBillPage.displayName = 'IOUBillPage';
export default IOUBillPage;
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
deleted file mode 100755
index 069ff51c7e57..000000000000
--- a/src/pages/iou/IOUModal.js
+++ /dev/null
@@ -1,536 +0,0 @@
-import _ from 'underscore';
-import React, {Component} from 'react';
-import {View, TouchableOpacity} from 'react-native';
-import PropTypes from 'prop-types';
-import lodashGet from 'lodash/get';
-import {withOnyx} from 'react-native-onyx';
-import Str from 'expensify-common/lib/str';
-import IOUAmountPage from './steps/IOUAmountPage';
-import IOUParticipantsPage from './steps/IOUParticipantsPage/IOUParticipantsPage';
-import IOUConfirmPage from './steps/IOUConfirmPage';
-import Header from '../../components/Header';
-import styles from '../../styles/styles';
-import Icon from '../../components/Icon';
-import * as IOU from '../../libs/actions/IOU';
-import * as Expensicons from '../../components/Icon/Expensicons';
-import Navigation from '../../libs/Navigation/Navigation';
-import ONYXKEYS from '../../ONYXKEYS';
-import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
-import compose from '../../libs/compose';
-import * as OptionsListUtils from '../../libs/OptionsListUtils';
-import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
-import AnimatedStep from '../../components/AnimatedStep';
-import ScreenWrapper from '../../components/ScreenWrapper';
-import Tooltip from '../../components/Tooltip';
-import CONST from '../../CONST';
-import * as PersonalDetails from '../../libs/actions/PersonalDetails';
-import withCurrentUserPersonalDetails from '../../components/withCurrentUserPersonalDetails';
-import networkPropTypes from '../../components/networkPropTypes';
-import {withNetwork} from '../../components/OnyxProvider';
-import reportPropTypes from '../reportPropTypes';
-import * as ReportUtils from '../../libs/ReportUtils';
-import * as ReportScrollManager from '../../libs/ReportScrollManager';
-
-/**
- * IOU modal for requesting money and splitting bills.
- */
-const propTypes = {
- /** Whether the IOU is for a single request or a group bill split */
- hasMultipleParticipants: PropTypes.bool,
-
- /** The type of IOU report, i.e. bill, request, send */
- iouType: PropTypes.string,
-
- /** The report passed via the route */
- // eslint-disable-next-line react/no-unused-prop-types
- report: reportPropTypes,
-
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
- // Holds data related to IOU view state, rather than the underlying IOU data.
- iou: PropTypes.shape({
- /** Whether or not transaction creation has started */
- creatingIOUTransaction: PropTypes.bool,
-
- /** Whether or not transaction creation has resulted to error */
- error: PropTypes.bool,
-
- // Selected Currency Code of the current IOU
- selectedCurrencyCode: PropTypes.string,
- }),
-
- /** Personal details of all the users */
- personalDetails: PropTypes.shape({
- /** Primary login of participant */
- login: PropTypes.string,
-
- /** Display Name of participant */
- displayName: PropTypes.string,
-
- /** Avatar url of participant */
- avatar: PropTypes.string,
- }),
-
- /** Personal details of the current user */
- currentUserPersonalDetails: PropTypes.shape({
- // Local Currency Code of the current user
- localCurrencyCode: PropTypes.string,
- }),
-
- ...withLocalizePropTypes,
-};
-
-const defaultProps = {
- hasMultipleParticipants: false,
- report: {
- participants: [],
- },
- iouType: CONST.IOU.IOU_TYPE.REQUEST,
- currentUserPersonalDetails: {
- localCurrencyCode: CONST.CURRENCY.USD,
- },
- personalDetails: {},
- iou: {
- creatingIOUTransaction: false,
- error: false,
- selectedCurrencyCode: null,
- },
-};
-
-// Determines type of step to display within Modal, value provides the title for that page.
-const Steps = {
- IOUAmount: 'iou.amount',
- IOUParticipants: 'iou.participants',
- IOUConfirm: 'iou.confirm',
-};
-
-class IOUModal extends Component {
- constructor(props) {
- super(props);
- this.navigateToPreviousStep = this.navigateToPreviousStep.bind(this);
- this.navigateToNextStep = this.navigateToNextStep.bind(this);
- this.addParticipants = this.addParticipants.bind(this);
- this.createTransaction = this.createTransaction.bind(this);
- this.updateComment = this.updateComment.bind(this);
- this.sendMoney = this.sendMoney.bind(this);
-
- const participants = lodashGet(props, 'report.participants', []);
- const participantsWithDetails = _.map(OptionsListUtils.getPersonalDetailsForLogins(participants, props.personalDetails), personalDetails => ({
- login: personalDetails.login,
- text: personalDetails.displayName,
- firstName: lodashGet(personalDetails, 'firstName', ''),
- lastName: lodashGet(personalDetails, 'lastName', ''),
- alternateText: Str.isSMSLogin(personalDetails.login) ? Str.removeSMSDomain(personalDetails.login) : personalDetails.login,
- icons: [{
- source: ReportUtils.getAvatar(personalDetails.avatar, personalDetails.login),
- name: personalDetails.login,
- type: CONST.ICON_TYPE_AVATAR,
- }],
- keyForList: personalDetails.login,
- payPalMeAddress: lodashGet(personalDetails, 'payPalMeAddress', ''),
- phoneNumber: lodashGet(personalDetails, 'phoneNumber', ''),
- }));
-
- this.state = {
- previousStepIndex: 0,
- currentStepIndex: 0,
- participants: participantsWithDetails,
-
- // amount is currency in decimal format
- amount: '',
- comment: '',
- };
-
- // Skip IOUParticipants step if participants are passed in
- if (participants.length) {
- // The steps to be shown within the create IOU flow.
- this.steps = [Steps.IOUAmount, Steps.IOUConfirm];
- } else {
- this.steps = [Steps.IOUAmount, Steps.IOUParticipants, Steps.IOUConfirm];
- }
- }
-
- componentDidMount() {
- PersonalDetails.openIOUModalPage();
- IOU.setIOUSelectedCurrency(this.props.currentUserPersonalDetails.localCurrencyCode);
- }
-
- componentDidUpdate(prevProps) {
- const wasCreatingIOUTransaction = lodashGet(prevProps, 'iou.creatingIOUTransaction');
- const iouError = lodashGet(this.props, 'iou.error');
- if (prevProps.network.isOffline && !this.props.network.isOffline) {
- PersonalDetails.openIOUModalPage();
- }
-
- // Successfully close the modal if transaction creation has ended and there is no error
- if (wasCreatingIOUTransaction && !lodashGet(this.props, 'iou.creatingIOUTransaction') && !iouError) {
- Navigation.dismissModal();
- }
-
- // If transaction fails, handling it here
- if (wasCreatingIOUTransaction && iouError === true) {
- // Navigating to Enter Amount Page
- // eslint-disable-next-line react/no-did-update-set-state
- this.setState({currentStepIndex: 0});
- this.creatingIOUTransaction = false;
- }
-
- const currentSelectedCurrencyCode = lodashGet(this.props, 'iou.selectedCurrencyCode');
- if (lodashGet(prevProps, 'iou.selectedCurrencyCode') !== currentSelectedCurrencyCode) {
- IOU.setIOUSelectedCurrency(currentSelectedCurrencyCode);
- }
- }
-
- /**
- * Decides our animation type based on whether we're increasing or decreasing
- * our step index.
- * @returns {String}
- */
- getDirection() {
- if (this.state.previousStepIndex < this.state.currentStepIndex) {
- return 'in';
- }
- if (this.state.previousStepIndex > this.state.currentStepIndex) {
- return 'out';
- }
-
- // Doesn't animate the step when first opening the modal
- if (this.state.previousStepIndex === this.state.currentStepIndex) {
- return null;
- }
- }
-
- /**
- * Retrieve title for current step, based upon current step and type of IOU
- *
- * @returns {String}
- */
- getTitleForStep() {
- const currentStepIndex = this.state.currentStepIndex;
- const isSendingMoney = this.props.iouType === CONST.IOU.IOU_TYPE.SEND;
- if (currentStepIndex === 1 || currentStepIndex === 2) {
- const formattedAmount = this.props.numberFormat(
- this.state.amount, {
- style: 'currency',
- currency: this.props.iou.selectedCurrencyCode,
- },
- );
- if (isSendingMoney) {
- return this.props.translate('iou.send', {
- amount: formattedAmount,
- });
- }
- return this.props.translate(
- this.props.hasMultipleParticipants ? 'iou.split' : 'iou.request', {
- amount: formattedAmount,
- },
- );
- }
- if (currentStepIndex === 0) {
- if (isSendingMoney) {
- return this.props.translate('iou.sendMoney');
- }
- return this.props.translate(this.props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney');
- }
-
- return this.props.translate(this.steps[currentStepIndex]) || '';
- }
-
- /**
- * Update comment whenever user enters any new text
- *
- * @param {String} comment
- */
- updateComment(comment) {
- this.setState({
- comment,
- });
- }
-
- /**
- * Update participants whenever user selects the payment recipient
- *
- * @param {Array} participants
- */
- addParticipants(participants) {
- this.setState({
- participants,
- });
- }
-
- /**
- * Navigate to the next IOU step if possible
- */
- navigateToPreviousStep() {
- if (this.state.currentStepIndex <= 0) {
- return;
- }
- this.setState(prevState => ({
- previousStepIndex: prevState.currentStepIndex,
- currentStepIndex: prevState.currentStepIndex - 1,
- }));
- }
-
- /**
- * Navigate to the previous IOU step if possible
- */
- navigateToNextStep() {
- if (this.state.currentStepIndex >= this.steps.length - 1) {
- return;
- }
-
- this.setState(prevState => ({
- previousStepIndex: prevState.currentStepIndex,
- currentStepIndex: prevState.currentStepIndex + 1,
- }));
- }
-
- /**
- * Checks if user has a GOLD wallet then creates a paid IOU report on the fly
- *
- * @param {String} paymentMethodType
- */
- sendMoney(paymentMethodType) {
- const amount = Math.round(this.state.amount * 100);
- const currency = this.props.iou.selectedCurrencyCode;
- const comment = this.state.comment.trim();
- const participant = this.state.participants[0];
-
- if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
- IOU.sendMoneyElsewhere(
- this.props.report,
- amount,
- currency,
- comment,
- this.props.currentUserPersonalDetails.login,
- participant,
- );
- return;
- }
-
- if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.PAYPAL_ME) {
- IOU.sendMoneyViaPaypal(
- this.props.report,
- amount,
- currency,
- comment,
- this.props.currentUserPersonalDetails.login,
- participant,
- );
- return;
- }
-
- if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) {
- IOU.sendMoneyWithWallet(
- this.props.report,
- amount,
- currency,
- comment,
- this.props.currentUserPersonalDetails.login,
- participant,
- );
- }
- }
-
- /**
- * @param {Array} selectedParticipants
- */
- createTransaction(selectedParticipants) {
- const reportID = lodashGet(this.props, 'route.params.reportID', '');
- const comment = this.state.comment.trim();
-
- // IOUs created from a group report will have a reportID param in the route.
- // Since the user is already viewing the report, we don't need to navigate them to the report
- if (this.props.hasMultipleParticipants && CONST.REGEX.NUMBER.test(reportID)) {
- IOU.splitBill(
- selectedParticipants,
- this.props.currentUserPersonalDetails.login,
- this.state.amount,
- comment,
- this.props.iou.selectedCurrencyCode,
- this.props.preferredLocale,
- reportID,
- );
- return;
- }
-
- // If the IOU is created from the global create menu, we also navigate the user to the group report
- if (this.props.hasMultipleParticipants) {
- IOU.splitBillAndOpenReport(
- selectedParticipants,
- this.props.currentUserPersonalDetails.login,
- this.state.amount,
- comment,
- this.props.iou.selectedCurrencyCode,
- this.props.preferredLocale,
- );
- return;
- }
- IOU.requestMoney(
- this.props.report,
- Math.round(this.state.amount * 100),
- this.props.iou.selectedCurrencyCode,
- this.props.currentUserPersonalDetails.login,
- selectedParticipants[0],
- comment,
- );
- }
-
- renderHeader() {
- return (
-
-
- {this.state.currentStepIndex > 0
- && (
-
-
-
-
-
-
-
- )}
-
-
-
- Navigation.dismissModal()}
- style={[styles.touchableButtonImage]}
- accessibilityRole="button"
- accessibilityLabel={this.props.translate('common.close')}
- >
-
-
-
-
-
-
- );
- }
-
- render() {
- const currentStep = this.steps[this.state.currentStepIndex];
- const reportID = lodashGet(this.props, 'route.params.reportID', '');
- return (
-
- {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
- <>
-
- {!didScreenTransitionEnd && }
- {didScreenTransitionEnd && (
- <>
- {currentStep === Steps.IOUAmount && (
-
- {this.renderHeader()}
- {
- this.setState({amount});
- this.navigateToNextStep();
- }}
- reportID={reportID}
- hasMultipleParticipants={this.props.hasMultipleParticipants}
- selectedAmount={this.state.amount}
- navigation={this.props.navigation}
- iouType={this.props.iouType}
- />
-
- )}
- {currentStep === Steps.IOUParticipants && (
-
- {this.renderHeader()}
-
-
- )}
- {currentStep === Steps.IOUConfirm && (
-
- {this.renderHeader()}
- {
- // Prevent creating multiple transactions if the button is pressed repeatedly
- if (this.creatingIOUTransaction) {
- return;
- }
- this.creatingIOUTransaction = true;
- this.createTransaction(selectedParticipants);
- ReportScrollManager.scrollToBottom();
- }}
- onSendMoney={(paymentMethodType) => {
- if (this.creatingIOUTransaction) {
- return;
- }
- this.creatingIOUTransaction = true;
- this.sendMoney(paymentMethodType);
- ReportScrollManager.scrollToBottom();
- }}
- hasMultipleParticipants={this.props.hasMultipleParticipants}
- participants={_.filter(this.state.participants, email => this.props.currentUserPersonalDetails.login !== email.login)}
- iouAmount={this.state.amount}
- comment={this.state.comment}
- onUpdateComment={this.updateComment}
- iouType={this.props.iouType}
-
- // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button.
- // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people,
- // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill
- // split rather than forcing the user to create a new group, just for that expense. The reportID is empty, when the action was initiated from
- // the floating-action-button (since it is something that exists outside the context of a report).
- canModifyParticipants={!_.isEmpty(reportID)}
- />
-
- )}
- >
- )}
-
- >
- )}
-
- );
- }
-}
-
-IOUModal.propTypes = propTypes;
-IOUModal.defaultProps = defaultProps;
-
-export default compose(
- withLocalize,
- withNetwork(),
- withCurrentUserPersonalDetails,
- withOnyx({
- report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`,
- },
- iou: {
- key: ONYXKEYS.IOU,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS,
- },
- }),
-)(IOUModal);
diff --git a/src/pages/iou/IOURequestPage.js b/src/pages/iou/IOURequestPage.js
index 7b6e32ac9ab3..0d45ba531878 100644
--- a/src/pages/iou/IOURequestPage.js
+++ b/src/pages/iou/IOURequestPage.js
@@ -1,7 +1,7 @@
import React from 'react';
-import IOUModal from './IOUModal';
+import MoneyRequestModal from './MoneyRequestModal';
// eslint-disable-next-line react/jsx-props-no-spreading
-const IOURequestPage = props => ;
+const IOURequestPage = props => ;
IOURequestPage.displayName = 'IOURequestPage';
export default IOURequestPage;
diff --git a/src/pages/iou/IOUSendPage.js b/src/pages/iou/IOUSendPage.js
index 79a83acaf8ea..c9aec6022293 100644
--- a/src/pages/iou/IOUSendPage.js
+++ b/src/pages/iou/IOUSendPage.js
@@ -1,8 +1,8 @@
import React from 'react';
import CONST from '../../CONST';
-import IOUModal from './IOUModal';
+import MoneyRequestModal from './MoneyRequestModal';
// eslint-disable-next-line react/jsx-props-no-spreading
-const IOUSendPage = props => ;
+const IOUSendPage = props => ;
IOUSendPage.displayName = 'IOUSendPage';
export default IOUSendPage;
diff --git a/src/pages/iou/ModalHeader.js b/src/pages/iou/ModalHeader.js
new file mode 100644
index 000000000000..a89b184fb897
--- /dev/null
+++ b/src/pages/iou/ModalHeader.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import {View, TouchableOpacity} from 'react-native';
+import PropTypes from 'prop-types';
+import styles from '../../styles/styles';
+import Icon from '../../components/Icon';
+import Header from '../../components/Header';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import * as Expensicons from '../../components/Icon/Expensicons';
+import Tooltip from '../../components/Tooltip';
+import Navigation from '../../libs/Navigation/Navigation';
+
+const propTypes = {
+ /** Title of the header */
+ title: PropTypes.string.isRequired,
+
+ /** Should we show the back button? */
+ shouldShowBackButton: PropTypes.bool,
+
+ /** Callback to fire on back button press */
+ onBackButtonPress: PropTypes.func,
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ shouldShowBackButton: true,
+ onBackButtonPress: () => Navigation.goBack(),
+};
+
+const ModalHeader = props => (
+
+
+ {props.shouldShowBackButton
+ && (
+
+
+
+
+
+
+
+ )}
+
+
+
+ Navigation.dismissModal()}
+ style={[styles.touchableButtonImage]}
+ accessibilityRole="button"
+ accessibilityLabel={props.translate('common.close')}
+ >
+
+
+
+
+
+
+);
+
+ModalHeader.displayName = 'ModalHeader';
+ModalHeader.propTypes = propTypes;
+ModalHeader.defaultProps = defaultProps;
+export default withLocalize(ModalHeader);
diff --git a/src/pages/iou/MoneyRequestModal.js b/src/pages/iou/MoneyRequestModal.js
new file mode 100644
index 000000000000..00e92b26cecc
--- /dev/null
+++ b/src/pages/iou/MoneyRequestModal.js
@@ -0,0 +1,449 @@
+import _ from 'underscore';
+import React, {
+ useState, useEffect, useRef, useCallback, useMemo,
+} from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import lodashGet from 'lodash/get';
+import {withOnyx} from 'react-native-onyx';
+import Str from 'expensify-common/lib/str';
+import IOUAmountPage from './steps/IOUAmountPage';
+import IOUParticipantsPage from './steps/IOUParticipantsPage/IOUParticipantsPage';
+import IOUConfirmPage from './steps/IOUConfirmPage';
+import ModalHeader from './ModalHeader';
+import styles from '../../styles/styles';
+import * as IOU from '../../libs/actions/IOU';
+import Navigation from '../../libs/Navigation/Navigation';
+import ONYXKEYS from '../../ONYXKEYS';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import compose from '../../libs/compose';
+import * as OptionsListUtils from '../../libs/OptionsListUtils';
+import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
+import AnimatedStep from '../../components/AnimatedStep';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import CONST from '../../CONST';
+import * as PersonalDetails from '../../libs/actions/PersonalDetails';
+import withCurrentUserPersonalDetails from '../../components/withCurrentUserPersonalDetails';
+import networkPropTypes from '../../components/networkPropTypes';
+import {withNetwork} from '../../components/OnyxProvider';
+import reportPropTypes from '../reportPropTypes';
+import * as ReportUtils from '../../libs/ReportUtils';
+import * as ReportScrollManager from '../../libs/ReportScrollManager';
+
+/**
+ * A modal used for requesting money, splitting bills or sending money.
+ */
+const propTypes = {
+ /** Whether the request is for a single request or a group bill split */
+ hasMultipleParticipants: PropTypes.bool,
+
+ /** The type of IOU report, i.e. bill, request, send */
+ iouType: PropTypes.string,
+
+ /** The report passed via the route */
+ // eslint-disable-next-line react/no-unused-prop-types
+ report: reportPropTypes,
+
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
+ // Holds data related to request view state, rather than the underlying request data.
+ iou: PropTypes.shape({
+ /** Whether or not transaction creation has started */
+ creatingIOUTransaction: PropTypes.bool,
+
+ /** Whether or not transaction creation has resulted to error */
+ error: PropTypes.bool,
+
+ // Selected Currency Code of the current request
+ selectedCurrencyCode: PropTypes.string,
+ }),
+
+ /** Personal details of all the users */
+ personalDetails: PropTypes.shape({
+ /** Primary login of participant */
+ login: PropTypes.string,
+
+ /** Display Name of participant */
+ displayName: PropTypes.string,
+
+ /** Avatar url of participant */
+ avatar: PropTypes.string,
+ }),
+
+ /** Personal details of the current user */
+ currentUserPersonalDetails: PropTypes.shape({
+ // Local Currency Code of the current user
+ localCurrencyCode: PropTypes.string,
+ }),
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ hasMultipleParticipants: false,
+ report: {
+ participants: [],
+ },
+ iouType: CONST.IOU.IOU_TYPE.REQUEST,
+ currentUserPersonalDetails: {
+ localCurrencyCode: CONST.CURRENCY.USD,
+ },
+ personalDetails: {},
+ iou: {
+ creatingIOUTransaction: false,
+ error: false,
+ selectedCurrencyCode: null,
+ },
+};
+
+// Determines type of step to display within Modal, value provides the title for that page.
+const Steps = {
+ IOUAmount: 'iou.amount',
+ IOUParticipants: 'iou.participants',
+ IOUConfirm: 'iou.confirm',
+};
+
+const MoneyRequestModal = (props) => {
+ const reportParticipants = lodashGet(props, 'report.participants', []);
+ const participantsWithDetails = _.map(OptionsListUtils.getPersonalDetailsForLogins(reportParticipants, props.personalDetails), personalDetails => ({
+ login: personalDetails.login,
+ text: personalDetails.displayName,
+ firstName: lodashGet(personalDetails, 'firstName', ''),
+ lastName: lodashGet(personalDetails, 'lastName', ''),
+ alternateText: Str.isSMSLogin(personalDetails.login) ? Str.removeSMSDomain(personalDetails.login) : personalDetails.login,
+ icons: [{
+ source: ReportUtils.getAvatar(personalDetails.avatar, personalDetails.login),
+ name: personalDetails.login,
+ type: CONST.ICON_TYPE_AVATAR,
+ }],
+ keyForList: personalDetails.login,
+ payPalMeAddress: lodashGet(personalDetails, 'payPalMeAddress', ''),
+ phoneNumber: lodashGet(personalDetails, 'phoneNumber', ''),
+ }));
+
+ // Skip IOUParticipants step if participants are passed in
+ const steps = reportParticipants.length ? [Steps.IOUAmount, Steps.IOUConfirm] : [Steps.IOUAmount, Steps.IOUParticipants, Steps.IOUConfirm];
+
+ const prevCreatingIOUTransactionStatusRef = useRef(lodashGet(props.iou, 'creatingIOUTransaction'));
+ const prevNetworkStatusRef = useRef(props.network.isOffline);
+
+ const [previousStepIndex, setPreviousStepIndex] = useState(0);
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+ const [participants, setParticipants] = useState(participantsWithDetails);
+ const [amount, setAmount] = useState('');
+ const [comment, setComment] = useState('');
+
+ useEffect(() => {
+ PersonalDetails.openMoneyRequestModalPage();
+ IOU.setIOUSelectedCurrency(props.currentUserPersonalDetails.localCurrencyCode);
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- props.currentUserPersonalDetails will always exist from Onyx and we don't want this effect to run again
+ }, []);
+
+ useEffect(() => {
+ // We only want to check if we just finished creating an IOU transaction
+ // We check it within this effect because we're sending the request optimistically but if an error occurs from the API, we will update the iou state with the error later
+ if (!prevCreatingIOUTransactionStatusRef.current || lodashGet(props.iou, 'creatingIOUTransaction')) {
+ return;
+ }
+
+ if (lodashGet(props.iou, 'error') === true) {
+ setCurrentStepIndex(0);
+ } else {
+ Navigation.dismissModal();
+ }
+ }, [props.iou]);
+
+ useEffect(() => {
+ if (props.network.isOffline || !prevNetworkStatusRef.current) {
+ return;
+ }
+
+ // User came back online, so let's refetch the currency details based on location
+ PersonalDetails.openMoneyRequestModalPage();
+ }, [props.network.isOffline]);
+
+ useEffect(() => {
+ // Used to store previous prop values to compare on next render
+ prevNetworkStatusRef.current = props.network.isOffline;
+ prevCreatingIOUTransactionStatusRef.current = lodashGet(props.iou, 'creatingIOUTransaction');
+ });
+
+ /**
+ * Decides our animation type based on whether we're increasing or decreasing
+ * our step index.
+ * @returns {String|null}
+ */
+ const direction = useMemo(() => {
+ if (previousStepIndex < currentStepIndex) {
+ return 'in';
+ }
+ if (previousStepIndex > currentStepIndex) {
+ return 'out';
+ }
+
+ // Doesn't animate the step when first opening the modal
+ if (previousStepIndex === currentStepIndex) {
+ return null;
+ }
+ }, [previousStepIndex, currentStepIndex]);
+
+ /**
+ * Retrieve title for current step, based upon current step and type of request
+ *
+ * @returns {String}
+ */
+ const titleForStep = useMemo(() => {
+ const isSendingMoney = props.iouType === CONST.IOU.IOU_TYPE.SEND;
+ if (currentStepIndex === 1 || currentStepIndex === 2) {
+ const formattedAmount = props.numberFormat(
+ amount, {
+ style: 'currency',
+ currency: props.iou.selectedCurrencyCode,
+ },
+ );
+ if (isSendingMoney) {
+ return props.translate('iou.send', {
+ amount: formattedAmount,
+ });
+ }
+ return props.translate(
+ props.hasMultipleParticipants ? 'iou.split' : 'iou.request', {
+ amount: formattedAmount,
+ },
+ );
+ }
+ if (currentStepIndex === 0) {
+ if (isSendingMoney) {
+ return props.translate('iou.sendMoney');
+ }
+ return props.translate(props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney');
+ }
+
+ return props.translate(steps[currentStepIndex]) || '';
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- props does not need to be a dependency as it will always exist
+ }, [amount, currentStepIndex, props.hasMultipleParticipants, props.iou.selectedCurrencyCode, props.iouType, props.numberFormat, steps]);
+
+ /**
+ * Navigate to the previous request step if possible
+ */
+ const navigateToPreviousStep = useCallback(() => {
+ if (currentStepIndex <= 0) {
+ return;
+ }
+
+ setPreviousStepIndex(currentStepIndex);
+ setCurrentStepIndex(currentStepIndex - 1);
+ }, [currentStepIndex]);
+
+ /**
+ * Navigate to the next request step if possible
+ */
+ const navigateToNextStep = useCallback(() => {
+ if (currentStepIndex >= steps.length - 1) {
+ return;
+ }
+
+ setPreviousStepIndex(currentStepIndex);
+ setCurrentStepIndex(currentStepIndex + 1);
+ }, [currentStepIndex, steps.length]);
+
+ /**
+ * Checks if user has a GOLD wallet then creates a paid IOU report on the fly
+ *
+ * @param {String} paymentMethodType
+ */
+ const sendMoney = useCallback((paymentMethodType) => {
+ const amountInDollars = Math.round(amount * 100);
+ const currency = props.iou.selectedCurrencyCode;
+ const trimmedComment = comment.trim();
+ const participant = participants[0];
+
+ if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
+ IOU.sendMoneyElsewhere(
+ props.report,
+ amountInDollars,
+ currency,
+ trimmedComment,
+ props.currentUserPersonalDetails.login,
+ participant,
+ );
+ return;
+ }
+
+ if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.PAYPAL_ME) {
+ IOU.sendMoneyViaPaypal(
+ props.report,
+ amountInDollars,
+ currency,
+ trimmedComment,
+ props.currentUserPersonalDetails.login,
+ participant,
+ );
+ return;
+ }
+
+ if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) {
+ IOU.sendMoneyWithWallet(
+ props.report,
+ amountInDollars,
+ currency,
+ trimmedComment,
+ props.currentUserPersonalDetails.login,
+ participant,
+ );
+ }
+ }, [amount, comment, participants, props.currentUserPersonalDetails.login, props.iou.selectedCurrencyCode, props.report]);
+
+ /**
+ * @param {Array} selectedParticipants
+ */
+ const createTransaction = useCallback((selectedParticipants) => {
+ const reportID = lodashGet(props.route, 'params.reportID', '');
+ const trimmedComment = comment.trim();
+
+ // IOUs created from a group report will have a reportID param in the route.
+ // Since the user is already viewing the report, we don't need to navigate them to the report
+ if (props.hasMultipleParticipants && CONST.REGEX.NUMBER.test(reportID)) {
+ IOU.splitBill(
+ selectedParticipants,
+ props.currentUserPersonalDetails.login,
+ amount,
+ trimmedComment,
+ props.iou.selectedCurrencyCode,
+ props.preferredLocale,
+ reportID,
+ );
+ return;
+ }
+
+ // If the request is created from the global create menu, we also navigate the user to the group report
+ if (props.hasMultipleParticipants) {
+ IOU.splitBillAndOpenReport(
+ selectedParticipants,
+ props.currentUserPersonalDetails.login,
+ amount,
+ trimmedComment,
+ props.iou.selectedCurrencyCode,
+ props.preferredLocale,
+ );
+ return;
+ }
+ IOU.requestMoney(
+ props.report,
+ Math.round(amount * 100),
+ props.iou.selectedCurrencyCode,
+ props.currentUserPersonalDetails.login,
+ selectedParticipants[0],
+ trimmedComment,
+ );
+ }, [amount, comment, props.currentUserPersonalDetails.login, props.hasMultipleParticipants, props.iou.selectedCurrencyCode, props.preferredLocale, props.report, props.route]);
+
+ const currentStep = steps[currentStepIndex];
+ const reportID = lodashGet(props, 'route.params.reportID', '');
+ const shouldShowBackButton = currentStepIndex > 0;
+ const modalHeader = ;
+ return (
+
+ {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
+ <>
+
+ {!didScreenTransitionEnd && }
+ {didScreenTransitionEnd && (
+ <>
+ {currentStep === Steps.IOUAmount && (
+
+ {modalHeader}
+ {
+ setAmount(value);
+ navigateToNextStep();
+ }}
+ reportID={reportID}
+ hasMultipleParticipants={props.hasMultipleParticipants}
+ selectedAmount={amount}
+ navigation={props.navigation}
+ iouType={props.iouType}
+ />
+
+ )}
+ {currentStep === Steps.IOUParticipants && (
+
+ {modalHeader}
+ setParticipants(selectedParticipants)}
+ onStepComplete={navigateToNextStep}
+ safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
+ />
+
+ )}
+ {currentStep === Steps.IOUConfirm && (
+
+ {modalHeader}
+ {
+ // TODO: ADD HANDLING TO DISABLE BUTTON FUNCTIONALITY WHILE REQUEST IS IN FLIGHT
+ createTransaction(selectedParticipants);
+ ReportScrollManager.scrollToBottom();
+ }}
+ onSendMoney={(paymentMethodType) => {
+ // TODO: ADD HANDLING TO DISABLE BUTTON FUNCTIONALITY WHILE REQUEST IS IN FLIGHT
+ sendMoney(paymentMethodType);
+ ReportScrollManager.scrollToBottom();
+ }}
+ hasMultipleParticipants={props.hasMultipleParticipants}
+ participants={_.filter(participants, email => props.currentUserPersonalDetails.login !== email.login)}
+ iouAmount={amount}
+ comment={comment}
+ onUpdateComment={value => setComment(value)}
+ iouType={props.iouType}
+
+ // The participants can only be modified when the action is initiated from directly within a group chat and not the floating-action-button.
+ // This is because when there is a group of people, say they are on a trip, and you have some shared expenses with some of the people,
+ // but not all of them (maybe someone skipped out on dinner). Then it's nice to be able to select/deselect people from the group chat bill
+ // split rather than forcing the user to create a new group, just for that expense. The reportID is empty, when the action was initiated from
+ // the floating-action-button (since it is something that exists outside the context of a report).
+ canModifyParticipants={!_.isEmpty(reportID)}
+ />
+
+ )}
+ >
+ )}
+
+ >
+ )}
+
+ );
+};
+
+MoneyRequestModal.displayName = 'MoneyRequestModal';
+MoneyRequestModal.propTypes = propTypes;
+MoneyRequestModal.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withNetwork(),
+ withCurrentUserPersonalDetails,
+ withOnyx({
+ report: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`,
+ },
+ iou: {
+ key: ONYXKEYS.IOU,
+ },
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS,
+ },
+ }),
+)(MoneyRequestModal);
diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js
index ea321b0e0a9b..f176f73f93fb 100644
--- a/src/pages/iou/steps/IOUConfirmPage.js
+++ b/src/pages/iou/steps/IOUConfirmPage.js
@@ -11,10 +11,10 @@ const propTypes = {
/** Callback to to parent modal to send money */
onSendMoney: PropTypes.func.isRequired,
- /** Callback to update comment from IOUModal */
+ /** Callback to update comment from MoneyRequestModal */
onUpdateComment: PropTypes.func,
- /** Comment value from IOUModal */
+ /** Comment value from MoneyRequestModal */
comment: PropTypes.string,
/** Should we request a single or multiple participant selection from user */
@@ -23,7 +23,7 @@ const propTypes = {
/** IOU amount */
iouAmount: PropTypes.string.isRequired,
- /** Selected participants from IOUMOdal with login */
+ /** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(PropTypes.shape({
login: PropTypes.string.isRequired,
alternateText: PropTypes.string,
diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsPage.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsPage.js
index 2d948552611e..93a27f88946f 100644
--- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsPage.js
+++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsPage.js
@@ -16,10 +16,10 @@ const propTypes = {
/** Should we request a single or multiple participant selection from user */
hasMultipleParticipants: PropTypes.bool.isRequired,
- /** Callback to add participants in IOUModal */
+ /** Callback to add participants in MoneyRequestModal */
onAddParticipants: PropTypes.func.isRequired,
- /** Selected participants from IOUModal with login */
+ /** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(PropTypes.shape({
login: PropTypes.string.isRequired,
alternateText: PropTypes.string,
diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js
index 8d094f3cf466..8929889884e7 100755
--- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js
+++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js
@@ -18,7 +18,7 @@ const propTypes = {
/** Callback to inform parent modal of success */
onStepComplete: PropTypes.func.isRequired,
- /** Callback to add participants in IOUModal */
+ /** Callback to add participants in MoneyRequestModal */
onAddParticipants: PropTypes.func.isRequired,
/** All of the personal details for everyone */
diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
index fed5ed23c072..4d7c05c461e1 100755
--- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
+++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
@@ -22,10 +22,10 @@ const propTypes = {
/** Callback to inform parent modal of success */
onStepComplete: PropTypes.func.isRequired,
- /** Callback to add participants in IOUModal */
+ /** Callback to add participants in MoneyRequestModal */
onAddParticipants: PropTypes.func.isRequired,
- /** Selected participants from IOUModal with login */
+ /** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(PropTypes.shape({
login: PropTypes.string.isRequired,
alternateText: PropTypes.string,