diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index c3245e9dc200..3585d0f52e40 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -103,7 +103,7 @@ The Contributor+ will copy/paste it into a new comment and complete it after the
- [ ] Android / Chrome
- [ ] MacOS / Chrome
- [ ] MacOS / Desktop
-- [ ] I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
+- [ ] If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
- [ ] I verified proper code patterns were followed (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code))
- [ ] I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. `toggleReport` and not `onIconClick`).
- [ ] I verified that comments were added to code that is not self explanatory
diff --git a/.github/actions/javascript/contributorChecklist/action.yml b/.github/actions/javascript/contributorChecklist/action.yml
index 16a6dbea0b04..787fac8182ce 100644
--- a/.github/actions/javascript/contributorChecklist/action.yml
+++ b/.github/actions/javascript/contributorChecklist/action.yml
@@ -4,6 +4,9 @@ inputs:
GITHUB_TOKEN:
description: Auth token for New Expensify Github
required: true
+ CHECKLIST:
+ description: The checklist to look for, either 'contributor' or 'contributorPlus'
+ required: true
runs:
using: 'node16'
main: './index.js'
diff --git a/.github/actions/javascript/contributorChecklist/contributorChecklist.js b/.github/actions/javascript/contributorChecklist/contributorChecklist.js
index b0c1f9a885d6..62036078d24c 100644
--- a/.github/actions/javascript/contributorChecklist/contributorChecklist.js
+++ b/.github/actions/javascript/contributorChecklist/contributorChecklist.js
@@ -1,5 +1,6 @@
const core = require('@actions/core');
const github = require('@actions/github');
+const _ = require('underscore');
const GitHubUtils = require('../../../libs/GithubUtils');
/* eslint-disable max-len */
@@ -96,43 +97,44 @@ const completedContributorPlusChecklist = `- [x] I have verified the author chec
- [x] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
- [x] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.`;
+// True if we are validating a contributor checklist, otherwise we are validating a contributor+ checklist
+const verifyingContributorChecklist = core.getInput('CHECKLIST', {required: true}) === 'contributor';
const issue = github.context.payload.issue ? github.context.payload.issue.number : github.context.payload.pull_request.number;
const combinedData = [];
-function printUncheckedItems(result) {
- const checklist = result.split('\n');
+function getPullRequestBody() {
+ return GitHubUtils.octokit.pulls.get({
+ owner: GitHubUtils.GITHUB_OWNER,
+ repo: GitHubUtils.APP_REPO,
+ pull_number: issue,
+ }).then(({data: pullRequestComment}) => pullRequestComment.body);
+}
- checklist.forEach((line) => {
- // Provide a search string with the first 30 characters to figure out if the checkbox item is in the checklist
- const lineSearchString = line.replace('- [ ] ', '').slice(0, 30);
- if (line.includes('- [ ]') && (completedContributorChecklist.includes(lineSearchString) || completedContributorPlusChecklist.includes(lineSearchString))) {
- console.log(`Unchecked checklist item: ${line}`);
- }
- });
+function getAllReviewComments() {
+ return GitHubUtils.paginate(GitHubUtils.octokit.pulls.listReviews, {
+ owner: GitHubUtils.GITHUB_OWNER,
+ repo: GitHubUtils.APP_REPO,
+ pull_number: issue,
+ per_page: 100,
+ }, response => _.map(response.data, review => review.body));
}
-// Get all user text from the pull request, review comments, and pull request comments
-GitHubUtils.octokit.pulls.get({
- owner: GitHubUtils.GITHUB_OWNER,
- repo: GitHubUtils.APP_REPO,
- pull_number: issue,
-}).then(({data: pullRequestComment}) => {
- combinedData.push(pullRequestComment.body);
-}).then(() => GitHubUtils.octokit.pulls.listReviews({
- owner: GitHubUtils.GITHUB_OWNER,
- repo: GitHubUtils.APP_REPO,
- pull_number: issue,
-})).then(({data: pullRequestReviewComments}) => {
- pullRequestReviewComments.forEach(pullRequestReviewComment => combinedData.push(pullRequestReviewComment.body));
-})
- .then(() => GitHubUtils.octokit.issues.listComments({
+function getAllComments() {
+ return GitHubUtils.paginate(GitHubUtils.octokit.issues.listComments, {
owner: GitHubUtils.GITHUB_OWNER,
repo: GitHubUtils.APP_REPO,
issue_number: issue,
per_page: 100,
- }))
- .then(({data: pullRequestComments}) => {
- pullRequestComments.forEach(pullRequestComment => combinedData.push(pullRequestComment.body));
+ }, response => _.map(response.data, comment => comment.body));
+}
+
+getPullRequestBody()
+ .then(pullRequestBody => combinedData.push(pullRequestBody))
+ .then(() => getAllReviewComments())
+ .then(reviewComments => combinedData.push(...reviewComments))
+ .then(() => getAllComments())
+ .then(comments => combinedData.push(...comments))
+ .then(() => {
let contributorChecklistComplete = false;
let contributorPlusChecklistComplete = false;
@@ -143,26 +145,24 @@ GitHubUtils.octokit.pulls.get({
if (comment.includes(completedContributorChecklist.replace(whitespace, ''))) {
contributorChecklistComplete = true;
- } else if (comment.includes('- [')) {
- printUncheckedItems(combinedData[i]);
}
if (comment.includes(completedContributorPlusChecklist.replace(whitespace, ''))) {
contributorPlusChecklistComplete = true;
- } else if (comment.includes('- [')) {
- printUncheckedItems(combinedData[i]);
}
}
- if (!contributorChecklistComplete) {
+ if (verifyingContributorChecklist && !contributorChecklistComplete) {
+ console.log('Make sure you are using the most up to date checklist found here: https://raw.githubusercontent.com/Expensify/App/main/.github/PULL_REQUEST_TEMPLATE.md');
core.setFailed('Contributor checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
return;
}
- if (!contributorPlusChecklistComplete) {
- core.setFailed('Contributor plus checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
+ if (!verifyingContributorChecklist && !contributorPlusChecklistComplete) {
+ console.log('Make sure you are using the most up to date checklist found here: https://raw.githubusercontent.com/Expensify/App/main/.github/PULL_REQUEST_TEMPLATE.md');
+ core.setFailed('Contributor+ checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
return;
}
- console.log('All checklists are complete 🎉');
+ console.log(`${verifyingContributorChecklist ? 'Contributor' : 'Contributor+'} checklist is complete 🎉`);
});
diff --git a/.github/actions/javascript/contributorChecklist/index.js b/.github/actions/javascript/contributorChecklist/index.js
index fb5607e4253d..1041b6906cfd 100644
--- a/.github/actions/javascript/contributorChecklist/index.js
+++ b/.github/actions/javascript/contributorChecklist/index.js
@@ -10,6 +10,7 @@ module.exports =
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
+const _ = __nccwpck_require__(3571);
const GitHubUtils = __nccwpck_require__(7999);
/* eslint-disable max-len */
@@ -106,43 +107,44 @@ const completedContributorPlusChecklist = `- [x] I have verified the author chec
- [x] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
- [x] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.`;
+// True if we are validating a contributor checklist, otherwise we are validating a contributor+ checklist
+const verifyingContributorChecklist = core.getInput('CHECKLIST', {required: true}) === 'contributor';
const issue = github.context.payload.issue ? github.context.payload.issue.number : github.context.payload.pull_request.number;
const combinedData = [];
-function printUncheckedItems(result) {
- const checklist = result.split('\n');
+function getPullRequestBody() {
+ return GitHubUtils.octokit.pulls.get({
+ owner: GitHubUtils.GITHUB_OWNER,
+ repo: GitHubUtils.APP_REPO,
+ pull_number: issue,
+ }).then(({data: pullRequestComment}) => pullRequestComment.body);
+}
- checklist.forEach((line) => {
- // Provide a search string with the first 30 characters to figure out if the checkbox item is in the checklist
- const lineSearchString = line.replace('- [ ] ', '').slice(0, 30);
- if (line.includes('- [ ]') && (completedContributorChecklist.includes(lineSearchString) || completedContributorPlusChecklist.includes(lineSearchString))) {
- console.log(`Unchecked checklist item: ${line}`);
- }
- });
+function getAllReviewComments() {
+ return GitHubUtils.paginate(GitHubUtils.octokit.pulls.listReviews, {
+ owner: GitHubUtils.GITHUB_OWNER,
+ repo: GitHubUtils.APP_REPO,
+ pull_number: issue,
+ per_page: 100,
+ }, response => _.map(response.data, review => review.body));
}
-// Get all user text from the pull request, review comments, and pull request comments
-GitHubUtils.octokit.pulls.get({
- owner: GitHubUtils.GITHUB_OWNER,
- repo: GitHubUtils.APP_REPO,
- pull_number: issue,
-}).then(({data: pullRequestComment}) => {
- combinedData.push(pullRequestComment.body);
-}).then(() => GitHubUtils.octokit.pulls.listReviews({
- owner: GitHubUtils.GITHUB_OWNER,
- repo: GitHubUtils.APP_REPO,
- pull_number: issue,
-})).then(({data: pullRequestReviewComments}) => {
- pullRequestReviewComments.forEach(pullRequestReviewComment => combinedData.push(pullRequestReviewComment.body));
-})
- .then(() => GitHubUtils.octokit.issues.listComments({
+function getAllComments() {
+ return GitHubUtils.paginate(GitHubUtils.octokit.issues.listComments, {
owner: GitHubUtils.GITHUB_OWNER,
repo: GitHubUtils.APP_REPO,
issue_number: issue,
per_page: 100,
- }))
- .then(({data: pullRequestComments}) => {
- pullRequestComments.forEach(pullRequestComment => combinedData.push(pullRequestComment.body));
+ }, response => _.map(response.data, comment => comment.body));
+}
+
+getPullRequestBody()
+ .then(pullRequestBody => combinedData.push(pullRequestBody))
+ .then(() => getAllReviewComments())
+ .then(reviewComments => combinedData.push(...reviewComments))
+ .then(() => getAllComments())
+ .then(comments => combinedData.push(...comments))
+ .then(() => {
let contributorChecklistComplete = false;
let contributorPlusChecklistComplete = false;
@@ -153,28 +155,26 @@ GitHubUtils.octokit.pulls.get({
if (comment.includes(completedContributorChecklist.replace(whitespace, ''))) {
contributorChecklistComplete = true;
- } else if (comment.includes('- [')) {
- printUncheckedItems(combinedData[i]);
}
if (comment.includes(completedContributorPlusChecklist.replace(whitespace, ''))) {
contributorPlusChecklistComplete = true;
- } else if (comment.includes('- [')) {
- printUncheckedItems(combinedData[i]);
}
}
- if (!contributorChecklistComplete) {
+ if (verifyingContributorChecklist && !contributorChecklistComplete) {
+ console.log('Make sure you are using the most up to date checklist found here: https://raw.githubusercontent.com/Expensify/App/main/.github/PULL_REQUEST_TEMPLATE.md');
core.setFailed('Contributor checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
return;
}
- if (!contributorPlusChecklistComplete) {
- core.setFailed('Contributor plus checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
+ if (!verifyingContributorChecklist && !contributorPlusChecklistComplete) {
+ console.log('Make sure you are using the most up to date checklist found here: https://raw.githubusercontent.com/Expensify/App/main/.github/PULL_REQUEST_TEMPLATE.md');
+ core.setFailed('Contributor+ checklist is not completely filled out. Please check every box to verify you\'ve thought about the item.');
return;
}
- console.log('All checklists are complete 🎉');
+ console.log(`${verifyingContributorChecklist ? 'Contributor' : 'Contributor+'} checklist is complete 🎉`);
});
diff --git a/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.js b/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.js
index cf3f27ee662c..15ee2869a14d 100644
--- a/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.js
+++ b/.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.js
@@ -87,9 +87,13 @@ if (pullRequestNumber) {
...DEFAULT_PAYLOAD,
state: 'all',
})
+ .then(({data}) => _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title)).number)
+ .then(matchingPRNum => GithubUtils.octokit.pulls.get({
+ ...DEFAULT_PAYLOAD,
+ pull_number: matchingPRNum,
+ }))
.then(({data}) => {
- const matchingPR = _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title));
- outputMergeCommitHash(matchingPR);
- outputMergeActor(matchingPR);
+ outputMergeCommitHash(data);
+ outputMergeActor(data);
});
}
diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js
index 692751956bbf..b06196149058 100644
--- a/.github/actions/javascript/getPullRequestDetails/index.js
+++ b/.github/actions/javascript/getPullRequestDetails/index.js
@@ -97,10 +97,14 @@ if (pullRequestNumber) {
...DEFAULT_PAYLOAD,
state: 'all',
})
+ .then(({data}) => _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title)).number)
+ .then(matchingPRNum => GithubUtils.octokit.pulls.get({
+ ...DEFAULT_PAYLOAD,
+ pull_number: matchingPRNum,
+ }))
.then(({data}) => {
- const matchingPR = _.find(data, PR => PR.user.login === user && titleRegex.test(PR.title));
- outputMergeCommitHash(matchingPR);
- outputMergeActor(matchingPR);
+ outputMergeCommitHash(data);
+ outputMergeActor(data);
});
}
diff --git a/.github/workflows/contributorChecklists.yml b/.github/workflows/contributorChecklists.yml
new file mode 100644
index 000000000000..692ba8944956
--- /dev/null
+++ b/.github/workflows/contributorChecklists.yml
@@ -0,0 +1,14 @@
+name: Contributor Checklist
+
+on: pull_request
+
+jobs:
+ checklist:
+ runs-on: ubuntu-latest
+ if: github.actor != 'OSBotify' && (github.event_name == 'pull_request' && contains(github.event.pull_request.body, '- ['))
+ steps:
+ - name: contributorChecklist.js
+ uses: Expensify/App/.github/actions/javascript/contributorChecklist@andrew-checklist-3
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ CHECKLIST: 'contributor'
diff --git a/.github/workflows/contributorPlusChecklists.yml b/.github/workflows/contributorPlusChecklists.yml
new file mode 100644
index 000000000000..5acffb511386
--- /dev/null
+++ b/.github/workflows/contributorPlusChecklists.yml
@@ -0,0 +1,14 @@
+name: Contributor+ Checklist
+
+on: issue_comment
+
+jobs:
+ checklist:
+ runs-on: ubuntu-latest
+ if: github.actor != 'OSBotify' && (contains(github.event.issue.pull_request.url, 'http') && github.event_name == 'issue_comment' && contains(github.event.comment.body, '- ['))
+ steps:
+ - name: contributorChecklist.js
+ uses: Expensify/App/.github/actions/javascript/contributorChecklist@andrew-checklist-3
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ CHECKLIST: 'contributorPlus'
diff --git a/.github/workflows/testChecklists.yml b/.github/workflows/testChecklists.yml
deleted file mode 100644
index a242f4e5f943..000000000000
--- a/.github/workflows/testChecklists.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Contributor Checklist
-
-on:
- issue_comment:
- pull_request_review:
- pull_request:
- types: [opened, edited, synchronize]
-
-jobs:
- checklist:
- runs-on: ubuntu-latest
- # Only run when a comment, PR, or PR review comment contains a checklist item
- if: ${{ github.actor != 'OSBotify' || (github.event.issue.pull_request && github.event_name == 'issue_comment' && contains(github.event.comment.body, '- [') || github.event_name == 'pull_request_review' && contains(github.event.review.body, '- [') || github.event_name == 'pull_request' && contains(github.event.pull_request.body, '- [')) }}
- steps:
- - name: contributorChecklist.js
- uses: Expensify/App/.github/actions/javascript/contributorChecklist@main
- with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 75f7d621bacc..c0f9e32884d9 100644
--- a/README.md
+++ b/README.md
@@ -119,7 +119,7 @@ Alternatively, you can also set up debugger using [Flipper](https://fbflipper.co
Our React Native Android app now uses the `Hermes` JS engine which requires your browser for remote debugging. These instructions are specific to Chrome since that's what the Hermes documentation provided.
1. Navigate to `chrome://inspect`
2. Use the `Configure...` button to add the Metro server address (typically `localhost:8081`, check your `Metro` output)
-3. You should now see a "Hermes React Native" target with an "inspect" link which can be used to bring up a debugger. If you don't see the "inspect" link, make sure the Metro server is running.
+3. You should now see a "Hermes React Native" target with an "inspect" link which can be used to bring up a debugger. If you don't see the "inspect" link, make sure the Metro server is running
4. You can now use the Chrome debug tools. See [React Native Debugging Hermes](https://reactnative.dev/docs/hermes#debugging-hermes-using-google-chromes-devtools)
## Web
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4dcc674c352d..7e22a8623b0e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -155,8 +155,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001019702
- versionName "1.1.97-2"
+ versionCode 1001019707
+ versionName "1.1.97-7"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/ios/Certificates.p12.gpg b/ios/Certificates.p12.gpg
index 7a7ec35e4dcd..c4a68891f6e4 100644
Binary files a/ios/Certificates.p12.gpg and b/ios/Certificates.p12.gpg differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index f2f5c64fe627..289ba96a2413 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.97.2
+ 1.1.97.7
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index ba7ee1904a98..11549d1d5412 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.1.97.2
+ 1.1.97.7
diff --git a/ios/chat_expensify_appstore.mobileprovision.gpg b/ios/chat_expensify_appstore.mobileprovision.gpg
index 7dca07c8c0e4..d083c5449b22 100644
Binary files a/ios/chat_expensify_appstore.mobileprovision.gpg and b/ios/chat_expensify_appstore.mobileprovision.gpg differ
diff --git a/package-lock.json b/package-lock.json
index 9e6999e5eef2..50eaad994c61 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.1.97-2",
+ "version": "1.1.97-7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.1.97-2",
+ "version": "1.1.97-7",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -130,7 +130,7 @@
"electron-notarize": "^1.2.1",
"electron-reloader": "^1.2.1",
"eslint": "^7.6.0",
- "eslint-config-expensify": "2.0.29",
+ "eslint-config-expensify": "2.0.30",
"eslint-loader": "^4.0.2",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
@@ -20247,9 +20247,9 @@
}
},
"node_modules/eslint-config-expensify": {
- "version": "2.0.29",
- "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.29.tgz",
- "integrity": "sha512-oUIWZFF+lilWFgPrdAs4V5PjOnQrCAG3rg9t5KrusR8jvYRx6xo/8jn2bXrJp7FMdnx+4gpNAhKNtsuZTsCjag==",
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.30.tgz",
+ "integrity": "sha512-SmZTvgUXTRmE4PRlSVDP1LgSZzrxZCgRaKJYiGso+oWl7GcB0HADmrS4nARQswfd4SWqRDy8zsY+j7+GuG8f1A==",
"dev": true,
"dependencies": {
"@lwc/eslint-plugin-lwc": "^0.11.0",
@@ -57197,9 +57197,9 @@
}
},
"eslint-config-expensify": {
- "version": "2.0.29",
- "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.29.tgz",
- "integrity": "sha512-oUIWZFF+lilWFgPrdAs4V5PjOnQrCAG3rg9t5KrusR8jvYRx6xo/8jn2bXrJp7FMdnx+4gpNAhKNtsuZTsCjag==",
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.30.tgz",
+ "integrity": "sha512-SmZTvgUXTRmE4PRlSVDP1LgSZzrxZCgRaKJYiGso+oWl7GcB0HADmrS4nARQswfd4SWqRDy8zsY+j7+GuG8f1A==",
"dev": true,
"requires": {
"@lwc/eslint-plugin-lwc": "^0.11.0",
diff --git a/package.json b/package.json
index 3255f3863795..85672217086e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.97-2",
+ "version": "1.1.97-7",
"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.",
@@ -157,7 +157,7 @@
"electron-notarize": "^1.2.1",
"electron-reloader": "^1.2.1",
"eslint": "^7.6.0",
- "eslint-config-expensify": "2.0.29",
+ "eslint-config-expensify": "2.0.30",
"eslint-loader": "^4.0.2",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
diff --git a/src/CONST.js b/src/CONST.js
index a575a51cf1c6..903f70bdaa56 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -268,6 +268,7 @@ const CONST = {
MESSAGE: {
TYPE: {
COMMENT: 'COMMENT',
+ TEXT: 'TEXT',
},
},
TYPE: {
@@ -281,6 +282,10 @@ const CONST = {
POLICY_ROOM: 'policyRoom',
POLICY_EXPENSE_CHAT: 'policyExpenseChat',
},
+ WORKSPACE_CHAT_ROOMS: {
+ ANNOUNCE: '#announce',
+ ADMINS: '#admins',
+ },
STATE_NUM: {
OPEN: 0,
PROCESSING: 1,
@@ -667,6 +672,7 @@ const CONST = {
ADMIN: 'admin',
},
ROOM_PREFIX: '#',
+ CUSTOM_UNIT_RATE_BASE_OFFSET: 100,
},
CUSTOM_UNITS: {
diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js
index 1727cc719a19..c450642df1c8 100644
--- a/src/components/AddPaymentMethodMenu.js
+++ b/src/components/AddPaymentMethodMenu.js
@@ -67,6 +67,7 @@ const AddPaymentMethodMenu = props => (
AddPaymentMethodMenu.propTypes = propTypes;
AddPaymentMethodMenu.defaultProps = defaultProps;
+AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu';
export default compose(
withWindowDimensions,
diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js
index 6e0ebb9718ee..1b749cc72be4 100644
--- a/src/components/AddressSearch.js
+++ b/src/components/AddressSearch.js
@@ -237,6 +237,7 @@ const AddressSearch = (props) => {
AddressSearch.propTypes = propTypes;
AddressSearch.defaultProps = defaultProps;
+AddressSearch.displayName = 'AddressSearch';
export default withLocalize(React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
diff --git a/src/components/ArrowKeyFocusManager.js b/src/components/ArrowKeyFocusManager.js
index dcb915d46bcd..46cff9371656 100644
--- a/src/components/ArrowKeyFocusManager.js
+++ b/src/components/ArrowKeyFocusManager.js
@@ -26,7 +26,7 @@ class ArrowKeyFocusManager extends Component {
const arrowDownConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_DOWN;
this.unsubscribeArrowUpKey = KeyboardShortcut.subscribe(arrowUpConfig.shortcutKey, () => {
- if (this.props.maxIndex < 1) {
+ if (this.props.maxIndex < 0) {
return;
}
@@ -41,7 +41,7 @@ class ArrowKeyFocusManager extends Component {
}, arrowUpConfig.descriptionKey, arrowUpConfig.modifiers, true);
this.unsubscribeArrowDownKey = KeyboardShortcut.subscribe(arrowDownConfig.shortcutKey, () => {
- if (this.props.maxIndex < 1) {
+ if (this.props.maxIndex < 0) {
return;
}
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index e5235244b719..64999e9005db 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -120,7 +120,7 @@ class AttachmentModal extends PureComponent {
const fileName = fullFileName.trim();
const splitFileName = fileName.split('.');
const fileExtension = splitFileName.pop();
- return {fileName, fileExtension};
+ return {fileName: splitFileName.join('.'), fileExtension};
}
/**
diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js
index 38074a39342b..6ac3180e174c 100644
--- a/src/components/CheckboxWithLabel.js
+++ b/src/components/CheckboxWithLabel.js
@@ -127,7 +127,6 @@ class CheckboxWithLabel extends React.Component {
CheckboxWithLabel.propTypes = propTypes;
CheckboxWithLabel.defaultProps = defaultProps;
-CheckboxWithLabel.displayName = 'CheckboxWithLabel';
export default React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js
index a7c1d0045170..37c951ca84dd 100755
--- a/src/components/Composer/index.js
+++ b/src/components/Composer/index.js
@@ -3,6 +3,7 @@ import {StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
+import Str from 'expensify-common/lib/str';
import RNTextInput from '../RNTextInput';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import Growl from '../../libs/Growl';
@@ -130,6 +131,7 @@ class Composer extends React.Component {
},
};
this.dragNDropListener = this.dragNDropListener.bind(this);
+ this.paste = this.paste.bind(this);
this.handlePaste = this.handlePaste.bind(this);
this.handlePastedHTML = this.handlePastedHTML.bind(this);
this.handleWheel = this.handleWheel.bind(this);
@@ -233,15 +235,12 @@ class Composer extends React.Component {
}
/**
- * Manually place the pasted HTML into Composer
- *
- * @param {String} html - pasted HTML
+ * Set pasted text to clipboard
+ * @param {String} text
*/
- handlePastedHTML(html) {
- const parser = new ExpensiMark();
- const markdownText = parser.htmlToMarkdown(html);
+ paste(text) {
try {
- document.execCommand('insertText', false, markdownText);
+ document.execCommand('insertText', false, text);
this.updateNumberOfLines();
// Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view.
@@ -251,6 +250,16 @@ class Composer extends React.Component {
} catch (e) {}
}
+ /**
+ * Manually place the pasted HTML into Composer
+ *
+ * @param {String} html - pasted HTML
+ */
+ handlePastedHTML(html) {
+ const parser = new ExpensiMark();
+ this.paste(parser.htmlToMarkdown(html));
+ }
+
/**
* Check the paste event for an attachment, parse the data and call onPasteFile from props with the selected file,
* Otherwise, convert pasted HTML to Markdown and set it on the composer.
@@ -258,13 +267,14 @@ class Composer extends React.Component {
* @param {ClipboardEvent} event
*/
handlePaste(event) {
+ event.preventDefault();
+
const {files, types} = event.clipboardData;
const TEXT_HTML = 'text/html';
// If paste contains files, then trigger file management
if (files.length > 0) {
// Prevent the default so we do not post the file name into the text box
- event.preventDefault();
this.props.onPasteFile(event.clipboardData.files[0]);
return;
}
@@ -273,7 +283,6 @@ class Composer extends React.Component {
if (types.includes(TEXT_HTML)) {
const pastedHTML = event.clipboardData.getData(TEXT_HTML);
- event.preventDefault();
const domparser = new DOMParser();
const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images;
@@ -309,7 +318,11 @@ class Composer extends React.Component {
}
this.handlePastedHTML(pastedHTML);
+ return;
}
+
+ const plainText = event.clipboardData.getData('text/plain');
+ this.paste(Str.htmlDecode(plainText));
}
/**
diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js
index 2b073627ef51..0ad914db7d78 100644
--- a/src/components/DotIndicatorMessage.js
+++ b/src/components/DotIndicatorMessage.js
@@ -45,6 +45,10 @@ const DotIndicatorMessage = (props) => {
.keys()
.sortBy()
.map(key => props.messages[key])
+
+ // Using uniq here since some fields are wrapped by the same OfflineWithFeedback component (e.g. WorkspaceReimburseView)
+ // and can potentially pass the same error.
+ .uniq()
.value();
return (
@@ -63,6 +67,7 @@ const DotIndicatorMessage = (props) => {
DotIndicatorMessage.propTypes = propTypes;
DotIndicatorMessage.defaultProps = defaultProps;
+DotIndicatorMessage.displayName = 'DotIndicatorMessage';
export default DotIndicatorMessage;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
index 7fc61a36e95b..59c05e73ed97 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
@@ -64,6 +64,5 @@ class PreRenderer extends React.Component {
}
PreRenderer.propTypes = htmlRendererPropTypes;
-PreRenderer.displayName = 'PreRenderer';
export default withLocalize(PreRenderer);
diff --git a/src/components/HeaderWithCloseButton.js b/src/components/HeaderWithCloseButton.js
index 8b83e61ac279..27ab5dbbe890 100755
--- a/src/components/HeaderWithCloseButton.js
+++ b/src/components/HeaderWithCloseButton.js
@@ -220,7 +220,6 @@ class HeaderWithCloseButton extends Component {
HeaderWithCloseButton.propTypes = propTypes;
HeaderWithCloseButton.defaultProps = defaultProps;
-HeaderWithCloseButton.displayName = 'HeaderWithCloseButton';
export default compose(
withLocalize,
diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js
index 0bd2bb5da688..f29abef842e1 100644
--- a/src/components/MultipleAvatars.js
+++ b/src/components/MultipleAvatars.js
@@ -101,4 +101,6 @@ const MultipleAvatars = (props) => {
MultipleAvatars.defaultProps = defaultProps;
MultipleAvatars.propTypes = propTypes;
+MultipleAvatars.displayName = 'MultipleAvatars';
+
export default memo(MultipleAvatars);
diff --git a/src/components/PlaidLink/index.native.js b/src/components/PlaidLink/index.native.js
index e30e9072a6f6..2fa8f6260779 100644
--- a/src/components/PlaidLink/index.native.js
+++ b/src/components/PlaidLink/index.native.js
@@ -32,4 +32,6 @@ const PlaidLink = (props) => {
PlaidLink.propTypes = plaidLinkPropTypes;
PlaidLink.defaultProps = plaidLinkDefaultProps;
+PlaidLink.displayName = 'PlaidLink';
+
export default PlaidLink;
diff --git a/src/components/PopoverMenu/BasePopoverMenu.js b/src/components/PopoverMenu/BasePopoverMenu.js
index 32551ba203d0..52d876c48792 100644
--- a/src/components/PopoverMenu/BasePopoverMenu.js
+++ b/src/components/PopoverMenu/BasePopoverMenu.js
@@ -10,7 +10,10 @@ import {
propTypes as createMenuPropTypes,
defaultProps as defaultCreateMenuPropTypes,
} from './popoverMenuPropTypes';
+import ArrowKeyFocusManager from '../ArrowKeyFocusManager';
import Text from '../Text';
+import KeyboardShortcut from '../../libs/KeyboardShortcut';
+import CONST from '../../CONST';
const propTypes = {
/** Callback fired when the menu is completely closed */
@@ -26,13 +29,71 @@ const defaultProps = {
};
class BasePopoverMenu extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ focusedIndex: -1,
+ };
+ this.updateFocusedIndex = this.updateFocusedIndex.bind(this);
+ this.resetFocusAndHideModal = this.resetFocusAndHideModal.bind(this);
+ this.removeKeyboardListener = this.removeKeyboardListener.bind(this);
+ this.attachKeyboardListener = this.attachKeyboardListener.bind(this);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.isVisible === prevProps.isVisible) {
+ return;
+ }
+
+ if (this.props.isVisible) {
+ this.attachKeyboardListener();
+ } else {
+ this.removeKeyboardListener();
+ }
+ }
+
+ componentWillUnmount() {
+ this.removeKeyboardListener();
+ }
+
+ attachKeyboardListener() {
+ const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ENTER;
+ this.unsubscribeEnterKey = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => {
+ if (this.state.focusedIndex === -1) {
+ return;
+ }
+ this.props.onItemSelected(this.props.menuItems[this.state.focusedIndex]);
+ this.updateFocusedIndex(-1); // Reset the focusedIndex on selecting any menu
+ }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true);
+ }
+
+ removeKeyboardListener() {
+ if (!this.unsubscribeEnterKey) {
+ return;
+ }
+ this.unsubscribeEnterKey();
+ }
+
+ /**
+ * @param {Number} index
+ */
+ updateFocusedIndex(index) {
+ this.setState({focusedIndex: index});
+ }
+
+ resetFocusAndHideModal() {
+ this.updateFocusedIndex(-1); // Reset the focusedIndex on modal hide
+ this.removeKeyboardListener();
+ this.props.onMenuHide();
+ }
+
render() {
return (
)}
- {_.map(this.props.menuItems, item => (
-
);
diff --git a/src/components/SafeArea/index.ios.js b/src/components/SafeArea/index.ios.js
index ead107f90a22..3d84c4620677 100644
--- a/src/components/SafeArea/index.ios.js
+++ b/src/components/SafeArea/index.ios.js
@@ -13,5 +13,6 @@ SafeArea.propTypes = {
/** App content */
children: PropTypes.node.isRequired,
};
+SafeArea.displayName = 'SafeArea';
export default SafeArea;
diff --git a/src/components/ScreenWrapper/index.android.js b/src/components/ScreenWrapper/index.android.js
index aee674422f6b..e4b64a51bb40 100644
--- a/src/components/ScreenWrapper/index.android.js
+++ b/src/components/ScreenWrapper/index.android.js
@@ -15,5 +15,6 @@ defaultProps.keyboardAvoidingViewBehavior = 'height';
ScreenWrapper.propTypes = propTypes;
ScreenWrapper.defaultProps = defaultProps;
+ScreenWrapper.displayName = 'ScreenWrapper';
export default ScreenWrapper;
diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js
index 29c537442985..187e75159561 100644
--- a/src/components/ScreenWrapper/index.js
+++ b/src/components/ScreenWrapper/index.js
@@ -12,5 +12,6 @@ const ScreenWrapper = props => (
);
ScreenWrapper.propTypes = propTypes;
ScreenWrapper.defaultProps = defaultProps;
+ScreenWrapper.displayName = 'ScreenWrapper';
export default ScreenWrapper;
diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js
index f04ae6a26b92..1654854799d2 100644
--- a/src/components/TestToolMenu.js
+++ b/src/components/TestToolMenu.js
@@ -76,6 +76,8 @@ const TestToolMenu = props => (
TestToolMenu.propTypes = propTypes;
TestToolMenu.defaultProps = defaultProps;
+TestToolMenu.displayName = 'TestToolMenu';
+
export default compose(
withNetwork(),
withOnyx({
diff --git a/src/components/TestToolRow.js b/src/components/TestToolRow.js
index 80405a898bb0..505dfefa2172 100644
--- a/src/components/TestToolRow.js
+++ b/src/components/TestToolRow.js
@@ -26,4 +26,6 @@ const TestToolRow = props => (
);
TestToolRow.propTypes = propTypes;
+TestToolRow.displayName = 'TestToolRow';
+
export default TestToolRow;
diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js
index df829f7fd89c..eec708d91113 100644
--- a/src/components/TextInput/BaseTextInput.js
+++ b/src/components/TextInput/BaseTextInput.js
@@ -14,6 +14,7 @@ import * as Expensicons from '../Icon/Expensicons';
import Text from '../Text';
import * as styleConst from './styleConst';
import * as StyleUtils from '../../styles/StyleUtils';
+import variables from '../../styles/variables';
import Checkbox from '../Checkbox';
import getSecureEntryKeyboardType from '../../libs/getSecureEntryKeyboardType';
import CONST from '../../CONST';
@@ -33,6 +34,7 @@ class BaseTextInput extends Component {
textInputWidth: 0,
prefixWidth: 0,
selection: props.selection,
+ height: variables.componentSizeLarge,
// Value should be kept in state for the autoGrow feature to work - https://github.com/Expensify/App/pull/8232#issuecomment-1077282006
value,
@@ -217,6 +219,9 @@ class BaseTextInput extends Component {
>
!this.props.multiline && this.setState({height: event.nativeEvent.layout.height})}
style={[
textInputContainerStyles,
@@ -268,6 +273,7 @@ class BaseTextInput extends Component {
!hasLabel && styles.pv0,
this.props.prefixCharacter && StyleUtils.getPaddingLeft(this.state.prefixWidth + styles.pl1.paddingLeft),
this.props.secureTextEntry && styles.secureInput,
+ !this.props.multiline && {height: this.state.height},
]}
multiline={this.props.multiline}
maxLength={this.props.maxLength}
diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js
index 294d15fef3ac..b3825784832d 100644
--- a/src/components/ThreeDotsMenu/index.js
+++ b/src/components/ThreeDotsMenu/index.js
@@ -54,19 +54,20 @@ class ThreeDotsMenu extends Component {
constructor(props) {
super(props);
- this.togglePopupMenu = this.togglePopupMenu.bind(this);
+ this.hidePopoverMenu = this.hidePopoverMenu.bind(this);
+ this.showPopoverMenu = this.showPopoverMenu.bind(this);
this.state = {
isPopupMenuVisible: false,
};
+ this.button = null;
}
- /**
- * Toggles the popup menu visibility
- */
- togglePopupMenu() {
- this.setState(prevState => ({
- isPopupMenuVisible: !prevState.isPopupMenuVisible,
- }));
+ showPopoverMenu() {
+ this.setState({isPopupMenuVisible: true});
+ }
+
+ hidePopoverMenu() {
+ this.setState({isPopupMenuVisible: false});
}
render() {
@@ -76,11 +77,12 @@ class ThreeDotsMenu extends Component {
{
- this.togglePopupMenu();
+ this.showPopoverMenu();
if (this.props.onIconPress) {
this.props.onIconPress();
}
}}
+ ref={el => this.button = el}
style={[styles.touchableButtonImage, ...this.props.iconStyles]}
>
this.togglePopupMenu()}
+ onItemSelected={this.hidePopoverMenu}
menuItems={this.props.menuItems}
/>
>
@@ -104,7 +106,7 @@ class ThreeDotsMenu extends Component {
ThreeDotsMenu.propTypes = propTypes;
ThreeDotsMenu.defaultProps = defaultProps;
-ThreeDotsMenu.displayName = 'ThreeDotsMenu';
+
export default withLocalize(ThreeDotsMenu);
export {ThreeDotsMenuItemPropTypes};
diff --git a/src/components/WalletStatementModal/index.js b/src/components/WalletStatementModal/index.js
index 60d57fc5c14b..762bed5b6296 100644
--- a/src/components/WalletStatementModal/index.js
+++ b/src/components/WalletStatementModal/index.js
@@ -66,7 +66,7 @@ class WalletStatementModal extends React.Component {
WalletStatementModal.propTypes = walletStatementPropTypes;
WalletStatementModal.defaultProps = walletStatementDefaultProps;
-WalletStatementModal.displayName = 'WalletStatementModal';
+
export default compose(
withLocalize,
withOnyx({
diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.js
index 01feae5fa021..9c525a12420c 100644
--- a/src/components/WalletStatementModal/index.native.js
+++ b/src/components/WalletStatementModal/index.native.js
@@ -59,7 +59,7 @@ class WalletStatementModal extends React.Component {
WalletStatementModal.propTypes = walletStatementPropTypes;
WalletStatementModal.defaultProps = walletStatementDefaultProps;
-WalletStatementModal.displayName = 'WalletStatementModal';
+
export default compose(
withLocalize,
withOnyx({
diff --git a/src/languages/en.js b/src/languages/en.js
index cfc3803a2c91..00d6f9e6f9d3 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -792,7 +792,6 @@ export default {
issueAndManageCards: 'Issue and manage cards',
reconcileCards: 'Reconcile cards',
settlementFrequency: 'Settlement frequency',
- growlMessageOnCreate: 'Workspace created',
growlMessageOnSave: 'Your workspace settings were successfully saved!',
deleteConfirmation: 'Are you sure you want to delete this workspace?',
growlMessageOnDelete: 'Workspace deleted',
@@ -802,7 +801,6 @@ export default {
new: {
newWorkspace: 'New workspace',
getTheExpensifyCardAndMore: 'Get the Expensify Card and more',
- genericFailureMessage: 'An error occurred creating the workspace, please try again.',
},
people: {
genericFailureMessage: 'An error occurred removing a user from the workspace, please try again.',
diff --git a/src/languages/es.js b/src/languages/es.js
index b30a1995ffcc..75554f7c91e8 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -794,7 +794,6 @@ export default {
issueAndManageCards: 'Emitir y gestionar tarjetas',
reconcileCards: 'Reconciliar tarjetas',
settlementFrequency: 'Frecuencia de liquidación',
- growlMessageOnCreate: 'El espacio de trabajo ha sido creado',
growlMessageOnSave: '¡La configuración del espacio de trabajo se ha guardado correctamente!',
growlMessageOnDelete: 'Espacio de trabajo eliminado',
deleteConfirmation: '¿Estás seguro de que quieres eliminar este espacio de trabajo?',
@@ -804,7 +803,6 @@ export default {
new: {
newWorkspace: 'Nuevo espacio de trabajo',
getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y más',
- genericFailureMessage: 'Se ha producido un error al intentar crear el espacio de trabajo. Por favor, inténtalo de nuevo.',
},
people: {
genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor inténtalo más tarde.',
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index c5e7b93c8060..2e2a493ed468 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -156,7 +156,7 @@ function reconnectApp() {
* will occur.
* When the exitTo route is 'workspace/new', we create a new
- * workspace and navigate to it via Policy.createAndGetPolicyList.
+ * workspace and navigate to it
*
* We subscribe to the session using withOnyx in the AuthScreens and
* pass it in as a parameter. withOnyx guarantees that the value has been read
@@ -177,7 +177,7 @@ function setUpPoliciesAndNavigate(session) {
&& Str.startsWith(url.pathname, Str.normalizeUrl(ROUTES.TRANSITION_FROM_OLD_DOT))
&& exitTo === ROUTES.WORKSPACE_NEW;
if (shouldCreateFreePolicy) {
- Policy.createAndGetPolicyList();
+ Policy.createWorkspace();
return;
}
if (!isLoggingInAsNewUser && exitTo) {
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index e37c9fc5fe8e..64f190168720 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -111,58 +111,6 @@ function updateAllPolicies(policyCollection) {
});
}
-/**
- * Merges the passed in login into the specified policy
- *
- * @param {String} [name]
- * @param {Boolean} [shouldAutomaticallyReroute]
- * @returns {Promise}
- */
-function create(name = '') {
- let res = null;
- return DeprecatedAPI.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name})
- .then((response) => {
- if (response.jsonCode !== 200) {
- // Show the user feedback
- const errorMessage = Localize.translateLocal('workspace.new.genericFailureMessage');
- Growl.error(errorMessage, 5000);
- return;
- }
- Growl.show(Localize.translateLocal('workspace.common.growlMessageOnCreate'), CONST.GROWL.SUCCESS, 3000);
- res = response;
-
- // Fetch the default reports and the policyExpenseChat reports on the policy
- Report.fetchChatReportsByIDs([response.policy.chatReportIDAdmins, response.policy.chatReportIDAnnounce, response.ownerPolicyExpenseChatID]);
-
- // 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 Promise.all([
- Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, {
- id: response.policyID,
- type: response.policy.type,
- name: response.policy.name,
- role: CONST.POLICY.ROLE.ADMIN,
- outputCurrency: response.policy.outputCurrency,
- }),
- Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${response.policyID}`, getSimplifiedEmployeeList(response.policy.employeeList)),
- ]);
- })
- .then(() => Promise.resolve(lodashGet(res, 'policyID')));
-}
-
-/**
- * @param {String} policyID
- */
-function navigateToPolicy(policyID) {
- Navigation.navigate(policyID ? ROUTES.getWorkspaceInitialRoute(policyID) : ROUTES.HOME);
-}
-
-/**
- * @param {String} [name]
- */
-function createAndNavigate(name = '') {
- create(name).then(navigateToPolicy);
-}
-
/**
* Delete the policy
*
@@ -225,19 +173,6 @@ function getPolicyList() {
});
}
-function createAndGetPolicyList() {
- let newPolicyID;
- create()
- .then((policyID) => {
- newPolicyID = policyID;
- return getPolicyList();
- })
- .then(() => {
- Navigation.dismissModal();
- navigateToPolicy(newPolicyID);
- });
-}
-
/**
* @param {String} policyID
*/
@@ -574,20 +509,31 @@ function clearWorkspaceGeneralSettingsErrors(policyID) {
* @param {Object} errors
*/
function setWorkspaceErrors(policyID, errors) {
+ if (!allPolicies[policyID]) {
+ return;
+ }
+
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errors: null});
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {errors});
}
/**
* @param {String} policyID
- * @param {Number} customUnitID
+ * @param {String} customUnitID
+ * @param {String} customUnitRateID
*/
-function removeUnitError(policyID, customUnitID) {
+function clearCustomUnitErrors(policyID, customUnitID, customUnitRateID) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
customUnits: {
[customUnitID]: {
errors: null,
pendingAction: null,
+ onyxRates: {
+ [customUnitRateID]: {
+ errors: null,
+ pendingAction: null,
+ },
+ },
},
},
});
@@ -597,23 +543,27 @@ function removeUnitError(policyID, customUnitID) {
* @param {String} policyID
*/
function hideWorkspaceAlertMessage(policyID) {
+ if (!allPolicies[policyID]) {
+ return;
+ }
+
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''});
}
/**
* @param {String} policyID
* @param {Object} currentCustomUnit
- * @param {Object} values The new custom unit values
+ * @param {Object} newCustomUnit
*/
-function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
+function updateWorkspaceCustomUnit(policyID, currentCustomUnit, newCustomUnit) {
const optimisticData = [
{
onyxMethod: 'merge',
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
- [values.customUnitID]: {
- ...values,
+ [newCustomUnit.customUnitID]: {
+ ...newCustomUnit,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
@@ -627,7 +577,7 @@ function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
customUnits: {
- [values.customUnitID]: {
+ [newCustomUnit.customUnitID]: {
pendingAction: null,
errors: null,
},
@@ -657,40 +607,81 @@ function updateWorkspaceCustomUnit(policyID, currentCustomUnit, values) {
API.write('UpdateWorkspaceCustomUnit', {
policyID,
- customUnit: JSON.stringify(values),
+ customUnit: JSON.stringify(newCustomUnit),
}, {optimisticData, successData, failureData});
}
/**
* @param {String} policyID
+ * @param {Object} currentCustomUnitRate
* @param {String} customUnitID
- * @param {Object} values
+ * @param {Object} newCustomUnitRate
*/
-function setCustomUnitRate(policyID, customUnitID, values) {
- DeprecatedAPI.Policy_CustomUnitRate_Update({
- policyID: policyID.toString(),
- customUnitID: customUnitID.toString(),
- customUnitRate: JSON.stringify(values),
- lastModified: null,
- })
- .then((response) => {
- if (response.jsonCode !== 200) {
- throw new Error();
- }
+function updateCustomUnitRate(policyID, currentCustomUnitRate, customUnitID, newCustomUnitRate) {
+ const optimisticData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [newCustomUnitRate.customUnitRateID]: {
+ ...newCustomUnitRate,
+ errors: null,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
- updateLocalPolicyValues(policyID, {
- customUnit: {
- rate: {
- id: values.customUnitRateID,
- name: values.name,
- value: Number(values.rate),
+ const successData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [newCustomUnitRate.customUnitRateID]: {
+ pendingAction: null,
+ },
+ },
},
},
- });
- }).catch(() => {
- // Show the user feedback
- Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
- });
+ },
+ },
+ ];
+
+ const failureData = [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ customUnits: {
+ [customUnitID]: {
+ onyxRates: {
+ [currentCustomUnitRate.customUnitRateID]: {
+ ...currentCustomUnitRate,
+ errors: {
+ [DateUtils.getMicroseconds()]: Localize.translateLocal('workspace.reimburse.updateCustomUnitError'),
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ API.write('UpdateWorkspaceCustomUnitRate', {
+ policyID,
+ customUnitID,
+ customUnitRate: JSON.stringify(newCustomUnitRate),
+ }, {optimisticData, successData, failureData});
}
/**
@@ -802,6 +793,155 @@ function generatePolicyID() {
return _.times(16, () => Math.floor(Math.random() * 16).toString(16)).join('').toUpperCase();
}
+/**
+ * Optimistically creates a new workspace and default workspace chats
+ */
+function createWorkspace() {
+ const policyID = generatePolicyID();
+ const workspaceName = generateDefaultWorkspaceName();
+
+ const {
+ announceChatReportID,
+ announceChatData,
+ announceReportActionData,
+ adminsChatReportID,
+ adminsChatData,
+ adminsReportActionData,
+ expenseChatReportID,
+ expenseChatData,
+ expenseReportActionData,
+ } = Report.createOptimisticWorkspaceChats(policyID, workspaceName);
+
+ // We need to use makeRequestWithSideEffects as we try to redirect to the policy right after creation
+ // The policy hasn't been merged in Onyx data at this point, leading to an intermittent Not Found screen
+ // eslint-disable-next-line rulesdir/no-api-side-effects-method
+ API.makeRequestWithSideEffects('CreateWorkspace', {
+ policyID,
+ announceChatReportID,
+ adminsChatReportID,
+ expenseChatReportID,
+ policyName: workspaceName,
+ type: CONST.POLICY.TYPE.FREE,
+ },
+ {
+ optimisticData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ id: policyID,
+ type: CONST.POLICY.TYPE.FREE,
+ name: workspaceName,
+ role: CONST.POLICY.ROLE.ADMIN,
+ owner: sessionEmail,
+ outputCurrency: 'USD',
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`,
+ value: {
+ [sessionEmail]: {
+ role: CONST.POLICY.ROLE.ADMIN,
+ errors: {},
+ },
+ },
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`,
+ value: announceChatData,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`,
+ value: announceReportActionData,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`,
+ value: adminsChatData,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`,
+ value: adminsReportActionData,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`,
+ value: expenseChatData,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`,
+ value: expenseReportActionData,
+ }],
+ successData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {pendingAction: null},
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`,
+ value: {pendingAction: null},
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`,
+ value: {pendingAction: null},
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`,
+ value: {pendingAction: null},
+ }],
+ failureData: [{
+ onyxMethod: CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.SET,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`,
+ value: null,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`,
+ value: null,
+ }],
+ }).then(() => {
+ Navigation.navigate(ROUTES.getWorkspaceInitialRoute(policyID));
+ });
+}
+
function openWorkspaceReimburseView(policyID) {
API.read('OpenWorkspaceReimburseView', {policyID});
}
@@ -813,22 +953,26 @@ function openWorkspaceMembersPage(policyID, clientMemberEmails) {
});
}
+function openWorkspaceInvitePage(policyID, clientMemberEmails) {
+ API.read('OpenWorkspaceInvitePage', {
+ policyID,
+ clientMemberEmails: JSON.stringify(clientMemberEmails),
+ });
+}
+
export {
getPolicyList,
loadFullPolicy,
removeMembers,
invite,
isAdminOfFreePolicy,
- create,
update,
setWorkspaceErrors,
- removeUnitError,
+ clearCustomUnitErrors,
hideWorkspaceAlertMessage,
deletePolicy,
- createAndNavigate,
- createAndGetPolicyList,
updateWorkspaceCustomUnit,
- setCustomUnitRate,
+ updateCustomUnitRate,
updateLastAccessedWorkspace,
subscribeToPolicyEvents,
clearDeleteMemberError,
@@ -841,5 +985,7 @@ export {
updateWorkspaceAvatar,
clearAvatarErrors,
generatePolicyID,
+ createWorkspace,
openWorkspaceMembersPage,
+ openWorkspaceInvitePage,
};
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 66df72f91915..105ca3964226 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -588,13 +588,27 @@ function fetchAllReports(
* Creates an optimistic chat report with a randomly generated reportID and as much information as we currently have
*
* @param {Array} participantList
+ * @param {String} reportName
+ * @param {String} chatType
+ * @param {String} policyID
+ * @param {String} ownerEmail
+ * @param {Boolean} isOwnPolicyExpenseChat
+ * @param {String} oldPolicyName
* @returns {Object}
*/
-function createOptimisticChatReport(participantList) {
+function createOptimisticChatReport(
+ participantList,
+ reportName = 'Chat Report',
+ chatType = '',
+ policyID = '_FAKE_',
+ ownerEmail = '__FAKE__',
+ isOwnPolicyExpenseChat = false,
+ oldPolicyName = '',
+) {
return {
- chatType: '',
+ chatType,
hasOutstandingIOU: false,
- isOwnPolicyExpenseChat: false,
+ isOwnPolicyExpenseChat,
isPinned: false,
lastActorEmail: '',
lastMessageHtml: '',
@@ -604,18 +618,97 @@ function createOptimisticChatReport(participantList) {
lastVisitedTimestamp: 0,
maxSequenceNumber: 0,
notificationPreference: '',
- oldPolicyName: '',
- ownerEmail: '__FAKE__',
+ oldPolicyName,
+ ownerEmail,
participants: participantList,
- policyID: '_FAKE_',
+ policyID,
reportID: ReportUtils.generateReportID(),
- reportName: 'Chat Report',
+ reportName,
stateNum: 0,
statusNum: 0,
visibility: undefined,
};
}
+/**
+ * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically
+ * @param {String} ownerEmail
+ * @returns {Object}
+ */
+function createOptimisticCreatedReportAction(ownerEmail) {
+ return {
+ 0: {
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ actorEmail: currentUserEmail,
+ actorAccountID: currentUserAccountID,
+ message: [
+ {
+ type: CONST.REPORT.MESSAGE.TYPE.TEXT,
+ style: 'strong',
+ text: ownerEmail === currentUserEmail ? 'You' : ownerEmail,
+ },
+ {
+ type: CONST.REPORT.MESSAGE.TYPE.TEXT,
+ style: 'normal',
+ text: ' created this report',
+ },
+ ],
+ person: [
+ {
+ type: CONST.REPORT.MESSAGE.TYPE.TEXT,
+ style: 'strong',
+ text: lodashGet(personalDetails, [currentUserEmail, 'displayName'], currentUserEmail),
+ },
+ ],
+ automatic: false,
+ sequenceNumber: 0,
+ avatar: lodashGet(personalDetails, [currentUserEmail, 'avatar'], ReportUtils.getDefaultAvatar(currentUserEmail)),
+ timestamp: moment().unix(),
+ shouldShow: true,
+ },
+ };
+}
+
+/**
+ * @param {String} policyID
+ * @param {String} policyName
+ * @returns {Object}
+ */
+function createOptimisticWorkspaceChats(policyID, policyName) {
+ const announceChatData = createOptimisticChatReport(
+ [currentUserEmail],
+ CONST.REPORT.WORKSPACE_CHAT_ROOMS.ANNOUNCE,
+ CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE,
+ policyID,
+ null,
+ false,
+ policyName,
+ );
+ const announceChatReportID = announceChatData.reportID;
+ const announceReportActionData = createOptimisticCreatedReportAction(announceChatData.ownerEmail);
+
+ const adminsChatData = createOptimisticChatReport([currentUserEmail], CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, policyID, null, false, policyName);
+ const adminsChatReportID = adminsChatData.reportID;
+ const adminsReportActionData = createOptimisticCreatedReportAction(adminsChatData.ownerEmail);
+
+ const expenseChatData = createOptimisticChatReport([currentUserEmail], '', CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, policyID, currentUserEmail, true, policyName);
+ const expenseChatReportID = expenseChatData.reportID;
+ const expenseReportActionData = createOptimisticCreatedReportAction(expenseChatData.ownerEmail);
+
+ return {
+ announceChatReportID,
+ announceChatData,
+ announceReportActionData,
+ adminsChatReportID,
+ adminsChatData,
+ adminsReportActionData,
+ expenseChatReportID,
+ expenseChatData,
+ expenseReportActionData,
+ };
+}
+
/**
* @param {Number} reportID
* @param {String} [text]
@@ -1538,7 +1631,9 @@ export {
readOldestAction,
openReport,
openPaymentDetailsPage,
+ createOptimisticWorkspaceChats,
createOptimisticChatReport,
+ createOptimisticCreatedReportAction,
updatePolicyRoomName,
clearPolicyRoomNameErrors,
};
diff --git a/src/libs/actions/Wallet.js b/src/libs/actions/Wallet.js
index ea9d12375508..838f2e3b06ab 100644
--- a/src/libs/actions/Wallet.js
+++ b/src/libs/actions/Wallet.js
@@ -186,8 +186,8 @@ function updatePersonalDetails(personalDetails) {
const ssn = personalDetails.ssn || '';
const phoneNumber = personalDetails.phoneNumber || '';
API.write('UpdatePersonalDetailsForWallet', {
- firstName,
- lastName,
+ legalFirstName: firstName,
+ legalLastName: lastName,
dob,
addressStreet,
addressCity,
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index 26a25ff2c9e7..dffae4360b76 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -478,17 +478,6 @@ function User_IsUsingExpensifyCard() {
return Network.post('User_IsUsingExpensifyCard', {});
}
-/**
- * @param {Object} parameters
- * @param {String} [parameters.type]
- * @param {String} [parameters.policyName]
- * @returns {Promise}
- */
-function Policy_Create(parameters) {
- const commandName = 'Policy_Create';
- return Network.post(commandName, parameters);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.policyID
@@ -640,7 +629,6 @@ export {
Wallet_GetOnfidoSDKToken,
TransferWalletBalance,
GetLocalCurrency,
- Policy_Create,
Policy_CustomUnitRate_Update,
Policy_Employees_Remove,
PreferredLocale_Update,
diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js
index dc5245702c88..525b20760d75 100644
--- a/src/pages/AddPersonalBankAccountPage.js
+++ b/src/pages/AddPersonalBankAccountPage.js
@@ -209,7 +209,6 @@ class AddPersonalBankAccountPage extends React.Component {
AddPersonalBankAccountPage.propTypes = propTypes;
AddPersonalBankAccountPage.defaultProps = defaultProps;
-AddPersonalBankAccountPage.displayName = 'AddPersonalBankAccountPage';
export default compose(
withLocalize,
diff --git a/src/pages/EnablePayments/ActivateStep.js b/src/pages/EnablePayments/ActivateStep.js
index c02d6451a659..94454e8ce78a 100644
--- a/src/pages/EnablePayments/ActivateStep.js
+++ b/src/pages/EnablePayments/ActivateStep.js
@@ -88,5 +88,5 @@ class ActivateStep extends React.Component {
ActivateStep.propTypes = propTypes;
ActivateStep.defaultProps = defaultProps;
-ActivateStep.displayName = 'ActivateStep';
+
export default withLocalize(ActivateStep);
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index e53fb503c298..42eac4ea43c2 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -170,7 +170,7 @@ class BaseSidebarScreen extends Component {
iconHeight: 40,
text: this.props.translate('workspace.new.newWorkspace'),
description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'),
- onSelected: () => Policy.createAndNavigate(),
+ onSelected: () => Policy.createWorkspace(),
},
] : []),
]}
diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
index 273061aa4e3c..9d0571ab16df 100755
--- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
+++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js
@@ -236,7 +236,6 @@ class IOUParticipantsSplit extends Component {
}
}
-IOUParticipantsSplit.displayName = 'IOUParticipantsSplit';
IOUParticipantsSplit.propTypes = propTypes;
IOUParticipantsSplit.defaultProps = defaultProps;
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index f23f7b1205a6..621724908951 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -279,7 +279,6 @@ class InitialSettingsPage extends React.Component {
InitialSettingsPage.propTypes = propTypes;
InitialSettingsPage.defaultProps = defaultProps;
-InitialSettingsPage.displayName = 'InitialSettingsPage';
export default compose(
withLocalize,
diff --git a/src/pages/settings/Payments/AddPayPalMePage.js b/src/pages/settings/Payments/AddPayPalMePage.js
index 2e8bdff1d8e7..8fd4d262a487 100644
--- a/src/pages/settings/Payments/AddPayPalMePage.js
+++ b/src/pages/settings/Payments/AddPayPalMePage.js
@@ -103,7 +103,6 @@ class AddPayPalMePage extends React.Component {
AddPayPalMePage.propTypes = propTypes;
AddPayPalMePage.defaultProps = defaultProps;
-AddPayPalMePage.displayName = 'AddPayPalMePage';
export default compose(
withLocalize,
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index e652cda0c86d..250a7e2021ff 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -307,7 +307,6 @@ class ProfilePage extends Component {
ProfilePage.propTypes = propTypes;
ProfilePage.defaultProps = defaultProps;
-ProfilePage.displayName = 'ProfilePage';
export default compose(
withLocalize,
diff --git a/src/pages/settings/Security/CloseAccountPage.js b/src/pages/settings/Security/CloseAccountPage.js
index 96e8aa70be4a..f6e411d29135 100644
--- a/src/pages/settings/Security/CloseAccountPage.js
+++ b/src/pages/settings/Security/CloseAccountPage.js
@@ -143,7 +143,6 @@ class CloseAccountPage extends Component {
CloseAccountPage.propTypes = propTypes;
CloseAccountPage.defaultProps = defaultProps;
-CloseAccountPage.displayName = 'CloseAccountPage';
export default compose(
withLocalize,
diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js
index 00f1c1d629a1..38f0cc781cb5 100755
--- a/src/pages/signin/ResendValidationForm.js
+++ b/src/pages/signin/ResendValidationForm.js
@@ -100,6 +100,7 @@ const ResendValidationForm = (props) => {
ResendValidationForm.propTypes = propTypes;
ResendValidationForm.defaultProps = defaultProps;
+ResendValidationForm.displayName = 'ResendValidationForm';
export default compose(
withLocalize,
diff --git a/src/pages/wallet/WalletStatementPage.js b/src/pages/wallet/WalletStatementPage.js
index d1056ec5c21a..572aa672bff4 100644
--- a/src/pages/wallet/WalletStatementPage.js
+++ b/src/pages/wallet/WalletStatementPage.js
@@ -120,7 +120,7 @@ class WalletStatementPage extends React.Component {
WalletStatementPage.propTypes = propTypes;
WalletStatementPage.defaultProps = defaultProps;
-WalletStatementPage.displayName = 'WalletStatementPage';
+
export default compose(
withLocalize,
withOnyx({
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 270927d030db..cbcccda54c62 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -21,7 +21,7 @@ import compose from '../../libs/compose';
import Avatar from '../../components/Avatar';
import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView';
import withPolicy, {policyPropTypes, policyDefaultProps} from './withPolicy';
-import * as PolicyActions from '../../libs/actions/Policy';
+import * as Policy from '../../libs/actions/Policy';
import * as PolicyUtils from '../../libs/PolicyUtils';
import CONST from '../../CONST';
import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount';
@@ -74,7 +74,7 @@ class WorkspaceInitialPage extends React.Component {
* Call the delete policy and hide the modal
*/
confirmDeleteAndHideModal() {
- PolicyActions.deletePolicy(this.props.policy.id);
+ Policy.deletePolicy(this.props.policy.id);
this.toggleDeleteModal(false);
}
@@ -145,7 +145,7 @@ class WorkspaceInitialPage extends React.Component {
{
icon: Expensicons.Plus,
text: this.props.translate('workspace.new.newWorkspace'),
- onSelected: () => PolicyActions.createAndNavigate(),
+ onSelected: () => Policy.createWorkspace(),
}, {
icon: Expensicons.Trashcan,
text: this.props.translate('workspace.common.delete'),
@@ -245,7 +245,6 @@ class WorkspaceInitialPage extends React.Component {
WorkspaceInitialPage.propTypes = propTypes;
WorkspaceInitialPage.defaultProps = defaultProps;
-WorkspaceInitialPage.displayName = 'WorkspaceInitialPage';
export default compose(
withLocalize,
diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js
index 1ee96f4620eb..86fcc48b9757 100644
--- a/src/pages/workspace/WorkspaceInvitePage.js
+++ b/src/pages/workspace/WorkspaceInvitePage.js
@@ -21,6 +21,9 @@ import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndica
import * as Link from '../../libs/actions/Link';
import Text from '../../components/Text';
import withPolicy, {policyPropTypes, policyDefaultProps} from './withPolicy';
+import {withNetwork} from '../../components/OnyxProvider';
+import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView';
+import networkPropTypes from '../../components/networkPropTypes';
const personalDetailsPropTypes = PropTypes.shape({
/** The login of the person (either email or phone number) */
@@ -52,6 +55,7 @@ const propTypes = {
...policyPropTypes,
...withLocalizePropTypes,
+ ...networkPropTypes,
};
const defaultProps = policyDefaultProps;
@@ -84,6 +88,19 @@ class WorkspaceInvitePage extends React.Component {
componentDidMount() {
this.clearErrors();
+
+ const clientPolicyMembers = _.keys(this.props.policyMemberList);
+ Policy.openWorkspaceInvitePage(this.props.route.params.policyID, clientPolicyMembers);
+ }
+
+ componentDidUpdate(prevProps) {
+ const isReconnecting = prevProps.network.isOffline && !this.props.network.isOffline;
+ if (!isReconnecting) {
+ return;
+ }
+
+ const clientPolicyMembers = _.keys(this.props.policyMemberList);
+ Policy.openWorkspaceInvitePage(this.props.route.params.policyID, clientPolicyMembers);
}
getExcludedUsers() {
@@ -230,22 +247,23 @@ class WorkspaceInvitePage extends React.Component {
return (
{({didScreenTransitionEnd}) => (
- <>
- {
- this.clearErrors();
- Navigation.dismissModal();
- }}
- shouldShowGetAssistanceButton
- guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
- shouldShowBackButton
- onBackButtonPress={() => Navigation.goBack()}
- />
-
- {!didScreenTransitionEnd && }
- {didScreenTransitionEnd && (
+
+ <>
+ {
+ this.clearErrors();
+ Navigation.dismissModal();
+ }}
+ shouldShowGetAssistanceButton
+ guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
+ shouldShowBackButton
+ onBackButtonPress={() => Navigation.goBack()}
+ />
+
+ {!didScreenTransitionEnd && }
+ {didScreenTransitionEnd && (
- )}
-
-
-
- this.setState({welcomeNote: text})}
+ )}
+
+
+
+ this.setState({welcomeNote: text})}
+ />
+
+ {}}
+ message={this.props.policy.alertMessage}
+ containerStyles={[styles.flexReset, styles.mb0, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
/>
+ {
+ e.preventDefault();
+ Link.openExternalLink(CONST.PRIVACY_URL);
+ }}
+ accessibilityRole="link"
+ href={CONST.PRIVACY_URL}
+ style={[styles.mh5, styles.mv2, styles.alignSelfStart]}
+ >
+ {({hovered, pressed}) => (
+
+
+ {this.props.translate('common.privacyPolicy')}
+
+
+ )}
+
- {}}
- message={this.props.policy.alertMessage}
- containerStyles={[styles.flexReset, styles.mb0, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
- />
- {
- e.preventDefault();
- Link.openExternalLink(CONST.PRIVACY_URL);
- }}
- accessibilityRole="link"
- href={CONST.PRIVACY_URL}
- style={[styles.mh5, styles.mv2, styles.alignSelfStart]}
- >
- {({hovered, pressed}) => (
-
-
- {this.props.translate('common.privacyPolicy')}
-
-
- )}
-
-
- >
+ >
+
)}
);
@@ -334,6 +353,7 @@ WorkspaceInvitePage.defaultProps = defaultProps;
export default compose(
withLocalize,
withPolicy,
+ withNetwork(),
withOnyx({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
index 30da75a415b5..5b9c3e6a976d 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
@@ -39,7 +39,7 @@ const propTypes = {
attributes: PropTypes.shape({
unit: PropTypes.string,
}),
- rates: PropTypes.arrayOf(
+ onyxRates: PropTypes.objectOf(
PropTypes.shape({
customUnitRateID: PropTypes.string,
name: PropTypes.string,
@@ -62,14 +62,14 @@ class WorkspaceReimburseView extends React.Component {
constructor(props) {
super(props);
const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), unit => unit.name === 'Distance');
+ const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'onyxRates', {}), rate => rate.name === 'Default Rate');
this.state = {
unitID: lodashGet(distanceCustomUnit, 'customUnitID', ''),
unitName: lodashGet(distanceCustomUnit, 'name', ''),
unitValue: lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'),
- rateID: lodashGet(distanceCustomUnit, 'rates[0].customUnitRateID', ''),
- rateName: lodashGet(distanceCustomUnit, 'rates[0].name', ''),
- rateValue: this.getRateDisplayValue(lodashGet(distanceCustomUnit, 'rates[0].rate', 0) / 100),
+ unitRateID: lodashGet(customUnitRate, 'customUnitRateID', ''),
+ unitRateValue: this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET),
outputCurrency: lodashGet(props, 'policy.outputCurrency', ''),
};
@@ -98,14 +98,13 @@ class WorkspaceReimburseView extends React.Component {
.values()
.findWhere({name: CONST.CUSTOM_UNITS.NAME_DISTANCE})
.value();
-
+ const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'onyxRates', {}), rate => rate.name === 'Default Rate');
this.setState({
unitID: lodashGet(distanceCustomUnit, 'customUnitID', ''),
unitName: lodashGet(distanceCustomUnit, 'name', ''),
unitValue: lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'),
- rateID: lodashGet(distanceCustomUnit, 'rates[0].customUnitRateID', ''),
- rateName: lodashGet(distanceCustomUnit, 'rates[0].name', ''),
- rateValue: this.getRateDisplayValue(lodashGet(distanceCustomUnit, 'rates[0].rate', 0) / 100),
+ unitRateID: lodashGet(customUnitRate, 'customUnitRateID'),
+ unitRateValue: this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / 100),
});
}
@@ -130,10 +129,10 @@ class WorkspaceReimburseView extends React.Component {
const isInvalidRateValue = value !== '' && !CONST.REGEX.RATE_VALUE.test(value);
this.setState(prevState => ({
- rateValue: !isInvalidRateValue ? value : prevState.rateValue,
+ unitRateValue: !isInvalidRateValue ? value : prevState.unitRateValue,
}), () => {
// Set the corrected value with a delay and sync to the server
- this.updateRateValueDebounced(this.state.rateValue);
+ this.updateRateValueDebounced(this.state.unitRateValue);
});
}
@@ -156,7 +155,7 @@ class WorkspaceReimburseView extends React.Component {
return;
}
- this.updateRateValueDebounced(this.state.rateValue);
+ this.updateRateValueDebounced(this.state.unitRateValue);
}
updateRateValue(value) {
@@ -167,14 +166,15 @@ class WorkspaceReimburseView extends React.Component {
}
this.setState({
- rateValue: numValue.toFixed(3),
+ unitRateValue: numValue.toFixed(3),
});
- Policy.setCustomUnitRate(this.props.policyID, this.state.unitID, {
- customUnitRateID: this.state.rateID,
- name: this.state.rateName,
- rate: numValue.toFixed(3) * 100,
- }, null);
+ const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), unit => unit.name === 'Distance');
+ const currentCustomUnitRate = lodashGet(distanceCustomUnit, ['onyxRates', this.state.unitRateID], {});
+ Policy.updateCustomUnitRate(this.props.policyID, currentCustomUnitRate, this.state.unitID, {
+ ...currentCustomUnitRate,
+ rate: numValue.toFixed(3) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
+ });
}
render() {
@@ -214,9 +214,13 @@ class WorkspaceReimburseView extends React.Component {
{this.props.translate('workspace.reimburse.trackDistanceCopy')}
Policy.removeUnitError(this.props.policyID, this.state.unitID)}
+ errors={{
+ ...lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'errors'], {}),
+ ...lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'onyxRates', this.state.unitRateID, 'errors'], {}),
+ }}
+ pendingAction={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'pendingAction'])
+ || lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'onyxRates', this.state.unitRateID, 'pendingAction'])}
+ onClose={() => Policy.clearCustomUnitErrors(this.props.policyID, this.state.unitID, this.state.unitRateID)}
>
@@ -224,7 +228,7 @@ class WorkspaceReimburseView extends React.Component {
label={this.props.translate('workspace.reimburse.trackDistanceRate')}
placeholder={this.state.outputCurrency}
onChangeText={value => this.setRate(value)}
- value={this.state.rateValue}
+ value={this.state.unitRateValue}
autoCompleteType="off"
autoCorrect={false}
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
@@ -289,7 +293,6 @@ class WorkspaceReimburseView extends React.Component {
}
WorkspaceReimburseView.propTypes = propTypes;
-WorkspaceReimburseView.displayName = 'WorkspaceReimburseView';
export default compose(
withPolicy,
diff --git a/src/pages/workspace/withPolicy.js b/src/pages/workspace/withPolicy.js
index ca3d0fa2f450..34182871eb4f 100644
--- a/src/pages/workspace/withPolicy.js
+++ b/src/pages/workspace/withPolicy.js
@@ -42,9 +42,6 @@ const policyPropTypes = {
/** The URL for the policy avatar */
avatar: PropTypes.string,
- /** A list of emails for the employees on the policy */
- employeeList: PropTypes.arrayOf(PropTypes.string),
-
/** Errors on the policy keyed by microtime */
errors: PropTypes.objectOf(PropTypes.string),
diff --git a/src/styles/addOutlineWidth/index.js b/src/styles/addOutlineWidth/index.js
index db9ba66fe087..a25bc887dc10 100644
--- a/src/styles/addOutlineWidth/index.js
+++ b/src/styles/addOutlineWidth/index.js
@@ -17,6 +17,7 @@ function withOutlineWidth(obj, val, error = false) {
return {
...obj,
outlineWidth: val,
+ outlineStyle: val ? 'auto' : 'none',
boxShadow: val !== 0 ? `0px 0px 0px ${val}px ${error ? themeDefault.badgeDangerBG : themeDefault.borderFocus}` : 'none',
};
}
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 1560efac1b03..467a5609a4bf 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -729,7 +729,6 @@ const styles = {
paddingBottom: 8,
paddingHorizontal: 11,
borderWidth: 0,
- borderRadius: variables.componentBorderRadiusNormal,
},
textInputMultiline: {