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} - - -