diff --git a/.github/actions/checkDeployBlockers/checkDeployBlockers.js b/.github/actions/checkDeployBlockers/checkDeployBlockers.js
index 000ea410b94d..c0c50d2cce4e 100644
--- a/.github/actions/checkDeployBlockers/checkDeployBlockers.js
+++ b/.github/actions/checkDeployBlockers/checkDeployBlockers.js
@@ -16,9 +16,8 @@ const run = function () {
console.log('Checking for unverified PRs or unresolved deploy blockers', data);
// Check the issue description to see if there are any unfinished/un-QAed items in the checklist.
- const uncheckedBoxRegex = /-\s\[\s]/g;
- const matches = uncheckedBoxRegex.exec(data.body);
- if (matches !== null) {
+ const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`);
+ if (uncheckedBoxRegex.test(data.body)) {
console.log('An unverified PR or unresolved deploy blocker was found.');
core.setOutput('HAS_DEPLOY_BLOCKERS', true);
return;
diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js
index 2162f09f07f5..79cbf3100e5b 100644
--- a/.github/actions/checkDeployBlockers/index.js
+++ b/.github/actions/checkDeployBlockers/index.js
@@ -26,9 +26,8 @@ const run = function () {
console.log('Checking for unverified PRs or unresolved deploy blockers', data);
// Check the issue description to see if there are any unfinished/un-QAed items in the checklist.
- const uncheckedBoxRegex = /-\s\[\s]/g;
- const matches = uncheckedBoxRegex.exec(data.body);
- if (matches !== null) {
+ const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`);
+ if (uncheckedBoxRegex.test(data.body)) {
console.log('An unverified PR or unresolved deploy blocker was found.');
core.setOutput('HAS_DEPLOY_BLOCKERS', true);
return;
@@ -512,6 +511,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js
index 613ccb0401cb..1fc4bf16c44f 100644
--- a/.github/actions/createOrUpdateStagingDeploy/index.js
+++ b/.github/actions/createOrUpdateStagingDeploy/index.js
@@ -701,6 +701,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/getPullRequestDetails/index.js b/.github/actions/getPullRequestDetails/index.js
index 6e9e8b9fe4cf..342373eebb12 100644
--- a/.github/actions/getPullRequestDetails/index.js
+++ b/.github/actions/getPullRequestDetails/index.js
@@ -565,6 +565,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/getReleaseBody/index.js b/.github/actions/getReleaseBody/index.js
index 794b52333290..e34ba2955069 100644
--- a/.github/actions/getReleaseBody/index.js
+++ b/.github/actions/getReleaseBody/index.js
@@ -483,6 +483,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/isPullRequestMergeable/index.js b/.github/actions/isPullRequestMergeable/index.js
index d6e271850742..e8f8e84cacea 100644
--- a/.github/actions/isPullRequestMergeable/index.js
+++ b/.github/actions/isPullRequestMergeable/index.js
@@ -501,6 +501,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js
index 5a0e38ed3de3..7ea200ca6ff0 100644
--- a/.github/actions/isStagingDeployLocked/index.js
+++ b/.github/actions/isStagingDeployLocked/index.js
@@ -464,6 +464,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js
index 9fc180ac5ec9..cfb143b877a3 100644
--- a/.github/actions/markPullRequestsAsDeployed/index.js
+++ b/.github/actions/markPullRequestsAsDeployed/index.js
@@ -616,6 +616,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js
index a7ce41077ef5..4b34fae9bfce 100644
--- a/.github/actions/reopenIssueWithComment/index.js
+++ b/.github/actions/reopenIssueWithComment/index.js
@@ -475,6 +475,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/actions/triggerWorkflowAndWait/index.js b/.github/actions/triggerWorkflowAndWait/index.js
index 9ba263668e8e..7f1a048fe333 100644
--- a/.github/actions/triggerWorkflowAndWait/index.js
+++ b/.github/actions/triggerWorkflowAndWait/index.js
@@ -634,6 +634,7 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
/***/ }),
diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js
index d750c7340ccb..a17a2b875543 100644
--- a/.github/libs/GithubUtils.js
+++ b/.github/libs/GithubUtils.js
@@ -424,3 +424,4 @@ module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;
+module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX;
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8b9de823d1b5..7753bc0d30cb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,6 +12,10 @@ You can create as many accounts as needed in order to test your changes directly
##### Generating Multiple Test Accounts
You can generate multiple test accounts by using a `+` postfix, for example if your email is test@test.com, you can create multiple New Expensify accounts connected to the same email address by using test+123@test.com, test+456@test.com, etc.
+#### Working on beta features
+
+Some features are locked behind beta flags while development is ongoing. As a contributor you can work on these beta features locally by overriding the [`Permissions.canUseAllBetas` function](https://github.com/Expensify/App/blob/5e268df7f2989ed04bc64c0c86ed77faf134554d/src/libs/Permissions.js#L10-L12) to return `true`.
+
## Code of Conduct
This project and everyone participating in it is governed by the Expensify [Code of Conduct](https://github.com/Expensify/App/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [contributors@expensify.com](mailto:contributors@expensify.com).
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e374f6e0e982..eaadee9751c7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -152,8 +152,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001013200
- versionName "1.1.32-0"
+ versionCode 1001013201
+ versionName "1.1.32-1"
}
splits {
abi {
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 579c34777417..c59bd66518e2 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -31,7 +31,7 @@
CFBundleVersion
- 1.1.32.0
+ 1.1.32.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 0716a4531166..492062abb533 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.1.32.0
+ 1.1.32.1
diff --git a/package-lock.json b/package-lock.json
index 51b672317a7c..4de489ed0f3d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.32-0",
+ "version": "1.1.32-1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -37116,17 +37116,17 @@
"integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0="
},
"react-native-modal": {
- "version": "11.10.0",
- "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-11.10.0.tgz",
- "integrity": "sha512-syRYDJYSh16bR37R5EKU9T/wC+5bEOfF17IUqf5URdhbEDd+hxyMInC++l45E8oI+MtdOaEp9yAws5xDqk8dnA==",
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.0.tgz",
+ "integrity": "sha512-k6r9T31mc7HIDFj1V53ceAAN1dwc8052c4JLtDVEmEQ19Bbq9yiLXoDsQuNb+hB8A+2tVOXmo5Gq4IQfb11upw==",
"requires": {
"prop-types": "^15.6.2",
"react-native-animatable": "1.3.3"
}
},
"react-native-onyx": {
- "version": "git+https://github.com/Expensify/react-native-onyx.git#40c5b14dcc77e1193d027ecc9dd5f2563516e148",
- "from": "git+https://github.com/Expensify/react-native-onyx.git#40c5b14dcc77e1193d027ecc9dd5f2563516e148",
+ "version": "git+https://github.com/Expensify/react-native-onyx.git#f3814ff18405e7021c1bb51011396ce37474df86",
+ "from": "git+https://github.com/Expensify/react-native-onyx.git#f3814ff18405e7021c1bb51011396ce37474df86",
"requires": {
"ascii-table": "0.0.9",
"expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c",
diff --git a/package.json b/package.json
index 1184f35c2fb0..dc3ecd2cb348 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.32-0",
+ "version": "1.1.32-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.",
@@ -93,8 +93,8 @@
"react-native-image-picker": "^4.1.2",
"react-native-image-size": "^1.1.3",
"react-native-keyboard-spacer": "^0.4.1",
- "react-native-modal": "^11.10.0",
- "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#40c5b14dcc77e1193d027ecc9dd5f2563516e148",
+ "react-native-modal": "^13.0.0",
+ "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#f3814ff18405e7021c1bb51011396ce37474df86",
"react-native-pdf": "^6.2.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
diff --git a/src/CONST.js b/src/CONST.js
index 42580b06b157..b449636dfd57 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -213,6 +213,7 @@ const CONST = {
TYPE: {
IOU: 'IOU',
ADDCOMMENT: 'ADDCOMMENT',
+ RENAMED: 'RENAMED',
},
},
ERROR: {
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 2e77064d3f87..e701f99ab6a1 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -166,6 +166,9 @@ export default {
// Are we loading the create policy room command
IS_LOADING_CREATE_POLICY_ROOM: 'isLoadingCratePolicyRoom',
+ // Are we loading the rename policy room command
+ IS_LOADING_RENAME_POLICY_ROOM: 'isLoadingRenamePolicyRoom',
+
// Is Keyboard shortcuts modal open?
IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen',
@@ -177,4 +180,7 @@ export default {
// The policyID of the last workspace whose settings were accessed by the user
LAST_ACCESSED_WORKSPACE_POLICY_ID: 'lastAccessedWorkspacePolicyID',
+
+ // Validating Email?
+ USER_SIGN_UP: 'userSignUp',
};
diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index 8f72a23afad5..f3837f8ad61d 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -117,7 +117,7 @@ class AddPlaidBankAccount extends React.Component {
this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props);
this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey);
this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, {
- password: 'passwordForm.error.incorrectLoginOrPassword',
+ password: 'passwordForm.error.incorrectPassword',
}, inputKey);
}
diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js
index badf3687ccc3..461849319b56 100644
--- a/src/components/ImageView/index.native.js
+++ b/src/components/ImageView/index.native.js
@@ -69,6 +69,10 @@ class ImageView extends PureComponent {
imageWidth = Math.round(this.props.windowWidth);
imageHeight = Math.round(imageWidth * aspectRatio);
}
+
+ // Resize the image to max dimensions possible on the Native platforms to prevent crashes on Android. To keep the same behavior, apply to IOS as well.
+ const maxDimensionsScale = 11;
+ imageHeight = Math.min(imageHeight, (this.props.windowHeight * maxDimensionsScale));
this.setState({imageHeight, imageWidth});
});
}
diff --git a/src/components/ReportActionItem/RenameAction.js b/src/components/ReportActionItem/RenameAction.js
new file mode 100644
index 000000000000..4b83ba66a610
--- /dev/null
+++ b/src/components/ReportActionItem/RenameAction.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import lodashGet from 'lodash/get';
+import Text from '../Text';
+import styles from '../../styles/styles';
+import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes';
+import withLocalize, {withLocalizePropTypes} from '../withLocalize';
+
+const propTypes = {
+ /** All the data of the action */
+ action: PropTypes.shape(reportActionPropTypes).isRequired,
+
+ ...withLocalizePropTypes,
+};
+
+const RenameAction = (props) => {
+ const displayName = lodashGet(props.action, ['message', 0, 'text']);
+ const oldName = lodashGet(props.action, 'originalMessage.oldName', '');
+ const newName = lodashGet(props.action, 'originalMessage.newName', '');
+
+ return (
+
+
+ {displayName}
+
+ {props.translate('newRoomPage.renamedRoomAction', {oldName, newName})}
+
+ );
+};
+
+RenameAction.propTypes = propTypes;
+RenameAction.displayName = 'RenameAction';
+
+export default withLocalize(RenameAction);
diff --git a/src/languages/en.js b/src/languages/en.js
index 1520446a1498..f5b321abe683 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -381,8 +381,8 @@ export default {
},
paymentMethodList: {
addPaymentMethod: 'Add payment method',
- addDebitCard: 'Add debit card',
- addBankAccount: 'Add bank account',
+ addNewDebitCard: 'Add new debit card',
+ addNewBankAccount: 'Add new bank account',
accountLastFour: 'Account ending in',
cardLastFour: 'Card ending in',
addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app.',
@@ -432,6 +432,7 @@ export default {
twoFactorCode: 'Two factor code',
requiredWhen2FAEnabled: 'Required when 2FA is enabled',
error: {
+ incorrectPassword: 'Incorrect password. Please try again.',
incorrectLoginOrPassword: 'Incorrect login or password. Please try again.',
twoFactorAuthenticationEnabled: 'You have 2FA enabled on this account. Please sign in using your email or phone number.',
invalidLoginOrPassword: 'Invalid login or password. Please try again or reset your password.',
@@ -478,8 +479,9 @@ export default {
setPassword: 'Set password',
newPasswordPrompt: 'Your password must have at least 8 characters, 1 capital letter, 1 lowercase letter, and 1 number.',
passwordFormTitle: 'Welcome back to the New Expensify! Please set your password.',
- passwordNotSet: 'We were unable to set your new password correctly.',
- accountNotValidated: 'We were unable to validate your account. The validation code may have expired.',
+ passwordNotSet: 'We were unable to set your new password. We have sent you a new password link to try again.',
+ setPasswordLinkInvalid: 'This set password link is invalid or has expired. A new one is waiting for you in your email inbox!',
+ verifyingAccount: 'Verifying account',
},
stepCounter: ({step, total}) => `Step ${step} of ${total}`,
bankAccount: {
@@ -886,11 +888,14 @@ export default {
restrictedDescription: 'People in your workspace are able to find this room using Search',
privateDescription: 'Only people invited to this room are able to find it',
createRoom: 'Create Room',
+ policyRoomRenamed: 'Policy room renamed!',
roomAlreadyExistsError: 'A room with this name already exists',
roomNameReservedError: 'This name is reserved and cannot be used',
+ renamedRoomAction: ({oldName, newName}) => ` renamed this room from ${oldName} to ${newName}`,
social: 'social',
selectAWorkspace: 'Select a workspace',
growlMessageOnError: 'Unable to create policy room, please check your connection and try again.',
+ growlMessageOnRenameError: 'Unable to rename policy room, please check your connection and try again.',
visibilityOptions: {
restricted: 'Restricted',
private: 'Private',
diff --git a/src/languages/es.js b/src/languages/es.js
index 627dd67ccda3..6276a13be41b 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -381,8 +381,8 @@ export default {
},
paymentMethodList: {
addPaymentMethod: 'Agrega método de pago',
- addDebitCard: 'Agregar tarjeta de débito',
- addBankAccount: 'Agregar cuenta de banco',
+ addNewDebitCard: 'Agregar nueva tarjeta de débito',
+ addNewBankAccount: 'Agregar nueva cuenta de banco',
accountLastFour: 'Cuenta con terminación',
cardLastFour: 'Tarjeta con terminacíon',
addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación.',
@@ -432,6 +432,7 @@ export default {
twoFactorCode: 'Autenticación de 2 factores',
requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado',
error: {
+ incorrectPassword: 'Contraseña incorrecta. Por favor inténtalo de nuevo.',
incorrectLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo',
twoFactorAuthenticationEnabled: 'Tienes autenticación de 2 factores activada en esta cuenta. Por favor conéctate usando su email o número de teléfono',
invalidLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo o resetea tu clave',
@@ -478,8 +479,9 @@ export default {
setPassword: 'Configura tu contraseña',
newPasswordPrompt: 'La contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.',
passwordFormTitle: '¡Bienvenido de vuelta al Nuevo Expensify! Por favor, elige una contraseña.',
- passwordNotSet: 'No pudimos establecer to contaseña correctamente.',
- accountNotValidated: 'No pudimos validar tu cuenta. Es posible que el enlace de validación haya caducado.',
+ passwordNotSet: 'No pudimos cambiar tu clave. Te hemos enviado un nuevo enlace para que intentes cambiar la clave nuevamente.',
+ setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.',
+ verifyingAccount: 'Verificando cuenta',
},
stepCounter: ({step, total}) => `Paso ${step} de ${total}`,
bankAccount: {
@@ -888,11 +890,14 @@ export default {
restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala a través de "Buscar"',
privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla',
createRoom: 'Crea una sala de chat',
+ policyRoomRenamed: '¡Espacio de trabajo renombrado!',
roomAlreadyExistsError: 'Ya existe una sala con este nombre',
roomNameReservedError: 'Este nombre está reservado y no puede usarse',
+ renamedRoomAction: ({oldName, newName}) => ` cambió el nombre de la sala de ${oldName} a ${newName}`,
social: 'social',
selectAWorkspace: 'Seleccionar un espacio de trabajo',
growlMessageOnError: 'No ha sido posible crear el espacio de trabajo, por favor comprueba tu conexión e inténtalo de nuevo.',
+ growlMessageOnRenameError: 'No ha sido posible cambiar el nomdre del espacio de trabajo, por favor comprueba tu conexión e inténtalo de nuevo.',
visibilityOptions: {
restricted: 'Restringida',
private: 'Privada',
diff --git a/src/libs/API.js b/src/libs/API.js
index 7542f9690984..22b205dfbd0e 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -359,8 +359,8 @@ function AddBillingCard(parameters) {
/**
- * @param {Object} parameters
- * @param {String} parameters.oldPassword
+ * @param {{password: String, oldPassword: String}} parameters
+ * @param {String} parameters.authToken
* @param {String} parameters.password
* @returns {Promise}
*/
@@ -727,7 +727,7 @@ function SetNameValuePair(parameters) {
/**
* @param {Object} parameters
- * @param {Number} parameters.email
+ * @param {string} parameters.email
* @returns {Promise}
*/
function ResetPassword(parameters) {
@@ -740,7 +740,7 @@ function ResetPassword(parameters) {
* @param {Object} parameters
* @param {String} parameters.password
* @param {String} parameters.validateCode
- * @param {String} parameters.accountID
+ * @param {Number} parameters.accountID
* @returns {Promise}
*/
function SetPassword(parameters) {
@@ -1167,6 +1167,19 @@ function CreatePolicyRoom(parameters) {
return Network.post(commandName, parameters);
}
+/**
+ * Renames a user-created policy room
+ * @param {Object} parameters
+ * @param {String} parameters.reportID
+ * @param {String} parameters.reportName
+ * @return {Promise}
+ */
+function RenameReport(parameters) {
+ const commandName = 'RenameReport';
+ requireParameters(['reportID', 'reportName'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
/**
* Transfer Wallet balance and takes either the bankAccoundID or fundID
* @param {Object} parameters
@@ -1194,6 +1207,7 @@ export {
CreateChatReport,
CreateLogin,
CreatePolicyRoom,
+ RenameReport,
DeleteFund,
DeleteLogin,
DeleteBankAccount,
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 4dc9af374968..17984bdbe36e 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -395,7 +395,8 @@ function getOptions(reports, personalDetails, activeReportID, {
const logins = lodashGet(report, ['participants'], []);
// Report data can sometimes be incomplete. If we have no logins or reportID then we will skip this entry.
- if (!report || !report.reportID || (_.isEmpty(logins) && !ReportUtils.isChatRoom(report))) {
+ const shouldFilterNoParticipants = _.isEmpty(logins) && !ReportUtils.isChatRoom(report) && !ReportUtils.isDefaultRoom(report);
+ if (!report || !report.reportID || shouldFilterNoParticipants) {
return;
}
@@ -405,7 +406,7 @@ function getOptions(reports, personalDetails, activeReportID, {
: '';
const reportContainsIOUDebt = iouReportOwner && iouReportOwner !== currentUserLogin;
- const shouldFilterReportIfEmpty = !showReportsWithNoComments && report.lastMessageTimestamp === 0;
+ const shouldFilterReportIfEmpty = !showReportsWithNoComments && report.lastMessageTimestamp === 0 && !ReportUtils.isDefaultRoom(report);
const shouldFilterReportIfRead = hideReadReports && report.unreadActionCount === 0;
const shouldFilterReport = shouldFilterReportIfEmpty || shouldFilterReportIfRead;
if (report.reportID !== activeReportID
diff --git a/src/libs/PaymentUtils.js b/src/libs/PaymentUtils.js
index eb55234ece6f..674bffc9b8c5 100644
--- a/src/libs/PaymentUtils.js
+++ b/src/libs/PaymentUtils.js
@@ -65,7 +65,7 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '',
combinedPaymentMethods.push({
title: card.addressName,
description: formattedCardNumber,
- methodID: card.cardNumber,
+ methodID: card.fundID,
icon,
iconSize,
key: `card-${card.cardNumber}`,
diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js
index 4cfcc941ee8a..94a1b6cedb6f 100644
--- a/src/libs/actions/PaymentMethods.js
+++ b/src/libs/actions/PaymentMethods.js
@@ -184,10 +184,10 @@ function clearDebitCardFormErrorAndSubmit() {
* Call the API to transfer wallet balance.
* @param {Object} paymentMethod
* @param {*} paymentMethod.methodID
- * @param {String} paymentMethod.type
+ * @param {String} paymentMethod.accountType
*/
function transferWalletBalance(paymentMethod) {
- const paymentMethodIDKey = paymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT
+ const paymentMethodIDKey = paymentMethod.accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT
? CONST.PAYMENT_METHOD_ID_KEYS.BANK_ACCOUNT
: CONST.PAYMENT_METHOD_ID_KEYS.DEBIT_CARD;
const parameters = {
@@ -209,13 +209,10 @@ function transferWalletBalance(paymentMethod) {
});
}
-/**
- * Set the transfer account and reset the transfer data for Wallet balance transfer
- * @param {String} selectedAccountID
- */
-function saveWalletTransferAccountAndResetData(selectedAccountID) {
+function resetWalletTransferData() {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {
- selectedAccountID,
+ selectedAccountType: '',
+ selectedAccountID: null,
filterPaymentMethodType: null,
loading: false,
shouldShowConfirmModal: false,
@@ -229,6 +226,22 @@ function saveWalletTransferAmount(transferAmount) {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount});
}
+/**
+ * @param {String} selectedAccountType
+ * @param {String} selectedAccountID
+ */
+function saveWalletTransferAccountTypeAndID(selectedAccountType, selectedAccountID) {
+ Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {selectedAccountType, selectedAccountID});
+}
+
+/**
+ * Toggles the user's selected type of payment method (bank account or debit card) on the wallet transfer balance screen.
+ * @param {String} filterPaymentMethodType
+ */
+function saveWalletTransferMethodType(filterPaymentMethodType) {
+ Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {filterPaymentMethodType});
+}
+
function dismissWalletConfirmModal() {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: false});
}
@@ -243,7 +256,9 @@ export {
continueSetup,
clearDebitCardFormErrorAndSubmit,
transferWalletBalance,
- saveWalletTransferAccountAndResetData,
+ resetWalletTransferData,
saveWalletTransferAmount,
+ saveWalletTransferAccountTypeAndID,
+ saveWalletTransferMethodType,
dismissWalletConfirmModal,
};
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 22453fcd4ee9..aaeb85f953d9 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -8,7 +8,6 @@ import CONST from '../../CONST';
import NetworkConnection from '../NetworkConnection';
import * as API from '../API';
import NameValuePair from './NameValuePair';
-import * as ReportUtils from '../reportUtils';
import * as OptionsListUtils from '../OptionsListUtils';
import Growl from '../Growl';
import * as Localize from '../Localize';
@@ -146,6 +145,7 @@ function fetchPersonalDetails() {
* Get personal details from report participants.
*
* @param {Object} reports
+ * @returns {Promise}
*/
function getFromReportParticipants(reports) {
const participantEmails = _.chain(reports)
@@ -155,10 +155,10 @@ function getFromReportParticipants(reports) {
.value();
if (participantEmails.length === 0) {
- return;
+ return Promise.resolve({});
}
- API.PersonalDetails_GetForEmails({emailList: participantEmails.join(',')})
+ return API.PersonalDetails_GetForEmails({emailList: participantEmails.join(',')})
.then((data) => {
const existingDetails = _.pick(data, participantEmails);
@@ -173,36 +173,7 @@ function getFromReportParticipants(reports) {
const formattedPersonalDetails = formatPersonalDetails(details);
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, formattedPersonalDetails);
-
- // The personalDetails of the participants contain their avatar images. Here we'll go over each
- // report and based on the participants we'll link up their avatars to report icons. This will
- // skip over default rooms which aren't named by participants.
- const reportsToUpdate = {};
- _.each(reports, (report) => {
- if (report.participants.length <= 0 && !ReportUtils.isChatRoom(report)) {
- return;
- }
-
- const avatars = OptionsListUtils.getReportIcons(report, details);
- const reportName = ReportUtils.isChatRoom(report)
- ? report.reportName
- : _.chain(report.participants)
- .filter(participant => participant !== currentUserEmail)
- .map(participant => lodashGet(
- formattedPersonalDetails,
- [participant, 'displayName'],
- participant,
- ))
- .value()
- .join(', ');
-
- reportsToUpdate[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`] = {icons: avatars, reportName};
- });
-
- // We use mergeCollection such that it updates ONYXKEYS.COLLECTION.REPORT in one go.
- // Any withOnyx subscribers to this key will also receive the complete updated props just once
- // than updating props for each report and re-rendering had merge been used.
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate);
+ return details;
});
}
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index f71a681b24ce..5dd0dd627051 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -10,6 +10,7 @@ import * as Localize from '../Localize';
import Navigation from '../Navigation/Navigation';
import ROUTES from '../../ROUTES';
import * as OptionsListUtils from '../OptionsListUtils';
+import * as Report from './Report';
const allPolicies = {};
Onyx.connect({
@@ -111,6 +112,9 @@ function create(name = '') {
}
res = response;
+ // Fetch the default reports on the policy
+ Report.fetchChatReportsByIDs([response.policy.chatReportIDAdmins, response.policy.chatReportIDAnnounce]);
+
// We are awaiting this merge so that we can guarantee our policy is available to any React components connected to the policies collection before we navigate to a new route.
return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, {
employeeList: getSimplifiedEmployeeList(response.policy.employeeList),
@@ -158,7 +162,9 @@ function deletePolicy(policyID) {
// Removing the workspace data from Onyx as well
return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, null);
- }).then(() => {
+ })
+ .then(() => Report.fetchAllReports(false, true))
+ .then(() => {
Navigation.dismissModal();
Navigation.navigate(ROUTES.HOME);
return Promise.resolve();
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 0519db956ad4..5dd955510b08 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -10,6 +10,7 @@ import * as Pusher from '../Pusher/pusher';
import LocalNotification from '../Notification/LocalNotification';
import PushNotification from '../Notification/PushNotification';
import * as PersonalDetails from './PersonalDetails';
+import * as User from './User';
import Navigation from '../Navigation/Navigation';
import * as ActiveClientManager from '../ActiveClientManager';
import Visibility from '../Visibility';
@@ -24,6 +25,7 @@ import Timers from '../Timers';
import * as ReportActions from './ReportActions';
import Growl from '../Growl';
import * as Localize from '../Localize';
+import * as OptionsListUtils from '../OptionsListUtils';
let currentUserEmail;
let currentUserAccountID;
@@ -318,6 +320,39 @@ function fetchIOUReportID(debtorEmail) {
});
}
+function configureReportNameAndIcon(reports, details) {
+ // The personalDetails of the participants contain their avatar images. Here we'll go over each
+ // report and based on the participants we'll link up their avatars to report icons. This will
+ // skip over default rooms which aren't named by participants.
+
+ const reportsToUpdate = {};
+ _.each(reports, (report) => {
+ if (report.participants.length <= 0 && !ReportUtils.isChatRoom(report)) {
+ return;
+ }
+
+ const avatars = ReportUtils.isChatRoom(report) ? (['']) : OptionsListUtils.getReportIcons(report, details);
+ const reportName = ReportUtils.isChatRoom(report)
+ ? report.reportName
+ : _.chain(report.participants)
+ .filter(participant => participant !== currentUserEmail)
+ .map(participant => lodashGet(
+ details,
+ [participant, 'displayName'],
+ participant,
+ ))
+ .value()
+ .join(', ');
+
+ reportsToUpdate[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`] = {icons: avatars, reportName};
+ });
+
+ // We use mergeCollection such that it updates ONYXKEYS.COLLECTION.REPORT in one go.
+ // Any withOnyx subscribers to this key will also receive the complete updated props just once
+ // than updating props for each report and re-rendering had merge been used.
+ Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate);
+}
+
/**
* Fetches chat reports when provided a list of chat report IDs.
* If the shouldRedirectIfInaccessible flag is set, we redirect to the Concierge chat
@@ -398,8 +433,12 @@ function fetchChatReportsByIDs(chatList, shouldRedirectIfInaccessible = false) {
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, reportIOUData);
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, simplifiedReports);
+ const simplifiedReportsList = _.values(simplifiedReports);
+
// Fetch the personal details if there are any
- PersonalDetails.getFromReportParticipants(_.values(simplifiedReports));
+ PersonalDetails.getFromReportParticipants(simplifiedReportsList)
+ .then(details => configureReportNameAndIcon(simplifiedReportsList, details));
+
return fetchedReports;
})
.catch((err) => {
@@ -584,7 +623,10 @@ function updateReportWithNewAction(
setLocalLastRead(reportID, newMaxSequenceNumber);
}
- const messageText = lodashGet(reportAction, ['message', 0, 'text'], '');
+ let messageText = lodashGet(reportAction, ['message', 0, 'text'], '');
+ if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) {
+ messageText = lodashGet(reportAction, 'originalMessage.html', '');
+ }
// Always merge the reportID into Onyx
// If the report doesn't exist in Onyx yet, then all the rest of the data will be filled out
@@ -1181,6 +1223,18 @@ function addAction(reportID, text, file) {
console.error(response.message);
return;
}
+
+ if (response.jsonCode === 666 && reportID === conciergeChatReportID) {
+ Growl.error(Localize.translateLocal('reportActionCompose.blockedFromConcierge'));
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
+ [optimisticReportActionID]: null,
+ });
+
+ // The fact that the API is returning this error means the BLOCKED_FROM_CONCIERGE nvp in the user details has changed since the last time we checked, so let's update
+ User.getUserDetails();
+ return;
+ }
+
updateReportWithNewAction(reportID, response.reportAction);
});
}
@@ -1530,6 +1584,30 @@ function createPolicyRoom(policyID, reportName, visibility) {
.finally(() => Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, false));
}
+/**
+ * Renames a user created Policy Room.
+ * @param {String} reportID
+ * @param {String} reportName
+ */
+function renameReport(reportID, reportName) {
+ Onyx.set(ONYXKEYS.IS_LOADING_RENAME_POLICY_ROOM, true);
+ API.RenameReport({reportID, reportName})
+ .then((response) => {
+ if (response.jsonCode !== 200) {
+ Growl.error(response.message);
+ return;
+ }
+ Growl.success(Localize.translateLocal('newRoomPage.policyRoomRenamed'));
+
+ // Update the report name so that the LHN and header display the updated name
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {reportName});
+ })
+ .catch(() => {
+ Growl.error(Localize.translateLocal('newRoomPage.growlMessageOnRenameError'));
+ })
+ .finally(() => Onyx.set(ONYXKEYS.IS_LOADING_RENAME_POLICY_ROOM, false));
+}
+
export {
fetchAllReports,
fetchActions,
@@ -1559,5 +1637,6 @@ export {
setReportWithDraft,
fetchActionsWithLoadingState,
createPolicyRoom,
+ renameReport,
getLastReadSequenceNumber,
};
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index 245ba1c37395..490a7951648a 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -293,11 +293,10 @@ function resetPassword() {
*
* @param {String} password
* @param {String} validateCode
- * @param {String} accountID
+ * @param {Number} accountID
*/
function setPassword(password, validateCode, accountID) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true, validateCodeExpired: false});
-
API.SetPassword({
password,
validateCode,
@@ -384,52 +383,84 @@ function clearAccountMessages() {
}
/**
+ * Calls change password and signs if if successful. Otherwise, we request a new magic link
+ * if we know the account email. Otherwise or finally we redirect to the root of the nav.
* @param {String} authToken
* @param {String} password
*/
function changePasswordAndSignIn(authToken, password) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {validateSessionExpired: false});
API.ChangePassword({
authToken,
password,
})
.then((responsePassword) => {
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: null});
if (responsePassword.jsonCode === 200) {
signIn(password);
return;
}
-
+ if (responsePassword.jsonCode === 407 && !credentials.login) {
+ // authToken has expired, and we don't have the email set to request a new magic link.
+ // send user to login page to enter email.
+ Navigation.navigate(ROUTES.HOME);
+ return;
+ }
+ if (responsePassword.jsonCode === 407) {
+ // authToken has expired, and we have the account email, so we request a new magic link.
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validateCodeExpired: true, error: null});
+ resetPassword();
+ Navigation.navigate(ROUTES.HOME);
+ return;
+ }
Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.passwordNotSet'});
});
}
/**
- * @param {String} accountID
+ * Call set or change password based on if we have an auth token
+ * @param {Number} accountID
* @param {String} validateCode
* @param {String} password
+ * @param {String} authToken
*/
-function validateEmail(accountID, validateCode, password) {
+
+function setOrChangePassword(accountID, validateCode, password, authToken) {
+ if (authToken) {
+ changePasswordAndSignIn(authToken, password);
+ return;
+ }
+ setPassword(password, validateCode, accountID);
+}
+
+/**
+ * @param {Number} accountID
+ * @param {String} validateCode
+ * @param {String} login
+ * @param {String} authToken
+ */
+function validateEmail(accountID, validateCode) {
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: true});
+ Onyx.merge(ONYXKEYS.SESSION, {error: ''});
API.ValidateEmail({
accountID,
validateCode,
})
.then((responseValidate) => {
if (responseValidate.jsonCode === 200) {
- changePasswordAndSignIn(responseValidate.authToken, password);
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: responseValidate.authToken});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {login: responseValidate.email});
return;
}
-
- if (responseValidate.title === CONST.PASSWORD_PAGE.ERROR.ALREADY_VALIDATED) {
- // If the email is already validated, set the password using the validate code
- setPassword(
- password,
- validateCode,
- accountID,
- );
- return;
+ if (responseValidate.jsonCode === 666) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
}
-
- Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.accountNotValidated'});
- });
+ if (responseValidate.jsonCode === 401) {
+ Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.setPasswordLinkInvalid'});
+ }
+ })
+ .finally(Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: false}));
}
// It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to
@@ -493,6 +524,7 @@ function setShouldShowComposeInput(shouldShowComposeInput) {
export {
continueSessionFromECom,
fetchAccountDetails,
+ setOrChangePassword,
setPassword,
signIn,
signInWithShortLivedToken,
@@ -507,4 +539,5 @@ export {
authenticatePusher,
reauthenticatePusher,
setShouldShowComposeInput,
+ changePasswordAndSignIn,
};
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 455812491ee2..b3ec5411b526 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -103,6 +103,7 @@ function getUserDetails() {
CONST.NVP.PAYPAL_ME_ADDRESS,
CONST.NVP.PREFERRED_EMOJI_SKIN_TONE,
CONST.NVP.FREQUENTLY_USED_EMOJIS,
+ CONST.NVP.BLOCKED_FROM_CONCIERGE,
].join(','),
})
.then((response) => {
diff --git a/src/libs/virtualKeyboard/index.js b/src/libs/virtualKeyboard/index.js
new file mode 100644
index 000000000000..f28dc84c0bcf
--- /dev/null
+++ b/src/libs/virtualKeyboard/index.js
@@ -0,0 +1,18 @@
+import _ from 'underscore';
+
+/**
+ * Is the virtual keyboard open?
+ *
+ * @returns {Boolean|null} – null if the VirtualKeyboard API is unavailable
+ */
+function isOpen() {
+ if (!_.has(navigator, 'virtualKeyboard')) {
+ return null;
+ }
+ return navigator.virtualKeyboard.boundingRect.y > 0;
+}
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ isOpen,
+};
diff --git a/src/libs/virtualKeyboard/index.native.js b/src/libs/virtualKeyboard/index.native.js
new file mode 100644
index 000000000000..6c21a32ffcdf
--- /dev/null
+++ b/src/libs/virtualKeyboard/index.native.js
@@ -0,0 +1,33 @@
+import {Keyboard} from 'react-native';
+
+let isVirtualKeyboardOpen = false;
+
+Keyboard.addListener(
+ 'keyboardDidShow',
+ () => {
+ isVirtualKeyboardOpen = true;
+ },
+);
+
+Keyboard.addListener(
+ 'keyboardDidHide',
+ () => {
+ isVirtualKeyboardOpen = false;
+ },
+);
+
+/**
+ * Is the virtual keyboard open?
+ *
+ * Note – the web equivalent of this function may return null.
+ *
+ * @returns {Boolean}
+ */
+function isOpen() {
+ return isVirtualKeyboardOpen;
+}
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ isOpen,
+};
diff --git a/src/pages/ReportSettingsPage.js b/src/pages/ReportSettingsPage.js
index 1543d8686d1c..ac3e914988ca 100644
--- a/src/pages/ReportSettingsPage.js
+++ b/src/pages/ReportSettingsPage.js
@@ -89,7 +89,7 @@ class ReportSettingsPage extends Component {
}
render() {
- const shouldDisableRename = ReportUtils.isDefaultRoom(this.props.report) || ReportUtils.isArchivedRoom(this.props.report);
+ const shouldDisableRename = ReportUtils.isDefaultRoom(this.props.report) || ReportUtils.isArchivedRoom(this.props.report) || this.props.isLoadingRenamePolicyRoom;
const linkedWorkspace = _.find(this.props.policies, policy => policy.id === this.props.report.policyID);
return (
@@ -141,10 +141,8 @@ class ReportSettingsPage extends Component {