diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index bc2a3f357b5a..5b3a1187da7a 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -6,6 +6,8 @@ runs:
steps:
# Version: 3.0.2
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
+ with:
+ fetch-depth: 0
- uses: actions/setup-node@09ba51f18e18a3756fea1f54d09c6745c064491d
with:
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index e377a4c9e23d..7a81ddc52c9d 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -338,9 +338,6 @@ jobs:
steps:
- uses: Expensify/App/.github/actions/composite/setupNode@main
- - name: Fetch tags
- run: git fetch --tags
-
- name: Set version
run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV"
diff --git a/README.md b/README.md
index 9d421dbe8db0..85cf05ac619e 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,7 @@ You can use any IDE or code editing tool for developing on any platform. Use you
For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11580) for installing cocoapods.
* To install the iOS dependencies, run: `npm install && cd ios/ && pod install && cd ..`
+* If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699)
* To run a on a **Development Simulator**: `npm run ios`
* Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile
@@ -53,6 +54,7 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11
* To install the Android dependencies, run: `npm install`
* Go through the instructions on [this SO post](https://stackoverflow.com/c/expensify/questions/13283/13284#13284) to start running the app on android.
* For more information, go through the official React-Native instructions on [this page](https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android
+* If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699)
* To run a on a **Development Emulator**: `npm run android`
* Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 86b49122badc..79527d76cbe7 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 1001018505
- versionName "1.1.85-5"
+ versionCode 1001018605
+ versionName "1.1.86-5"
}
splits {
abi {
diff --git a/assets/images/dot-indicator.svg b/assets/images/dot-indicator.svg
new file mode 100644
index 000000000000..49be497b35c1
--- /dev/null
+++ b/assets/images/dot-indicator.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/assets/images/product-illustrations/tada--blue.svg b/assets/images/product-illustrations/tada--blue.svg
new file mode 100644
index 000000000000..5430863ca145
--- /dev/null
+++ b/assets/images/product-illustrations/tada--blue.svg
@@ -0,0 +1,54 @@
+
+
+
diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js
index e6002e4166d0..a10bea368e89 100644
--- a/config/webpack/webpack.common.js
+++ b/config/webpack/webpack.common.js
@@ -151,6 +151,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({
alias: {
'react-native-config': 'react-web-config',
'react-native$': 'react-native-web',
+ 'react-content-loader/native': 'react-content-loader',
},
// React Native libraries may have web-specific module implementations that appear with the extension `.web.js`
diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md
index 5a06599d8936..2cde9aff521f 100644
--- a/contributingGuides/CONTRIBUTING.md
+++ b/contributingGuides/CONTRIBUTING.md
@@ -37,6 +37,8 @@ Payment for your contributions will be made no less than 7 days after the pull r
New contributors are limited to working on one job at a time, however experienced contributors may work on numerous jobs simultaneously.
+Please be aware that compensation for any support in solving an issue is provided **entirely at Expensify’s discretion**. Personal time or resources applied towards investigating a proposal **will not guarantee compensation**. Compensation is only guaranteed to those who **[propose a solution and get hired for that job](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#propose-a-solution-for-the-job)**. We understand there may be cases where a selected proposal may take inspiration from a previous proposal. Unfortunately, it’s not possible for us to evaluate every individual case and we have no process that can efficiently do so. Issues with higher rewards come with higher risk factors so try to keep things civil and make the best proposal you can. Once again, **any information provided may not necessarily lead to you getting hired for that issue or compensated in any way.**
+
## Finding Jobs
There are two ways you can find a job that you can contribute to:
diff --git a/desktop/README.md b/desktop/README.md
index 87ca4dc36b24..5a09cc5273cc 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -48,6 +48,8 @@ Testing the auto-update process can be a little involved. The most effective way
**Note:** In order to test with a notarized build you'll need to have a paid Apple developer account.
+You can inspect auto update related logs in the log file at `~/Library/Logs/new.expensify.desktop/main.log`
+
## Setting up Min.IO
Rather than pushing new builds to the production S3 bucket, the best way to test locally is to use [Min.IO](https://min.io). Min.IO is an S3-compatible service that you can set up and deploy locally. In order to set up a local Min.IO instance to emulate an S3 bucket, follow these steps:
@@ -139,3 +141,9 @@ To see the actual `app.asar` content run the following script
npx asar extract desktop-build/mac/New\ Expensify.app/Contents/Resources/app.asar ./unpacked-asar
```
The expected size of `app.asar` = `desktop/dist/www/` + `desktop/node_modules/`;
+
+
+# Logging
+
+- `main` process logs are written to `~/Library/Logs/new.expensify.desktop/main.log`
+- `renderer` logs can be observed live in the developer console (⌘ Cmd + ⌥ Option + I)
diff --git a/desktop/main.js b/desktop/main.js
index b2ad339d9647..0eb944fca509 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -33,12 +33,12 @@ app.commandLine.appendSwitch('enable-network-information-downlink-max');
// See https://github.com/sindresorhus/electron-context-menu
contextMenu();
-// Send all autoUpdater logs to a log file: ~/Library/Logs/new.expensify/main.log
+// Send all autoUpdater logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log
// See https://www.npmjs.com/package/electron-log
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
-// Send all Console logs to a log file: ~/Library/Logs/new.expensify/main.log
+// Send all Console logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log
// See https://www.npmjs.com/package/electron-log
_.assign(console, log.functions);
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 8c3237fe6620..68b2d20f6da4 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.1.85
+ 1.1.86
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.85.5
+ 1.1.86.5
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index a66aa9c97c46..4dd5cc25cccf 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.1.85
+ 1.1.86
CFBundleSignature
????
CFBundleVersion
- 1.1.85.5
+ 1.1.86.5
diff --git a/package-lock.json b/package-lock.json
index 8e31dc3aa8ba..f766ad66603f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.85-5",
+ "version": "1.1.86-5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3954,6 +3954,15 @@
}
}
},
+ "@jest/schemas": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz",
+ "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==",
+ "dev": true,
+ "requires": {
+ "@sinclair/typebox": "^0.24.1"
+ }
+ },
"@jest/test-sequencer": {
"version": "26.6.0",
"resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.0.tgz",
@@ -6836,6 +6845,12 @@
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
+ "@sinclair/typebox": {
+ "version": "0.24.20",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.20.tgz",
+ "integrity": "sha512-kVaO5aEFZb33nPMTZBxiPEkY+slxiPtqC7QX8f9B3eGOMBvEfuMfxp9DSTTCsRJPumPKjrge4yagyssO4q6qzQ==",
+ "dev": true
+ },
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -21683,9 +21698,9 @@
"dev": true
},
"@types/babel__core": {
- "version": "7.1.10",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.10.tgz",
- "integrity": "sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw==",
+ "version": "7.1.19",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz",
+ "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==",
"dev": true,
"requires": {
"@babel/parser": "^7.1.0",
@@ -21696,18 +21711,18 @@
}
},
"@types/babel__generator": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz",
- "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==",
+ "version": "7.6.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz",
+ "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@types/babel__template": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.3.tgz",
- "integrity": "sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==",
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz",
+ "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==",
"dev": true,
"requires": {
"@babel/parser": "^7.1.0",
@@ -21833,7 +21848,7 @@
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/lodash": {
@@ -22640,7 +22655,7 @@
"absolute-path": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz",
- "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c="
+ "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA=="
},
"accepts": {
"version": "1.3.7",
@@ -22659,7 +22674,7 @@
"acorn-dynamic-import": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz",
- "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
+ "integrity": "sha512-GKp5tQ8h0KMPWIYGRHHXI1s5tUpZixZ3IHF2jAu42wSCf6In/G873s6/y4DdKdhWvzhu1T6mE1JgvnhAKqyYYQ==",
"requires": {
"acorn": "^4.0.3"
},
@@ -22667,7 +22682,7 @@
"acorn": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
- "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+ "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug=="
}
}
},
@@ -22810,7 +22825,7 @@
"align-text": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
- "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==",
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@@ -23157,7 +23172,7 @@
"app-root-dir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz",
- "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=",
+ "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==",
"dev": true
},
"appdirsjs": {
@@ -23205,7 +23220,7 @@
"aria-query": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
- "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+ "integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7",
@@ -23215,7 +23230,7 @@
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA=="
},
"arr-flatten": {
"version": "1.1.0",
@@ -23225,12 +23240,12 @@
"arr-union": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+ "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q=="
},
"array-filter": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
- "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw="
+ "integrity": "sha512-VW0FpCIhjZdarWjIz8Vpva7U95fl2Jn+b+mmFFMLn8PIVscOQcAgEznwUzTEuUHuqZqIxwzRlcaN/urTFFQoiw=="
},
"array-find-index": {
"version": "1.0.2",
@@ -23280,17 +23295,17 @@
"array-map": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
- "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI="
+ "integrity": "sha512-123XMszMB01QKVptpDQ7x1m1pP5NmJIG1kbl0JSPPRezvwQChxAN0Gvzo7rvR1IZ2tOL2tmiy7kY/KKgnpVVpg=="
},
"array-reduce": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
- "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys="
+ "integrity": "sha512-8jR+StqaC636u7h3ye1co3lQRefgVVUQUhuAmRbDqIMeR2yuXzRvkCNQiQ5J/wbREmoBLNtp13dhaaVpZQDRUw=="
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
- "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
"dev": true,
"requires": {
"array-uniq": "^1.0.1"
@@ -23299,13 +23314,13 @@
"array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
- "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
"dev": true
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ=="
},
"array.prototype.flat": {
"version": "1.2.5",
@@ -23918,7 +23933,7 @@
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
- "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"asar": {
"version": "3.0.3",
@@ -23944,7 +23959,7 @@
"ascii-table": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/ascii-table/-/ascii-table-0.0.9.tgz",
- "integrity": "sha1-BqZgTWpV1L9BqaR9mHLXp42jHnM="
+ "integrity": "sha512-xpkr6sCDIYTPqzvjG8M3ncw1YOTaloWZOyrUmicoEifBEKzQzt+ooUpRpQ/AbOoJfO/p2ZKiyp79qHThzJDulQ=="
},
"asn1": {
"version": "0.2.4",
@@ -24000,13 +24015,13 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"dev": true
},
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+ "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw=="
},
"ast-types": {
"version": "0.14.2",
@@ -24026,7 +24041,7 @@
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
- "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
"dev": true
},
"async": {
@@ -24056,7 +24071,7 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
"at-least-node": {
@@ -24120,7 +24135,7 @@
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
- "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
"dev": true,
"requires": {
"chalk": "^1.1.3",
@@ -24131,19 +24146,19 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
@@ -24198,7 +24213,7 @@
"babel-helper-function-name": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
- "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+ "integrity": "sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==",
"dev": true,
"requires": {
"babel-helper-get-function-arity": "^6.24.1",
@@ -24211,7 +24226,7 @@
"babel-helper-get-function-arity": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
- "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+ "integrity": "sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0",
@@ -24219,51 +24234,317 @@
}
},
"babel-jest": {
- "version": "26.6.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.0.tgz",
- "integrity": "sha512-JI66yILI7stzjHccAoQtRKcUwJrJb4oMIxLTirL3GdAjGpaUBQSjZDFi9LsPkN4gftsS4R2AThAJwOjJxadwbg==",
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz",
+ "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==",
"dev": true,
"requires": {
- "@jest/transform": "^26.6.0",
- "@jest/types": "^26.6.0",
- "@types/babel__core": "^7.1.7",
- "babel-plugin-istanbul": "^6.0.0",
- "babel-preset-jest": "^26.5.0",
+ "@jest/transform": "^28.1.3",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^28.1.3",
"chalk": "^4.0.0",
- "graceful-fs": "^4.2.4",
+ "graceful-fs": "^4.2.9",
"slash": "^3.0.0"
},
"dependencies": {
+ "@babel/parser": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz",
+ "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==",
+ "dev": true
+ },
+ "@jest/transform": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz",
+ "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^28.1.3",
+ "@jridgewell/trace-mapping": "^0.3.13",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^1.4.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^28.1.3",
+ "jest-regex-util": "^28.0.2",
+ "jest-util": "^28.1.3",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.1"
+ }
+ },
"@jest/types": {
- "version": "26.6.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.0.tgz",
- "integrity": "sha512-8pDeq/JVyAYw7jBGU83v8RMYAkdrRxLG3BGnAJuqaQAUd6GWBmND2uyl+awI88+hit48suLoLjNFtR+ZXxWaYg==",
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz",
+ "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==",
"dev": true,
"requires": {
+ "@jest/schemas": "^28.1.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
- "@types/yargs": "^15.0.0",
+ "@types/yargs": "^17.0.8",
"chalk": "^4.0.0"
}
},
"@types/istanbul-reports": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
- "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
+ "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==",
"dev": true,
"requires": {
"@types/istanbul-lib-report": "*"
}
},
- "chalk": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
- "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "@types/yargs": {
+ "version": "17.0.10",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz",
+ "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==",
"dev": true,
"requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "@types/yargs-parser": "*"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "ci-info": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz",
+ "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "graceful-fs": {
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "istanbul-lib-coverage": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
+ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "dev": true
+ },
+ "istanbul-lib-instrument": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz",
+ "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ }
+ },
+ "jest-haste-map": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz",
+ "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^28.1.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "fsevents": "^2.3.2",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^28.0.2",
+ "jest-util": "^28.1.3",
+ "jest-worker": "^28.1.3",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ }
+ },
+ "jest-regex-util": {
+ "version": "28.0.2",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz",
+ "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==",
+ "dev": true
+ },
+ "jest-util": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz",
+ "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^28.1.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "dependencies": {
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ }
+ }
+ },
+ "jest-worker": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz",
+ "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ }
+ },
+ "makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "requires": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "dependencies": {
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ }
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "pirates": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
+ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "requires": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "write-file-atomic": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz",
+ "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
}
}
}
@@ -24317,7 +24598,7 @@
"babel-messages": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
- "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0"
@@ -24326,7 +24607,7 @@
"babel-plugin-add-react-displayname": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz",
- "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=",
+ "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==",
"dev": true
},
"babel-plugin-apply-mdx-type-prop": {
@@ -24370,14 +24651,14 @@
}
},
"babel-plugin-jest-hoist": {
- "version": "26.5.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz",
- "integrity": "sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==",
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz",
+ "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==",
"dev": true,
"requires": {
"@babel/template": "^7.3.3",
"@babel/types": "^7.3.3",
- "@types/babel__core": "^7.0.0",
+ "@types/babel__core": "^7.1.14",
"@types/babel__traverse": "^7.0.6"
}
},
@@ -24829,7 +25110,7 @@
"babel-plugin-syntax-class-properties": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
- "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
+ "integrity": "sha512-chI3Rt9T1AbrQD1s+vxw3KcwC9yHtF621/MacuItITfZX344uhQoANjpoSJZleAmW2tjlolqB/f+h7jIqXa7pA==",
"dev": true
},
"babel-plugin-syntax-trailing-function-commas": {
@@ -24840,7 +25121,7 @@
"babel-plugin-transform-class-properties": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
- "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
+ "integrity": "sha512-n4jtBA3OYBdvG5PRMKsMXJXHfLYw/ZOmtxCLOOwz6Ro5XlrColkStLnz1AS1L2yfPA9BKJ1ZNlmVCLjAL9DSIg==",
"dev": true,
"requires": {
"babel-helper-function-name": "^6.24.1",
@@ -24852,12 +25133,12 @@
"babel-plugin-transform-remove-console": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz",
- "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A="
+ "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg=="
},
"babel-preset-current-node-syntax": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz",
- "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
+ "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==",
"dev": true,
"requires": {
"@babel/plugin-syntax-async-generators": "^7.8.4",
@@ -24870,7 +25151,8 @@
"@babel/plugin-syntax-numeric-separator": "^7.8.3",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-top-level-await": "^7.8.3"
}
},
"babel-preset-fbjs": {
@@ -24908,19 +25190,19 @@
}
},
"babel-preset-jest": {
- "version": "26.5.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz",
- "integrity": "sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==",
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz",
+ "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==",
"dev": true,
"requires": {
- "babel-plugin-jest-hoist": "^26.5.0",
- "babel-preset-current-node-syntax": "^0.1.3"
+ "babel-plugin-jest-hoist": "^28.1.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
- "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
"dev": true,
"requires": {
"core-js": "^2.4.0",
@@ -24938,7 +25220,7 @@
"babel-template": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
- "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+ "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
@@ -24951,7 +25233,7 @@
"babel-traverse": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
- "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+ "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
@@ -24991,7 +25273,7 @@
"babel-types": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
- "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
@@ -25078,7 +25360,7 @@
"base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
- "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
+ "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"base64-js": {
"version": "1.3.1",
@@ -25094,7 +25376,7 @@
"batch-processor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
- "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=",
+ "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==",
"dev": true
},
"bcrypt-pbkdf": {
@@ -25276,7 +25558,7 @@
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"boolean": {
"version": "3.2.0",
@@ -25413,7 +25695,7 @@
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
- "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
},
"browser-process-hrtime": {
"version": "1.0.0",
@@ -25570,13 +25852,13 @@
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
- "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
"dev": true
},
"buffer-from": {
@@ -25593,7 +25875,7 @@
"buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
- "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
},
"builder-util": {
"version": "22.13.1",
@@ -25740,12 +26022,12 @@
"builtin-status-codes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
- "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
+ "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="
},
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
- "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="
},
"c8": {
"version": "7.11.3",
@@ -26023,13 +26305,13 @@
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
- "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
+ "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==",
"dev": true
},
"caller-callsite": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
- "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==",
"requires": {
"callsites": "^2.0.0"
}
@@ -26037,7 +26319,7 @@
"caller-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
- "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==",
"requires": {
"caller-callsite": "^2.0.0"
}
@@ -26045,7 +26327,7 @@
"callsites": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
- "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
+ "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ=="
},
"camel-case": {
"version": "4.1.1",
@@ -26091,7 +26373,7 @@
"camelize": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
- "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg=="
},
"caniuse-lite": {
"version": "1.0.30001252",
@@ -26128,7 +26410,7 @@
"center-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
- "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==",
"requires": {
"align-text": "^0.1.3",
"lazy-cache": "^1.0.3"
@@ -26262,7 +26544,7 @@
"chromium-pickle-js": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
- "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=",
+ "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
"dev": true
},
"ci-info": {
@@ -26352,7 +26634,7 @@
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
- "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
"requires": {
"restore-cursor": "^2.0.0"
}
@@ -28424,7 +28706,7 @@
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
- "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
+ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
"dev": true
},
"dns-packet": {
@@ -28440,7 +28722,7 @@
"dns-txt": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
- "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==",
+ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
"dev": true,
"requires": {
"buffer-indexof": "^1.0.0"
@@ -30041,7 +30323,7 @@
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
@@ -30050,7 +30332,7 @@
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
"dev": true,
"requires": {
"p-locate": "^2.0.0",
@@ -30069,7 +30351,7 @@
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
"dev": true,
"requires": {
"p-limit": "^1.1.0"
@@ -30078,13 +30360,13 @@
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
"dev": true
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
"dev": true
}
}
@@ -31564,7 +31846,7 @@
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
- "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+ "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
"version": "1.1.2",
@@ -32535,7 +32817,7 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true
}
}
@@ -32856,7 +33138,7 @@
"hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
- "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
+ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
"dev": true,
"requires": {
"inherits": "^2.0.1",
@@ -33019,7 +33301,7 @@
"http-deceiver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
- "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
+ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
"dev": true
},
"http-errors": {
@@ -34733,6 +35015,58 @@
"pretty-format": "^26.6.0"
},
"dependencies": {
+ "@jest/transform": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz",
+ "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.1.0",
+ "@jest/types": "^26.6.2",
+ "babel-plugin-istanbul": "^6.0.0",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^1.4.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "graceful-fs": "^4.2.4",
+ "jest-haste-map": "^26.6.2",
+ "jest-regex-util": "^26.0.0",
+ "jest-util": "^26.6.2",
+ "micromatch": "^4.0.2",
+ "pirates": "^4.0.1",
+ "slash": "^3.0.0",
+ "source-map": "^0.6.1",
+ "write-file-atomic": "^3.0.0"
+ },
+ "dependencies": {
+ "@jest/types": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+ "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^15.0.0",
+ "chalk": "^4.0.0"
+ }
+ },
+ "jest-util": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
+ "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^26.6.2",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.4",
+ "is-ci": "^2.0.0",
+ "micromatch": "^4.0.2"
+ }
+ }
+ }
+ },
"@jest/types": {
"version": "26.6.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.0.tgz",
@@ -34755,6 +35089,59 @@
"@types/istanbul-lib-report": "*"
}
},
+ "babel-jest": {
+ "version": "26.6.3",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz",
+ "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==",
+ "dev": true,
+ "requires": {
+ "@jest/transform": "^26.6.2",
+ "@jest/types": "^26.6.2",
+ "@types/babel__core": "^7.1.7",
+ "babel-plugin-istanbul": "^6.0.0",
+ "babel-preset-jest": "^26.6.2",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.4",
+ "slash": "^3.0.0"
+ },
+ "dependencies": {
+ "@jest/types": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+ "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^15.0.0",
+ "chalk": "^4.0.0"
+ }
+ }
+ }
+ },
+ "babel-plugin-jest-hoist": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz",
+ "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.0.0",
+ "@types/babel__traverse": "^7.0.6"
+ }
+ },
+ "babel-preset-jest": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz",
+ "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-jest-hoist": "^26.6.2",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ }
+ },
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -34857,6 +35244,12 @@
"react-is": "^16.12.0"
}
},
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -40728,7 +41121,7 @@
"multicast-dns-service-types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
- "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
+ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true
},
"mute-stream": {
@@ -42985,6 +43378,11 @@
"resolved": "https://registry.npmjs.org/react-collapse/-/react-collapse-5.1.0.tgz",
"integrity": "sha512-5v0ywsn9HjiR/odNzbRDs0RZfrnbdSippJebWOBCFFDA12Vx8DddrbI4qWVf1P2wTiVagrpcSy07AU0b6+gM9Q=="
},
+ "react-content-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-6.2.0.tgz",
+ "integrity": "sha512-r1dI6S+uHNLW68qraLE2njJYOuy6976PpCExuCZUcABWbfnF3FMcmuESRI8L4Bj45wnZ7n8g71hkPLzbma7/Cw=="
+ },
"react-devtools-core": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.22.1.tgz",
@@ -43901,7 +44299,7 @@
"ajv": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
- "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "integrity": "sha512-I/bSHSNEcFFqXLf91nchoNB9D1Kie3QKcWdchYUaoIg1+1bdWDkdfdlvdIOJbi9U8xR0y+MWc5D+won9v95WlQ==",
"requires": {
"co": "^4.6.0",
"json-stable-stringify": "^1.0.1"
@@ -43910,12 +44308,12 @@
"ajv-keywords": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
- "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw="
+ "integrity": "sha512-vuBv+fm2s6cqUyey2A7qYcvsik+GMDJsw8BARP2sDE76cqmaZVarsvHf7Vx6VJ0Xk8gLl+u3MoAPf6gKzJefeA=="
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
},
"big.js": {
"version": "3.2.0",
@@ -43925,7 +44323,7 @@
"camelcase": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
- "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
+ "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g=="
},
"cliui": {
"version": "2.1.0",
@@ -44110,7 +44508,7 @@
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
- "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+ "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg=="
},
"cliui": {
"version": "3.2.0",
@@ -44135,7 +44533,7 @@
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
- "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
+ "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg=="
}
}
}
@@ -44952,7 +45350,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"reselect": {
@@ -45042,7 +45440,7 @@
"retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
"dev": true
},
"reusify": {
@@ -45260,7 +45658,7 @@
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
- "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
+ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
"dev": true
},
"selfsigned": {
@@ -45408,7 +45806,7 @@
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
- "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
+ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
"dev": true,
"requires": {
"accepts": "~1.3.4",
@@ -45432,7 +45830,7 @@
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true,
"requires": {
"depd": "~1.1.2",
@@ -45444,13 +45842,13 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"setprototypeof": {
@@ -45620,7 +46018,7 @@
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
"requires": {
"is-arrayish": "^0.3.1"
},
@@ -46196,7 +46594,7 @@
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
- "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
+ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-hash-64": {
"version": "1.0.3",
diff --git a/package.json b/package.json
index 12ce50c8408f..b49996102aba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.85-5",
+ "version": "1.1.86-5",
"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.",
@@ -77,6 +77,7 @@
"pusher-js": "^7.0.6",
"react": "^17.0.2",
"react-collapse": "^5.1.0",
+ "react-content-loader": "^6.1.0",
"react-dom": "^17.0.2",
"react-native": "0.66.4",
"react-native-collapsible": "^1.6.0",
@@ -139,7 +140,7 @@
"@welldone-software/why-did-you-render": "^6.2.0",
"ajv-cli": "^5.0.0",
"babel-eslint": "^10.1.0",
- "babel-jest": "^26.2.2",
+ "babel-jest": "^28.1.3",
"babel-loader": "^8.1.0",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-react-native-web": "^0.13.5",
diff --git a/src/CONST.js b/src/CONST.js
index 3fd74205bb05..13cee4e76827 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -12,8 +12,17 @@ const CONST = {
ANDROID_PACKAGE_NAME,
ANIMATED_TRANSITION: 300,
- // 50 megabytes in bytes
- API_MAX_ATTACHMENT_SIZE: 52428800,
+ API_ATTACHMENT_VALIDATIONS: {
+ // Same as the PHP layer allows
+ ALLOWED_EXTENSIONS: ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'html', 'txt', 'rtf', 'doc', 'docx', 'htm', 'tiff', 'tif', 'xml'],
+
+ // 50 megabytes in bytes
+ MAX_SIZE: 52428800,
+
+ // An arbitrary size, but the same minimum as in the PHP layer
+ MIN_SIZE: 240,
+ },
+
AVATAR_MAX_ATTACHMENT_SIZE: 6291456,
NEW_EXPENSIFY_URL: ACTIVE_EXPENSIFY_URL,
APP_DOWNLOAD_LINKS: {
@@ -106,6 +115,7 @@ const CONST = {
FREE_PLAN: 'freePlan',
DEFAULT_ROOMS: 'defaultRooms',
BETA_EXPENSIFY_WALLET: 'expensifyWallet',
+ BETA_COMMENT_LINKING: 'commentLinking',
INTERNATIONALIZATION: 'internationalization',
IOU_SEND: 'sendMoney',
POLICY_ROOMS: 'policyRooms',
@@ -466,9 +476,15 @@ const CONST = {
NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 300,
EMOJI_PICKER_ITEM_HEIGHT: 40,
EMOJI_PICKER_HEADER_HEIGHT: 38,
-
COMPOSER_MAX_HEIGHT: 125,
-
+ CHAT_SKELETON_VIEW: {
+ AVERAGE_ROW_HEIGHT: 80,
+ HEIGHT_FOR_ROW_COUNT: {
+ 1: 60,
+ 2: 80,
+ 3: 100,
+ },
+ },
EMAIL: {
CONCIERGE: 'concierge@expensify.com',
HELP: 'help@expensify.com',
@@ -680,6 +696,7 @@ const CONST = {
CARD_SECURITY_CODE: /^[0-9]{3,4}$/,
CARD_EXPIRATION_DATE: /^(0[1-9]|1[0-2])([^0-9])?([0-9]{4}|([0-9]{2}))$/,
PAYPAL_ME_USERNAME: /^[a-zA-Z0-9]+$/,
+ RATE_VALUE: /^\d{1,8}(\.\d*)?$/,
// Adapted from: https://gist.github.com/dperini/729294
// eslint-disable-next-line max-len
@@ -739,6 +756,7 @@ const CONST = {
SET: 'set',
},
},
+ MICROSECONDS_PER_MS: 1000,
};
export default CONST;
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index d5c05583b581..355cf8d37c2c 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -103,6 +103,9 @@ export default {
POLICY: 'policy_',
REPORTS_WITH_DRAFT: 'reportWithDraft_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
+ IS_LOADING_INITIAL_REPORT_ACTIONS: 'isLoadingInitialReportActions_',
+ IS_LOADING_MORE_REPORT_ACTIONS: 'isLoadingMoreReportActions_',
+ POLICY_MEMBER_LIST: 'policyMemberList_',
},
// Indicates which locale should be used
@@ -162,9 +165,6 @@ export default {
// The number of minutes a user has to wait for a call.
INBOX_CALL_USER_WAIT_TIME: 'inboxCallUserWaitTime',
- // Are report actions loading?
- IS_LOADING_REPORT_ACTIONS: 'isLoadingReportActions',
-
// Is report data loading?
IS_LOADING_REPORT_DATA: 'isLoadingReportData',
diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index 62598e658f0f..c0f926430b0c 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -148,12 +148,12 @@ class AddPlaidBankAccount extends React.Component {
)}
- {this.props.plaidData.error && (
+ {Boolean(this.props.plaidData.error) && (
{this.props.plaidData.error}
)}
- {token && (
+ {Boolean(token) && (
{
@@ -217,6 +217,7 @@ export default compose(
},
plaidLinkToken: {
key: ONYXKEYS.PLAID_LINK_TOKEN,
+ initWithStoredValues: false,
},
}),
)(AddPlaidBankAccount);
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index dda46321ef53..28c429876596 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import {View} from 'react-native';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
-import _ from 'lodash';
+import lodashExtend from 'lodash/extend';
+import _ from 'underscore';
import CONST from '../CONST';
import Navigation from '../libs/Navigation/Navigation';
import Modal from './Modal';
@@ -77,14 +78,17 @@ class AttachmentModal extends PureComponent {
reportId: null,
showArrows: false,
file: {name: lodashGet(props, 'originalFileName', '')},
+ isAttachmentInvalid: false,
+ attachmentInvalidReasonTitle: null,
+ attachmentInvalidReason: null,
sourceURL: props.sourceURL,
modalType: CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE,
};
this.submitAndClose = this.submitAndClose.bind(this);
this.closeConfirmModal = this.closeConfirmModal.bind(this);
- this.isValidSize = this.isValidSize.bind(this);
this.onArrowPress = this.onArrowPress.bind(this);
+ this.validateAndDisplayFileToUpload = this.validateAndDisplayFileToUpload.bind(this);
}
// this prevents a bug in iOS that would show the last image before closing then opening on a new image
@@ -119,22 +123,30 @@ class AttachmentModal extends PureComponent {
* @returns {String}
*/
getModalType(sourceUrl, file) {
- const modalType = (sourceUrl
- && (Str.isPDF(sourceUrl) || (file && Str.isPDF(file.name || this.props.translate('attachmentView.unknownFilename')))))
+ return (
+ sourceUrl
+ && (
+ Str.isPDF(sourceUrl)
+ || (
+ file
+ && Str.isPDF(file.name || this.props.translate('attachmentView.unknownFilename'))
+ )
+ )
+ )
? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE
: CONST.MODAL.MODAL_TYPE.CENTERED;
- return modalType;
}
/**
* Returns the filename split into fileName and fileExtension
+ *
+ * @param {String} fullFileName
* @returns {Object}
*/
- splitExtensionFromFileName() {
- const fullFileName = lodashGet(this.state, 'file.name', '').trim();
- const splittedFileName = fullFileName.split('.');
- const fileExtension = splittedFileName.pop();
- const fileName = splittedFileName.join('.');
+ splitExtensionFromFileName(fullFileName) {
+ const fileName = fullFileName.trim();
+ const splitFileName = fileName.split('.');
+ const fileExtension = splitFileName.pop();
return {fileName, fileExtension};
}
@@ -148,7 +160,7 @@ class AttachmentModal extends PureComponent {
}
if (this.props.onConfirm) {
- this.props.onConfirm(_.extend(this.state.file, {source: this.state.sourceURL}));
+ this.props.onConfirm(lodashExtend(this.state.file, {source: this.state.sourceURL}));
}
this.setState({isModalOpen: false});
@@ -158,16 +170,70 @@ class AttachmentModal extends PureComponent {
* Close the confirm modal.
*/
closeConfirmModal() {
- this.setState({isConfirmModalOpen: false});
+ this.setState({isAttachmentInvalid: false});
}
/**
- * Check if the attachment size is less than the API size limit.
* @param {Object} file
* @returns {Boolean}
*/
- isValidSize(file) {
- return !file || lodashGet(file, 'size', 0) < CONST.API_MAX_ATTACHMENT_SIZE;
+ isValidFile(file) {
+ if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
+ this.setState({
+ isAttachmentInvalid: true,
+ attachmentInvalidReasonTitle: this.props.translate('attachmentPicker.attachmentTooLarge'),
+ attachmentInvalidReason: this.props.translate('attachmentPicker.sizeExceeded'),
+ });
+ return false;
+ }
+
+ if (lodashGet(file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
+ this.setState({
+ isAttachmentInvalid: true,
+ attachmentInvalidReasonTitle: this.props.translate('attachmentPicker.attachmentTooSmall'),
+ attachmentInvalidReason: this.props.translate('attachmentPicker.sizeNotMet'),
+ });
+ return false;
+ }
+
+ const {fileExtension} = this.splitExtensionFromFileName(lodashGet(file, 'name', ''));
+ if (!_.contains(CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_EXTENSIONS, fileExtension)) {
+ const invalidReason = `${this.props.translate('attachmentPicker.notAllowedExtension')} ${CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_EXTENSIONS.join(', ')}`;
+ this.setState({
+ isAttachmentInvalid: true,
+ attachmentInvalidReasonTitle: this.props.translate('attachmentPicker.wrongFileType'),
+ attachmentInvalidReason: invalidReason,
+ });
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param {Object} file
+ */
+ validateAndDisplayFileToUpload(file) {
+ if (!file) {
+ return;
+ }
+
+ if (!this.isValidFile(file)) {
+ return;
+ }
+
+ if (file instanceof File) {
+ const source = URL.createObjectURL(file);
+ const modalType = this.getModalType(source, file);
+ this.setState({
+ isModalOpen: true, sourceURL: source, file, modalType,
+ });
+ } else {
+ const modalType = this.getModalType(file.uri, file);
+ this.setState({
+ isModalOpen: true, sourceURL: file.uri, file, modalType,
+ });
+ }
}
render() {
@@ -179,7 +245,7 @@ class AttachmentModal extends PureComponent {
? [styles.imageModalImageCenterContainer]
: [styles.imageModalImageCenterContainer, styles.p5];
- const {fileName, fileExtension} = this.splitExtensionFromFileName();
+ const {fileName, fileExtension} = this.splitExtensionFromFileName(lodashGet(this.state, 'file.name') || this.props.originalFileName);
return (
<>
@@ -234,34 +300,19 @@ class AttachmentModal extends PureComponent {
/>
)}
+
+
{this.props.children({
- displayFileInModal: ({file}) => {
- if (!this.isValidSize(file)) {
- this.setState({isConfirmModalOpen: true});
- return;
- }
- if (file instanceof File) {
- const source = URL.createObjectURL(file);
- const modalType = this.getModalType(source, file);
- this.setState({
- isModalOpen: true, sourceURL: source, file, modalType,
- });
- } else {
- const modalType = this.getModalType(file.uri, file);
- this.setState({
- isModalOpen: true, sourceURL: file.uri, file, modalType,
- });
- }
- },
+ displayFileInModal: this.validateAndDisplayFileToUpload,
show: () => {
const route = Navigation.getActiveRoute();
let reportId = null;
diff --git a/src/components/Avatar.js b/src/components/Avatar.js
index ff51d2c427b8..eb5f28f64efe 100644
--- a/src/components/Avatar.js
+++ b/src/components/Avatar.js
@@ -8,6 +8,7 @@ import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import * as StyleUtils from '../styles/StyleUtils';
import * as Expensicons from './Icon/Expensicons';
+import getAvatarDefaultSource from '../libs/getAvatarDefaultSource';
const propTypes = {
/** Source for the avatar. Can be a URL or an icon. */
@@ -70,7 +71,12 @@ class Avatar extends PureComponent {
/>
)
: (
- this.setState({imageError: true})} />
+ this.setState({imageError: true})}
+ />
)}
);
diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js
index 61a7a159e9ee..2e8082a963d7 100644
--- a/src/components/BigNumberPad.js
+++ b/src/components/BigNumberPad.js
@@ -63,7 +63,7 @@ class BigNumberPad extends React.Component {
style={[styles.flex1, marginLeft]}
text={column === '<' ? column : this.props.toLocaleDigit(column)}
onLongPress={() => this.handleLongPress(column)}
- onPress={() => this.props.numberPressed(column === '<' ? column : this.props.toLocaleDigit(column))}
+ onPress={() => this.props.numberPressed(column)}
onPressIn={ControlSelection.block}
onPressOut={() => {
clearInterval(this.state.timer);
diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js
index c0a606f32958..a7c1d0045170 100755
--- a/src/components/Composer/index.js
+++ b/src/components/Composer/index.js
@@ -108,6 +108,8 @@ const IMAGE_EXTENSIONS = {
'image/webp': 'webp',
};
+const COPY_DROP_EFFECT = 'copy';
+
/**
* Enable Markdown parsing.
* On web we like to have the Text Input field always focused so the user can easily type a new chat
@@ -197,13 +199,15 @@ class Composer extends React.Component {
dragNDropListener(e) {
let isOriginComposer = false;
const handler = () => {
+ // Setting dropEffect for dragover is required for '+' icon on certain platforms/browsers (eg. Safari)
switch (e.type) {
case 'dragover':
e.preventDefault();
+ e.dataTransfer.dropEffect = COPY_DROP_EFFECT;
this.props.onDragOver(e, isOriginComposer);
break;
case 'dragenter':
- e.dataTransfer.dropEffect = 'copy';
+ e.dataTransfer.dropEffect = COPY_DROP_EFFECT;
this.props.onDragEnter(e, isOriginComposer);
break;
case 'dragleave':
diff --git a/src/components/ConfirmModal.js b/src/components/ConfirmModal.js
index 5c89135bef7b..bf7d1e2fffda 100755
--- a/src/components/ConfirmModal.js
+++ b/src/components/ConfirmModal.js
@@ -7,7 +7,7 @@ import ConfirmContent from './ConfirmContent';
const propTypes = {
/** Title of the modal */
- title: PropTypes.string.isRequired,
+ title: PropTypes.string,
/** A callback to call when the form has been submitted */
onConfirm: PropTypes.func.isRequired,
@@ -54,6 +54,7 @@ const defaultProps = {
onCancel: () => {},
shouldShowCancelButton: true,
shouldSetModalVisibility: true,
+ title: '',
onModalHide: () => {},
};
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js
index 387f58bc6c6b..2c9ef4a3ad33 100755
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js
@@ -86,8 +86,10 @@ class EmojiPickerMenu extends Component {
this.isMobileLandscape = this.isMobileLandscape.bind(this);
this.onSelectionChange = this.onSelectionChange.bind(this);
this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this);
+ this.setFirstNonHeaderIndex = this.setFirstNonHeaderIndex.bind(this);
this.currentScrollOffset = 0;
+ this.firstNonHeaderIndex = 0;
this.state = {
filteredEmojis: this.emojis,
@@ -111,6 +113,7 @@ class EmojiPickerMenu extends Component {
this.props.forwardedRef(this.searchInput);
}
this.setupEventHandlers();
+ this.setFirstNonHeaderIndex(this.emojis);
}
componentWillUnmount() {
@@ -126,6 +129,14 @@ class EmojiPickerMenu extends Component {
this.setState({selection: event.nativeEvent.selection});
}
+ /**
+ * Find and store index of the first emoji item
+ * @param {Array} filteredEmojis
+ */
+ setFirstNonHeaderIndex(filteredEmojis) {
+ this.firstNonHeaderIndex = _.findIndex(filteredEmojis, item => !item.spacer && !item.header);
+ }
+
/**
* Setup and attach keypress/mouse handlers for highlight navigation.
*/
@@ -214,10 +225,12 @@ class EmojiPickerMenu extends Component {
* @param {String} arrowKey
*/
highlightAdjacentEmoji(arrowKey) {
- const firstNonHeaderIndex = this.state.filteredEmojis.length === this.emojis.length ? this.numColumns : 0;
+ if (this.state.filteredEmojis.length === 0) {
+ return;
+ }
// Arrow Down and Arrow Right enable arrow navigation when search is focused
- if (this.searchInput && this.searchInput.isFocused() && this.state.filteredEmojis.length) {
+ if (this.searchInput && this.searchInput.isFocused()) {
if (arrowKey !== 'ArrowDown' && arrowKey !== 'ArrowRight') {
return;
}
@@ -242,7 +255,7 @@ class EmojiPickerMenu extends Component {
// If nothing is highlighted and an arrow key is pressed
// select the first emoji
if (this.state.highlightedIndex === -1) {
- this.setState({highlightedIndex: firstNonHeaderIndex});
+ this.setState({highlightedIndex: this.firstNonHeaderIndex});
this.scrollToHighlightedIndex();
return;
}
@@ -273,7 +286,7 @@ class EmojiPickerMenu extends Component {
break;
case 'ArrowLeft':
move(-1,
- () => this.state.highlightedIndex - 1 < firstNonHeaderIndex,
+ () => this.state.highlightedIndex - 1 < this.firstNonHeaderIndex,
() => {
// Reaching start of the list, arrow left set the focus to searchInput.
this.focusInputWithTextSelect();
@@ -286,7 +299,7 @@ class EmojiPickerMenu extends Component {
case 'ArrowUp':
move(
-this.numColumns,
- () => this.state.highlightedIndex - this.numColumns < firstNonHeaderIndex,
+ () => this.state.highlightedIndex - this.numColumns < this.firstNonHeaderIndex,
() => {
// Reaching start of the list, arrow up set the focus to searchInput.
this.focusInputWithTextSelect();
@@ -356,6 +369,7 @@ class EmojiPickerMenu extends Component {
headerIndices: this.unfilteredHeaderIndices,
highlightedIndex: -1,
});
+ this.setFirstNonHeaderIndex(this.emojis);
return;
}
@@ -370,6 +384,7 @@ class EmojiPickerMenu extends Component {
// Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky
this.setState({filteredEmojis: newFilteredEmojiList, headerIndices: [], highlightedIndex: 0});
+ this.setFirstNonHeaderIndex(newFilteredEmojiList);
}
/**
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index 9c64691a2220..a78850c71505 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -19,6 +19,7 @@ import ClosedSign from '../../../assets/images/closed-sign.svg';
import Collapse from '../../../assets/images/collapse.svg';
import Concierge from '../../../assets/images/concierge.svg';
import CreditCard from '../../../assets/images/creditcard.svg';
+import DotIndicator from '../../../assets/images/dot-indicator.svg';
import DownArrow from '../../../assets/images/down.svg';
import Download from '../../../assets/images/download.svg';
import Emoji from '../../../assets/images/emoji.svg';
@@ -109,6 +110,7 @@ export {
CreditCard,
DeletedRoomAvatar,
DomainRoomAvatar,
+ DotIndicator,
DownArrow,
Download,
Emoji,
diff --git a/src/components/Icon/Illustrations.js b/src/components/Icon/Illustrations.js
index 9da4bcae764f..f7466159a964 100644
--- a/src/components/Icon/Illustrations.js
+++ b/src/components/Icon/Illustrations.js
@@ -14,6 +14,7 @@ import MoneyMousePink from '../../../assets/images/product-illustrations/money-m
import ReceiptYellow from '../../../assets/images/product-illustrations/receipt--yellow.svg';
import RocketOrange from '../../../assets/images/product-illustrations/rocket--orange.svg';
import TadaYellow from '../../../assets/images/product-illustrations/tada--yellow.svg';
+import TadaBlue from '../../../assets/images/product-illustrations/tada--blue.svg';
import GpsTrackOrange from '../../../assets/images/product-illustrations/gps-track--orange.svg';
export {
@@ -33,5 +34,6 @@ export {
ReceiptYellow,
RocketOrange,
TadaYellow,
+ TadaBlue,
GpsTrackOrange,
};
diff --git a/src/components/InlineCodeBlock/WrappedText.js b/src/components/InlineCodeBlock/WrappedText.js
index be693e534b11..506ca51a4cdf 100644
--- a/src/components/InlineCodeBlock/WrappedText.js
+++ b/src/components/InlineCodeBlock/WrappedText.js
@@ -36,6 +36,10 @@ const defaultProps = {
};
const WrappedText = (props) => {
+ if (!_.isString(props.children)) {
+ return null;
+ }
+
const textMatrix = getTextMatrix(props.children);
return (
<>
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index 0be18a83a916..08b3085c0be3 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -14,6 +14,8 @@ import Badge from './Badge';
import CONST from '../CONST';
import menuItemPropTypes from './menuItemPropTypes';
import SelectCircle from './SelectCircle';
+import colors from '../styles/colors';
+import variables from '../styles/variables';
const propTypes = {
...menuItemPropTypes,
@@ -40,6 +42,7 @@ const defaultProps = {
onPress: () => {},
interactive: true,
fallbackIcon: Expensicons.FallbackAvatar,
+ brickRoadIndicator: undefined,
};
const MenuItem = props => (
@@ -119,7 +122,6 @@ const MenuItem = props => (
{props.badgeText && }
-
{/* Since subtitle can be of type number, we should allow 0 to be shown */}
{(props.subtitle || props.subtitle === 0) && (
@@ -130,6 +132,16 @@ const MenuItem = props => (
)}
+ {props.brickRoadIndicator && (
+
+
+
+ )}
{props.shouldShowRightIcon && (
{
+ if (containerStyles.length) {
+ return containerStyles;
+ }
+ return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator;
+};
+
const OfflineIndicator = (props) => {
if (!props.network.isOffline) {
return null;
@@ -24,16 +44,18 @@ const OfflineIndicator = (props) => {
return (
-
+
{props.translate('common.youAppearToBeOffline')}
@@ -41,9 +63,11 @@ const OfflineIndicator = (props) => {
};
OfflineIndicator.propTypes = propTypes;
+OfflineIndicator.defaultProps = defaultProps;
OfflineIndicator.displayName = 'OfflineIndicator';
export default compose(
+ withWindowDimensions,
withLocalize,
withNetwork(),
)(OfflineIndicator);
diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js
new file mode 100644
index 000000000000..196a4da3d403
--- /dev/null
+++ b/src/components/OfflineWithFeedback.js
@@ -0,0 +1,131 @@
+import _ from 'underscore';
+import React from 'react';
+import {Pressable, View} from 'react-native';
+import PropTypes from 'prop-types';
+import compose from '../libs/compose';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import {withNetwork} from './OnyxProvider';
+import networkPropTypes from './networkPropTypes';
+import Text from './Text';
+import styles from '../styles/styles';
+import Tooltip from './Tooltip';
+import Icon from './Icon';
+import * as Expensicons from './Icon/Expensicons';
+import * as StyleUtils from '../styles/StyleUtils';
+import colors from '../styles/colors';
+import variables from '../styles/variables';
+
+/**
+ * This component should be used when we are using the offline pattern B (offline with feedback).
+ * You should enclose any element that should have feedback that the action was taken offline and it will take
+ * care of adding the appropriate styles for pending actions and displaying the dismissible error.
+ */
+
+const propTypes = {
+ /** The type of action that's pending */
+ pendingAction: PropTypes.oneOf(['add', 'update', 'delete']),
+
+ /** The errors to display */
+ // eslint-disable-next-line react/forbid-prop-types
+ errors: PropTypes.object,
+
+ /** A function to run when the X button next to the error is clicked */
+ onClose: PropTypes.func.isRequired,
+
+ /** The content that needs offline feedback */
+ children: PropTypes.node.isRequired,
+
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
+ /** Additional styles to add after local styles. Applied to Pressable portion of button */
+ style: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.object),
+ PropTypes.object,
+ ]),
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ pendingAction: null,
+ errors: null,
+ style: [],
+};
+
+/**
+ * This method applies the strikethrough to all the children passed recursively
+ * @param {Array} children
+ * @return {Array}
+ */
+function applyStrikeThrough(children) {
+ return React.Children.map(children, (child) => {
+ if (!React.isValidElement(child)) {
+ return child;
+ }
+ const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted)};
+ if (child.props.children) {
+ props.children = applyStrikeThrough(child.props.children);
+ }
+ return React.cloneElement(child, props);
+ });
+}
+
+const OfflineWithFeedback = (props) => {
+ const hasErrors = !_.isEmpty(props.errors);
+ const isOfflinePendingAction = props.network.isOffline && props.pendingAction;
+ const isUpdateOrDeleteError = hasErrors && (props.pendingAction === 'delete' || props.pendingAction === 'update');
+ const isAddError = hasErrors && props.pendingAction === 'add';
+ const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError;
+ const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete';
+ const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !hasErrors;
+ let children = props.children;
+ const sortedErrors = _.chain(props.errors)
+ .keys()
+ .sortBy()
+ .map(key => props.errors[key])
+ .value();
+
+ // Apply strikethrough to children if needed, but skip it if we are not going to render them
+ if (needsStrikeThrough && !hideChildren) {
+ children = applyStrikeThrough(children);
+ }
+ return (
+
+ {!hideChildren && (
+
+ {children}
+
+ )}
+ {hasErrors && (
+
+
+
+
+
+ {_.map(sortedErrors, (error, i) => (
+ {error}
+ ))}
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+OfflineWithFeedback.propTypes = propTypes;
+OfflineWithFeedback.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withNetwork(),
+)(OfflineWithFeedback);
diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js
index 61901bec3050..a6a75bceb48a 100644
--- a/src/components/OptionRow.js
+++ b/src/components/OptionRow.js
@@ -62,6 +62,8 @@ const propTypes = {
/** Whether this option should be disabled */
isDisabled: PropTypes.bool,
+ style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+
...withLocalizePropTypes,
};
@@ -77,6 +79,7 @@ const defaultProps = {
onSelectRow: () => {},
isDisabled: false,
optionIsFocused: false,
+ style: null,
};
const OptionRow = (props) => {
@@ -86,12 +89,12 @@ const OptionRow = (props) => {
: styles.sidebarLinkText;
const textUnreadStyle = (props.option.isUnread || props.forceTextUnreadStyle)
? [textStyle, styles.sidebarLinkTextUnread] : [textStyle];
- const displayNameStyle = props.mode === CONST.OPTION_MODE.COMPACT
+ const displayNameStyle = StyleUtils.combineStyles(props.mode === CONST.OPTION_MODE.COMPACT
? [styles.optionDisplayName, ...textUnreadStyle, styles.optionDisplayNameCompact, styles.mr2]
- : [styles.optionDisplayName, ...textUnreadStyle];
- const alternateTextStyle = props.mode === CONST.OPTION_MODE.COMPACT
+ : [styles.optionDisplayName, ...textUnreadStyle], props.style);
+ const alternateTextStyle = StyleUtils.combineStyles(props.mode === CONST.OPTION_MODE.COMPACT
? [textStyle, styles.optionAlternateText, styles.textLabelSupporting, styles.optionAlternateTextCompact]
- : [textStyle, styles.optionAlternateText, styles.textLabelSupporting];
+ : [textStyle, styles.optionAlternateText, styles.textLabelSupporting], props.style);
const contentContainerStyles = props.mode === CONST.OPTION_MODE.COMPACT
? [styles.flex1, styles.flexRow, styles.overflowHidden, styles.alignItemsCenter]
: [styles.flex1];
diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js
new file mode 100644
index 000000000000..51b0fe54f73b
--- /dev/null
+++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Rect, Circle} from 'react-native-svg';
+import SkeletonViewContentLoader from 'react-content-loader/native';
+import CONST from '../../CONST';
+
+const propTypes = {
+ /** Number of rows to show in Skeleton UI block */
+ numberOfRows: PropTypes.number.isRequired,
+};
+
+const SkeletonViewLines = props => (
+
+
+
+
+ {props.numberOfRows > 1 && }
+ {props.numberOfRows > 2 && }
+
+);
+
+SkeletonViewLines.displayName = 'SkeletonViewLines';
+SkeletonViewLines.propTypes = propTypes;
+export default SkeletonViewLines;
diff --git a/src/components/ReportActionsSkeletonView/index.js b/src/components/ReportActionsSkeletonView/index.js
new file mode 100644
index 000000000000..6d04a2a5001e
--- /dev/null
+++ b/src/components/ReportActionsSkeletonView/index.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SkeletonViewLines from './SkeletonViewLines';
+import CONST from '../../CONST';
+
+const propTypes = {
+ /** Height of the container component */
+ containerHeight: PropTypes.number.isRequired,
+};
+
+const ReportActionsSkeletonView = (props) => {
+ // Determines the number of content items based on container height
+ const possibleVisibleContentItems = Math.floor(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
+ const skeletonViewLines = [];
+ for (let index = 0; index < possibleVisibleContentItems; index++) {
+ const iconIndex = (index + 1) % 4;
+ switch (iconIndex) {
+ case 2:
+ skeletonViewLines.push();
+ break;
+ case 0:
+ skeletonViewLines.push();
+ break;
+ default:
+ skeletonViewLines.push();
+ }
+ }
+ return <>{skeletonViewLines}>;
+};
+
+ReportActionsSkeletonView.displayName = 'ReportActionsSkeletonView';
+ReportActionsSkeletonView.propTypes = propTypes;
+export default ReportActionsSkeletonView;
diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js
index 2a79f6b0a055..a083af0f2e16 100644
--- a/src/components/TextInput/BaseTextInput.js
+++ b/src/components/TextInput/BaseTextInput.js
@@ -14,7 +14,6 @@ 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 getSecureEntryKeyboardType from '../../libs/getSecureEntryKeyboardType';
class BaseTextInput extends Component {
@@ -31,7 +30,6 @@ class BaseTextInput extends Component {
passwordHidden: props.secureTextEntry,
textInputWidth: 0,
prefixWidth: 0,
- 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,
@@ -214,7 +212,6 @@ class BaseTextInput extends Component {
>
this.setState({height: event.nativeEvent.layout.height})}
style={[
textInputContainerStyles,
@@ -267,7 +264,6 @@ class BaseTextInput extends Component {
!hasLabel && styles.pv0,
this.props.prefixCharacter && StyleUtils.getPaddingLeft(this.state.prefixWidth + styles.pl1.paddingLeft),
this.props.secureTextEntry && styles.secureInput,
- {height: this.state.height},
]}
multiline={this.props.multiline}
maxLength={this.props.maxLength}
diff --git a/src/languages/en.js b/src/languages/en.js
index ddfca9d73d35..c1ea9b8db04b 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -119,6 +119,10 @@ export default {
chooseDocument: 'Choose document',
attachmentTooLarge: 'Attachment too large',
sizeExceeded: 'Attachment size is larger than 50 MB limit.',
+ attachmentTooSmall: 'Attachment too small',
+ sizeNotMet: 'Attachment size must be greater than 240 bytes',
+ wrongFileType: 'Attachment is the wrong type',
+ notAllowedExtension: 'Attachments must be one of the following types: ',
},
composer: {
noExtentionFoundForMimeType: 'No extension found for mime type',
@@ -387,16 +391,12 @@ export default {
deleteBankAccountSuccess: 'Bank account successfully deleted',
deleteDebitCardSuccess: 'Debit Card successfully deleted',
deletePayPalSuccess: 'PayPal.me successfully deleted',
- allSet: 'All Set!',
- transferConfirmText: ({amount}) => `${amount} will hit your account shortly!`,
- gotIt: 'Got it, Thanks!',
error: {
notOwnerOfBankAccount: 'There was an error setting this bank account as your default payment method.',
invalidBankAccount: 'This bank account is temporarily suspended.',
notOwnerOfFund: 'There was an error setting this card as your default payment method.',
setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.',
},
- addBankAccountSuccess: 'Your bank account has successfully been added.',
addBankAccountFailure: 'And unexpected error occurred while trying to add your bank account. Please try again.',
},
transferAmountPage: {
@@ -407,6 +407,9 @@ export default {
achSummary: 'No fee',
whichAccount: 'Which Account?',
fee: 'Fee',
+ transferSuccess: 'Transfer successful!',
+ transferDetailBankAccount: 'Your money should arrive in the next 1-3 business days.',
+ transferDetailDebitCard: 'Your money should arrive immediately.',
failedTransfer: 'Your balance isn’t fully settled. Please transfer to a bank account.',
},
chooseTransferAccountPage: {
@@ -569,6 +572,8 @@ export default {
enterPassword: 'Enter Expensify password',
alreadyAdded: 'This account has already been added.',
chooseAccountLabel: 'Account',
+ successTitle: 'Personal bank account added!',
+ successMessage: 'Congrats, your bank account is set up and ready to receive reimbursements.',
},
attachmentView: {
unknownFilename: 'Unknown filename',
diff --git a/src/languages/es.js b/src/languages/es.js
index 11136b8a23bd..ccc8b1ef36d1 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -119,6 +119,10 @@ export default {
chooseDocument: 'Elegir documento',
attachmentTooLarge: 'Archivo adjunto demasiado grande',
sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.',
+ attachmentTooSmall: 'Archivo adjunto demasiado pequeño',
+ sizeNotMet: 'El archivo adjunto debe ser mas grande que 240 bytes',
+ wrongFileType: 'El tipo del archivo adjunto es incorrecto',
+ notAllowedExtension: 'Los archivos adjuntos deben ser de uno de los siguientes tipos: ',
},
composer: {
noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido',
@@ -387,16 +391,12 @@ export default {
deleteBankAccountSuccess: 'Cuenta bancaria eliminada correctamente',
deleteDebitCardSuccess: 'Tarjeta de débito eliminada correctamente',
deletePayPalSuccess: 'PayPal.me eliminada correctamente',
- allSet: 'Todo listo!',
- transferConfirmText: ({amount}) => `${amount} llegará a tu cuenta en breve!`,
- gotIt: 'Gracias!',
error: {
notOwnerOfBankAccount: 'Ha ocurrido un error al establecer esta cuenta bancaria como tu método de pago predeterminado.',
invalidBankAccount: 'Esta cuenta bancaria está temporalmente suspendida.',
notOwnerOfFund: 'Ha ocurrido un error al establecer esta tarjeta de crédito como tu método de pago predeterminado.',
setDefaultFailure: 'No se ha podido configurar el método de pago.',
},
- addBankAccountSuccess: 'Su cuenta bancaria ha sido añadida con éxito.',
addBankAccountFailure: 'Y ocurrió un error inesperado al intentar agregar su cuenta bancaria. Inténtalo de nuevo.',
},
transferAmountPage: {
@@ -407,6 +407,9 @@ export default {
achSummary: 'Sin cargo',
whichAccount: '¿Que cuenta?',
fee: 'Tarifa',
+ transferSuccess: '¡Transferencia exitosa!',
+ transferDetailBankAccount: 'Tu dinero debería llegar en 1-3 días laborables.',
+ transferDetailDebitCard: 'Tu dinero debería llegar de inmediato.',
failedTransfer: 'Tu saldo no se ha acreditado completamente. Por favor transfiere los fondos a una cuenta bancaria.',
},
chooseTransferAccountPage: {
@@ -569,6 +572,8 @@ export default {
enterPassword: 'Escribe tu contraseña de Expensify',
alreadyAdded: 'Esta cuenta ya ha sido agregada.',
chooseAccountLabel: 'Cuenta',
+ successTitle: '¡Cuenta bancaria personal añadida!',
+ successMessage: 'Enhorabuena, tu cuenta bancaria está lista para recibir reembolsos.',
},
attachmentView: {
unknownFilename: 'Archivo desconocido',
diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js
index 522b2500a519..0c432888b3b8 100644
--- a/src/libs/DateUtils.js
+++ b/src/libs/DateUtils.js
@@ -29,7 +29,7 @@ let timezone = CONST.DEFAULT_TIME_ZONE;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS,
callback: (val) => {
- timezone = lodashGet(val, currentUserEmail, 'timezone', CONST.DEFAULT_TIME_ZONE);
+ timezone = lodashGet(val, [currentUserEmail, 'timezone'], CONST.DEFAULT_TIME_ZONE);
},
});
@@ -161,6 +161,15 @@ function setTimezoneUpdated() {
lastUpdatedTimezoneTime = moment();
}
+/**
+ * Get the UNIX timestamp in microseconds, with millisecond precision.
+ *
+ * @returns {Number}
+ */
+function getMicroseconds() {
+ return Date.now() * CONST.MICROSECONDS_PER_MS;
+}
+
/**
* @namespace DateUtils
*/
@@ -173,6 +182,7 @@ const DateUtils = {
getCurrentTimezone,
canUpdateTimezone,
setTimezoneUpdated,
+ getMicroseconds,
};
export default DateUtils;
diff --git a/src/libs/Firebase/index.native.js b/src/libs/Firebase/index.native.js
index adebd4c80ca0..49df7a999f7b 100644
--- a/src/libs/Firebase/index.native.js
+++ b/src/libs/Firebase/index.native.js
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */
import perf from '@react-native-firebase/perf';
+import lodashGet from 'lodash/get';
import * as Environment from '../Environment/Environment';
import Log from '../Log';
@@ -32,12 +33,11 @@ function startTrace(customEventName) {
*/
function stopTrace(customEventName) {
const stop = global.performance.now();
-
if (Environment.isDevelopment()) {
return;
}
- const {trace, start} = traceMap[customEventName];
+ const trace = lodashGet(traceMap, [customEventName, 'trace']);
if (!trace) {
return;
}
@@ -45,6 +45,7 @@ function stopTrace(customEventName) {
trace.stop();
// Uncomment to inspect logs on release builds
+ // const start = lodashGet(traceMap, [customEventName, 'start']);
// Log.info(`sidebar_loaded: ${stop - start} ms`, true);
delete traceMap[customEventName];
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 13920d1c6dd4..4779e0ae1ca9 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -31,14 +31,11 @@ import MainDrawerNavigator from './MainDrawerNavigator';
// Modal Stack Navigators
import * as ModalStackNavigators from './ModalStackNavigators';
import SCREENS from '../../../SCREENS';
-import Timers from '../../Timers';
import ValidateLoginPage from '../../../pages/ValidateLoginPage';
import defaultScreenOptions from './defaultScreenOptions';
import * as App from '../../actions/App';
import * as Session from '../../actions/Session';
import LogOutPreviousUserPage from '../../../pages/LogOutPreviousUserPage';
-import networkPropTypes from '../../../components/networkPropTypes';
-import {withNetwork} from '../../../components/OnyxProvider';
let currentUserEmail;
Onyx.connect({
@@ -60,7 +57,7 @@ Onyx.connect({
return;
}
- const timezone = lodashGet(val, currentUserEmail, 'timezone', {});
+ const timezone = lodashGet(val, [currentUserEmail, 'timezone'], {});
const currentTimezone = moment.tz.guess(true);
// If the current timezone is different than the user's timezone, and their timezone is set to automatic
@@ -88,9 +85,6 @@ const modalScreenListeners = {
};
const propTypes = {
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
...windowDimensionsPropTypes,
};
@@ -117,23 +111,11 @@ class AuthScreens extends React.Component {
// Listen for report changes and fetch some data we need on initialization
UnreadIndicatorUpdater.listenForReportChanges();
- App.getAppData(false);
- App.openApp();
+ App.getAppData();
+ App.openApp(this.props.allPolicies);
App.fixAccountAndReloadData();
App.setUpPoliciesAndNavigate(this.props.session);
-
- // Refresh the personal details, timezone and betas every 30 minutes
- // There is no pusher event that sends updated personal details data yet
- // See https://github.com/Expensify/ReactNativeChat/issues/468
- this.interval = Timers.register(setInterval(() => {
- if (this.props.network.isOffline) {
- return;
- }
- PersonalDetails.fetchPersonalDetails();
- User.getBetas();
- }, 1000 * 60 * 30));
-
Timing.end(CONST.TIMING.HOMEPAGE_INITIAL_RENDER);
const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH;
@@ -333,10 +315,12 @@ class AuthScreens extends React.Component {
AuthScreens.propTypes = propTypes;
export default compose(
withWindowDimensions,
- withNetwork(),
withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
+ allPolicies: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
}),
)(AuthScreens);
diff --git a/src/libs/Navigation/CustomActions.js b/src/libs/Navigation/DeprecatedCustomActions.js
similarity index 99%
rename from src/libs/Navigation/CustomActions.js
rename to src/libs/Navigation/DeprecatedCustomActions.js
index c5fb5f2a4154..1c192d60f93e 100644
--- a/src/libs/Navigation/CustomActions.js
+++ b/src/libs/Navigation/DeprecatedCustomActions.js
@@ -16,6 +16,7 @@ function getActiveState() {
/**
* Go back to the Main Drawer
+ * @deprecated
* @param {Object} navigationRef
*/
function navigateBackToRootDrawer() {
@@ -64,6 +65,7 @@ function getScreenNameFromState(state) {
*
* More context here: https://github.com/react-navigation/react-navigation/issues/9744
*
+ * @deprecated
* @param {String} route
* @returns {Function}
*/
diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js
index c19f10c635d8..6891244a2760 100644
--- a/src/libs/Navigation/Navigation.js
+++ b/src/libs/Navigation/Navigation.js
@@ -5,7 +5,7 @@ import Onyx from 'react-native-onyx';
import Log from '../Log';
import linkTo from './linkTo';
import ROUTES from '../../ROUTES';
-import CustomActions from './CustomActions';
+import DeprecatedCustomActions from './DeprecatedCustomActions';
import ONYXKEYS from '../../ONYXKEYS';
import linkingConfig from './linkingConfig';
import navigationRef from './navigationRef';
@@ -140,7 +140,7 @@ function navigate(route = ROUTES.HOME) {
}
if (isDrawerRoute(route)) {
- navigationRef.current.dispatch(CustomActions.pushDrawerRoute(route));
+ navigationRef.current.dispatch(DeprecatedCustomActions.pushDrawerRoute(route));
return;
}
@@ -161,7 +161,7 @@ function dismissModal(shouldOpenDrawer = false) {
? shouldOpenDrawer
: false;
- CustomActions.navigateBackToRootDrawer();
+ DeprecatedCustomActions.navigateBackToRootDrawer();
if (normalizedShouldOpenDrawer) {
openDrawer();
}
diff --git a/src/libs/Network/enhanceParameters.js b/src/libs/Network/enhanceParameters.js
index 9c7356685ce1..093c0d96c087 100644
--- a/src/libs/Network/enhanceParameters.js
+++ b/src/libs/Network/enhanceParameters.js
@@ -19,7 +19,6 @@ function isAuthTokenRequired(command) {
'SetPassword',
'User_SignUp',
'ResendValidateCode',
- 'ResetPassword',
'User_ReopenAccount',
'ValidateEmail',
], command);
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 53ae93cf8b74..ad98e8fac8f5 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -89,10 +89,6 @@ Onyx.connect({
},
});
-// We are initializing a default avatar here so that we use the same default color for each user we are inviting. This
-// will update when the OptionsListUtils re-loads. But will stay the same color for the life of the JS session.
-const defaultAvatarForUserToInvite = ReportUtils.getDefaultAvatar();
-
/**
* Adds expensify SMS domain (@expensify.sms) if login is a phone number and if it's not included yet
*
@@ -591,7 +587,7 @@ function getOptions(reports, personalDetails, activeReportID, {
userToInvite = createOption([login], personalDetails, null, {
showChatPreviewLine,
});
- userToInvite.icons = [defaultAvatarForUserToInvite];
+ userToInvite.icons = [ReportUtils.getDefaultAvatar(login)];
}
// If we are prioritizing 1:1 chats in search, do it only once we started searching
diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js
index 2f61c5ecde68..5de388f8f605 100644
--- a/src/libs/Permissions.js
+++ b/src/libs/Permissions.js
@@ -67,6 +67,14 @@ function canUseWallet(betas) {
return _.contains(betas, CONST.BETAS.BETA_EXPENSIFY_WALLET) || canUseAllBetas(betas);
}
+/**
+ * @param {Array} betas
+ * @returns {Boolean}
+ */
+function canUseCommentLinking(betas) {
+ return _.contains(betas, CONST.BETAS.BETA_COMMENT_LINKING) || canUseAllBetas(betas);
+}
+
/**
* We're requiring you to be added to the policy rooms beta on dev,
* since contributors have been reporting a number of false issues related to the feature being under development.
@@ -94,6 +102,7 @@ export default {
canUseInternationalization,
canUseIOUSend,
canUseWallet,
+ canUseCommentLinking,
canUsePolicyRooms,
canUsePolicyExpenseChat,
};
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index 066cf4970776..29f22e0f68c0 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -1,3 +1,4 @@
+import lodashGet from 'lodash/get';
import _ from 'underscore';
import CONST from '../CONST';
@@ -7,7 +8,8 @@ import CONST from '../CONST';
*/
function isDeletedAction(reportAction) {
// A deleted comment has either an empty array or an object with html field with empty string as value
- return reportAction.message.length === 0 || reportAction.message[0].html === '';
+ const message = lodashGet(reportAction, 'message', []);
+ return message.length === 0 || lodashGet(message, [0, 'html']) === '';
}
/**
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 058c90d1aa61..515101b5665f 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -510,6 +510,19 @@ function navigateToDetailsPage(report) {
Navigation.navigate(ROUTES.getReportParticipantsRoute(report.reportID));
}
+/**
+ * Generate a random reportID between 98000000 (the number of reports before the switch from sequential to random)
+ * and the maximum safe integer of js (53 bits aka 9,007,199,254,740,991)
+ *
+ * In a test of 500M reports (28 years of reports at our current max rate) we got 20-40 collisions meaning that
+ * this is more than random enough for our needs.
+ *
+ * @returns {Number}
+ */
+function generateReportID() {
+ return Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER - 98000000)) + 98000000;
+}
+
export {
getReportParticipantsTitle,
isReportMessageAttachment,
@@ -538,4 +551,5 @@ export {
getDisplayNamesWithTooltips,
getReportName,
navigateToDetailsPage,
+ generateReportID,
};
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index d5eafab203ba..e9a044f5c280 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -2,6 +2,7 @@ import {AppState, Linking} from 'react-native';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
+import _ from 'underscore';
import * as API from '../API';
import ONYXKEYS from '../../ONYXKEYS';
import * as DeprecatedAPI from '../deprecatedAPI';
@@ -9,7 +10,6 @@ import CONST from '../../CONST';
import Log from '../Log';
import Performance from '../Performance';
import Timing from './Timing';
-import * as PersonalDetails from './PersonalDetails';
import * as Report from './Report';
import * as BankAccounts from './BankAccounts';
import * as Policy from './Policy';
@@ -33,6 +33,18 @@ Onyx.connect({
initWithStoredValues: false,
});
+const allPolicies = {};
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.POLICY,
+ callback: (val, key) => {
+ if (!val || !key) {
+ return;
+ }
+
+ allPolicies[key] = {...allPolicies[key], ...val};
+ },
+});
+
/**
* @param {String} url
*/
@@ -53,7 +65,7 @@ function setLocale(locale) {
// Optimistically change preferred locale
const optimisticData = [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
value: locale,
},
@@ -85,36 +97,47 @@ AppState.addEventListener('change', (nextAppState) => {
/**
* Fetches data needed for app initialization
- *
- * @param {Boolean} shouldSyncPolicyList Should be false if the initial policy needs to be created. Otherwise, should be true.
* @returns {Promise}
*/
-function getAppData(shouldSyncPolicyList = true) {
+function getAppData() {
BankAccounts.fetchUserWallet();
- if (shouldSyncPolicyList) {
- Policy.getPolicyList();
- }
-
// We should update the syncing indicator when personal details and reports are both done fetching.
return Promise.all([
- PersonalDetails.fetchPersonalDetails(),
- Report.fetchAllReports(true, true),
+ Report.fetchAllReports(true),
]);
}
+/**
+ * Gets a comma separated list of locally stored policy ids
+ *
+ * @param {Array} policies
+ * @return {String}
+ */
+function getPolicyIDList(policies) {
+ return _.chain(policies)
+ .filter(Boolean)
+ .map(policy => policy.id)
+ .join(',');
+}
+
/**
* Fetches data needed for app initialization
+ * @param {Array} policies
*/
-function openApp() {
- API.read('OpenApp');
+function openApp(policies) {
+ API.read('OpenApp', {
+ policyIDList: getPolicyIDList(policies),
+ });
}
/**
* Refreshes data when the app reconnects
*/
function reconnectApp() {
- API.read('ReconnectApp');
+ API.read('ReconnectApp', {
+ policyIDList: getPolicyIDList(allPolicies),
+ });
}
/**
@@ -128,7 +151,7 @@ function fixAccountAndReloadData() {
return;
}
Log.info('FixAccount found updates for this user, so data will be reinitialized', true, response);
- getAppData(false);
+ getAppData();
});
}
@@ -159,7 +182,6 @@ function setUpPoliciesAndNavigate(session) {
Linking.getInitialURL()
.then((url) => {
if (!url) {
- Policy.getPolicyList();
return;
}
const path = new URL(url).pathname;
@@ -173,7 +195,6 @@ function setUpPoliciesAndNavigate(session) {
Policy.createAndGetPolicyList();
return;
}
- Policy.getPolicyList();
if (!isLoggingInAsNewUser && exitTo) {
Navigation.isNavigationReady()
.then(() => {
@@ -187,7 +208,7 @@ function setUpPoliciesAndNavigate(session) {
// When the app reconnects from being offline, fetch all initialization data
NetworkConnection.onReconnect(() => {
- getAppData(true);
+ getAppData();
reconnectApp();
});
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 3362e158b1b1..8fb86b3c25dd 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -70,7 +70,7 @@ function addPersonalBankAccount(account, password) {
const onyxData = {
optimisticData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: true,
@@ -80,18 +80,18 @@ function addPersonalBankAccount(account, password) {
],
successData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: false,
error: '',
- success: Localize.translateLocal('paymentsPage.addBankAccountSuccess'),
+ shouldShowSuccess: true,
},
},
],
failureData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: false,
diff --git a/src/libs/actions/Inbox.js b/src/libs/actions/Inbox.js
index 3c4b9090c935..5a101fdb51d6 100644
--- a/src/libs/actions/Inbox.js
+++ b/src/libs/actions/Inbox.js
@@ -1,4 +1,5 @@
import Onyx from 'react-native-onyx';
+import CONST from '../../CONST';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
@@ -17,7 +18,7 @@ function requestCall({
taskID, policyID, firstName, lastName, phoneNumber, phoneNumberExtension,
}) {
const optimisticData = [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.REQUEST_CALL_FORM,
value: {
loading: true,
@@ -26,7 +27,7 @@ function requestCall({
const successData = [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.REQUEST_CALL_FORM,
value: {
loading: false,
@@ -37,7 +38,7 @@ function requestCall({
];
const failureData = [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.REQUEST_CALL_FORM,
value: {
loading: false,
@@ -61,7 +62,7 @@ function requestCall({
function openRequestCallPage() {
// Reset the error message in case we had one set from a previous failed attempt at requesting a call.
const optimisticData = [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.REQUEST_CALL_FORM,
value: {
error: '',
diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js
index 3cc666f0ffbf..c31232e38cde 100644
--- a/src/libs/actions/Link.js
+++ b/src/libs/actions/Link.js
@@ -1,12 +1,13 @@
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
+import {Linking} from 'react-native';
import ONYXKEYS from '../../ONYXKEYS';
import Growl from '../Growl';
import * as Localize from '../Localize';
import CONST from '../../CONST';
-import * as DeprecatedAPI from '../deprecatedAPI';
import CONFIG from '../../CONFIG';
import asyncOpenURL from '../asyncOpenURL';
+import * as API from '../API';
let isNetworkOffline = false;
Onyx.connect({
@@ -38,7 +39,7 @@ function openOldDotLink(url) {
return;
}
- function buildOldDotURL({shortLivedAuthToken}) {
+ function buildOldDotURL(shortLivedAuthToken) {
const hasHashParams = url.indexOf('#') !== -1;
const hasURLParams = url.indexOf('?') !== -1;
@@ -46,7 +47,17 @@ function openOldDotLink(url) {
return `${CONFIG.EXPENSIFY.EXPENSIFY_URL}${url}${hasHashParams || hasURLParams ? '&' : '?'}authToken=${shortLivedAuthToken}&email=${encodeURIComponent(currentUserEmail)}`;
}
- asyncOpenURL(DeprecatedAPI.GetShortLivedAuthToken(), buildOldDotURL);
+ // We use makeRequestWithSideEffects here because we need to block until after we get the shortLivedAuthToken back from the server (link won't work without it!).
+ // eslint-disable-next-line rulesdir/no-api-side-effects-method
+ API.makeRequestWithSideEffects(
+ 'OpenOldDotLink', {}, {},
+ ).then((response) => {
+ if (response.jsonCode === 200) {
+ Linking.openURL(buildOldDotURL(response.shortLivedAuthToken));
+ } else {
+ Growl.show(response.message, CONST.GROWL.WARNING);
+ }
+ });
}
/**
diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js
index c728f1b08d9f..dc9ad432fd56 100644
--- a/src/libs/actions/PaymentMethods.js
+++ b/src/libs/actions/PaymentMethods.js
@@ -10,9 +10,9 @@ import Growl from '../Growl';
import * as Localize from '../Localize';
import Navigation from '../Navigation/Navigation';
import * as CardUtils from '../CardUtils';
-import ROUTES from '../../ROUTES';
import * as User from './User';
import * as store from './ReimbursementAccount/store';
+import ROUTES from '../../ROUTES';
/**
* Deletes a debit card
@@ -121,7 +121,7 @@ function makeDefaultPaymentMethod(password, bankAccountID, fundID, previousPayme
}, {
optimisticData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.USER_WALLET,
value: {
walletLinkedAccountID: bankAccountID || fundID,
@@ -131,7 +131,7 @@ function makeDefaultPaymentMethod(password, bankAccountID, fundID, previousPayme
],
failureData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.USER_WALLET,
value: {
walletLinkedAccountID: previousPaymentMethodID,
@@ -215,21 +215,42 @@ function transferWalletBalance(paymentMethod) {
: CONST.PAYMENT_METHOD_ID_KEYS.DEBIT_CARD;
const parameters = {
[paymentMethodIDKey]: paymentMethod.methodID,
+ shouldReturnOnyxData: true,
};
- Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: true});
- DeprecatedAPI.TransferWalletBalance(parameters)
- .then((response) => {
- if (response.jsonCode !== 200) {
- throw new Error(response.message);
- }
- Onyx.merge(ONYXKEYS.USER_WALLET, {currentBalance: 0});
- Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: true, loading: false});
- Navigation.navigate(ROUTES.SETTINGS_PAYMENTS);
- }).catch(() => {
- Growl.error(Localize.translateLocal('transferAmountPage.failedTransfer'));
- Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: false});
- });
+ API.write('TransferWalletBalance', parameters, {
+ optimisticData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.WALLET_TRANSFER,
+ value: {
+ loading: true,
+ error: null,
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.WALLET_TRANSFER,
+ value: {
+ loading: false,
+ shouldShowSuccess: true,
+ paymentMethodType: paymentMethod.accountType,
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.WALLET_TRANSFER,
+ value: {
+ loading: false,
+ shouldShowSuccess: false,
+ },
+ },
+ ],
+ });
}
function resetWalletTransferData() {
@@ -238,17 +259,10 @@ function resetWalletTransferData() {
selectedAccountID: null,
filterPaymentMethodType: null,
loading: false,
- shouldShowConfirmModal: false,
+ shouldShowSuccess: false,
});
}
-/**
- * @param {Number} transferAmount
- */
-function saveWalletTransferAmount(transferAmount) {
- Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount});
-}
-
/**
* @param {String} selectedAccountType
* @param {String} selectedAccountID
@@ -265,8 +279,9 @@ function saveWalletTransferMethodType(filterPaymentMethodType) {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {filterPaymentMethodType});
}
-function dismissWalletConfirmModal() {
- Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: false});
+function dismissSuccessfulTransferBalancePage() {
+ Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowSuccess: false});
+ Navigation.navigate(ROUTES.SETTINGS_PAYMENTS);
}
export {
@@ -278,11 +293,10 @@ export {
kycWallRef,
continueSetup,
clearDebitCardFormErrorAndSubmit,
+ dismissSuccessfulTransferBalancePage,
transferWalletBalance,
resetWalletTransferData,
- saveWalletTransferAmount,
saveWalletTransferAccountTypeAndID,
saveWalletTransferMethodType,
- dismissWalletConfirmModal,
cleanLocalReimbursementData,
};
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 636073e1ff68..bcb939a1b2f1 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -117,27 +117,6 @@ function formatPersonalDetails(personalDetailsList) {
return formattedResult;
}
-/**
- * Get the personal details for our organization
- * @returns {Promise}
- */
-function fetchPersonalDetails() {
- return DeprecatedAPI.Get({
- returnValueList: 'personalDetailsList',
- })
- .then((data) => {
- // If personalDetailsList does not have the current user ensure we initialize their details with an empty
- // object at least
- const personalDetailsList = _.isEmpty(data.personalDetailsList) ? {} : data.personalDetailsList;
- if (!personalDetailsList[currentUserEmail]) {
- personalDetailsList[currentUserEmail] = {};
- }
-
- const allPersonalDetails = formatPersonalDetails(personalDetailsList);
- Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, allPersonalDetails);
- });
-}
-
/**
* Gets the first and last name from the user's personal details.
* If the login is the same as the displayName, then they don't exist,
@@ -327,7 +306,6 @@ function deleteAvatar(defaultAvatarURL) {
}
export {
- fetchPersonalDetails,
formatPersonalDetails,
getFromReportParticipants,
getDisplayName,
diff --git a/src/libs/actions/Plaid.js b/src/libs/actions/Plaid.js
index cb9a9bc3352e..dc88c99c2647 100644
--- a/src/libs/actions/Plaid.js
+++ b/src/libs/actions/Plaid.js
@@ -2,6 +2,7 @@ import getPlaidLinkTokenParameters from '../getPlaidLinkTokenParameters';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
import * as Localize from '../Localize';
+import CONST from '../../CONST';
/**
* Gets the Plaid Link token used to initialize the Plaid SDK
@@ -27,7 +28,7 @@ function openPlaidBankAccountSelector(publicToken, bankName, allowDebit) {
bank: bankName,
}, {
optimisticData: [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: true,
@@ -36,7 +37,7 @@ function openPlaidBankAccountSelector(publicToken, bankName, allowDebit) {
},
}],
successData: [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: false,
@@ -44,7 +45,7 @@ function openPlaidBankAccountSelector(publicToken, bankName, allowDebit) {
},
}],
failureData: [{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: false,
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index c4117360bd7e..40b96e408565 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -190,7 +190,7 @@ function deletePolicy(policyID) {
// Removing the workspace data from Onyx as well
return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, null);
})
- .then(() => Report.fetchAllReports(false, true))
+ .then(() => Report.fetchAllReports(false))
.then(() => {
Navigation.goBack();
return Promise.resolve();
@@ -525,7 +525,7 @@ function subscribeToPolicyEvents() {
if (!_.isEmpty(policyExpenseChatIDs)) {
Report.fetchChatReportsByIDs(policyExpenseChatIDs);
_.each(policyExpenseChatIDs, (reportID) => {
- Report.fetchActions(reportID);
+ Report.fetchInitialActions(reportID);
});
}
@@ -539,6 +539,16 @@ function subscribeToPolicyEvents() {
});
}
+/**
+ * Checks if we have any errors stored within the POLICY_MEMBER_LIST. Determines whether we should show a red brick road error or not
+ * Data structure: {email: {role:'bla', errors: []}, email2: {role:'bla', errors: [{1231312313: 'Unable to do X'}]}, ...}
+ * @param {Object} policyMemberList
+ * @returns {Boolean}
+ */
+function hasPolicyMemberError(policyMemberList) {
+ return _.some(policyMemberList, member => !_.isEmpty(member.errors));
+}
+
export {
getPolicyList,
loadFullPolicy,
@@ -557,4 +567,5 @@ export {
setCustomUnitRate,
updateLastAccessedWorkspace,
subscribeToPolicyEvents,
+ hasPolicyMemberError,
};
diff --git a/src/libs/actions/ReimbursementAccount/errors.js b/src/libs/actions/ReimbursementAccount/errors.js
index d720eaf13874..9c4b53f5ffef 100644
--- a/src/libs/actions/ReimbursementAccount/errors.js
+++ b/src/libs/actions/ReimbursementAccount/errors.js
@@ -36,7 +36,7 @@ function setBankAccountFormValidationErrors(errors) {
* Clear validation messages from reimbursement account
*/
function resetReimbursementAccount() {
- this.setBankAccountFormValidationErrors({});
+ setBankAccountFormValidationErrors({});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {successRoute: null});
}
diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
index 4f54352169ff..3ef49fbc854f 100644
--- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
+++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
@@ -1,12 +1,10 @@
-import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../../ONYXKEYS';
-import * as DeprecatedAPI from '../../deprecatedAPI';
import CONST from '../../../CONST';
import * as store from './store';
-import Growl from '../../Growl';
import Navigation from '../../Navigation/Navigation';
import ROUTES from '../../../ROUTES';
+import * as API from '../../API';
/**
* Reset user's reimbursement account. This will delete the bank account.
@@ -20,35 +18,33 @@ function resetFreePlanBankAccount() {
throw new Error('Missing credentials when attempting to reset free plan bank account');
}
- // Create a copy of the reimbursementAccount data since we are going to optimistically wipe it so the UI changes quickly.
- // If the API request fails we will set this data back into Onyx.
- const previousACHData = {...store.getReimbursementAccountInSetup()};
- Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: null, shouldShowResetModal: false});
- DeprecatedAPI.DeleteBankAccount({bankAccountID, ownerEmail: store.getCredentials().login})
- .then((response) => {
- if (response.jsonCode !== 200) {
- // Unable to delete bank account so we restore the bank account details
- Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: previousACHData});
- Growl.error('Sorry we were unable to delete this bank account. Please try again later');
- return;
- }
+ const achData = {
+ useOnfido: true,
+ policyID: '',
+ isInSetup: true,
+ domainLimit: 0,
+ currentStep: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT,
+ };
- // Reset reimbursement account, and clear draft user input
- const achData = {
- useOnfido: true,
- policyID: '',
- isInSetup: true,
- domainLimit: 0,
- currentStep: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT,
- };
-
- Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData});
- Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, null);
-
- // Clear the NVP for the bank account so the user can add a new one and navigate back to bank account page
- DeprecatedAPI.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: ''});
- Navigation.navigate(ROUTES.getBankAccountRoute());
+ API.write('RestartBankAccountSetup',
+ {
+ bankAccountID,
+ ownerEmail: store.getCredentials().login,
+ },
+ {
+ optimisticData: [{
+ onyxMethod: 'merge',
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ value: {achData, shouldShowResetModal: false},
+ },
+ {
+ onyxMethod: 'set',
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT,
+ value: null,
+ }],
});
+
+ Navigation.navigate(ROUTES.getBankAccountRoute());
}
export default resetFreePlanBankAccount;
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index f851f172e111..31384254d8a6 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -21,7 +21,6 @@ import CONST from '../../CONST';
import Log from '../Log';
import * as LoginUtils from '../LoginUtils';
import * as ReportUtils from '../ReportUtils';
-import Timers from '../Timers';
import * as ReportActions from './ReportActions';
import Growl from '../Growl';
import * as Localize from '../Localize';
@@ -50,10 +49,10 @@ Onyx.connect({
callback: val => lastViewedReportID = val ? Number(val) : null,
});
-let myPersonalDetails;
+let personalDetails;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS,
- callback: val => myPersonalDetails = val[currentUserEmail],
+ callback: val => personalDetails = val,
});
const allReports = {};
@@ -132,7 +131,7 @@ function getParticipantEmailsFromReport({sharedReportList, reportNameValuePairs,
/**
* Only store the minimal amount of data in Onyx that needs to be stored
- * because space is limited
+ * because space is limited.
*
* @param {Object} report
* @param {Number} report.reportID
@@ -189,7 +188,6 @@ function getSimplifiedReportObject(report) {
lastMessageTimestamp,
lastMessageText: isLastMessageAttachment ? '[Attachment]' : lastMessageText,
lastActorEmail,
- hasOutstandingIOU: false,
notificationPreference,
stateNum: report.state,
statusNum: report.status,
@@ -441,43 +439,6 @@ function fetchIOUReportByID(iouReportID, chatReportID, shouldRedirectIfEmpty = f
});
}
-/**
- * If an iouReport is open (has an IOU, but is not yet paid) then we sync the reportIDs of both chatReport and
- * iouReport in Onyx, simplifying IOU data retrieval and reducing necessary API calls when displaying IOU components:
- * - chatReport: {id: 123, iouReportID: 987, ...}
- * - iouReport: {id: 987, chatReportID: 123, ...}
- *
- * The reports must remain in sync when the iouReport is modified. This function ensures that we sync reportIds after
- * fetching the iouReport and therefore should only be called if we are certain that the fetched iouReport is currently
- * open - else we would overwrite the existing open iouReportID with a closed iouReportID.
- *
- * Examples of correct usage include 'receieving a push notification', or 'paying an IOU', because both of these cases can only
- * occur for an iouReport that is currently open (notifications are not sent for closed iouReports, and you cannot pay a closed
- * IOU). Send Money is an incorrect use case, because these IOUReports are never associated with the chatReport and this would
- * prevent outstanding IOUs from showing.
- *
- * @param {Number} iouReportID - ID of the report we are fetching
- * @param {Number} chatReportID - associated chatReportID, used to sync the reports
- */
-function fetchIOUReportByIDAndUpdateChatReport(iouReportID, chatReportID) {
- fetchIOUReportByID(iouReportID, chatReportID)
- .then((iouReportObject) => {
- // Now sync the chatReport data to ensure it has a reference to the updated iouReportID
- const chatReportObject = {
- hasOutstandingIOU: iouReportObject.stateNum === CONST.REPORT.STATE_NUM.PROCESSING
- && iouReportObject.total !== 0,
- iouReportID: iouReportObject.reportID,
- };
-
- if (!chatReportObject.hasOutstandingIOU) {
- chatReportObject.iouReportID = null;
- }
-
- const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`;
- Onyx.merge(reportKey, chatReportObject);
- });
-}
-
/**
* @param {Number} reportID
* @param {Number} sequenceNumber
@@ -691,19 +652,10 @@ function fetchOrCreateChatReport(participants, shouldNavigate = true) {
* Get the actions of a report
*
* @param {Number} reportID
- * @param {Number} [offset]
* @returns {Promise}
*/
-function fetchActions(reportID, offset) {
- const reportActionsOffset = !_.isUndefined(offset) ? offset : -1;
-
- if (!_.isNumber(reportActionsOffset)) {
- Log.alert('[Report] Offset provided is not a number', {
- offset,
- reportActionsOffset,
- });
- return;
- }
+function fetchActions(reportID) {
+ const reportActionsOffset = -1;
return DeprecatedAPI.Report_GetHistory({
reportID,
@@ -721,27 +673,24 @@ function fetchActions(reportID, offset) {
}
/**
- * Get the actions of a report
+ * Get the initial actions of a report
*
* @param {Number} reportID
- * @param {Number} [offset]
*/
-function fetchActionsWithLoadingState(reportID, offset) {
- Onyx.set(ONYXKEYS.IS_LOADING_REPORT_ACTIONS, true);
- fetchActions(reportID, offset)
- .finally(() => Onyx.set(ONYXKEYS.IS_LOADING_REPORT_ACTIONS, false));
+function fetchInitialActions(reportID) {
+ Onyx.set(`${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${reportID}`, true);
+ fetchActions(reportID)
+ .finally(() => Onyx.set(`${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${reportID}`, false));
}
/**
* Get all of our reports
*
* @param {Boolean} shouldRecordHomePageTiming whether or not performance timing should be measured
- * @param {Boolean} shouldDelayActionsFetch when the app loads we want to delay the fetching of additional actions
* @returns {Promise}
*/
function fetchAllReports(
shouldRecordHomePageTiming = false,
- shouldDelayActionsFetch = false,
) {
Onyx.set(ONYXKEYS.IS_LOADING_REPORT_DATA, true);
return DeprecatedAPI.Get({
@@ -776,50 +725,42 @@ function fetchAllReports(
if (shouldRecordHomePageTiming) {
Timing.end(CONST.TIMING.HOMEPAGE_REPORTS_LOADED);
}
-
- // Delay fetching report history as it significantly increases sign in to interactive time.
- // Register the timer so we can clean it up if the user quickly logs out after logging in. If we don't
- // cancel the timer we'll make unnecessary API requests from the sign in page.
- Timers.register(setTimeout(() => {
- // Filter reports to see which ones have actions we need to fetch so we can preload Onyx with new
- // content and improve chat switching experience by only downloading content we don't have yet.
- // This improves performance significantly when reconnecting by limiting API requests and unnecessary
- // data processing by Onyx.
- const reportIDsWithMissingActions = _.chain(returnedReports)
- .map(report => report.reportID)
- .filter(reportID => ReportActions.isReportMissingActions(reportID, lodashGet(allReports, [reportID, 'maxSequenceNumber'])))
- .value();
-
- // Once we have the reports that are missing actions we will find the intersection between the most
- // recently accessed reports and reports missing actions. Then we'll fetch the history for a small
- // set to avoid making too many network requests at once.
- const reportIDsToFetchActions = _.chain(ReportUtils.sortReportsByLastVisited(allReports))
- .map(report => report.reportID)
- .reverse()
- .intersection(reportIDsWithMissingActions)
- .slice(0, 10)
- .value();
-
- if (_.isEmpty(reportIDsToFetchActions)) {
- Log.info('[Report] Local reportActions up to date. Not fetching additional actions.');
- return;
- }
-
- Log.info('[Report] Fetching reportActions for reportIDs: ', false, {
- reportIDs: reportIDsToFetchActions,
- });
- _.each(reportIDsToFetchActions, (reportID) => {
- const offset = ReportActions.dangerouslyGetReportActionsMaxSequenceNumber(reportID, false);
- fetchActions(reportID, offset);
- });
-
- // We are waiting a set amount of time to allow the UI to finish loading before bogging it down with
- // more requests and operations. Startup delay is longer since there is a lot more work done to build
- // up the UI when the app first initializes.
- }, shouldDelayActionsFetch ? CONST.FETCH_ACTIONS_DELAY.STARTUP : CONST.FETCH_ACTIONS_DELAY.RECONNECT));
});
}
+/**
+ * Creates an optimistic report with a randomly generated reportID and as much information as we currently have
+ *
+ * @param {Array} participantList
+ * @returns {Object}
+ */
+function createOptimisticReport(participantList) {
+ return {
+ chatType: '',
+ hasOutstandingIOU: false,
+ isOwnPolicyExpenseChat: false,
+ isPinned: false,
+ lastActorEmail: '',
+ lastMessageHtml: '',
+ lastMessageText: null,
+ lastReadSequenceNumber: undefined,
+ lastMessageTimestamp: 0,
+ lastVisitedTimestamp: 0,
+ maxSequenceNumber: 0,
+ notificationPreference: '',
+ oldPolicyName: '',
+ ownerEmail: '__FAKE__',
+ participants: participantList,
+ policyID: '_FAKE_',
+ reportID: ReportUtils.generateReportID(),
+ reportName: 'Chat Report',
+ stateNum: 0,
+ statusNum: 0,
+ unreadActionCount: 0,
+ visibility: undefined,
+ };
+}
+
/**
* @param {Number} reportID
* @param {String} [text]
@@ -859,7 +800,7 @@ function buildOptimisticReportAction(reportID, text, file) {
person: [
{
style: 'strong',
- text: myPersonalDetails.displayName || currentUserEmail,
+ text: lodashGet(personalDetails, [currentUserEmail, 'displayName'], currentUserEmail),
type: 'TEXT',
},
],
@@ -868,7 +809,7 @@ function buildOptimisticReportAction(reportID, text, file) {
// Use the client generated ID as a optimistic action ID so we can remove it later
sequenceNumber: optimisticReportActionID,
clientID: optimisticReportActionID,
- avatar: myPersonalDetails.avatar,
+ avatar: lodashGet(personalDetails, [currentUserEmail, 'avatar'], ReportUtils.getDefaultAvatar(currentUserEmail)),
timestamp: moment().unix(),
message: [
{
@@ -1127,6 +1068,38 @@ function openReport(reportID) {
});
}
+/**
+ * Gets the older actions that have not been read yet.
+ * Normally happens when you scroll up on a chat, and the actions have not been read yet.
+ *
+ * @param {Number} reportID
+ * @param {Number} oldestActionSequenceNumber
+ */
+function readOldestAction(reportID, oldestActionSequenceNumber) {
+ API.read('ReadOldestAction',
+ {
+ reportID,
+ reportActionsOffset: oldestActionSequenceNumber,
+ },
+ {
+ optimisticData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
+ value: true,
+ }],
+ successData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
+ value: false,
+ }],
+ failureData: [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
+ value: false,
+ }],
+ });
+}
+
/**
* Gets the IOUReport and the associated report actions.
*
@@ -1382,9 +1355,8 @@ function syncChatAndIOUReports(chatReport, iouReport) {
}
simplifiedReport[chatReportKey] = getSimplifiedReportObject(chatReport);
simplifiedReport[chatReportKey].hasOutstandingIOU = iouReport.stateNum
- === (CONST.REPORT.STATE_NUM.PROCESSING && iouReport.total !== 0);
+ === CONST.REPORT.STATE_NUM.PROCESSING && iouReport.total !== 0;
simplifiedIouReport[iouReportKey] = getSimplifiedIOUReport(iouReport, chatReport.reportID);
-
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, simplifiedIouReport);
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, simplifiedReport);
}
@@ -1525,18 +1497,6 @@ function viewNewReportAction(reportID, action) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, updatedReportObject);
- // If chat report receives an action with IOU and we have an IOUReportID, update IOU object
- if (action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.originalMessage.IOUReportID) {
- const iouReportID = action.originalMessage.IOUReportID;
-
- // If the IOUDetails object exists we are in the Send Money flow, and we should not fetch and update the chatReport
- // as this would overwrite any existing IOUs. For all other cases we must update the chatReport with the iouReportID as
- // if we don't, new IOUs would not be displayed and paid IOUs would still show as unpaid.
- if (action.originalMessage.IOUDetails === undefined) {
- fetchIOUReportByIDAndUpdateChatReport(iouReportID, reportID);
- }
- }
-
const notificationPreference = lodashGet(allReports, [reportID, 'notificationPreference'], CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS);
if (!ActiveClientManager.isClientTheLeader()) {
Log.info('[LOCAL_NOTIFICATION] Skipping notification because this client is not the leader');
@@ -1625,11 +1585,9 @@ Onyx.connect({
export {
fetchAllReports,
- fetchActions,
fetchOrCreateChatReport,
fetchChatReportsByIDs,
fetchIOUReportByID,
- fetchIOUReportByIDAndUpdateChatReport,
addComment,
addAttachment,
updateNotificationPreference,
@@ -1650,12 +1608,14 @@ export {
navigateToConciergeChat,
handleInaccessibleReport,
setReportWithDraft,
- fetchActionsWithLoadingState,
+ fetchInitialActions,
createPolicyRoom,
renameReport,
setIsComposerFullSize,
markCommentAsUnread,
readNewestAction,
+ readOldestAction,
openReport,
openPaymentDetailsPage,
+ createOptimisticReport,
};
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index 95673cc696af..5059a434ad43 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -78,12 +78,12 @@ function signOut() {
const optimisticData = [
{
- onyxMethod: 'set',
+ onyxMethod: CONST.ONYX.METHOD.SET,
key: ONYXKEYS.SESSION,
value: null,
},
{
- onyxMethod: 'set',
+ onyxMethod: CONST.ONYX.METHOD.SET,
key: ONYXKEYS.CREDENTIALS,
value: null,
},
@@ -302,11 +302,41 @@ function signInWithShortLivedToken(email, shortLivedToken) {
* User forgot the password so let's send them the link to reset their password
*/
function resetPassword() {
- Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true, forgotPassword: true});
- DeprecatedAPI.ResetPassword({email: credentials.login})
- .finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false, validateCodeExpired: false});
- });
+ API.write('RequestPasswordReset', {
+ email: credentials.login,
+ },
+ {
+ optimisticData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: true,
+ forgotPassword: true,
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ validateCodeExpired: false,
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: 'merge',
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ validateCodeExpired: false,
+ },
+ },
+ ],
+ });
}
/**
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 9c1841f40030..4f22eb497350 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -50,21 +50,21 @@ function updatePassword(oldPassword, password) {
}, {
optimisticData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {...CONST.DEFAULT_ACCOUNT_DATA, loading: true},
},
],
successData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {loading: false},
},
],
failureData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {loading: false},
},
@@ -92,16 +92,6 @@ function closeAccount(message) {
});
}
-function getBetas() {
- DeprecatedAPI.User_GetBetas().then((response) => {
- if (response.jsonCode !== 200) {
- return;
- }
-
- Onyx.set(ONYXKEYS.BETAS, response.betas);
- });
-}
-
/**
* Fetches the data needed for user settings
*/
@@ -160,14 +150,14 @@ function updateNewsletterSubscription(isSubscribed) {
}, {
optimisticData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.USER,
value: {isSubscribedToNewsletter: isSubscribed},
},
],
failureData: [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.USER,
value: {isSubscribedToNewsletter: !isSubscribed},
},
@@ -251,19 +241,19 @@ function validateLogin(accountID, validateCode) {
* Checks the blockedFromConcierge object to see if it has an expiresAt key,
* and if so whether the expiresAt date of a user's ban is before right now
*
- * @param {Object} blockedFromConcierge
+ * @param {Object} blockedFromConciergeNVP
* @returns {Boolean}
*/
-function isBlockedFromConcierge(blockedFromConcierge) {
- if (_.isEmpty(blockedFromConcierge)) {
+function isBlockedFromConcierge(blockedFromConciergeNVP) {
+ if (_.isEmpty(blockedFromConciergeNVP)) {
return false;
}
- if (!blockedFromConcierge.expiresAt) {
+ if (!blockedFromConciergeNVP.expiresAt) {
return false;
}
- return moment().isBefore(moment(blockedFromConcierge.expiresAt), 'day');
+ return moment().isBefore(moment(blockedFromConciergeNVP.expiresAt), 'day');
}
/**
@@ -482,7 +472,6 @@ function generateStatementPDF(period) {
export {
updatePassword,
closeAccount,
- getBetas,
getUserDetails,
resendValidateCode,
updateNewsletterSubscription,
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index 48c994172910..5042475d22b9 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -146,15 +146,6 @@ function GetAccountStatus(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * Returns a short lived authToken for this account
- * @returns {Promise}
- */
-function GetShortLivedAuthToken() {
- const commandName = 'GetShortLivedAuthToken';
- return Network.post(commandName);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.debtorEmail
@@ -337,17 +328,6 @@ function SetNameValuePair(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * @param {Object} parameters
- * @param {string} parameters.email
- * @returns {Promise}
- */
-function ResetPassword(parameters) {
- const commandName = 'ResetPassword';
- requireParameters(['email'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.password
@@ -371,13 +351,6 @@ function User_Delete(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * @returns {Promise}
- */
-function User_GetBetas() {
- return Network.post('User_GetBetas');
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.email
@@ -755,7 +728,6 @@ export {
DeleteBankAccount,
Get,
GetAccountStatus,
- GetShortLivedAuthToken,
GetStatementPDF,
GetIOUReport,
GetFullPolicy,
@@ -773,13 +745,11 @@ export {
Report_GetHistory,
Report_EditComment,
ResendValidateCode,
- ResetPassword,
SetNameValuePair,
SetPassword,
UpdatePolicy,
User_SignUp,
User_Delete,
- User_GetBetas,
User_IsUsingExpensifyCard,
User_ReopenAccount,
User_SecondaryLogin_Send,
diff --git a/src/libs/getAvatarDefaultSource/index.js b/src/libs/getAvatarDefaultSource/index.js
new file mode 100644
index 000000000000..54c2a7d04b01
--- /dev/null
+++ b/src/libs/getAvatarDefaultSource/index.js
@@ -0,0 +1,7 @@
+/**
+ * Avatar icon flickers when message is sent for the first time, return and set the source as
+ * defaultSource prop of image to prevent avatar icon from flicker when running on Web/Desktop
+ * @param {String|Function} source The source of avatar image
+ * @return {Object} The image source
+ */
+export default source => ({uri: source});
diff --git a/src/libs/getAvatarDefaultSource/index.native.js b/src/libs/getAvatarDefaultSource/index.native.js
new file mode 100644
index 000000000000..1c1c79caf151
--- /dev/null
+++ b/src/libs/getAvatarDefaultSource/index.native.js
@@ -0,0 +1,5 @@
+/**
+ * Avatar icon does not flicker when running on Native, return and set undefined as defaultSource prop of image
+ * @return {Object} undefined
+ */
+export default () => undefined;
diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js
index 5a14ed6f7f45..dc5245702c88 100644
--- a/src/pages/AddPersonalBankAccountPage.js
+++ b/src/pages/AddPersonalBankAccountPage.js
@@ -13,10 +13,13 @@ import AddPlaidBankAccount from '../components/AddPlaidBankAccount';
import getPlaidOAuthReceivedRedirectURI from '../libs/getPlaidOAuthReceivedRedirectURI';
import compose from '../libs/compose';
import ONYXKEYS from '../ONYXKEYS';
-import KeyboardAvoidingView from '../components/KeyboardAvoidingView';
import Text from '../components/Text';
import styles from '../styles/styles';
+import * as Illustrations from '../components/Icon/Illustrations';
+import Icon from '../components/Icon';
+import defaultTheme from '../styles/themes/default';
import Button from '../components/Button';
+import FixedFooter from '../components/FixedFooter';
import FormScrollView from '../components/FormScrollView';
import FormAlertWithSubmitButton from '../components/FormAlertWithSubmitButton';
import FormHelper from '../libs/FormHelper';
@@ -29,7 +32,7 @@ const propTypes = {
...withLocalizePropTypes,
personalBankAccount: PropTypes.shape({
error: PropTypes.string,
- success: PropTypes.string,
+ shouldShowSuccess: PropTypes.bool,
loading: PropTypes.bool,
}),
};
@@ -37,7 +40,7 @@ const propTypes = {
const defaultProps = {
personalBankAccount: {
error: '',
- success: '',
+ shouldShowSuccess: false,
loading: false,
},
};
@@ -119,73 +122,86 @@ class AddPersonalBankAccountPage extends React.Component {
}
render() {
- const success = lodashGet(this.props, 'personalBankAccount.success', '');
+ const shouldShowSuccess = lodashGet(this.props, 'personalBankAccount.shouldShowSuccess', false);
const error = lodashGet(this.props, 'personalBankAccount.error', '');
const loading = lodashGet(this.props, 'personalBankAccount.loading', false);
return (
-
-
- {success ? (
- <>
-
- {success}
-
-
-
- >
- ) : (
-
-
- {
- this.setState({
- selectedPlaidBankAccount: params.selectedPlaidBankAccount,
- });
- }}
- onExitPlaid={Navigation.goBack}
- receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()}
- />
- {!_.isUndefined(this.state.selectedPlaidBankAccount) && (
-
- this.setState({password: text})}
- errorText={this.getErrorText('password')}
- hasError={this.getErrors().password}
- />
-
- )}
+
+ {shouldShowSuccess ? (
+ <>
+
+
+
+
+ {this.props.translate('addPersonalBankAccountPage.successTitle')}
+
+
+ {this.props.translate('addPersonalBankAccountPage.successMessage')}
+
+
+
+
+ >
+ ) : (
+
+
+ {
+ this.setState({
+ selectedPlaidBankAccount: params.selectedPlaidBankAccount,
+ });
+ }}
+ onExitPlaid={Navigation.goBack}
+ receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()}
+ />
{!_.isUndefined(this.state.selectedPlaidBankAccount) && (
-
+
+ this.setState({password: text})}
+ errorText={this.getErrorText('password')}
+ hasError={this.getErrors().password}
+ />
+
)}
-
- )}
-
+
+ {!_.isUndefined(this.state.selectedPlaidBankAccount) && (
+
+ )}
+
+ )}
);
}
diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js
index 2f3937ce1745..71daa5f13f72 100644
--- a/src/pages/EnablePayments/AdditionalDetailsStep.js
+++ b/src/pages/EnablePayments/AdditionalDetailsStep.js
@@ -359,6 +359,7 @@ class AdditionalDetailsStep extends React.Component {
this.clearErrorAndSetValue('phoneNumber', val)}
value={this.props.walletAdditionalDetailsDraft.phoneNumber || ''}
diff --git a/src/pages/ReportSettingsPage.js b/src/pages/ReportSettingsPage.js
index 5d9881feb60b..7440780e3269 100644
--- a/src/pages/ReportSettingsPage.js
+++ b/src/pages/ReportSettingsPage.js
@@ -48,10 +48,16 @@ const propTypes = {
/** The current user's notification preference for this report */
notificationPreference: PropTypes.string,
+
+ /** Access setting e.g. whether the report is "restricted" */
+ visibility: PropTypes.string,
+
+ /** Linked policy's ID */
+ policyID: PropTypes.string,
}).isRequired,
/** All reports shared with the user */
- reports: PropTypes.shape({
+ reports: PropTypes.objectOf(PropTypes.shape({
/** The report name */
reportName: PropTypes.string,
@@ -60,7 +66,7 @@ const propTypes = {
/** ID of the policy */
policyID: PropTypes.string,
- }).isRequired,
+ })).isRequired,
/** The policies which the user has access to and which the report could be tied to */
policies: PropTypes.shape({
@@ -74,6 +80,13 @@ const propTypes = {
const defaultProps = {
...fullPolicyDefaultProps,
+ report: {
+ reportID: 0,
+ reportName: '',
+ policyID: '',
+ notificationPreference: '',
+ visibility: '',
+ },
};
class ReportSettingsPage extends Component {
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 6410b34dcf3f..fd9282a88021 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -19,7 +19,7 @@ import ReportActionCompose from './report/ReportActionCompose';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import SwipeableView from '../../components/SwipeableView';
import CONST from '../../CONST';
-import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator';
+import ReportActionsSkeletonView from '../../components/ReportActionsSkeletonView';
import reportActionPropTypes from './report/reportActionPropTypes';
import ArchivedReportFooter from '../../components/ArchivedReportFooter';
import toggleReportActionComposeView from '../../libs/toggleReportActionComposeView';
@@ -66,6 +66,9 @@ const propTypes = {
/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),
+ /** Flag to check if the initial report actions data are loading */
+ isLoadingInitialReportActions: PropTypes.bool,
+
/** The policies which the user has access to */
policies: PropTypes.objectOf(PropTypes.shape({
/** The policy name */
@@ -89,6 +92,7 @@ const defaultProps = {
},
isComposerFullSize: false,
betas: [],
+ isLoadingInitialReportActions: false,
};
/**
@@ -112,13 +116,12 @@ class ReportScreen extends React.Component {
this.viewportOffsetTop = this.updateViewportOffsetTop.bind(this);
this.state = {
- isLoading: true,
+ skeletonViewContainerHeight: 0,
viewportOffsetTop: 0,
};
}
componentDidMount() {
- this.prepareTransition();
this.storeCurrentlyViewedReport();
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', this.viewportOffsetTop);
@@ -129,8 +132,6 @@ class ReportScreen extends React.Component {
if (this.props.route.params.reportID === prevProps.route.params.reportID) {
return;
}
-
- this.prepareTransition();
this.storeCurrentlyViewedReport();
}
@@ -162,16 +163,7 @@ class ReportScreen extends React.Component {
* @returns {Boolean}
*/
shouldShowLoader() {
- return this.state.isLoading || !getReportID(this.props.route);
- }
-
- /**
- * Configures a small loading transition and proceeds with rendering available data
- */
- prepareTransition() {
- this.setState({isLoading: true});
- clearTimeout(this.loadingTimerId);
- this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 0);
+ return !getReportID(this.props.route) || (_.isEmpty(this.props.reportActions) && this.props.isLoadingInitialReportActions);
}
/**
@@ -212,7 +204,6 @@ class ReportScreen extends React.Component {
if (isArchivedRoom) {
reportClosedAction = lodashFindLast(this.props.reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
}
-
return (
@@ -224,39 +215,45 @@ class ReportScreen extends React.Component {
this.setState({skeletonViewContainerHeight: event.nativeEvent.layout.height})}
>
- {this.shouldShowLoader() && }
- {!this.shouldShowLoader() && (
-
- )}
+ {this.shouldShowLoader()
+ ? (
+
+ )
+ : (
+
+ )}
{(isArchivedRoom || this.props.session.shouldShowComposeInput) && (
-
- {
- isArchivedRoom
- ? (
-
- ) : (
-
-
+ {
+ isArchivedRoom
+ ? (
+
-
- )
- }
-
+ ) : (
+
+
+
+ )
+ }
+
)}
@@ -288,6 +285,10 @@ export default withOnyx({
betas: {
key: ONYXKEYS.BETAS,
},
+ isLoadingInitialReportActions: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.IS_LOADING_INITIAL_REPORT_ACTIONS}${getReportID(route)}`,
+ initWithStoredValues: false,
+ },
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js
index ea0b478aaf63..f4db994ad0cd 100755
--- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js
+++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js
@@ -1,5 +1,6 @@
import React from 'react';
import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import PropTypes from 'prop-types';
import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles';
@@ -12,6 +13,7 @@ import withLocalize, {withLocalizePropTypes} from '../../../../components/withLo
import ContextMenuActions, {CONTEXT_MENU_TYPES} from './ContextMenuActions';
import compose from '../../../../libs/compose';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
+import ONYXKEYS from '../../../../ONYXKEYS';
const propTypes = {
/** String representing the context menu type [LINK, REPORT_ACTION] which controls context menu choices */
@@ -32,7 +34,7 @@ class BaseReportActionContextMenu extends React.Component {
}
render() {
- const shouldShowFilter = contextAction => contextAction.shouldShow(this.props.type, this.props.reportAction);
+ const shouldShowFilter = contextAction => contextAction.shouldShow(this.props.type, this.props.reportAction, this.props.betas);
return this.props.isVisible && (
@@ -65,5 +67,10 @@ BaseReportActionContextMenu.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withOnyx({
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
withWindowDimensions,
)(BaseReportActionContextMenu);
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index 575e2ff1686d..635721245c4a 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -13,6 +13,7 @@ import fileDownload from '../../../../libs/fileDownload';
import addEncryptedAuthTokenToURL from '../../../../libs/addEncryptedAuthTokenToURL';
import * as ContextMenuUtils from './ContextMenuUtils';
import * as Environment from '../../../../libs/Environment/Environment';
+import Permissions from '../../../../libs/Permissions';
/**
* Gets the HTML version of the message in an action.
@@ -121,7 +122,7 @@ export default [
{
textTranslateKey: 'reportActionContextMenu.copyLink',
icon: Expensicons.LinkCopy,
- shouldShow: () => true,
+ shouldShow: (type, reportAction, betas) => Permissions.canUseCommentLinking(betas),
onPress: (closePopover, {reportAction, reportID}) => {
Environment.getEnvironmentURL()
.then((environmentURL) => {
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 8e0aaeb6687d..6e58ae1f5715 100755
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -34,8 +34,8 @@ import * as ReportUtils from '../../../libs/ReportUtils';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import participantPropTypes from '../../../components/participantPropTypes';
import ParticipantLocalTime from './ParticipantLocalTime';
-import {withPersonalDetails} from '../../../components/OnyxProvider';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails';
+import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider';
import * as User from '../../../libs/actions/User';
import Tooltip from '../../../components/Tooltip';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
@@ -124,12 +124,14 @@ class ReportActionCompose extends React.Component {
this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this);
this.focus = this.focus.bind(this);
this.addEmojiToTextBox = this.addEmojiToTextBox.bind(this);
- this.comment = props.comment;
- this.shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
this.onSelectionChange = this.onSelectionChange.bind(this);
this.setTextInputRef = this.setTextInputRef.bind(this);
this.getInputPlaceholder = this.getInputPlaceholder.bind(this);
this.getIOUOptions = this.getIOUOptions.bind(this);
+ this.addAttachment = this.addAttachment.bind(this);
+
+ this.comment = props.comment;
+ this.shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
this.state = {
isFocused: this.shouldFocusInputOnScreenFocus,
@@ -447,6 +449,15 @@ class ReportActionCompose extends React.Component {
return trimmedComment;
}
+ /**
+ * @param {Object} file
+ */
+ addAttachment(file) {
+ const comment = this.prepareCommentAndResetComposer();
+ Report.addAttachment(this.props.reportID, file, comment);
+ this.setTextInputShouldClear(false);
+ }
+
/**
* Add a new comment to this chat
*
@@ -500,11 +511,7 @@ class ReportActionCompose extends React.Component {
>
{
- const comment = this.prepareCommentAndResetComposer();
- Report.addAttachment(this.props.reportID, file, comment);
- this.setTextInputShouldClear(false);
- }}
+ onConfirm={this.addAttachment}
>
{({displayFileInModal}) => (
<>
@@ -572,9 +579,7 @@ class ReportActionCompose extends React.Component {
text: this.props.translate('reportActionCompose.addAttachment'),
onSelected: () => {
openPicker({
- onPicked: (file) => {
- displayFileInModal({file});
- },
+ onPicked: displayFileInModal,
});
},
},
@@ -616,7 +621,7 @@ class ReportActionCompose extends React.Component {
return;
}
- displayFileInModal({file});
+ displayFileInModal(file);
this.setState({isDraggingOver: false});
}}
style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]}
@@ -624,7 +629,7 @@ class ReportActionCompose extends React.Component {
maxLines={this.state.maxLines}
onFocus={() => this.setIsFocused(true)}
onBlur={() => this.setIsFocused(false)}
- onPasteFile={file => displayFileInModal({file})}
+ onPasteFile={displayFileInModal}
shouldClear={this.state.textInputShouldClear}
onClear={() => this.setTextInputShouldClear(false)}
isDisabled={isComposeDisabled || isBlockedFromConcierge}
@@ -686,6 +691,7 @@ export default compose(
withDrawerState,
withNavigationFocus,
withLocalize,
+ withNetwork(),
withPersonalDetails(),
withCurrentUserPersonalDetails,
withOnyx({
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index 10e5b58de3cb..f36440dcabaf 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -28,6 +28,8 @@ import {withNetwork, withReportActionsDrafts} from '../../../components/OnyxProv
import RenameAction from '../../../components/ReportActionItem/RenameAction';
import InlineSystemMessage from '../../../components/InlineSystemMessage';
import styles from '../../../styles/styles';
+import * as User from '../../../libs/actions/User';
+import * as ReportUtils from '../../../libs/ReportUtils';
const propTypes = {
/** The ID of the report this action is on. */
@@ -139,16 +141,20 @@ class ReportActionItem extends Component {
);
} else {
children = !this.props.draftMessage
- ?
- : (
+ ? (
+
+ ) : (
this.textInput = el}
- report={this.props.report}
- blockedFromConcierge={this.props.blockedFromConcierge}
+ action={this.props.action}
+ draftMessage={this.props.draftMessage}
+ reportID={this.props.reportID}
+ index={this.props.index}
+ ref={el => this.textInput = el}
+ report={this.props.report}
+ shouldDisableEmojiPicker={
+ (ReportUtils.chatIncludesConcierge(this.props.report) && User.isBlockedFromConcierge(this.props.blockedFromConcierge))
+ || ReportUtils.isArchivedRoom(this.props.report)
+ }
/>
);
}
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index 964142ca5b95..ae62f0010789 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -14,6 +14,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import canUseTouchScreen from '../../../libs/canUseTouchscreen';
import compose from '../../../libs/compose';
+import * as StyleUtils from '../../../styles/StyleUtils';
const propTypes = {
/** The message fragment needing to be displayed */
@@ -117,16 +118,16 @@ const ReportActionItemFragment = (props) => {
return (
- {Str.htmlDecode(text)}
+ {StyleUtils.convertToLTR(Str.htmlDecode(text))}
{props.fragment.isEdited && (
-
- {` ${props.translate('reportActionCompose.edited')}`}
-
+
+ {` ${props.translate('reportActionCompose.edited')}`}
+
)}
);
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js
index c2b270c9085a..3e64cfaaf09c 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.js
+++ b/src/pages/home/report/ReportActionItemMessageEdit.js
@@ -16,10 +16,8 @@ import Button from '../../../components/Button';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import compose from '../../../libs/compose';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
-import * as ReportUtils from '../../../libs/ReportUtils';
import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu';
import VirtualKeyboard from '../../../libs/VirtualKeyboard';
-import * as User from '../../../libs/actions/User';
const propTypes = {
/** All the data of the action */
@@ -43,11 +41,8 @@ const propTypes = {
participants: PropTypes.arrayOf(PropTypes.string),
}),
- // The NVP describing a user's block status
- blockedFromConcierge: PropTypes.shape({
- // The date that the user will be unblocked
- expiresAt: PropTypes.string,
- }),
+ // Whether or not the emoji picker is disabled
+ shouldDisableEmojiPicker: PropTypes.bool,
/** Window Dimensions Props */
...windowDimensionsPropTypes,
@@ -59,7 +54,7 @@ const propTypes = {
const defaultProps = {
forwardedRef: () => {},
report: {},
- blockedFromConcierge: {},
+ shouldDisableEmojiPicker: false,
};
class ReportActionItemMessageEdit extends React.Component {
@@ -190,10 +185,6 @@ class ReportActionItemMessageEdit extends React.Component {
}
render() {
- const shouldDisableEmojiPicker = (ReportUtils.chatIncludesConcierge(this.props.report)
- && User.isBlockedFromConcierge(this.props.blockedFromConcierge))
- || ReportUtils.isArchivedRoom(this.props.report);
-
return (
@@ -225,7 +216,7 @@ class ReportActionItemMessageEdit extends React.Component {
/>
InteractionManager.runAfterInteractions(() => this.textInput.focus())}
onEmojiSelected={this.addEmojiToTextBox}
/>
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 4af69adc6672..424353b09ca6 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -1,20 +1,22 @@
import PropTypes from 'prop-types';
import React from 'react';
-import {ActivityIndicator, View} from 'react-native';
+import {View, Animated} from 'react-native';
import InvertedFlatList from '../../../components/InvertedFlatList';
import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState';
import compose from '../../../libs/compose';
import * as ReportScrollManager from '../../../libs/ReportScrollManager';
import styles from '../../../styles/styles';
-import themeColors from '../../../styles/themes/default';
import * as ReportUtils from '../../../libs/ReportUtils';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import {withPersonalDetails} from '../../../components/OnyxProvider';
import ReportActionItem from './ReportActionItem';
+import ReportActionsSkeletonView from '../../../components/ReportActionsSkeletonView';
import variables from '../../../styles/variables';
import participantPropTypes from '../../../components/participantPropTypes';
import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
import reportActionPropTypes from './reportActionPropTypes';
+import CONST from '../../../CONST';
+import * as StyleUtils from '../../../styles/StyleUtils';
const propTypes = {
/** Personal details of all the users */
@@ -48,7 +50,7 @@ const propTypes = {
mostRecentIOUReportSequenceNumber: PropTypes.number,
/** Are we loading more report actions? */
- isLoadingReportActions: PropTypes.bool.isRequired,
+ isLoadingMoreReportActions: PropTypes.bool.isRequired,
/** Callback executed on list layout */
onLayout: PropTypes.func.isRequired,
@@ -74,6 +76,22 @@ class ReportActionsList extends React.Component {
this.renderItem = this.renderItem.bind(this);
this.renderCell = this.renderCell.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
+
+ this.state = {
+ fadeInAnimation: new Animated.Value(0),
+ };
+ }
+
+ componentDidMount() {
+ this.fadeIn();
+ }
+
+ fadeIn() {
+ Animated.timing(this.state.fadeInAnimation, {
+ toValue: 1,
+ duration: 100,
+ useNativeDriver: true,
+ }).start();
}
/**
@@ -156,28 +174,34 @@ class ReportActionsList extends React.Component {
const extraData = (!this.props.isDrawerOpen && this.props.isSmallScreenWidth) ? this.props.report.newMarkerSequenceNumber : undefined;
const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(this.props.personalDetails, this.props.report);
return (
-
- : null}
- keyboardShouldPersistTaps="handled"
- onLayout={this.props.onLayout}
- onScroll={this.props.onScroll}
- extraData={extraData}
- />
+
+
+ )
+ : null}
+ keyboardShouldPersistTaps="handled"
+ onLayout={this.props.onLayout}
+ onScroll={this.props.onScroll}
+ extraData={extraData}
+ />
+
);
}
}
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index ef288001ce0e..ed80f0235ae7 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -66,7 +66,7 @@ const propTypes = {
isComposerFullSize: PropTypes.bool.isRequired,
/** Are we loading more report actions? */
- isLoadingReportActions: PropTypes.bool,
+ isLoadingMoreReportActions: PropTypes.bool,
/** Are we waiting for more report data? */
isLoadingReportData: PropTypes.bool,
@@ -87,7 +87,7 @@ const defaultProps = {
},
reportActions: {},
session: {},
- isLoadingReportActions: false,
+ isLoadingMoreReportActions: false,
isLoadingReportData: false,
};
@@ -164,7 +164,7 @@ class ReportActionsView extends React.Component {
return true;
}
- if (nextProps.isLoadingReportActions !== this.props.isLoadingReportActions) {
+ if (nextProps.isLoadingMoreReportActions !== this.props.isLoadingMoreReportActions) {
return true;
}
@@ -275,7 +275,7 @@ class ReportActionsView extends React.Component {
}
fetchData() {
- Report.fetchActions(this.props.reportID);
+ Report.fetchInitialActions(this.props.reportID);
}
/**
@@ -284,7 +284,7 @@ class ReportActionsView extends React.Component {
*/
loadMoreChats() {
// Only fetch more if we are not already fetching so that we don't initiate duplicate requests.
- if (this.props.isLoadingReportActions) {
+ if (this.props.isLoadingMoreReportActions) {
return;
}
@@ -299,8 +299,8 @@ class ReportActionsView extends React.Component {
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments, unless we're near the beginning, in which
// case just get everything starting from 0.
- const offset = Math.max(minSequenceNumber - CONST.REPORT.ACTIONS.LIMIT, 0);
- Report.fetchActionsWithLoadingState(this.props.reportID, offset);
+ const oldestActionSequenceNumber = Math.max(minSequenceNumber - CONST.REPORT.ACTIONS.LIMIT, 0);
+ Report.readOldestAction(this.props.reportID, oldestActionSequenceNumber);
}
scrollToBottomAndMarkReportAsRead() {
@@ -431,7 +431,7 @@ class ReportActionsView extends React.Component {
onLayout={this.recordTimeToMeasureItemLayout}
sortedReportActions={this.sortedReportActions}
mostRecentIOUReportSequenceNumber={this.mostRecentIOUReportSequenceNumber}
- isLoadingReportActions={this.props.isLoadingReportActions}
+ isLoadingMoreReportActions={this.props.isLoadingMoreReportActions}
loadMoreChats={this.loadMoreChats}
/>
@@ -457,8 +457,8 @@ export default compose(
isLoadingReportData: {
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
},
- isLoadingReportActions: {
- key: ONYXKEYS.IS_LOADING_REPORT_ACTIONS,
+ isLoadingMoreReportActions: {
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.IS_LOADING_MORE_REPORT_ACTIONS}${reportID}`,
initWithStoredValues: false,
},
}),
diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js
index e800542a00a5..b8b408b4c815 100644
--- a/src/pages/iou/IOUCurrencySelection.js
+++ b/src/pages/iou/IOUCurrencySelection.js
@@ -60,8 +60,10 @@ class IOUCurrencySelection extends Component {
* @returns {Array}
*/
getSections() {
+ if (this.state.searchValue.trim() && !this.state.currencyData.length) {
+ return [];
+ }
const sections = [];
-
sections.push({
title: this.props.translate('iOUCurrencySelection.allCurrencies'),
data: this.state.currencyData,
@@ -112,6 +114,7 @@ class IOUCurrencySelection extends Component {
}
render() {
+ const headerMessage = this.state.searchValue.trim() && !this.state.currencyData.length ? this.props.translate('common.noResultsFound') : '';
return (
@@ -126,6 +129,7 @@ class IOUCurrencySelection extends Component {
onChangeText={this.changeSearchValue}
shouldDelayFocus
placeholderText={this.props.translate('common.search')}
+ headerMessage={headerMessage}
/>
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index 98988f7a5ba3..14a24b473aad 100755
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -174,7 +174,7 @@ class IOUModal extends Component {
* Decides our animation type based on whether we're increasing or decreasing
* our step index.
* @returns {String}
- */
+ */
getDirection() {
if (this.state.previousStepIndex < this.state.currentStepIndex) {
return 'in';
diff --git a/src/pages/iou/steps/IOUAmountPage.js b/src/pages/iou/steps/IOUAmountPage.js
index 90d00cf77e04..28ca5c68034c 100755
--- a/src/pages/iou/steps/IOUAmountPage.js
+++ b/src/pages/iou/steps/IOUAmountPage.js
@@ -6,6 +6,7 @@ import {
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
+import _ from 'underscore';
import ONYXKEYS from '../../../ONYXKEYS';
import styles from '../../../styles/styles';
import BigNumberPad from '../../../components/BigNumberPad';
@@ -52,11 +53,12 @@ class IOUAmountPage extends React.Component {
this.updateAmountNumberPad = this.updateAmountNumberPad.bind(this);
this.updateAmount = this.updateAmount.bind(this);
+ this.stripCommaFromAmount = this.stripCommaFromAmount.bind(this);
this.focusTextInput = this.focusTextInput.bind(this);
this.navigateToCurrencySelectionPage = this.navigateToCurrencySelectionPage.bind(this);
this.state = {
- amount: props.selectedAmount.replace('.', this.props.fromLocaleDigit('.')),
+ amount: props.selectedAmount,
};
}
@@ -111,11 +113,30 @@ class IOUAmountPage extends React.Component {
* @returns {Boolean}
*/
validateAmount(amount) {
- const decimalSeparator = this.props.fromLocaleDigit('.');
- const decimalNumberRegex = RegExp(String.raw`^\d+([${decimalSeparator}]\d{0,2})?$`, 'i');
+ const decimalNumberRegex = new RegExp(/^\d+(,\d+)*(\.\d{0,2})?$/, 'i');
return amount === '' || (decimalNumberRegex.test(amount) && this.calculateAmountLength(amount) <= CONST.IOU.AMOUNT_MAX_LENGTH);
}
+ /**
+ * Strip comma from the amount
+ *
+ * @param {String} amount
+ * @returns {String}
+ */
+ stripCommaFromAmount(amount) {
+ return amount.replace(/,/g, '');
+ }
+
+ /**
+ * Adds a leading zero to the amount if user entered just the decimal separator
+ *
+ * @param {String} amount - Changed amount from user input
+ * @returns {String}
+ */
+ addLeadingZero(amount) {
+ return amount === '.' ? '0.' : amount;
+ }
+
/**
* Update amount with number or Backspace pressed for BigNumberPad.
* Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button
@@ -134,8 +155,8 @@ class IOUAmountPage extends React.Component {
}
this.setState((prevState) => {
- const amount = `${prevState.amount}${key}`;
- return this.validateAmount(amount) ? {amount} : prevState;
+ const amount = this.addLeadingZero(`${prevState.amount}${key}`);
+ return this.validateAmount(amount) ? {amount: this.stripCommaFromAmount(amount)} : prevState;
});
}
@@ -143,10 +164,36 @@ class IOUAmountPage extends React.Component {
* Update amount on amount change
* Validate new amount with decimal number regex up to 6 digits and 2 decimal digit
*
- * @param {String} amount - Changed amount from user input
+ * @param {String} text - Changed text from user input
+ */
+ updateAmount(text) {
+ this.setState((prevState) => {
+ const amount = this.addLeadingZero(this.replaceAllDigits(text, this.props.fromLocaleDigit));
+ return this.validateAmount(amount)
+ ? {amount: this.stripCommaFromAmount(amount)}
+ : prevState;
+ });
+ }
+
+ /**
+ * Replaces each character by calling `convertFn`. If `convertFn` throws an error, then
+ * the original character will be preserved.
+ *
+ * @param {String} text
+ * @param {Function} convertFn - `this.props.fromLocaleDigit` or `this.props.toLocaleDigit`
+ * @returns {String}
*/
- updateAmount(amount) {
- this.setState(prevState => (this.validateAmount(amount) ? {amount} : prevState));
+ replaceAllDigits(text, convertFn) {
+ return _.chain([...text])
+ .map((char) => {
+ try {
+ return convertFn(char);
+ } catch {
+ return char;
+ }
+ })
+ .join('')
+ .value();
}
navigateToCurrencySelectionPage() {
@@ -160,6 +207,8 @@ class IOUAmountPage extends React.Component {
}
render() {
+ const formattedAmount = this.replaceAllDigits(this.state.amount, this.props.toLocaleDigit);
+
return (
<>
this.props.onStepComplete(this.state.amount.replace(this.props.fromLocaleDigit('.'), '.'))}
+ onPress={() => this.props.onStepComplete(this.state.amount)}
pressOnEnter
isDisabled={!this.state.amount.length || parseFloat(this.state.amount) < 0.01}
text={this.props.translate('common.next')}
diff --git a/src/pages/policyMemberPropType.js b/src/pages/policyMemberPropType.js
new file mode 100644
index 000000000000..0e5c39e02369
--- /dev/null
+++ b/src/pages/policyMemberPropType.js
@@ -0,0 +1,12 @@
+import PropTypes from 'prop-types';
+
+export default PropTypes.shape({
+ /** Role of the user in the policy */
+ role: PropTypes.string,
+
+ /**
+ * Errors from api calls on the specific user
+ * {: 'error message', : 'error message 2'}
+ */
+ errors: PropTypes.objectOf(PropTypes.string),
+});
diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js
index 4f7ff002bb0a..f75554826ce0 100644
--- a/src/pages/settings/AboutPage/AboutPage.js
+++ b/src/pages/settings/AboutPage/AboutPage.js
@@ -79,6 +79,7 @@ const AboutPage = (props) => {
{
iconStyles: policy.avatarURL ? [] : [styles.popoverMenuIconEmphasized],
iconFill: themeColors.iconReversed,
fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
+ brickRoadIndicator: Policy.hasPolicyMemberError(lodashGet(props.policyMembers, `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policy.id}`, {})) ? 'error' : null,
}))
.value();
menuItems.push(...defaultMenuItems);
@@ -190,6 +198,7 @@ const InitialSettingsPage = (props) => {
shouldShowRightIcon
badgeText={(isPaymentItem && Permissions.canUseWallet(props.betas)) ? walletBalance : undefined}
fallbackIcon={item.fallbackIcon}
+ brickRoadIndicator={item.brickRoadIndicator}
/>
);
})}
@@ -214,6 +223,9 @@ export default compose(
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
+ policyMembers: {
+ key: ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST,
+ },
userWallet: {
key: ONYXKEYS.USER_WALLET,
},
diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js
index 2e5aeb84189d..23d70132c056 100755
--- a/src/pages/settings/PasswordPage.js
+++ b/src/pages/settings/PasswordPage.js
@@ -200,20 +200,20 @@ class PasswordPage extends Component {
onSubmitEditing={this.submit}
/>
{shouldShowNewPasswordPrompt && (
-
- {this.props.translate('passwordPage.newPasswordPrompt')}
-
+
+ {this.props.translate('passwordPage.newPasswordPrompt')}
+
)}
{_.every(this.state.errors, error => !error) && !_.isEmpty(this.props.account.error) && (
-
- {this.props.account.error}
-
+
+ {this.props.account.error}
+
)}
diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
index 8404c1264547..56f39910e421 100644
--- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
+++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
@@ -29,11 +29,11 @@ import ConfirmPopover from '../../../../components/ConfirmPopover';
import AddPaymentMethodMenu from '../../../../components/AddPaymentMethodMenu';
import CONST from '../../../../CONST';
import * as Expensicons from '../../../../components/Icon/Expensicons';
-import ConfirmModal from '../../../../components/ConfirmModal';
import KYCWall from '../../../../components/KYCWall';
import {propTypes, defaultProps} from './paymentsPagePropTypes';
import {withNetwork} from '../../../../components/OnyxProvider';
import * as PaymentUtils from '../../../../libs/PaymentUtils';
+import OfflineIndicator from '../../../../components/OfflineIndicator';
class BasePaymentsPage extends React.Component {
constructor(props) {
@@ -293,6 +293,7 @@ class BasePaymentsPage extends React.Component {
icon={Expensicons.Transfer}
onPress={triggerKYCFlow}
shouldShowRightIcon
+ disabled={this.props.network.isOffline}
/>
)}
@@ -435,19 +436,7 @@ class BasePaymentsPage extends React.Component {
shouldShowCancelButton
danger
/>
-
+
);
diff --git a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
index 1a441577aa02..8783a0e47249 100644
--- a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
+++ b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
@@ -41,7 +41,7 @@ const propTypes = {
const defaultProps = {
walletTransfer: {
- shouldShowConfirmModal: false,
+ shouldShowSuccess: false,
},
betas: [],
isLoadingPaymentMethods: true,
diff --git a/src/pages/settings/Payments/TransferBalancePage.js b/src/pages/settings/Payments/TransferBalancePage.js
index 2305b9532c41..b1699f367e8f 100644
--- a/src/pages/settings/Payments/TransferBalancePage.js
+++ b/src/pages/settings/Payments/TransferBalancePage.js
@@ -9,9 +9,12 @@ import ScreenWrapper from '../../../components/ScreenWrapper';
import Navigation from '../../../libs/Navigation/Navigation';
import styles from '../../../styles/styles';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import {withNetwork} from '../../../components/OnyxProvider';
import compose from '../../../libs/compose';
-import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView';
import * as Expensicons from '../../../components/Icon/Expensicons';
+import * as Illustrations from '../../../components/Icon/Illustrations';
+import Icon from '../../../components/Icon';
+import defaultTheme from '../../../styles/themes/default';
import MenuItem from '../../../components/MenuItem';
import CONST from '../../../CONST';
import variables from '../../../styles/variables';
@@ -25,6 +28,8 @@ import * as PaymentUtils from '../../../libs/PaymentUtils';
import cardPropTypes from '../../../components/cardPropTypes';
import userWalletPropTypes from '../../EnablePayments/userWalletPropTypes';
import ROUTES from '../../../ROUTES';
+import OfflineIndicator from '../../../components/OfflineIndicator';
+import FormAlertWithSubmitButton from '../../../components/FormAlertWithSubmitButton';
const propTypes = {
/** User's wallet information */
@@ -124,15 +129,6 @@ class TransferBalancePage extends React.Component {
return selectedAccount || defaultAccount;
}
- /**
- * @param {Number} transferAmount
- * @param {Object} selectedAccount
- */
- saveTransferAmountAndStartTransfer(transferAmount, selectedAccount) {
- PaymentMethods.saveWalletTransferAmount(transferAmount);
- PaymentMethods.transferWalletBalance(selectedAccount);
- }
-
/**
* @param {String} filterPaymentMethodType
*/
@@ -161,6 +157,43 @@ class TransferBalancePage extends React.Component {
}
render() {
+ if (this.props.walletTransfer.shouldShowSuccess && !this.props.walletTransfer.loading) {
+ return (
+
+
+
+
+
+
+ {this.props.translate('transferAmountPage.transferSuccess')}
+
+
+ {this.props.walletTransfer.paymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT
+ ? this.props.translate('transferAmountPage.transferDetailBankAccount')
+ : this.props.translate('transferAmountPage.transferDetailDebitCard')}
+
+
+
+
+
+
+ );
+ }
const selectedAccount = this.getSelectedPaymentMethodAccount();
const selectedPaymentType = selectedAccount && selectedAccount.accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT
? CONST.WALLET.TRANSFER_METHOD_TYPE.ACH
@@ -170,100 +203,100 @@ class TransferBalancePage extends React.Component {
const transferAmount = this.props.userWallet.currentBalance - calculatedFee;
const isTransferable = transferAmount > 0;
const isButtonDisabled = !isTransferable || !selectedAccount;
+ const error = this.props.walletTransfer.error;
return (
-
- Navigation.goBack()}
- onCloseButtonPress={() => Navigation.dismissModal(true)}
- />
-
-
+ Navigation.goBack()}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+
+
+
+
+
+ {_.map(this.paymentTypes, paymentType => (
+
-
-
- {_.map(this.paymentTypes, paymentType => (
-
+
+ {this.props.translate('transferAmountPage.whichAccount')}
+
+ {Boolean(selectedAccount)
+ && (
+
-
-
-
+
+
+
+
+ PaymentMethods.transferWalletBalance(selectedAccount)}
+ isDisabled={isButtonDisabled || this.props.network.isOffline}
+ message={error}
+ isAlertVisible={!_.isEmpty(error)}
+ />
+
+
);
}
@@ -274,6 +307,7 @@ TransferBalancePage.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
userWallet: {
key: ONYXKEYS.USER_WALLET,
diff --git a/src/pages/settings/Payments/walletTransferPropTypes.js b/src/pages/settings/Payments/walletTransferPropTypes.js
index 9d191d9211ba..8a2ab06c9066 100644
--- a/src/pages/settings/Payments/walletTransferPropTypes.js
+++ b/src/pages/settings/Payments/walletTransferPropTypes.js
@@ -6,14 +6,11 @@ const walletTransferPropTypes = PropTypes.shape({
/** Selected accountID for transfer */
selectedAccountID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- /** Amount being transferred */
- transferAmount: PropTypes.number,
-
/** Type to filter the payment Method list */
filterPaymentMethodType: PropTypes.oneOf([CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.BANK_ACCOUNT, '']),
- /** Whether the confirmModal is shown to user. */
- shouldShowConfirmModal: PropTypes.bool,
+ /** Whether the success screen is shown to user. */
+ shouldShowSuccess: PropTypes.bool,
});
export default walletTransferPropTypes;
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index ddd59b58d105..b2583dc7f2bd 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -76,8 +76,8 @@ class ProfilePage extends Component {
pronouns: this.props.currentUserPersonalDetails.pronouns,
hasPronounError: false,
hasSelfSelectedPronouns: !_.isEmpty(this.props.currentUserPersonalDetails.pronouns) && !this.props.currentUserPersonalDetails.pronouns.startsWith(CONST.PRONOUNS.PREFIX),
- selectedTimezone: lodashGet(this.props.currentUserPersonalDetails.timezone, 'selected', CONST.DEFAULT_TIME_ZONE.selected),
- isAutomaticTimezone: lodashGet(this.props.currentUserPersonalDetails.timezone, 'automatic', CONST.DEFAULT_TIME_ZONE.automatic),
+ selectedTimezone: lodashGet(this.props.currentUserPersonalDetails, 'timezone.selected', CONST.DEFAULT_TIME_ZONE.selected),
+ isAutomaticTimezone: lodashGet(this.props.currentUserPersonalDetails, 'timezone.automatic', CONST.DEFAULT_TIME_ZONE.automatic),
logins: this.getLogins(props.loginList),
avatar: {uri: lodashGet(this.props.currentUserPersonalDetails, 'avatar', ReportUtils.getDefaultAvatar(this.props.currentUserPersonalDetails.login))},
isAvatarChanged: false,
diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js
index 701cc41d8561..95956cb7b933 100755
--- a/src/pages/signin/ResendValidationForm.js
+++ b/src/pages/signin/ResendValidationForm.js
@@ -14,6 +14,9 @@ import compose from '../../libs/compose';
import redirectToSignIn from '../../libs/actions/SignInRedirect';
import Avatar from '../../components/Avatar';
import * as ReportUtils from '../../libs/ReportUtils';
+import OfflineIndicator from '../../components/OfflineIndicator';
+import networkPropTypes from '../../components/networkPropTypes';
+import {withNetwork} from '../../components/OnyxProvider';
const propTypes = {
/* Onyx Props */
@@ -39,6 +42,9 @@ const propTypes = {
accountExists: PropTypes.bool,
}),
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
...withLocalizePropTypes,
};
@@ -143,8 +149,10 @@ class ResendValidationForm extends React.Component {
text={this.props.translate('resendValidationForm.resendLink')}
isLoading={this.props.account.loading}
onPress={this.validateAndSubmitForm}
+ isDisabled={this.props.network.isOffline}
/>
+
>
);
}
@@ -155,6 +163,7 @@ ResendValidationForm.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
credentials: {key: ONYXKEYS.CREDENTIALS},
account: {key: ONYXKEYS.ACCOUNT},
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
index d01190a221c9..9ae0b280e24f 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
@@ -77,13 +77,16 @@ class WorkspaceReimburseView extends React.Component {
}
getRateDisplayValue(value) {
- return value.toString().replace('.', this.props.fromLocaleDigit('.'));
+ const numValue = parseFloat(value);
+ if (Number.isNaN(numValue)) {
+ return '';
+ }
+
+ return numValue.toFixed(3);
}
setRate(value) {
- const decimalSeparator = this.props.fromLocaleDigit('.');
- const rateValueRegex = RegExp(String.raw`^\d{1,8}([${decimalSeparator}]\d{0,3})?$`, 'i');
- const isInvalidRateValue = value !== '' && !rateValueRegex.test(value);
+ const isInvalidRateValue = value !== '' && !CONST.REGEX.RATE_VALUE.test(value);
this.setState(prevState => ({
rateValue: !isInvalidRateValue ? value : prevState.rateValue,
@@ -112,12 +115,16 @@ class WorkspaceReimburseView extends React.Component {
}
updateRateValue(value) {
- const numValue = parseFloat(value.replace(this.props.fromLocaleDigit('.'), '.'));
+ const numValue = parseFloat(value);
if (_.isNaN(numValue)) {
return;
}
+ this.setState({
+ rateValue: numValue.toFixed(3),
+ });
+
Policy.setCustomUnitRate(this.props.policyID, this.state.unitID, {
customUnitRateID: this.state.rateID,
name: this.state.rateName,
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index fe7113bb6314..38f5a412a0f3 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -437,6 +437,19 @@ function parseStyleAsArray(styleParam) {
return _.isArray(styleParam) ? styleParam : [styleParam];
}
+/**
+ * Receives any number of object or array style objects and returns them all as an array
+ * @param {Object|Object[]} allStyles
+ * @return {Object[]}
+ */
+function combineStyles(...allStyles) {
+ let finalStyles = [];
+ _.each(allStyles, (style) => {
+ finalStyles = finalStyles.concat(parseStyleAsArray(style));
+ });
+ return finalStyles;
+}
+
/**
* Get variable padding-left as style
* @param {Number} paddingLeft
@@ -448,6 +461,28 @@ function getPaddingLeft(paddingLeft) {
};
}
+/**
+ * Get animated opacity for report chat list
+ * @param {Animated.Value} fadeInAnimation
+ * @returns {Object}
+ */
+function getReportListAnimationStyle(fadeInAnimation) {
+ return {
+ ...styles.flex1,
+ opacity: fadeInAnimation,
+ };
+}
+
+/**
+ * Android only - convert RTL text to a LTR text using Unicode controls.
+ * https://www.w3.org/International/questions/qa-bidi-unicode-controls
+ * @param {String} text
+ * @returns {String}
+ */
+function convertToLTR(text) {
+ return `\u2066${text}`;
+}
+
export {
getAvatarSize,
getAvatarStyle,
@@ -474,5 +509,8 @@ export {
getMiniReportActionContextMenuWrapperStyle,
getPaymentMethodMenuWidth,
parseStyleAsArray,
+ combineStyles,
getPaddingLeft,
+ getReportListAnimationStyle,
+ convertToLTR,
};
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 0ce4f9518e0f..ad636e183bd0 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -15,6 +15,7 @@ import textInputAlignSelf from './utilities/textInputAlignSelf';
import positioning from './utilities/positioning';
import codeStyles from './codeStyles';
import visibility from './utilities/visibility';
+import writingDirection from './utilities/writingDirection';
import optionAlternateTextPlatformStyles from './optionAlternateTextPlatformStyles';
import pointerEventsNone from './pointerEventsNone';
import overflowXHidden from './overflowXHidden';
@@ -148,6 +149,7 @@ const styles = {
...positioning,
...wordBreak,
...whiteSpace,
+ ...writingDirection,
rateCol: {
margin: 0,
@@ -663,6 +665,14 @@ const styles = {
marginLeft: 48,
},
+ offlineIndicator: {
+ marginLeft: 48,
+ },
+
+ offlineIndicatorMobile: {
+ marginLeft: 25,
+ },
+
// Actions
actionAvatar: {
borderRadius: 20,
@@ -726,6 +736,7 @@ const styles = {
paddingBottom: 8,
paddingHorizontal: 11,
borderWidth: 0,
+ borderRadius: variables.componentBorderRadiusNormal,
},
textInputMultiline: {
@@ -2356,6 +2367,9 @@ const styles = {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
+ },
+
+ peopleRowOfflineFeedback: {
borderBottomWidth: 1,
borderColor: themeColors.border,
...spacing.pv2,
@@ -2377,6 +2391,36 @@ const styles = {
...whiteSpace.noWrap,
},
+ offlineFeedback: {
+ deleted: {
+ textDecorationLine: 'line-through',
+ textDecorationStyle: 'solid',
+ },
+ pending: {
+ opacity: 0.5,
+ },
+ error: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ container: {
+ ...spacing.pv2,
+ },
+ textContainer: {
+ flexDirection: 'column',
+ flex: 1,
+ },
+ text: {
+ color: themeColors.textSupporting,
+ flex: 1,
+ textAlignVertical: 'center',
+ fontSize: variables.fontSizeLabel,
+ },
+ errorDot: {
+ marginRight: 12,
+ },
+ },
+
sidebarPopover: {
width: variables.sideBarWidth - 68,
},
diff --git a/src/styles/utilities/writingDirection.js b/src/styles/utilities/writingDirection.js
new file mode 100644
index 000000000000..d9c630c86912
--- /dev/null
+++ b/src/styles/utilities/writingDirection.js
@@ -0,0 +1,13 @@
+/**
+ * Writing direction utility styles.
+ * Note: writingDirection isn't supported on Android. Unicode controls are being used for Android
+ * https://www.w3.org/International/questions/qa-bidi-unicode-controls
+ */
+export default {
+ rtl: {
+ writingDirection: 'rtl',
+ },
+ ltr: {
+ writingDirection: 'ltr',
+ },
+};
diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js
index 77d19c5eaa1a..da8c4de756fb 100644
--- a/tests/actions/ReportTest.js
+++ b/tests/actions/ReportTest.js
@@ -81,14 +81,7 @@ describe('actions/Report', () => {
User.subscribeToUserEvents();
return waitForPromisesToResolve();
})
- .then(() => TestHelper.fetchPersonalDetailsForTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, {
- [TEST_USER_LOGIN]: {
- accountID: TEST_USER_ACCOUNT_ID,
- email: TEST_USER_LOGIN,
- firstName: 'Test',
- lastName: 'User',
- },
- }))
+ .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID))
.then(() => {
// This is a fire and forget response, but once it completes we should be able to verify that we
// have an "optimistic" report action in Onyx.
@@ -111,7 +104,7 @@ describe('actions/Report', () => {
delete actionWithoutLoading.isLoading;
channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`,
value: {
reportID: REPORT_ID,
@@ -123,7 +116,7 @@ describe('actions/Report', () => {
},
},
{
- onyxMethod: 'merge',
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`,
value: {
[clientID]: null,
@@ -183,14 +176,7 @@ describe('actions/Report', () => {
// GIVEN a test user with initial data
return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN)
- .then(() => TestHelper.fetchPersonalDetailsForTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, {
- [TEST_USER_LOGIN]: {
- accountID: TEST_USER_ACCOUNT_ID,
- email: TEST_USER_LOGIN,
- firstName: 'Test',
- lastName: 'User',
- },
- }))
+ .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID))
.then(() => {
global.fetch = TestHelper.getGlobalFetchMock();
@@ -243,14 +229,7 @@ describe('actions/Report', () => {
User.subscribeToUserEvents();
return waitForPromisesToResolve();
})
- .then(() => TestHelper.fetchPersonalDetailsForTestUser(USER_1_ACCOUNT_ID, USER_1_LOGIN, {
- [USER_1_LOGIN]: {
- accountID: USER_1_ACCOUNT_ID,
- email: USER_1_LOGIN,
- firstName: 'Test',
- lastName: 'User',
- },
- }))
+ .then(() => TestHelper.setPersonalDetails(USER_1_LOGIN, USER_1_ACCOUNT_ID))
.then(() => {
// When a Pusher event is handled for a new report comment
channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [
diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js
index 9cee105e64b7..d619e9d72ab2 100644
--- a/tests/utils/TestHelper.js
+++ b/tests/utils/TestHelper.js
@@ -1,7 +1,10 @@
+import Onyx from 'react-native-onyx';
+import CONST from '../../src/CONST';
import * as Session from '../../src/libs/actions/Session';
-import * as PersonalDetails from '../../src/libs/actions/PersonalDetails';
import HttpUtils from '../../src/libs/HttpUtils';
+import ONYXKEYS from '../../src/ONYXKEYS';
import waitForPromisesToResolve from './waitForPromisesToResolve';
+import * as ReportUtils from '../../src/libs/ReportUtils';
/**
* Simulate signing in and make sure all API calls in this flow succeed. Every time we add
@@ -51,32 +54,6 @@ function signInWithTestUser(accountID = 1, login = 'test@user.com', password = '
});
}
-/**
- * Fetch and set personal details with provided personalDetailsList
- *
- * @param {Number} accountID
- * @param {String} email
- * @param {Object} personalDetailsList
- * @returns {Promise}
- */
-function fetchPersonalDetailsForTestUser(accountID, email, personalDetailsList) {
- const originalXHR = HttpUtils.xhr;
- HttpUtils.xhr = jest.fn();
-
- HttpUtils.xhr
- .mockImplementationOnce(() => Promise.resolve({
- accountID,
- email,
- personalDetailsList,
- }));
-
- PersonalDetails.fetchPersonalDetails();
- return waitForPromisesToResolve()
- .then(() => {
- HttpUtils.xhr = originalXHR;
- });
-}
-
/**
* Use for situations where fetch() is required.
*
@@ -98,8 +75,34 @@ function getGlobalFetchMock() {
});
}
+/**
+ * @param {String} login
+ * @param {Number} accountID
+ * @returns {Promise}
+ */
+function setPersonalDetails(login, accountID) {
+ const avatar = ReportUtils.getDefaultAvatar(login);
+ const details = {
+ accountID,
+ login,
+ avatar,
+ displayName: 'Test User',
+ firstName: 'Test',
+ lastName: 'User',
+ pronouns: '',
+ timezone: CONST.DEFAULT_TIME_ZONE,
+ payPalMeAddress: '',
+ phoneNumber: '',
+ avatarHighResolution: avatar,
+ };
+ Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {
+ [login]: details,
+ });
+ return waitForPromisesToResolve();
+}
+
export {
getGlobalFetchMock,
signInWithTestUser,
- fetchPersonalDetailsForTestUser,
+ setPersonalDetails,
};