diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index ad8b60700e39..68b3bd047ad8 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -260,6 +260,7 @@ const ONYXKEYS = {
TRANSACTION: 'transactions_',
SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_',
PRIVATE_NOTES_DRAFT: 'privateNotesDraft_',
+ NEXT_STEP: 'reportNextStep_',
// Manual request tab selector
SELECTED_TAB: 'selectedTab_',
diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js
index 49681f396181..8ae4672e758e 100644
--- a/src/components/MoneyReportHeader.js
+++ b/src/components/MoneyReportHeader.js
@@ -1,4 +1,5 @@
import React, {useMemo} from 'react';
+import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import {View} from 'react-native';
import PropTypes from 'prop-types';
@@ -15,11 +16,13 @@ import Navigation from '../libs/Navigation/Navigation';
import ROUTES from '../ROUTES';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
+import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
import SettlementButton from './SettlementButton';
import Button from './Button';
import * as IOU from '../libs/actions/IOU';
import * as CurrencyUtils from '../libs/CurrencyUtils';
import reportPropTypes from '../pages/reportPropTypes';
+import nextStepPropTypes from '../pages/nextStepPropTypes';
const propTypes = {
/** The report currently being looked at */
@@ -40,6 +43,9 @@ const propTypes = {
/** The chat report this report is linked to */
chatReport: reportPropTypes,
+ /** The next step for the report */
+ nextStep: nextStepPropTypes,
+
/** Personal details so we can get the ones for the report participants */
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired,
@@ -54,13 +60,14 @@ const propTypes = {
const defaultProps = {
chatReport: {},
+ nextStep: {},
session: {
email: null,
},
policy: {},
};
-function MoneyReportHeader({session, personalDetails, policy, chatReport, report: moneyRequestReport, isSmallScreenWidth}) {
+function MoneyReportHeader({session, personalDetails, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) {
const {translate} = useLocalize();
const reimbursableTotal = ReportUtils.getMoneyRequestReimbursableTotal(moneyRequestReport);
const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
@@ -81,9 +88,11 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report
return isManager && !isDraft && !isApproved && !isSettled;
}, [policyType, isManager, isDraft, isApproved, isSettled]);
const shouldShowSubmitButton = isDraft && reimbursableTotal !== 0;
- const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton;
+ const shouldShowNextSteps = isDraft && nextStep && (!_.isEmpty(nextStep.message) || !_.isEmpty(nextStep.expenseMessage));
+ const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextSteps;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableTotal, moneyRequestReport.currency);
+ const isMoreContentShown = shouldShowNextSteps || (shouldShowAnyButton && isSmallScreenWidth);
return (
@@ -96,7 +105,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report
personalDetails={personalDetails}
shouldShowBackButton={isSmallScreenWidth}
onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)}
- shouldShowBorderBottom={!shouldShowAnyButton || !isSmallScreenWidth}
+ // Shows border if no buttons or next steps are showing below the header
+ shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextSteps && !isSmallScreenWidth)}
>
{shouldShowSettlementButton && !isSmallScreenWidth && (
@@ -141,43 +151,50 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report
)}
- {shouldShowSettlementButton && isSmallScreenWidth && (
-
- IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport)}
- enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
- addBankAccountRoute={bankAccountRoute}
- shouldShowPaymentOptions
- formattedAmount={formattedAmount}
- />
-
- )}
- {shouldShowApproveButton && isSmallScreenWidth && (
-
-
- )}
- {shouldShowSubmitButton && isSmallScreenWidth && (
-
-
- )}
+
+ {shouldShowNextSteps && (
+
+
+
+ )}
+ {shouldShowSettlementButton && isSmallScreenWidth && (
+
+ IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport)}
+ enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
+ addBankAccountRoute={bankAccountRoute}
+ shouldShowPaymentOptions
+ formattedAmount={formattedAmount}
+ />
+
+ )}
+ {shouldShowApproveButton && isSmallScreenWidth && (
+
+
+ )}
+ {shouldShowSubmitButton && isSmallScreenWidth && (
+
+
+ )}
+
);
}
@@ -192,6 +209,9 @@ export default compose(
chatReport: {
key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`,
},
+ nextStep: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.NEXT_STEP}${report.reportID}`,
+ },
session: {
key: ONYXKEYS.SESSION,
},
diff --git a/src/components/MoneyReportHeaderStatusBar.js b/src/components/MoneyReportHeaderStatusBar.js
new file mode 100644
index 000000000000..9c4362b620d1
--- /dev/null
+++ b/src/components/MoneyReportHeaderStatusBar.js
@@ -0,0 +1,43 @@
+import React, {useMemo} from 'react';
+import {Text, View} from 'react-native';
+import _ from 'underscore';
+import styles from '../styles/styles';
+import * as NextStepUtils from '../libs/NextStepUtils';
+import useLocalize from '../hooks/useLocalize';
+import nextStepPropTypes from '../pages/nextStepPropTypes';
+import RenderHTML from './RenderHTML';
+
+const propTypes = {
+ /** The next step for the report */
+ nextStep: nextStepPropTypes,
+};
+
+const defaultProps = {
+ nextStep: {},
+};
+
+function MoneyReportHeaderStatusBar({nextStep}) {
+ const {translate} = useLocalize();
+
+ const messageContent = useMemo(() => {
+ const messageArray = _.isEmpty(nextStep.expenseMessage) ? nextStep.message : nextStep.expenseMessage;
+ return NextStepUtils.parseMessage(messageArray);
+ }, [nextStep.expenseMessage, nextStep.message]);
+
+ return (
+
+
+ {translate('iou.nextSteps')}
+
+
+
+
+
+ );
+}
+
+MoneyReportHeaderStatusBar.displayName = 'MoneyReportHeaderStatusBar';
+MoneyReportHeaderStatusBar.propTypes = propTypes;
+MoneyReportHeaderStatusBar.defaultProps = defaultProps;
+
+export default MoneyReportHeaderStatusBar;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 0e8512fb254f..328d774c16aa 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -552,6 +552,7 @@ export default {
settledElsewhere: 'Paid elsewhere',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount} with Expensify`,
payElsewhere: 'Pay elsewhere',
+ nextSteps: 'Next Steps',
requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`,
requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `requested ${formattedAmount}${comment ? ` for ${comment}` : ''}`,
splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`,
diff --git a/src/languages/es.ts b/src/languages/es.ts
index a8ee93e35282..4e8a77371ff2 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -544,6 +544,7 @@ export default {
settledElsewhere: 'Pagado de otra forma',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount} con Expensify`,
payElsewhere: 'Pagar de otra forma',
+ nextSteps: 'Pasos Siguientes',
requestAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`,
requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicité ${formattedAmount}${comment ? ` para ${comment}` : ''}`,
splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`,
diff --git a/src/libs/NextStepUtils.js b/src/libs/NextStepUtils.js
new file mode 100644
index 000000000000..78214bac7df9
--- /dev/null
+++ b/src/libs/NextStepUtils.js
@@ -0,0 +1,19 @@
+import _ from 'underscore';
+import Str from 'expensify-common/lib/str';
+
+function parseMessage(messageToParse) {
+ let nextStepHTML = '';
+
+ _.each(messageToParse, (part) => {
+ const tagType = part.type || 'span';
+ nextStepHTML += `<${tagType}>${Str.safeEscape(part.text)}${tagType}>`;
+ });
+
+ return nextStepHTML
+ .replace(/%expenses/g, 'this expense')
+ .replace(/%Expenses/g, 'This expense')
+ .replace(/%tobe/g, 'is');
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export {parseMessage};
diff --git a/src/pages/nextStepPropTypes.js b/src/pages/nextStepPropTypes.js
new file mode 100644
index 000000000000..b7a9e5b13033
--- /dev/null
+++ b/src/pages/nextStepPropTypes.js
@@ -0,0 +1,48 @@
+import PropTypes from 'prop-types';
+
+const messagePropType = PropTypes.shape({
+ text: PropTypes.string,
+ type: PropTypes.string,
+ action: PropTypes.string,
+});
+
+export default PropTypes.shape({
+ /** The message parts of the next step */
+ message: PropTypes.arrayOf(messagePropType),
+
+ /** The title for the next step */
+ title: PropTypes.string,
+
+ /** Whether the user should take some sort of action in order to unblock the report */
+ requiresUserAction: PropTypes.bool,
+
+ /** The type of next step */
+ type: PropTypes.oneOf(['neutral', 'alert', null]),
+
+ /** If the "Undo submit" button should be visible */
+ showUndoSubmit: PropTypes.bool,
+
+ /** Deprecated - If the next step should be displayed on mobile, related to OldApp */
+ showForMobile: PropTypes.bool,
+
+ /** If the next step should be displayed at the expense level */
+ showForExpense: PropTypes.bool,
+
+ /** An optional alternate message to display on expenses instead of what is provided in the "message" field */
+ expenseMessage: PropTypes.arrayOf(messagePropType),
+
+ /** The next person in the approval chain of the report */
+ nextReceiver: PropTypes.string,
+
+ /** An array of buttons to be displayed next to the next step */
+ buttons: PropTypes.arrayOf(
+ PropTypes.shape({
+ text: PropTypes.string,
+ tooltip: PropTypes.string,
+ disabled: PropTypes.bool,
+ hidden: PropTypes.bool,
+ // eslint-disable-next-line react/forbid-prop-types
+ data: PropTypes.array,
+ }),
+ ),
+});