diff --git a/android/app/build.gradle b/android/app/build.gradle
index 56a987269f6e..d8d0c11a1a0c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -90,8 +90,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001035409
- versionName "1.3.54-9"
+ versionCode 1001035411
+ versionName "1.3.54-11"
}
flavorDimensions "default"
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 3eb22374f955..92c61cb81b2c 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -203,6 +203,7 @@ platform :ios do
build_app(
workspace: "./ios/NewExpensify.xcworkspace",
scheme: "New Expensify",
+ output_name: "New Expensify.ipa",
export_options: {
manageAppVersionAndBuildNumber: false
}
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 844086950b32..8817f8560fed 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -32,7 +32,7 @@
CFBundleVersion
- 1.3.54.9
+ 1.3.54.11
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 2b3e076f1db8..31dab672cd4e 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.3.54.9
+ 1.3.54.11
diff --git a/package-lock.json b/package-lock.json
index 7951d7dc2c8f..3e886a89af2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.3.54-9",
+ "version": "1.3.54-11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.3.54-9",
+ "version": "1.3.54-11",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 1681ddfb597b..040b9a78896a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.3.54-9",
+ "version": "1.3.54-11",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST.js b/src/CONST.js
index a0c6cbf2bcf3..1644d40ad249 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -2537,6 +2537,17 @@ const CONST = {
DISTANCE: 'distance',
},
STATUS_TEXT_MAX_LENGTH: 100,
+ NAVIGATION: {
+ TYPE: {
+ FORCED_UP: 'FORCED_UP',
+ UP: 'UP',
+ },
+ ACTION_TYPE: {
+ REPLACE: 'REPLACE',
+ PUSH: 'PUSH',
+ NAVIGATE: 'NAVIGATE',
+ },
+ },
};
export default CONST;
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 64b3b960581f..836e47b2ccaf 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -118,6 +118,7 @@ export default {
DOWNLOAD: 'download_',
POLICY: 'policy_',
POLICY_MEMBERS: 'policyMembers_',
+ POLICY_CATEGORIES: 'policyCategories_',
WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_',
REPORT: 'report_',
REPORT_ACTIONS: 'reportActions_',
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index 92aa99df24d6..c05cf14f2fc1 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -82,6 +82,12 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic
description += ` • ${translate('iou.pending')}`;
}
+ // A temporary solution to hide the transaction detail
+ // This will be removed after we properly add the transaction as a prop
+ if (ReportActionsUtils.isDeletedAction(parentReportAction)) {
+ return null;
+ }
+
return (
diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js
index 41f66967cc00..39f722c6b48a 100644
--- a/src/libs/Navigation/Navigation.js
+++ b/src/libs/Navigation/Navigation.js
@@ -12,6 +12,7 @@ import NAVIGATORS from '../../NAVIGATORS';
import originalGetTopmostReportId from './getTopmostReportId';
import getStateFromPath from './getStateFromPath';
import SCREENS from '../../SCREENS';
+import CONST from '../../CONST';
let resolveNavigationIsReadyPromise;
const navigationIsReadyPromise = new Promise((resolve) => {
@@ -127,7 +128,7 @@ function goBack(fallbackRoute = ROUTES.HOME, shouldEnforceFallback = false, shou
}
if (shouldEnforceFallback || (isFirstRouteInNavigator && fallbackRoute)) {
- navigate(fallbackRoute, 'UP');
+ navigate(fallbackRoute, CONST.NAVIGATION.TYPE.UP);
return;
}
diff --git a/src/libs/Navigation/linkTo.js b/src/libs/Navigation/linkTo.js
index c610ae710992..884a8aa02190 100644
--- a/src/libs/Navigation/linkTo.js
+++ b/src/libs/Navigation/linkTo.js
@@ -4,6 +4,7 @@ import NAVIGATORS from '../../NAVIGATORS';
import linkingConfig from './linkingConfig';
import getTopmostReportId from './getTopmostReportId';
import getStateFromPath from './getStateFromPath';
+import CONST from '../../CONST';
/**
* Motivation for this function is described in NAVIGATION.md
@@ -59,19 +60,23 @@ export default function linkTo(navigation, path, type) {
const action = getActionFromState(state, linkingConfig.config);
// If action type is different than NAVIGATE we can't change it to the PUSH safely
- if (action.type === 'NAVIGATE') {
- // If this action is navigating to the report screen and the top most navigator is different from the one we want to navigate - PUSH
- if (action.payload.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR && getTopmostReportId(root.getState()) !== getTopmostReportId(state)) {
- action.type = 'PUSH';
+ if (action.type === CONST.NAVIGATION.ACTION_TYPE.NAVIGATE) {
+ // In case if type is 'FORCED_UP' we replace current screen with the provided. This means the current screen no longer exists in the stack
+ if (type === CONST.NAVIGATION.TYPE.FORCED_UP) {
+ action.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;
+
+ // If this action is navigating to the report screen and the top most navigator is different from the one we want to navigate - PUSH the new screen to the top of the stack
+ } else if (action.payload.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR && getTopmostReportId(root.getState()) !== getTopmostReportId(state)) {
+ action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
// If the type is UP, we deeplinked into one of the RHP flows and we want to replace the current screen with the previous one in the flow
// and at the same time we want the back button to go to the page we were before the deeplink
- } else if (type === 'UP') {
- action.type = 'REPLACE';
+ } else if (type === CONST.NAVIGATION.TYPE.UP) {
+ action.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;
// If this action is navigating to the RightModalNavigator and the last route on the root navigator is not RightModalNavigator then push
} else if (action.payload.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && _.last(root.getState().routes).name !== NAVIGATORS.RIGHT_MODAL_NAVIGATOR) {
- action.type = 'PUSH';
+ action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
}
}
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 60a9a34838b1..e9fb92a7b020 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -640,7 +640,7 @@ function getOptions(
const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase();
// Filter out all the reports that shouldn't be displayed
- const filteredReports = _.filter(reports, (report) => ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId(), false, null, betas, policies));
+ const filteredReports = _.filter(reports, (report) => ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId(), false, betas, policies));
// Sorting the reports works like this:
// - Order everything by the last message timestamp (descending)
diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js
index 582271d6610e..164f284a4ef5 100644
--- a/src/libs/PolicyUtils.js
+++ b/src/libs/PolicyUtils.js
@@ -10,7 +10,7 @@ import ONYXKEYS from '../ONYXKEYS';
* @returns {Array}
*/
function getActivePolicies(policies) {
- return _.filter(policies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+ return _.filter(policies, (policy) => policy && policy.isPolicyExpenseChatEnabled && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
}
/**
@@ -86,7 +86,7 @@ function getPolicyBrickRoadIndicatorStatus(policy, policyMembersCollection) {
function shouldShowPolicy(policy, isOffline) {
return (
policy &&
- policy.type === CONST.POLICY.TYPE.FREE &&
+ policy.isPolicyExpenseChatEnabled &&
policy.role === CONST.POLICY.ROLE.ADMIN &&
(isOffline || policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !_.isEmpty(policy.errors))
);
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index e5fe1437512e..f68cfe6adeba 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -566,6 +566,14 @@ function isMessageDeleted(reportAction) {
return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false);
}
+/**
+ * @param {*} reportAction
+ * @returns {Boolean}
+ */
+function isSplitBillAction(reportAction) {
+ return lodashGet(reportAction, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT;
+}
+
export {
getSortedReportActions,
getLastVisibleAction,
@@ -599,4 +607,5 @@ export {
isWhisperAction,
isPendingRemove,
getReportAction,
+ isSplitBillAction,
};
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index cc12f390dd7b..3974c339eb0f 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -1110,12 +1110,10 @@ function getDisplayNamesWithTooltips(personalDetailsList, isMultipleParticipantR
* Determines if a report has an IOU that is waiting for an action from the current user (either Pay or Add a credit bank account)
*
* @param {Object} report (chatReport or iouReport)
- * @param {Object} allReportsDict
* @returns {boolean}
*/
-function isWaitingForIOUActionFromCurrentUser(report, allReportsDict = null) {
- const allAvailableReports = allReportsDict || allReports;
- if (!report || !allAvailableReports) {
+function isWaitingForIOUActionFromCurrentUser(report) {
+ if (!report) {
return false;
}
@@ -1124,15 +1122,8 @@ function isWaitingForIOUActionFromCurrentUser(report, allReportsDict = null) {
return true;
}
- let reportToLook = report;
- if (report.iouReportID) {
- const iouReport = allAvailableReports[`${ONYXKEYS.COLLECTION.REPORT}${report.iouReportID}`];
- if (iouReport) {
- reportToLook = iouReport;
- }
- }
- // Money request waiting for current user to Pay (from chat or from iou report)
- if (reportToLook.ownerAccountID && (reportToLook.ownerAccountID !== currentUserAccountID || currentUserAccountID === reportToLook.managerID) && reportToLook.hasOutstandingIOU) {
+ // Money request waiting for current user to Pay (from expense or iou report)
+ if (report.hasOutstandingIOU && report.ownerAccountID && (report.ownerAccountID !== currentUserAccountID || currentUserAccountID === report.managerID)) {
return true;
}
@@ -2470,14 +2461,13 @@ function canAccessReport(report, policies, betas, allReportActions) {
* @param {Object} report
* @param {String} currentReportId
* @param {Boolean} isInGSDMode
- * @param {Object} iouReports
* @param {String[]} betas
* @param {Object} policies
* @param {Object} allReportActions
* @param {Boolean} excludeEmptyChats
* @returns {boolean}
*/
-function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, iouReports, betas, policies, allReportActions, excludeEmptyChats = false) {
+function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies, allReportActions, excludeEmptyChats = false) {
const isInDefaultMode = !isInGSDMode;
// Exclude reports that have no data because there wouldn't be anything to show in the option item.
@@ -2504,7 +2494,7 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, iouRep
}
// Include reports that are relevant to the user in any view mode. Criteria include having a draft, having an outstanding IOU, or being assigned to an open task.
- if (report.hasDraft || isWaitingForIOUActionFromCurrentUser(report, iouReports) || isWaitingForTaskCompleteFromAssignee(report)) {
+ if (report.hasDraft || isWaitingForIOUActionFromCurrentUser(report) || isWaitingForTaskCompleteFromAssignee(report)) {
return true;
}
diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js
index 8afe05650bc6..c384d3c17a39 100644
--- a/src/libs/SidebarUtils.js
+++ b/src/libs/SidebarUtils.js
@@ -86,9 +86,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p
const isInDefaultMode = !isInGSDMode;
// Filter out all the reports that shouldn't be displayed
- const reportsToDisplay = _.filter(allReportsDict, (report) =>
- ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, allReportsDict, betas, policies, allReportActions, true),
- );
+ const reportsToDisplay = _.filter(allReportsDict, (report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies, allReportActions, true));
if (_.isEmpty(reportsToDisplay)) {
// Display Concierge chat report when there is no report to be displayed
@@ -131,7 +129,7 @@ function getOrderedReportIDs(currentReportId, allReportsDict, betas, policies, p
return;
}
- if (ReportUtils.isWaitingForIOUActionFromCurrentUser(report, allReportsDict)) {
+ if (ReportUtils.isWaitingForIOUActionFromCurrentUser(report)) {
outstandingIOUReports.push(report);
return;
}
diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js
index efce0800e849..a8fd828d07d4 100644
--- a/src/libs/TransactionUtils.js
+++ b/src/libs/TransactionUtils.js
@@ -150,11 +150,11 @@ function getCurrency(transaction) {
* @returns {String}
*/
function getCreated(transaction) {
- const created = lodashGet(transaction, 'modifiedCreated', '');
+ const created = lodashGet(transaction, 'modifiedCreated', '') || lodashGet(transaction, 'created', '');
if (created) {
return format(new Date(created), CONST.DATE.FNS_FORMAT_STRING);
}
- return format(new Date(lodashGet(transaction, 'created', Date.now())), CONST.DATE.FNS_FORMAT_STRING);
+ return '';
}
/**
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 809491c14950..46cc71850ccb 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -66,12 +66,6 @@ Onyx.connect({
callback: (val) => (allPersonalDetails = val),
});
-let loginList;
-Onyx.connect({
- key: ONYXKEYS.LOGIN_LIST,
- callback: (val) => (loginList = val),
-});
-
/**
* Stores in Onyx the policy ID of the last workspace that was accessed by the user
* @param {String|null} policyID
@@ -161,16 +155,6 @@ function isAdminOfFreePolicy(policies) {
return _.some(policies, (policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN);
}
-/**
- * Is the user the owner of the given policy?
- *
- * @param {Object} policy
- * @returns {Boolean}
- */
-function isPolicyOwner(policy) {
- return _.keys(loginList).includes(policy.owner);
-}
-
/**
* Check if the user has any active free policies (aka workspaces)
*
@@ -933,6 +917,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
name: workspaceName,
role: CONST.POLICY.ROLE.ADMIN,
owner: sessionEmail,
+ isPolicyExpenseChatEnabled: true,
outputCurrency: lodashGet(allPersonalDetails, [sessionAccountID, 'localCurrencyCode'], CONST.CURRENCY.USD),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
customUnits,
@@ -1200,6 +1185,5 @@ export {
openWorkspaceInvitePage,
removeWorkspace,
setWorkspaceInviteMembersDraft,
- isPolicyOwner,
clearErrors,
};
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 533b6d0af650..9853ebdd1581 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -849,6 +849,20 @@ function handleReportChanged(report) {
return;
}
+ // It is possible that we optimistically created a DM/group-DM for a set of users for which a report already exists.
+ // In this case, the API will let us know by returning a preexistingReportID.
+ // We should clear out the optimistically created report and re-route the user to the preexisting report.
+ if (report && report.reportID && report.preexistingReportID) {
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null);
+
+ // Only re-route them if they are still looking at the optimistically created report
+ if (Navigation.getActiveRoute().includes(`/r/${report.reportID}`)) {
+ // Pass 'FORCED_UP' type to replace new report on second login with proper one in the Navigation
+ Navigation.navigate(ROUTES.getReportRoute(report.preexistingReportID), CONST.NAVIGATION.TYPE.FORCED_UP);
+ }
+ return;
+ }
+
if (report && report.reportID) {
allReports[report.reportID] = report;
@@ -1720,7 +1734,7 @@ function openReportFromDeepLink(url, isAuthenticated) {
InteractionManager.runAfterInteractions(() => {
SidebarUtils.isSidebarLoadedReady().then(() => {
if (reportID) {
- Navigation.navigate(ROUTES.getReportRoute(reportID), 'UP');
+ Navigation.navigate(ROUTES.getReportRoute(reportID), CONST.NAVIGATION.TYPE.UP);
}
if (route === ROUTES.CONCIERGE) {
navigateToConciergeChat();
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index 445f10c1812e..c5188da02195 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -1,8 +1,8 @@
-import React from 'react';
+import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
-import {format} from 'date-fns';
+import compose from '../libs/compose';
import CONST from '../CONST';
import Navigation from '../libs/Navigation/Navigation';
import ONYXKEYS from '../ONYXKEYS';
@@ -31,25 +31,39 @@ const propTypes = {
/** The report object for the thread report */
report: reportPropTypes,
+
+ /** The parent report object for the thread report */
+ parentReport: reportPropTypes,
};
const defaultProps = {
report: {},
+ parentReport: {},
};
-function EditRequestPage({report, route}) {
- const transactionID = lodashGet(ReportActionsUtils.getParentReportAction(report), 'originalMessage.IOUTransactionID', '');
- const transaction = TransactionUtils.getTransaction(transactionID);
+function EditRequestPage({report, route, parentReport}) {
+ const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+ const transaction = TransactionUtils.getLinkedTransaction(parentReportAction);
const {amount: transactionAmount, currency: transactionCurrency, comment: transactionDescription} = ReportUtils.getTransactionDetails(transaction);
// Take only the YYYY-MM-DD value
- const transactionCreatedDate = new Date(TransactionUtils.getCreated(transaction));
- const transactionCreated = format(transactionCreatedDate, CONST.DATE.FNS_FORMAT_STRING);
+ const transactionCreated = TransactionUtils.getCreated(transaction);
const fieldToEdit = lodashGet(route, ['params', 'field'], '');
+ const isDeleted = ReportActionsUtils.isDeletedAction(parentReportAction);
+ const isSetted = ReportUtils.isSettled(parentReport.reportID);
+
+ // Dismiss the modal when the request is paid or deleted
+ useEffect(() => {
+ if (!isDeleted && !isSetted) {
+ return;
+ }
+ Navigation.dismissModal();
+ }, [isDeleted, isSetted]);
+
// Update the transaction object and close the modal
function editMoneyRequest(transactionChanges) {
- IOU.editMoneyRequest(transactionID, report.reportID, transactionChanges);
+ IOU.editMoneyRequest(transaction.transactionID, report.reportID, transactionChanges);
Navigation.dismissModal();
}
@@ -114,8 +128,15 @@ function EditRequestPage({report, route}) {
EditRequestPage.displayName = 'EditRequestPage';
EditRequestPage.propTypes = propTypes;
EditRequestPage.defaultProps = defaultProps;
-export default withOnyx({
- report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`,
- },
-})(EditRequestPage);
+export default compose(
+ withOnyx({
+ report: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`,
+ },
+ }),
+ withOnyx({
+ parentReport: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`,
+ },
+ }),
+)(EditRequestPage);
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index fafcb2c94f8c..a83ab4a42080 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -32,7 +32,7 @@ import * as ReimbursementAccountProps from './reimbursementAccountPropTypes';
import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes';
import withPolicy from '../workspace/withPolicy';
import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView';
-import * as Policy from '../../libs/actions/Policy';
+import * as PolicyUtils from '../../libs/PolicyUtils';
const propTypes = {
/** Plaid SDK token to use to initialize the widget */
@@ -332,7 +332,7 @@ class ReimbursementAccountPage extends React.Component {
const policyName = lodashGet(this.props.policy, 'name');
const policyID = lodashGet(this.props.route.params, 'policyID');
- if (_.isEmpty(this.props.policy) || !Policy.isPolicyOwner(this.props.policy)) {
+ if (_.isEmpty(this.props.policy) || !PolicyUtils.isPolicyAdmin(this.props.policy)) {
return (
- type === CONTEXT_MENU_TYPES.REPORT_ACTION && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID),
+ shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => {
+ if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) {
+ return false;
+ }
+ const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID);
+ const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW;
+ const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionUtils.isSplitBillAction(reportAction);
+ return isCommentAction || isReportPreviewAction || isIOUAction;
+ },
onPress: (closePopover, {reportAction, reportID}) => {
if (closePopover) {
hideContextMenu(false, () => {
diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js
index c39eeddb7fd0..327885249843 100644
--- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js
+++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js
@@ -29,7 +29,19 @@ function PopoverReactionList(props) {
innerReactionListRef.current.showReactionList(event, reactionListAnchor);
};
- useImperativeHandle(props.innerRef, () => ({showReactionList}), []);
+ const hideReactionList = () => {
+ innerReactionListRef.current.hideReactionList();
+ };
+
+ /**
+ * Whether PopoverReactionList is active for the Report Action.
+ *
+ * @param {Number|String} actionID
+ * @return {Boolean}
+ */
+ const isActiveReportAction = (actionID) => Boolean(actionID) && reactionListReportActionID === actionID;
+
+ useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction}));
return (
() => {
- // ReportActionContextMenu and EmojiPicker are global component,
- // we use showContextMenu and showEmojiPicker to show them,
- // so we should also hide them when the current component is destroyed
+ // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components,
+ // we should also hide them when the current component is destroyed
if (ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) {
ReportActionContextMenu.hideContextMenu();
ReportActionContextMenu.hideDeleteModal();
@@ -142,8 +143,11 @@ function ReportActionItem(props) {
if (EmojiPickerAction.isActiveReportAction(props.action.reportActionID)) {
EmojiPickerAction.hideEmojiPicker(true);
}
+ if (reactionListRef.current && reactionListRef.current.isActiveReportAction(props.action.reportActionID)) {
+ reactionListRef.current.hideReactionList();
+ }
},
- [props.action.reportActionID],
+ [props.action.reportActionID, reactionListRef],
);
const isDraftEmpty = !props.draftMessage;
@@ -353,6 +357,7 @@ function ReportActionItem(props) {
{isHidden ? props.translate('moderation.revealMessage') : props.translate('moderation.hideMessage')}
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
index e692e4668f07..abade067f4fc 100644
--- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
@@ -36,6 +36,7 @@ const policySelector = (policy) =>
policy && {
type: policy.type,
role: policy.role,
+ isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled,
pendingAction: policy.pendingAction,
};
diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js
index f1633d62e490..4dd688950fbd 100644
--- a/src/pages/iou/MoneyRequestSelectorPage.js
+++ b/src/pages/iou/MoneyRequestSelectorPage.js
@@ -77,7 +77,10 @@ function MoneyRequestSelectorPage(props) {
};
return (
-
+
{({safeAreaPaddingBottomStyle}) => (
diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js
index 29f5baa173b4..0b5bf78142c5 100644
--- a/src/pages/iou/steps/NewRequestAmountPage.js
+++ b/src/pages/iou/steps/NewRequestAmountPage.js
@@ -177,6 +177,7 @@ function NewRequestAmountPage({route, iou, report}) {
return (
{({safeAreaPaddingBottomStyle}) => (
diff --git a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js
index 6780080ff382..29cefdc156a0 100644
--- a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js
+++ b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js
@@ -56,7 +56,10 @@ function CodesPage(props) {
}, []);
return (
-
+
+
Task.dismissModalAndClearOutTaskInfo()}
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 10732a661dfc..ef601ca066c5 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -183,7 +183,7 @@ function WorkspaceInitialPage(props) {
{({safeAreaPaddingBottomStyle}) => (
Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
- shouldShow={_.isEmpty(props.policy) || !Policy.isPolicyOwner(props.policy)}
+ shouldShow={_.isEmpty(props.policy) || !PolicyUtils.isPolicyAdmin(props.policy)}
subtitleKey={_.isEmpty(props.policy) ? undefined : 'workspace.common.notAuthorized'}
>
Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
>
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index bff4a55d94a0..6a49e043a54a 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -201,7 +201,7 @@ function WorkspaceInvitePage(props) {
const sections = didScreenTransitionEnd ? getSections() : [];
return (
Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
>
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 154a337ee5a6..64cd9d319beb 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -405,7 +405,7 @@ function WorkspaceMembersPage(props) {
>
{({safeAreaPaddingBottomStyle}) => (
Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
>
diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.js
index 801277b89c9a..fa04f5cfc49e 100644
--- a/src/pages/workspace/WorkspacePageWithSections.js
+++ b/src/pages/workspace/WorkspacePageWithSections.js
@@ -5,8 +5,8 @@ import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import styles from '../../styles/styles';
+import * as PolicyUtils from '../../libs/PolicyUtils';
import Navigation from '../../libs/Navigation/Navigation';
-import * as Policy from '../../libs/actions/Policy';
import compose from '../../libs/compose';
import ROUTES from '../../ROUTES';
import HeaderWithBackButton from '../../components/HeaderWithBackButton';
@@ -105,7 +105,7 @@ function WorkspacePageWithSections({backButtonRoute, children, footer, guidesCal
>
Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
- shouldShow={_.isEmpty(policy) || !Policy.isPolicyOwner(policy)}
+ shouldShow={_.isEmpty(policy) || !PolicyUtils.isPolicyAdmin(policy)}
subtitleKey={_.isEmpty(policy) ? undefined : 'workspace.common.notAuthorized'}
>
{
it('returns false when there is no report', () => {
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser()).toBe(false);
});
- it('returns false when there is no reports collection', () => {
+ it('returns false when the matched IOU report does not have an owner accountID', () => {
const report = {
...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
+ ownerAccountID: undefined,
+ hasOutstandingIOU: true,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
});
- it('returns false when the report has no iouReportID', () => {
- const report = LHNTestUtils.getFakeReport();
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}2`, {
- reportID: '2',
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
- });
- });
- it('returns false when there is no matching IOU report', () => {
- const report = {
- ...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
- };
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}2`, {
- reportID: '2',
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
- });
- });
- it('returns false when the matched IOU report does not have an owner email', () => {
- const report = {
- ...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
- };
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, {
- reportID: '1',
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
- });
- });
- it('returns false when the matched IOU report does not have an owner email', () => {
+ it('returns false when the linked iou report has an oustanding IOU', () => {
const report = {
...LHNTestUtils.getFakeReport(),
iouReportID: '1',
@@ -333,52 +304,37 @@ describe('ReportUtils', () => {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, {
reportID: '1',
ownerAccountID: 99,
+ hasOutstandingIOU: true,
}).then(() => {
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
});
});
- it('returns true when the report has an oustanding IOU', () => {
+ it('returns true when the report has no oustanding IOU but is waiting for a bank account and the logged user is the report owner', () => {
const report = {
...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
- hasOutstandingIOU: true,
+ hasOutstandingIOU: false,
+ ownerAccountID: currentUserAccountID,
+ isWaitingOnBankAccount: true,
};
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, {
- reportID: '1',
- ownerAccountID: 99,
- hasOutstandingIOU: true,
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true);
- });
+ expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true);
});
- it('returns false when the report has no oustanding IOU', () => {
+ it('returns true when the report has no oustanding IOU but is waiting for a bank account and the logged user is not the report owner', () => {
const report = {
...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
hasOutstandingIOU: false,
+ ownerAccountID: 97,
+ isWaitingOnBankAccount: true,
};
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, {
- reportID: '1',
- ownerAccountID: 99,
- hasOutstandingIOU: false,
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
- });
+ expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
});
- it('returns true when the report has no oustanding IOU but is waiting for a bank account', () => {
+ it('returns true when the report has oustanding IOU', () => {
const report = {
...LHNTestUtils.getFakeReport(),
- iouReportID: '1',
- hasOutstandingIOU: false,
+ ownerAccountID: 99,
+ hasOutstandingIOU: true,
+ isWaitingOnBankAccount: false,
};
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, {
- reportID: '1',
- ownerAccountID: currentUserEmail,
- hasOutstandingIOU: false,
- isWaitingOnBankAccount: true,
- }).then(() => {
- expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
- });
+ expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true);
});
});
diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js
index 734f062ec770..ef56fa8783b8 100644
--- a/tests/unit/SidebarOrderTest.js
+++ b/tests/unit/SidebarOrderTest.js
@@ -385,7 +385,7 @@ describe('Sidebar', () => {
};
const report3 = {
...LHNTestUtils.getFakeReport([5, 6], 1),
- hasOutstandingIOU: true,
+ hasOutstandingIOU: false,
// This has to be added after the IOU report is generated
iouReportID: null,
@@ -427,13 +427,12 @@ describe('Sidebar', () => {
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
- expect(displayNames).toHaveLength(4);
+ expect(displayNames).toHaveLength(3);
expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
})
);
});
@@ -700,21 +699,31 @@ describe('Sidebar', () => {
// Given three IOU reports containing the same IOU amounts
const report1 = {
...LHNTestUtils.getFakeReport([1, 2]),
- hasOutstandingIOU: true,
// This has to be added after the IOU report is generated
iouReportID: null,
};
const report2 = {
...LHNTestUtils.getFakeReport([3, 4]),
- hasOutstandingIOU: true,
// This has to be added after the IOU report is generated
iouReportID: null,
};
const report3 = {
...LHNTestUtils.getFakeReport([5, 6]),
- hasOutstandingIOU: true,
+ hasOutstandingIOU: false,
+
+ // This has to be added after the IOU report is generated
+ iouReportID: null,
+ };
+ const report4 = {
+ ...LHNTestUtils.getFakeReport([5, 6]),
+
+ // This has to be added after the IOU report is generated
+ iouReportID: null,
+ };
+ const report5 = {
+ ...LHNTestUtils.getFakeReport([5, 6]),
// This has to be added after the IOU report is generated
iouReportID: null,
@@ -733,7 +742,7 @@ describe('Sidebar', () => {
...LHNTestUtils.getFakeReport([9, 10]),
type: CONST.REPORT.TYPE.IOU,
ownerAccountID: 2,
- managerID: 2,
+ managerID: 3,
hasOutstandingIOU: true,
total: 10000,
currency: 'USD',
@@ -743,7 +752,27 @@ describe('Sidebar', () => {
...LHNTestUtils.getFakeReport([11, 12]),
type: CONST.REPORT.TYPE.IOU,
ownerAccountID: 2,
- managerID: 2,
+ managerID: 4,
+ hasOutstandingIOU: true,
+ total: 100000,
+ currency: 'USD',
+ chatReportID: report3.reportID,
+ };
+ const iouReport4 = {
+ ...LHNTestUtils.getFakeReport([11, 12]),
+ type: CONST.REPORT.TYPE.IOU,
+ ownerAccountID: 2,
+ managerID: 5,
+ hasOutstandingIOU: true,
+ total: 10000,
+ currency: 'USD',
+ chatReportID: report3.reportID,
+ };
+ const iouReport5 = {
+ ...LHNTestUtils.getFakeReport([11, 12]),
+ type: CONST.REPORT.TYPE.IOU,
+ ownerAccountID: 2,
+ managerID: 6,
hasOutstandingIOU: true,
total: 10000,
currency: 'USD',
@@ -753,6 +782,8 @@ describe('Sidebar', () => {
report1.iouReportID = iouReport1.reportID;
report2.iouReportID = iouReport2.reportID;
report3.iouReportID = iouReport3.reportID;
+ report4.iouReportID = iouReport4.reportID;
+ report5.iouReportID = iouReport5.reportID;
const currentlyLoggedInUserAccountID = 13;
LHNTestUtils.getDefaultRenderedSidebarLinks('0');
@@ -768,22 +799,26 @@ describe('Sidebar', () => {
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
+ [`${ONYXKEYS.COLLECTION.REPORT}${report4.reportID}`]: report4,
+ [`${ONYXKEYS.COLLECTION.REPORT}${report5.reportID}`]: report5,
[`${ONYXKEYS.COLLECTION.REPORT}${iouReport1.reportID}`]: iouReport1,
[`${ONYXKEYS.COLLECTION.REPORT}${iouReport2.reportID}`]: iouReport2,
[`${ONYXKEYS.COLLECTION.REPORT}${iouReport3.reportID}`]: iouReport3,
+ [`${ONYXKEYS.COLLECTION.REPORT}${iouReport4.reportID}`]: iouReport4,
+ [`${ONYXKEYS.COLLECTION.REPORT}${iouReport5.reportID}`]: iouReport5,
}),
)
- // Then the reports are ordered alphabetically since their amounts are the same
+ // Then the reports with the same amount are ordered alphabetically
.then(() => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(5);
- expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Two owes $100.00');
- expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Five, Six');
- expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('One, Two');
+ expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four owes $1,000.00');
+ expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five owes $100.00');
+ expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six owes $100.00');
+ expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three owes $100.00');
+ expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00');
})
);
});