diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1cdaa76a4443..85d05699e620 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -156,8 +156,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001021000
- versionName "1.2.10-0"
+ versionCode 1001021001
+ versionName "1.2.10-1"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index d442f5fc21cd..297fd9b761fd 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.2.10.0
+ 1.2.10.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 319b0779092c..f9cd7a7185ca 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.2.10.0
+ 1.2.10.1
diff --git a/package-lock.json b/package-lock.json
index c3d452344d05..aceddadbd9ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.2.10-0",
+ "version": "1.2.10-1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.2.10-0",
+ "version": "1.2.10-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -67,7 +67,7 @@
"react-native-image-picker": "^4.8.5",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.15",
+ "react-native-onyx": "1.0.17",
"react-native-pdf": "^6.6.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
@@ -77,7 +77,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "^3.1.4",
"react-native-screens": "^3.10.1",
- "react-native-svg": "^12.1.0",
+ "react-native-svg": "^12.4.4",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
"react-plaid-link": "3.3.2",
@@ -35515,9 +35515,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "1.0.15",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.15.tgz",
- "integrity": "sha512-uIJped+agmOppnCoDcs/w3qFertkLhLHyhmEEBXp0OhzNKuCs01wg7ccYFZxOVv+CtzFbtkLVB1VW3Ty/zYogA==",
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.17.tgz",
+ "integrity": "sha512-ls2GjURfpBcGnIkwVrg2uuLnTBwd0vrEiUvbMo+GF3k81GAp2flCkVTM7ciAbo155Izk50dm0uXHYq1PIjwTxw==",
"dependencies": {
"ascii-table": "0.0.9",
"lodash": "^4.17.21",
@@ -70130,9 +70130,9 @@
}
},
"react-native-onyx": {
- "version": "1.0.15",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.15.tgz",
- "integrity": "sha512-uIJped+agmOppnCoDcs/w3qFertkLhLHyhmEEBXp0OhzNKuCs01wg7ccYFZxOVv+CtzFbtkLVB1VW3Ty/zYogA==",
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.17.tgz",
+ "integrity": "sha512-ls2GjURfpBcGnIkwVrg2uuLnTBwd0vrEiUvbMo+GF3k81GAp2flCkVTM7ciAbo155Izk50dm0uXHYq1PIjwTxw==",
"requires": {
"ascii-table": "0.0.9",
"lodash": "^4.17.21",
diff --git a/package.json b/package.json
index 9d25af44e31c..9538790b9437 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.2.10-0",
+ "version": "1.2.10-1",
"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.",
@@ -94,7 +94,7 @@
"react-native-image-picker": "^4.8.5",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.15",
+ "react-native-onyx": "1.0.17",
"react-native-pdf": "^6.6.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
@@ -104,7 +104,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "^3.1.4",
"react-native-screens": "^3.10.1",
- "react-native-svg": "^12.1.0",
+ "react-native-svg": "^12.4.4",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
"react-plaid-link": "3.3.2",
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 162b459e7384..70327599ca22 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -180,6 +180,7 @@ export default {
FORMS: {
ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
REQUEST_CALL_FORM: 'requestCallForm',
+ REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount',
},
// Whether we should show the compose input or not
diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js
index 8a4447bb0cdc..6502852afd2a 100644
--- a/src/components/BlockingViews/FullPageNotFoundView.js
+++ b/src/components/BlockingViews/FullPageNotFoundView.js
@@ -18,10 +18,30 @@ const propTypes = {
/** If true, child components are replaced with a blocking "not found" view */
shouldShow: PropTypes.bool,
+
+ /** The key in the translations file to use for the title */
+ titleKey: PropTypes.string,
+
+ /** The key in the translations file to use for the subtitle */
+ subtitleKey: PropTypes.string,
+
+ /** Whether we should show a back icon */
+ shouldShowBackButton: PropTypes.bool,
+
+ /** Whether we should show a close button */
+ shouldShowCloseButton: PropTypes.bool,
+
+ /** Method to trigger when pressing the back button of the header */
+ onBackButtonPress: PropTypes.func,
};
const defaultProps = {
shouldShow: false,
+ titleKey: 'notFound.notHere',
+ subtitleKey: 'notFound.pageNotFound',
+ shouldShowBackButton: true,
+ shouldShowCloseButton: true,
+ onBackButtonPress: () => Navigation.dismissModal(),
};
// eslint-disable-next-line rulesdir/no-negated-variables
@@ -30,15 +50,16 @@ const FullPageNotFoundView = (props) => {
return (
<>
Navigation.dismissModal()}
+ shouldShowBackButton={props.shouldShowBackButton}
+ shouldShowCloseButton={props.shouldShowCloseButton}
+ onBackButtonPress={props.onBackButtonPress}
onCloseButtonPress={() => Navigation.dismissModal()}
/>
>
diff --git a/src/components/Form.js b/src/components/Form.js
index f42101700862..4e084cfb6209 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as FormActions from '../libs/actions/FormActions';
+import * as ErrorUtils from '../libs/ErrorUtils';
import styles from '../styles/styles';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
@@ -16,6 +17,9 @@ const propTypes = {
/** Text to be displayed in the submit button */
submitButtonText: PropTypes.string.isRequired,
+ /** Controls the submit button's visibility */
+ isSubmitButtonVisible: PropTypes.bool,
+
/** Callback to validate the form */
validate: PropTypes.func.isRequired,
@@ -32,8 +36,8 @@ const propTypes = {
/** Controls the loading state of the form */
isLoading: PropTypes.bool,
- /** Server side error message */
- error: PropTypes.string,
+ /** Server side errors keyed by microtime */
+ errors: PropTypes.objectOf(PropTypes.string),
}),
/** Contains draft values for each input in the form */
@@ -44,9 +48,10 @@ const propTypes = {
};
const defaultProps = {
+ isSubmitButtonVisible: true,
formState: {
isLoading: false,
- error: '',
+ errors: null,
},
draftValues: {},
};
@@ -75,6 +80,11 @@ class Form extends React.Component {
this.touchedInputs[inputID] = true;
}
+ getErrorMessage() {
+ const latestErrorMessage = ErrorUtils.getLatestErrorMessage(this.props.formState);
+ return this.props.formState.error || (typeof latestErrorMessage === 'string' ? latestErrorMessage : '');
+ }
+
submit() {
// Return early if the form is already submitting to avoid duplicate submission
if (this.props.formState.isLoading) {
@@ -100,7 +110,7 @@ class Form extends React.Component {
* @returns {Object} - An object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2}
*/
validate(values) {
- FormActions.setErrorMessage(this.props.formID, '');
+ FormActions.setErrors(this.props.formID, null);
const validationErrors = this.props.validate(values);
if (!_.isObject(validationErrors)) {
@@ -184,17 +194,19 @@ class Form extends React.Component {
>
{this.childrenWrapperWithProps(this.props.children)}
+ {this.props.isSubmitButtonVisible && (
0 || Boolean(this.props.formState.error)}
+ isAlertVisible={_.size(this.state.errors) > 0 || Boolean(this.getErrorMessage())}
isLoading={this.props.formState.isLoading}
- message={this.props.formState.error}
+ message={this.getErrorMessage()}
onSubmit={this.submit}
onFixTheErrorsLinkPressed={() => {
this.inputRefs[_.first(_.keys(this.state.errors))].focus();
}}
containerStyles={[styles.mh0, styles.mt5]}
/>
+ )}
>
diff --git a/src/languages/en.js b/src/languages/en.js
index 1b7e384a92dd..9ea22843b1f6 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -525,6 +525,7 @@ export default {
iouReportNotFound: 'The payment details you are looking for cannot be found.',
notHere: "Hmm... it's not here",
pageNotFound: 'That page is nowhere to be found.',
+ noAccess: 'You don\'t have access to this chat',
},
setPasswordPage: {
enterPassword: 'Enter a password',
@@ -822,6 +823,7 @@ export default {
error: {
genericAdd: 'There was a problem adding this workspace member.',
cannotRemove: 'You cannot remove yourself or the workspace owner.',
+ genericRemove: 'There was a problem removing that workspace member.',
},
},
card: {
diff --git a/src/languages/es.js b/src/languages/es.js
index 9fee9c421a5a..9676d7b748a2 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -525,6 +525,7 @@ export default {
iouReportNotFound: 'Los detalles del pago que estás buscando no se pudieron encontrar.',
notHere: 'Hmm… no está aquí',
pageNotFound: 'La página que buscas no existe.',
+ noAccess: 'No tienes acceso a este chat',
},
setPasswordPage: {
enterPassword: 'Escribe una contraseña',
@@ -824,6 +825,7 @@ export default {
error: {
genericAdd: 'Ha ocurrido un problema al agregar el miembro al espacio de trabajo',
cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.',
+ genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.',
},
},
card: {
diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js
index 7f0d4bf0cd91..908a500d6b72 100644
--- a/src/libs/ActiveClientManager/index.js
+++ b/src/libs/ActiveClientManager/index.js
@@ -1,3 +1,9 @@
+/**
+ * When you have many tabs in one browser, the data of Onyx is shared between all of them. Since we persist write requests in Onyx, we need to ensure that
+ * only one tab is processing those saved requests or we would be duplicating data (or creating errors).
+ * This file ensures exactly that by tracking all the clientIDs connected, storing the most recent one last and it considers that last clientID the "leader".
+ */
+
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
@@ -6,38 +12,47 @@ import * as ActiveClients from '../actions/ActiveClients';
const clientID = Str.guid();
const maxClients = 20;
-
-let activeClients;
-
-let resolveIsReadyPromise;
-const isReadyPromise = new Promise((resolve) => {
- resolveIsReadyPromise = resolve;
+let activeClients = [];
+let resolveSavedSelfPromise;
+const savedSelfPromise = new Promise((resolve) => {
+ resolveSavedSelfPromise = resolve;
});
/**
+ * Determines when the client is ready. We need to wait both till we saved our ID in onyx AND the init method was called
* @returns {Promise}
*/
function isReady() {
- return isReadyPromise;
+ return savedSelfPromise;
}
Onyx.connect({
key: ONYXKEYS.ACTIVE_CLIENTS,
callback: (val) => {
- activeClients = !val ? [] : val;
- if (activeClients.length >= maxClients) {
+ activeClients = val;
+
+ // Remove from the beginning of the list any clients that are past the limit, to avoid having thousands of them
+ let removed = false;
+ while (activeClients.length >= maxClients) {
activeClients.shift();
+ removed = true;
+ }
+
+ // Save the clients back to onyx, if they changed
+ if (removed) {
ActiveClients.setActiveClients(activeClients);
}
},
});
/**
- * Add our client ID to the list of active IDs
+ * Add our client ID to the list of active IDs.
+ * We want to ensure we have no duplicates and that the activeClient gets added at the end of the array (see isClientTheLeader)
*/
function init() {
- ActiveClients.addClient(clientID)
- .then(resolveIsReadyPromise);
+ activeClients = _.without(activeClients, clientID);
+ activeClients.push(clientID);
+ ActiveClients.setActiveClients(activeClients).then(resolveSavedSelfPromise);
}
/**
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index a402d67761b1..0b9b62c25916 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -20,7 +20,6 @@ import KeyboardShortcut from '../../KeyboardShortcut';
import Navigation from '../Navigation';
import * as User from '../../actions/User';
import * as Modal from '../../actions/Modal';
-import * as Policy from '../../actions/Policy';
import modalCardStyleInterpolator from './modalCardStyleInterpolator';
import createCustomModalStackNavigator from './createCustomModalStackNavigator';
@@ -100,7 +99,6 @@ class AuthScreens extends React.Component {
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
}).then(() => {
User.subscribeToUserEvents();
- Policy.subscribeToPolicyEvents();
});
// Listen for report changes and fetch some data we need on initialization
diff --git a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
index 96664751686b..26537c58fd1b 100644
--- a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
+++ b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
@@ -49,6 +49,12 @@ class BaseDrawerNavigator extends Component {
};
}
+ componentDidMount() {
+ // We need to resolve the isDrawerReady promise so that any pending drawer actions, like direct navigation from OldDot to
+ // a NewDot report, can happen.
+ Navigation.setIsDrawerReady();
+ }
+
componentDidUpdate(prevProps) {
if (prevProps.isSmallScreenWidth === this.props.isSmallScreenWidth) {
return;
diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js
index 30f6b4ca8399..ab88b8a600c8 100644
--- a/src/libs/Navigation/Navigation.js
+++ b/src/libs/Navigation/Navigation.js
@@ -15,6 +15,11 @@ const navigationIsReadyPromise = new Promise((resolve) => {
resolveNavigationIsReadyPromise = resolve;
});
+let resolveDrawerIsReadyPromise;
+const drawerIsReadyPromise = new Promise((resolve) => {
+ resolveDrawerIsReadyPromise = resolve;
+});
+
let isLoggedIn = false;
Onyx.connect({
key: ONYXKEYS.SESSION,
@@ -202,6 +207,17 @@ function setIsNavigationReady() {
resolveNavigationIsReadyPromise();
}
+/**
+ * @returns {Promise}
+ */
+function isDrawerReady() {
+ return drawerIsReadyPromise;
+}
+
+function setIsDrawerReady() {
+ resolveDrawerIsReadyPromise();
+}
+
export default {
canNavigate,
navigate,
@@ -214,6 +230,9 @@ export default {
setDidTapNotification,
isNavigationReady,
setIsNavigationReady,
+ isDrawerReady,
+ setIsDrawerReady,
+ isDrawerRoute,
};
export {
diff --git a/src/libs/actions/ActiveClients.js b/src/libs/actions/ActiveClients.js
index 2a3689b2c099..744944cfef1c 100644
--- a/src/libs/actions/ActiveClients.js
+++ b/src/libs/actions/ActiveClients.js
@@ -3,20 +3,13 @@ import ONYXKEYS from '../../ONYXKEYS';
/**
* @param {Array} activeClients
+ * @return {Promise}
*/
function setActiveClients(activeClients) {
- Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
-}
-
-/**
- * @param {Number} clientID
- * @returns {Promise}
- */
-function addClient(clientID) {
- return Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]);
+ return Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
}
export {
+ // eslint-disable-next-line import/prefer-default-export
setActiveClients,
- addClient,
};
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index d667e8ee078c..265de152b3d5 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -196,6 +196,18 @@ function setUpPoliciesAndNavigate(session) {
return;
}
if (!isLoggingInAsNewUser && exitTo) {
+ if (Navigation.isDrawerRoute(exitTo)) {
+ // The drawer navigation is only created after we have fetched reports from the server.
+ // Thus, if we use the standard navigation and try to navigate to a drawer route before
+ // the reports have been fetched, we will fail to navigate.
+ Navigation.isDrawerReady()
+ .then(() => {
+ // We must call dismissModal() to remove the /transition route from history
+ Navigation.dismissModal();
+ Navigation.navigate(exitTo);
+ });
+ return;
+ }
Navigation.isNavigationReady()
.then(() => {
// We must call dismissModal() to remove the /transition route from history
diff --git a/src/libs/actions/FormActions.js b/src/libs/actions/FormActions.js
index eb087e673361..188d10b759b7 100644
--- a/src/libs/actions/FormActions.js
+++ b/src/libs/actions/FormActions.js
@@ -10,10 +10,10 @@ function setIsLoading(formID, isLoading) {
/**
* @param {String} formID
- * @param {String} error
+ * @param {String} errors
*/
-function setErrorMessage(formID, error) {
- Onyx.merge(formID, {error});
+function setErrors(formID, errors) {
+ Onyx.merge(formID, {errors});
}
/**
@@ -26,6 +26,6 @@ function setDraftValues(formID, draftValues) {
export {
setIsLoading,
- setErrorMessage,
+ setErrors,
setDraftValues,
};
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 12efd891f05d..ffcbfdc7b443 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -6,15 +6,11 @@ import Str from 'expensify-common/lib/str';
import * as DeprecatedAPI from '../deprecatedAPI';
import * as API from '../API';
import ONYXKEYS from '../../ONYXKEYS';
-import Growl from '../Growl';
-import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
import * as Localize from '../Localize';
import Navigation from '../Navigation/Navigation';
import ROUTES from '../../ROUTES';
import * as OptionsListUtils from '../OptionsListUtils';
-import * as Report from './Report';
-import * as Pusher from '../Pusher/pusher';
import DateUtils from '../DateUtils';
import * as ReportUtils from '../ReportUtils';
@@ -209,29 +205,21 @@ function removeMembers(members, policyID) {
if (members.length === 0) {
return;
}
-
- const employeeListUpdate = {};
- _.each(members, login => employeeListUpdate[login] = null);
- Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate);
-
- // Make the API call to remove a login from the policy
- DeprecatedAPI.Policy_Employees_Remove({
+ const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`;
+ const optimisticData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: membersListKey,
+ value: _.object(members, Array(members.length).fill({pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE})),
+ }];
+ const failureData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: membersListKey,
+ value: _.object(members, Array(members.length).fill({errors: {[DateUtils.getMicroseconds()]: Localize.translateLocal('workspace.people.error.genericRemove')}})),
+ }];
+ API.write('DeleteMembersFromWorkspace', {
emailList: members.join(','),
policyID,
- })
- .then((data) => {
- if (data.jsonCode === 200) {
- return;
- }
-
- // Rollback removal on failure
- _.each(members, login => employeeListUpdate[login] = {});
- Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate);
-
- // Show the user feedback that the removal failed
- const errorMessage = data.jsonCode === 666 ? data.message : Localize.translateLocal('workspace.people.genericFailureMessage');
- Growl.show(errorMessage, CONST.GROWL.ERROR, 5000);
- });
+ }, {optimisticData, failureData});
}
/**
@@ -653,31 +641,6 @@ function updateLastAccessedWorkspace(policyID) {
Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID);
}
-/**
- * Subscribe to public-policyEditor-[policyID] events.
- */
-function subscribeToPolicyEvents() {
- _.each(allPolicies, (policy) => {
- const pusherChannelName = `public-policyEditor-${policy.id}${CONFIG.PUSHER.SUFFIX}`;
- Pusher.subscribe(pusherChannelName, 'policyEmployeeRemoved', ({removedEmails, policyExpenseChatIDs, defaultRoomChatIDs}) => {
- // Refetch the policy expense chats to update their state and their actions to get the archive reason
- if (!_.isEmpty(policyExpenseChatIDs)) {
- Report.fetchChatReportsByIDs(policyExpenseChatIDs);
- _.each(policyExpenseChatIDs, (reportID) => {
- Report.reconnect(reportID);
- });
- }
-
- // Remove the default chats if we are one of the users getting removed
- if (removedEmails.includes(sessionEmail) && !_.isEmpty(defaultRoomChatIDs)) {
- _.each(defaultRoomChatIDs, (chatID) => {
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatID}`, null);
- });
- }
- });
- });
-}
-
/**
* Removes an error after trying to delete a member
*
@@ -974,7 +937,6 @@ export {
updateWorkspaceCustomUnit,
updateCustomUnitRate,
updateLastAccessedWorkspace,
- subscribeToPolicyEvents,
clearDeleteMemberError,
clearAddMemberError,
clearDeleteWorkspaceError,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 9008c209e53f..d9ce4cba8cc7 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -331,14 +331,6 @@ function fetchChatReportsByIDs(chatList, shouldRedirectIfInaccessible = false) {
// Fetch the personal details if there are any
PersonalDetails.getFromReportParticipants(_.values(simplifiedReports));
return simplifiedReports;
- })
- .catch((err) => {
- if (err.message !== CONST.REPORT.ERROR.INACCESSIBLE_REPORT) {
- return;
- }
-
- // eslint-disable-next-line no-use-before-define
- handleInaccessibleReport();
});
}
@@ -1086,6 +1078,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
isEdited: true,
html: htmlForNewComment,
text: textForNewComment,
+ type: originalReportAction.message[0].type,
}],
},
};
@@ -1212,14 +1205,6 @@ function navigateToConciergeChat() {
Navigation.navigate(ROUTES.getReportRoute(conciergeChatReportID));
}
-/**
- * Handle the navigation when report is inaccessible
- */
-function handleInaccessibleReport() {
- Growl.error(Localize.translateLocal('notFound.chatYouLookingForCannotBeFound'));
- navigateToConciergeChat();
-}
-
/**
* Creates a policy room, fetches it, and navigates to it.
* @param {String} policyID
@@ -1544,7 +1529,6 @@ export {
getSimplifiedIOUReport,
syncChatAndIOUReports,
navigateToConciergeChat,
- handleInaccessibleReport,
setReportWithDraft,
createPolicyRoom,
addPolicyReport,
diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js
index 5fc49b9565fb..afaa3b1c7851 100644
--- a/src/pages/ReimbursementAccount/BankAccountManualStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js
@@ -1,4 +1,3 @@
-import _ from 'underscore';
import React from 'react';
import {Image, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
@@ -15,9 +14,8 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize
import * as ValidationUtils from '../../libs/ValidationUtils';
import compose from '../../libs/compose';
import ONYXKEYS from '../../ONYXKEYS';
-import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount';
import exampleCheckImage from './exampleCheckImage';
-import ReimbursementAccountForm from './ReimbursementAccountForm';
+import Form from '../../components/Form';
import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils';
const propTypes = {
@@ -27,69 +25,40 @@ const propTypes = {
class BankAccountManualStep extends React.Component {
constructor(props) {
super(props);
-
this.submit = this.submit.bind(this);
- this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this);
- this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey);
- this.state = {
- acceptTerms: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTerms', true),
- routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'),
- accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'),
- };
-
- // Map a field to the key of the error's translation
- this.errorTranslationKeys = {
- routingNumber: 'bankAccount.error.routingNumber',
- accountNumber: 'bankAccount.error.accountNumber',
- acceptTerms: 'common.error.acceptedTerms',
- };
+ this.validate = this.validate.bind(this);
}
/**
- * @returns {Boolean}
+ * @param {Object} values - form input values passed by the Form component
+ * @returns {Object}
*/
- validate() {
+ validate(values) {
const errorFields = {};
- const routingNumber = this.state.routingNumber.trim();
+ const routingNumber = values.routingNumber && values.routingNumber.trim();
- if (!CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(this.state.accountNumber.trim())) {
- errorFields.accountNumber = true;
+ if (!values.accountNumber || !CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim())) {
+ errorFields.accountNumber = this.props.translate('bankAccount.error.accountNumber');
}
- if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber)) {
- errorFields.routingNumber = true;
+ if (!routingNumber || !CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber)) {
+ errorFields.routingNumber = this.props.translate('bankAccount.error.routingNumber');
}
- if (!this.state.acceptTerms) {
- errorFields.acceptTerms = true;
+ if (!values.acceptedTerms) {
+ errorFields.acceptedTerms = this.props.translate('common.error.acceptedTerms');
}
- ReimbursementAccount.setBankAccountFormValidationErrors(errorFields);
-
- return _.size(errorFields) === 0;
+ return errorFields;
}
- submit() {
- if (!this.validate()) {
- return;
- }
+ submit(values) {
BankAccounts.connectBankAccountManually(
ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0),
- this.state.accountNumber,
- this.state.routingNumber,
+ values.accountNumber,
+ values.routingNumber,
ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidMask'),
);
}
- /**
- * @param {String} inputKey
- * @param {String} value
- */
- clearErrorAndSetValue(inputKey, value) {
- const newState = {[inputKey]: value};
- this.setState(newState);
- ReimbursementAccount.updateReimbursementAccountDraft(newState);
- ReimbursementAccountUtils.clearError(this.props, inputKey);
- }
-
render() {
const shouldDisableInputs = Boolean(ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID'));
@@ -104,7 +73,13 @@ class BankAccountManualStep extends React.Component {
onBackButtonPress={() => BankAccounts.setBankAccountSubStep(null)}
onCloseButtonPress={Navigation.dismissModal}
/>
-
+
+
>
);
}
diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js
index f738aecf7d8d..5519c4ef1769 100644
--- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js
@@ -11,8 +11,9 @@ import compose from '../../libs/compose';
import ONYXKEYS from '../../ONYXKEYS';
import AddPlaidBankAccount from '../../components/AddPlaidBankAccount';
import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount';
-import ReimbursementAccountForm from './ReimbursementAccountForm';
import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils';
+import Form from '../../components/Form';
+import styles from '../../styles/styles';
const propTypes = {
/** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */
@@ -69,7 +70,14 @@ class BankAccountPlaidStep extends React.Component {
onBackButtonPress={() => BankAccounts.setBankAccountSubStep(null)}
onCloseButtonPress={Navigation.dismissModal}
/>
-
+
+
>
);
}
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 91c2dfb5b3e2..bdf3a608b71f 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -29,6 +29,7 @@ import ReportFooter from './report/ReportFooter';
import Banner from '../../components/Banner';
import withLocalize from '../../components/withLocalize';
import reportPropTypes from '../reportPropTypes';
+import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView';
const propTypes = {
/** Navigation route context info provided by react navigation */
@@ -153,10 +154,6 @@ class ReportScreen extends React.Component {
*/
storeCurrentlyViewedReport() {
const reportIDFromPath = getReportID(this.props.route);
- if (_.isNaN(reportIDFromPath)) {
- Report.handleInaccessibleReport();
- return;
- }
// Always reset the state of the composer view when the current reportID changes
toggleReportActionComposeView(true);
@@ -216,56 +213,66 @@ class ReportScreen extends React.Component {
style={[styles.appContent, styles.flex1, {marginTop: this.state.viewportOffsetTop}]}
keyboardAvoidingViewBehavior={Platform.OS === 'android' ? '' : 'padding'}
>
-
- Navigation.navigate(ROUTES.HOME)}
- />
-
- {this.props.accountManagerReportID && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && (
-
- )}
- this.setState({skeletonViewContainerHeight: event.nativeEvent.layout.height})}
+ {
+ Navigation.navigate(ROUTES.HOME);
+ }}
>
- {this.shouldShowLoader()
- ? (
-
- )
- : (
-
- )}
-
-
+
+ Navigation.navigate(ROUTES.HOME)}
+ />
+
+ {this.props.accountManagerReportID && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && (
+
+ )}
+ this.setState({skeletonViewContainerHeight: event.nativeEvent.layout.height})}
+ >
+ {this.shouldShowLoader()
+ ? (
+
+ )
+ : (
+
+ )}
+
+
+
);
}
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 1de2d1759565..0b3f12570340 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -283,14 +283,20 @@ class WorkspaceMembersPage extends React.Component {
}
render() {
- const policyMemberList = _.keys(lodashGet(this.props, 'policyMemberList', {}));
- const removableMembers = _.without(policyMemberList, this.props.session.email, this.props.policy.owner);
- const data = _.chain(policyMemberList)
- .map(email => this.props.personalDetails[email])
- .filter()
- .sortBy(person => person.displayName.toLowerCase())
- .map(person => ({...person})) // TODO: here we will add the pendingAction and errors prop
- .value();
+ const policyMemberList = lodashGet(this.props, 'policyMemberList', {});
+ const removableMembers = [];
+ let data = [];
+ _.each(policyMemberList, (policyMember, email) => {
+ if (email !== this.props.session.email && email !== this.props.policy.owner) {
+ removableMembers.push(email);
+ }
+ const details = this.props.personalDetails[email] || {displayName: email, login: email};
+ data.push({
+ ...policyMember,
+ ...details,
+ });
+ });
+ data = _.sortBy(data, value => value.displayName.toLowerCase());
const policyID = lodashGet(this.props.route, 'params.policyID');
const policyName = lodashGet(this.props.policy, 'name');
diff --git a/src/pages/workspace/withPolicy.js b/src/pages/workspace/withPolicy.js
index d9c85a5387ae..a23ffdcbc97a 100644
--- a/src/pages/workspace/withPolicy.js
+++ b/src/pages/workspace/withPolicy.js
@@ -8,7 +8,6 @@ import CONST from '../../CONST';
import getComponentDisplayName from '../../libs/getComponentDisplayName';
import * as Policy from '../../libs/actions/Policy';
import ONYXKEYS from '../../ONYXKEYS';
-import policyMemberPropType from '../policyMemberPropType';
/**
* @param {Object} route
@@ -56,9 +55,6 @@ const policyPropTypes = {
*/
errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
}),
-
- /** The policy member list for the current route */
- policyMemberList: PropTypes.objectOf(policyMemberPropType),
};
const policyDefaultProps = {