diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml
index 350380aed2b9..51d7eae346a3 100644
--- a/.github/workflows/cherryPick.yml
+++ b/.github/workflows/cherryPick.yml
@@ -148,6 +148,7 @@ jobs:
--body "🍒 Cherry pick https://github.com/Expensify/App/pull/${{ github.event.inputs.PULL_REQUEST_NUMBER }} to staging 🍒" \
--label "automerge" \
--base "staging"
+ sleep 5
echo "::set-output name=PR_NUMBER::$(gh pr view --json 'number' --jq '.number')"
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
@@ -205,7 +206,7 @@ jobs:
- name: Auto-merge the PR
# Important: only auto-merge if there was no merge conflict and the PR is mergable (not blocked by a missing status check)!
if: ${{ fromJSON(steps.cherryPick.outputs.SHOULD_AUTOMERGE) && fromJSON(steps.isPullRequestMergeable.outputs.IS_MERGEABLE) }}
- run: gh pr merge --merge --delete-branch
+ run: gh pr merge ${{ steps.createPullRequest.outputs.pr_number }} --merge --delete-branch
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index 608aa21204e1..5575d05609f2 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -209,13 +209,12 @@ jobs:
max_attempts: 5
command: npm ci
- - uses: actions/cache@v2
- with:
- path: ios/Pods
- key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
-
- name: Install cocoapods
- run: cd ios && pod install
+ uses: nick-invision/retry@7c68161adf97a48beb850a595b8784ec57a98cbb
+ with:
+ timeout_minutes: 10
+ max_attempts: 5
+ command: cd ios && pod install
- name: Decrypt profile
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output chat_expensify_appstore.mobileprovision chat_expensify_appstore.mobileprovision.gpg
diff --git a/.github/workflows/updateProtectedBranch.yml b/.github/workflows/updateProtectedBranch.yml
index 40f00fb63cd0..b03689245b6f 100644
--- a/.github/workflows/updateProtectedBranch.yml
+++ b/.github/workflows/updateProtectedBranch.yml
@@ -86,6 +86,7 @@ jobs:
--body "Update version to ${{ env.NEW_VERSION }}" \
--label "automerge" \
--base ${{ github.event.inputs.TARGET_BRANCH }}
+ sleep 5
echo "::set-output name=PR_NUMBER::$(gh pr view --json 'number' --jq '.number')"
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
@@ -129,7 +130,7 @@ jobs:
run: exit 1
- name: Auto-merge the PR
- run: gh pr merge --merge --delete-branch
+ run: gh pr merge ${{ steps.createPullRequest.outputs.PR_NUMBER }} --merge --delete-branch
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/FORMS.md b/FORMS.md
index 65e9cfaeae0c..d80b2dff3310 100644
--- a/FORMS.md
+++ b/FORMS.md
@@ -210,4 +210,4 @@ Form.js will automatically provide the following props to any input with the inp
- defaultValue: The input default value.
- errorText: The translated error text that is returned by validate for that specific input.
- onBlur: An onBlur handler that calls validate.
-- onChange: An onChange handler that saves draft values and calls validate.
+- onInputChange: An onChange handler that saves draft values and calls validate for that input (inputA). Passing an inputID as a second param allows inputA to manipulate the input value of the provided inputID (inputB).
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 0b16d9136f37..d6d3e5dc1514 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 1001016003
- versionName "1.1.60-3"
+ versionCode 1001017003
+ versionName "1.1.70-3"
}
splits {
abi {
diff --git a/assets/images/avatars/fallback-avatar.svg b/assets/images/avatars/fallback-avatar.svg
new file mode 100644
index 000000000000..dc1a1497cfe5
--- /dev/null
+++ b/assets/images/avatars/fallback-avatar.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/assets/images/avatars/fallback-workspace-avatar.svg b/assets/images/avatars/fallback-workspace-avatar.svg
new file mode 100644
index 000000000000..ac2f58122a0f
--- /dev/null
+++ b/assets/images/avatars/fallback-workspace-avatar.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/assets/images/offline-cloud.svg b/assets/images/offline-cloud.svg
new file mode 100644
index 000000000000..d7dea2037561
--- /dev/null
+++ b/assets/images/offline-cloud.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/offline.svg b/assets/images/offline.svg
index a4d539125f31..f3b58e11221f 100644
--- a/assets/images/offline.svg
+++ b/assets/images/offline.svg
@@ -1,5 +1,5 @@
diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js
index 99cc83dfc5b9..e6002e4166d0 100644
--- a/config/webpack/webpack.common.js
+++ b/config/webpack/webpack.common.js
@@ -15,7 +15,6 @@ const includeModules = [
'react-native-webview',
'@react-native-picker',
'react-native-modal',
- 'react-native-onyx',
'react-native-gesture-handler',
'react-native-flipper',
'react-native-google-places-autocomplete',
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 93d6601ab4fa..e753f996c652 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.1.60
+ 1.1.70
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.1.60.3
+ 1.1.70.3
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 5e9bf6b0f020..a0f1f6647e12 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.1.60
+ 1.1.70
CFBundleSignature
????
CFBundleVersion
- 1.1.60.3
+ 1.1.70.3
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index fb504dc289e7..a4a11efbb120 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -435,6 +435,8 @@ PODS:
- React-jsinspector (0.66.4)
- React-logger (0.66.4):
- glog
+ - react-native-cameraroll (4.1.2):
+ - React-Core
- react-native-config (1.4.5):
- react-native-config/App (= 1.4.5)
- react-native-config/App (1.4.5):
@@ -445,7 +447,7 @@ PODS:
- React-Core
- react-native-image-picker (4.7.3):
- React-Core
- - react-native-netinfo (8.0.0):
+ - react-native-netinfo (8.3.0):
- React-Core
- react-native-pdf (6.2.2):
- React-Core
@@ -535,7 +537,7 @@ PODS:
- React-Core
- RNCMaskedView (0.2.4):
- React-Core
- - RNCPicker (1.9.11):
+ - RNCPicker (2.3.1):
- React-Core
- RNDateTimePicker (3.5.2):
- React-Core
@@ -659,6 +661,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
+ - "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-flipper (from `../node_modules/react-native-flipper`)
@@ -787,6 +790,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
+ react-native-cameraroll:
+ :path: "../node_modules/@react-native-community/cameraroll"
react-native-config:
:path: "../node_modules/react-native-config"
react-native-document-picker:
@@ -878,7 +883,7 @@ SPEC CHECKSUMS:
Airship: 29d674abeac754f783fc46c7d383d6f046687341
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
- DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
+ DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
FBLazyVector: e5569e42a1c79ca00521846c223173a57aca1fe1
FBReactNativeSpec: fe08c1cd7e2e205718d77ad14b34957cce949b58
Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f
@@ -900,7 +905,7 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: d8d346844eca5d9120c17d441a2f38596e8ed2b9
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
- glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
+ glog: 5337263514dd6f09803962437687240c5dc39aa4
GoogleAppMeasurement: 6b6a08fd9c71f4dbc89e0e812acca81d797aa342
GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
@@ -930,11 +935,12 @@ SPEC CHECKSUMS:
React-jsiexecutor: 94ce921e1d8ce7023366873ec371f3441383b396
React-jsinspector: d0374f7509d407d2264168b6d0fad0b54e300b85
React-logger: 933f80c97c633ee8965d609876848148e3fef438
+ react-native-cameraroll: 60ac50a5209777cbccfe8d7a62d0743a9da87060
react-native-config: 6502b1879f97ed5ac570a029961fc35ea606cd14
react-native-document-picker: 772d04a4bc5c35da9abe27b08ac271420ae3f9ef
react-native-flipper: cd9eabd8917104c1bbdca2621717cdca3b2addef
react-native-image-picker: ae1202414bd5c37c00b2a701daa5b6194a06b7d9
- react-native-netinfo: 0124c0695373fce63cea24aeebb97ab2d237947a
+ react-native-netinfo: ebbcd8fbe1a0ce7035e43cd18c5a545dcb93dd08
react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f
react-native-performance: 8edfa2bbc9a2af4a02f01d342118e413a95145e0
react-native-plaid-link-sdk: 9e0ebdaed648a237b36d5f6f6292b5147af92da7
@@ -958,7 +964,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: 8324611026e8dc3706f829953aa6e3899f581589
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCMaskedView: 138134c4d8a9421b4f2bf39055a79aa05c2d47b1
- RNCPicker: 6780c753e9e674065db90d9c965920516402579d
+ RNCPicker: f6c760d4b314585ff35165d8640d7917ae30afb1
RNDateTimePicker: c9911be59b1f8670b9f244b85af3a7c295e175ed
RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7
RNFBAnalytics: 8ba84c2d31c64374d054c8621b998f25145ffddc
@@ -979,4 +985,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 431123d7514c52fa4516724b89c20d02c87ad8c8
-COCOAPODS: 1.11.2
+COCOAPODS: 1.10.1
diff --git a/package-lock.json b/package-lock.json
index 91c8c59bdb3c..9f610e0d9d08 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.60-3",
+ "version": "1.1.70-3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6759,13 +6759,20 @@
}
},
"@react-navigation/drawer": {
- "version": "6.1.8",
- "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-6.1.8.tgz",
- "integrity": "sha512-kYE2EO5dianUuUcaYmAlYBcgtmvGm2fxWTQ5sn103cgPNidp4KBUR9ClkhF+btfRaHKq+8Ul5M6qvL0mBAv/Lg==",
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-6.3.0.tgz",
+ "integrity": "sha512-rbIpJCMeRVler6JI8eiHdvFEXbF8j8ii4cD42HeN9DqjpEJRfuz134ObM3O6Qd7h0k9U69exshbAUQ+7QaWesA==",
"requires": {
- "@react-navigation/elements": "^1.2.1",
+ "@react-navigation/elements": "^1.3.1",
"color": "^3.1.3",
"warn-once": "^0.1.0"
+ },
+ "dependencies": {
+ "@react-navigation/elements": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.3.tgz",
+ "integrity": "sha512-Lv2lR7si5gNME8dRsqz57d54m4FJtrwHRjNQLOyQO546ZxO+g864cSvoLC6hQedQU0+IJnPTsZiEI2hHqfpEpw=="
+ }
}
},
"@react-navigation/elements": {
@@ -23779,8 +23786,8 @@
}
},
"expensify-common": {
- "version": "git+https://github.com/Expensify/expensify-common.git#427295da130a4eacc184d38693664280d020dffd",
- "from": "git+https://github.com/Expensify/expensify-common.git#427295da130a4eacc184d38693664280d020dffd",
+ "version": "git+https://github.com/Expensify/expensify-common.git#e989d858aa2ab8191284ae106acf9cd323ab879d",
+ "from": "git+https://github.com/Expensify/expensify-common.git#e989d858aa2ab8191284ae106acf9cd323ab879d",
"requires": {
"classnames": "2.3.1",
"clipboard": "2.0.4",
@@ -23792,6 +23799,7 @@
"react-dom": "16.12.0",
"semver": "^7.3.5",
"simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
+ "string.prototype.replaceall": "^1.0.6",
"underscore": "1.13.1"
},
"dependencies": {
@@ -24755,7 +24763,6 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
@@ -24767,7 +24774,6 @@
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
"integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
@@ -24794,14 +24800,12 @@
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
- "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
- "dev": true
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"internal-slot": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
- "dev": true,
"requires": {
"get-intrinsic": "^1.1.0",
"has": "^1.0.3",
@@ -24811,20 +24815,17 @@
"is-callable": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
- "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==",
- "dev": true
+ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
},
"is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
- "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
- "dev": true
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA=="
},
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@@ -24834,7 +24835,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
- "dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -24842,14 +24842,12 @@
"object-inspect": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
- "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
- "dev": true
+ "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
},
"object.assign": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
- "dev": true,
"requires": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3",
@@ -24861,7 +24859,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
- "dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
@@ -24872,7 +24869,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
@@ -24882,7 +24878,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
@@ -24899,8 +24894,7 @@
"functions-have-names": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz",
- "integrity": "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==",
- "dev": true
+ "integrity": "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA=="
},
"fuse.js": {
"version": "3.6.1",
@@ -25205,7 +25199,7 @@
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
- "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+ "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
"requires": {
"delegate": "^3.1.2"
}
@@ -25366,6 +25360,14 @@
}
}
},
+ "has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "requires": {
+ "get-intrinsic": "^1.1.1"
+ }
+ },
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
@@ -36520,65 +36522,12 @@
}
},
"react-native-onyx": {
- "version": "git+https://github.com/Expensify/react-native-onyx.git#7ab6aed5ce9158f7017ee1c9fd8b5d725d57db73",
- "from": "git+https://github.com/Expensify/react-native-onyx.git#7ab6aed5ce9158f7017ee1c9fd8b5d725d57db73",
+ "version": "git+https://github.com/Expensify/react-native-onyx.git#4cc46d1ad70312d2e10adf7cdd7065ab9b472113",
+ "from": "git+https://github.com/Expensify/react-native-onyx.git#4cc46d1ad70312d2e10adf7cdd7065ab9b472113",
"requires": {
"ascii-table": "0.0.9",
- "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c",
- "lodash": "4.17.21",
+ "lodash": "^4.17.21",
"underscore": "^1.13.1"
- },
- "dependencies": {
- "expensify-common": {
- "version": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c",
- "from": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c",
- "requires": {
- "classnames": "2.2.5",
- "clipboard": "2.0.4",
- "html-entities": "^1.3.1",
- "jquery": "3.3.1",
- "lodash": "4.17.21",
- "prop-types": "15.7.2",
- "react": "16.12.0",
- "react-dom": "16.12.0",
- "semver": "^7.3.4",
- "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
- "underscore": "1.9.1"
- },
- "dependencies": {
- "underscore": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
- "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
- }
- }
- },
- "jquery": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
- "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
- },
- "react": {
- "version": "16.12.0",
- "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz",
- "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- }
- },
- "react-dom": {
- "version": "16.12.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz",
- "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.18.0"
- }
- }
}
},
"react-native-pdf": {
@@ -38422,6 +38371,7 @@
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+ "dev": true,
"requires": {
"lru-cache": "^6.0.0"
},
@@ -38430,6 +38380,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -38437,7 +38388,8 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
}
}
},
@@ -39681,6 +39633,203 @@
}
}
},
+ "string.prototype.replaceall": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.6.tgz",
+ "integrity": "sha512-OA8VDhE7ssNFlyoDXUHxw6V5cjgPrtosyJKqJX5i1P5tV9eUynsbhx1yz0g+Ye4fjFwAxhKLxt8GSRx2Aqc+Sw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.1",
+ "get-intrinsic": "^1.1.1",
+ "has-symbols": "^1.0.2",
+ "is-regex": "^1.1.4"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
+ "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.1.1",
+ "get-symbol-description": "^1.0.0",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "is-callable": "^1.2.4",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "regexp.prototype.flags": "^1.4.3",
+ "string.prototype.trimend": "^1.0.5",
+ "string.prototype.trimstart": "^1.0.5",
+ "unbox-primitive": "^1.0.2"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ },
+ "has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+ },
+ "internal-slot": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
+ "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
+ "requires": {
+ "get-intrinsic": "^1.1.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
+ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
+ },
+ "is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA=="
+ },
+ "is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object-inspect": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
+ },
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "requires": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
+ "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ }
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
+ "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ }
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
+ }
+ },
"string.prototype.trim": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz",
diff --git a/package.json b/package.json
index 4096beb17bfb..073e8d811ed9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.1.60-3",
+ "version": "1.1.70-3",
"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.",
@@ -55,14 +55,14 @@
"@react-native-masked-view/masked-view": "^0.2.4",
"@react-native-picker/picker": "^2.3.1",
"@react-navigation/compat": "^5.3.20",
- "@react-navigation/drawer": "6.1.8",
+ "@react-navigation/drawer": "6.3.0",
"@react-navigation/native": "6.0.8",
"@react-navigation/stack": "6.0.11",
"babel-plugin-transform-remove-console": "^6.9.4",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
"dotenv": "^8.2.0",
- "expensify-common": "git+https://github.com/Expensify/expensify-common.git#427295da130a4eacc184d38693664280d020dffd",
+ "expensify-common": "git+https://github.com/Expensify/expensify-common.git#e989d858aa2ab8191284ae106acf9cd323ab879d",
"fbjs": "^3.0.2",
"file-loader": "^6.0.0",
"html-entities": "^1.3.1",
@@ -91,7 +91,7 @@
"react-native-image-size": "^1.1.3",
"react-native-keyboard-spacer": "^0.4.1",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#7ab6aed5ce9158f7017ee1c9fd8b5d725d57db73",
+ "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#4cc46d1ad70312d2e10adf7cdd7065ab9b472113",
"react-native-pdf": "^6.2.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
diff --git a/src/CONST.js b/src/CONST.js
index e3a88d48cd7e..c0e26e78fca7 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -387,6 +387,8 @@ const CONST = {
EMOJI_FREQUENT_ROW_COUNT: 3,
+ EMOJI_INVISIBLE_CODEPOINT: 'fe0f',
+
TOOLTIP_MAX_LINES: 3,
LOGIN_TYPE: {
diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index 39d1a8f9dd09..73fb51e1a929 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -118,6 +118,7 @@ class AddPlaidBankAccount extends React.Component {
this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey);
this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, {
password: 'passwordForm.error.incorrectPassword',
+ selectedBank: 'bankAccount.error.noBankAccountSelected',
}, inputKey);
}
@@ -253,7 +254,7 @@ class AddPlaidBankAccount extends React.Component {
label: this.props.translate('bankAccount.chooseAnAccount'),
} : {}}
value={this.state.selectedIndex}
- hasError={this.getErrors().selectedBank}
+ errorText={this.getErrorText('selectedBank')}
/>
{!_.isUndefined(this.state.selectedIndex) && this.props.isPasswordRequired && (
diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js
index 9458a85361f4..7ad5474ae06b 100644
--- a/src/components/AddressSearch.js
+++ b/src/components/AddressSearch.js
@@ -106,7 +106,11 @@ const AddressSearch = (props) => {
if (_.size(values) === 0) {
return;
}
- props.onInputChange(values);
+ if (props.inputID) {
+ _.each(values, (value, key) => props.onInputChange(value, key));
+ } else {
+ props.onInputChange(values);
+ }
};
return (
@@ -161,7 +165,7 @@ const AddressSearch = (props) => {
label: props.label,
containerStyles: props.containerStyles,
errorText: props.errorText,
- hint: props.hint,
+ hint: displayListViewBorder ? undefined : props.hint,
value: props.value,
defaultValue: props.defaultValue,
inputID: props.inputID,
diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js
index bcb204c98369..af5be5de4d1a 100644
--- a/src/components/ArchivedReportFooter.js
+++ b/src/components/ArchivedReportFooter.js
@@ -1,37 +1,24 @@
-import lodashGet from 'lodash/get';
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
-import CONST from '../CONST';
import Banner from './Banner';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import compose from '../libs/compose';
import personalDetailsPropType from '../pages/personalDetailsPropType';
import ONYXKEYS from '../ONYXKEYS';
import * as ReportUtils from '../libs/ReportUtils';
+import reportActionPropTypes from '../pages/home/report/reportActionPropTypes';
const propTypes = {
- /** The reason this report was archived */
- reportClosedAction: PropTypes.shape({
- /** Message attached to the report closed action */
- originalMessage: PropTypes.shape({
- /** The reason the report was closed */
- reason: PropTypes.string.isRequired,
-
- /** (For accountMerged reason only), the email of the previous owner of this report. */
- oldLogin: PropTypes.string,
-
- /** (For accountMerged reason only), the email of the account the previous owner was merged into */
- newLogin: PropTypes.string,
- }).isRequired,
- }),
-
/** The archived report */
report: PropTypes.shape({
- /** The policy this report is attached to */
- policyID: PropTypes.string,
+ /** The email of the owner of the report */
+ ownerEmail: PropTypes.string,
}).isRequired,
+ /** Array of report actions for this report */
+ reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)).isRequired,
+
/** Personal details of all users */
personalDetails: PropTypes.objectOf(personalDetailsPropType).isRequired,
@@ -44,40 +31,14 @@ const propTypes = {
...withLocalizePropTypes,
};
-const defaultProps = {
- reportClosedAction: {
- originalMessage: {
- reason: CONST.REPORT.ARCHIVE_REASON.DEFAULT,
- },
- },
-};
-
const ArchivedReportFooter = (props) => {
- const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT);
- let displayName = lodashGet(props.personalDetails, `${props.report.ownerEmail}.displayName`, props.report.ownerEmail);
-
- let oldDisplayName;
- if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) {
- const newLogin = props.reportClosedAction.originalMessage.newLogin;
- const oldLogin = props.reportClosedAction.originalMessage.oldLogin;
- displayName = lodashGet(props.personalDetails, `${newLogin}.displayName`, newLogin);
- oldDisplayName = lodashGet(props.personalDetails, `${oldLogin}.displayName`, oldLogin);
- }
-
+ const archivedText = ReportUtils.getArchivedText(props.report, props.reportActions, props.personalDetails, props.policies);
return (
- ${displayName}`,
- oldDisplayName: `${oldDisplayName}`,
- policyName: `${ReportUtils.getPolicyName(props.report, props.policies)}`,
- })}
- shouldRenderHTML={archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT}
- />
+
);
};
ArchivedReportFooter.propTypes = propTypes;
-ArchivedReportFooter.defaultProps = defaultProps;
ArchivedReportFooter.displayName = 'ArchivedReportFooter';
export default compose(
diff --git a/src/components/Avatar.js b/src/components/Avatar.js
index 71ecd130ed9d..ff51d2c427b8 100644
--- a/src/components/Avatar.js
+++ b/src/components/Avatar.js
@@ -7,6 +7,7 @@ import Icon from './Icon';
import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import * as StyleUtils from '../styles/StyleUtils';
+import * as Expensicons from './Icon/Expensicons';
const propTypes = {
/** Source for the avatar. Can be a URL or an icon. */
@@ -23,6 +24,9 @@ const propTypes = {
/** The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' */
fill: PropTypes.string,
+
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
};
const defaultProps = {
@@ -31,9 +35,17 @@ const defaultProps = {
containerStyles: [],
size: CONST.AVATAR_SIZE.DEFAULT,
fill: themeColors.icon,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
class Avatar extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ imageError: false,
+ };
+ }
+
render() {
if (!this.props.source) {
return null;
@@ -45,13 +57,21 @@ class Avatar extends PureComponent {
];
const iconSize = StyleUtils.getAvatarSize(this.props.size);
+
return (
- {
- _.isFunction(this.props.source)
- ?
- :
- }
+ {_.isFunction(this.props.source) || this.state.imageError
+ ? (
+
+ )
+ : (
+ this.setState({imageError: true})} />
+ )}
);
}
diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js
index 02644c75fec2..2e0d4f3c8792 100644
--- a/src/components/AvatarWithImagePicker.js
+++ b/src/components/AvatarWithImagePicker.js
@@ -53,6 +53,9 @@ const propTypes = {
/** Size of Indicator */
size: PropTypes.oneOf([CONST.AVATAR_SIZE.LARGE, CONST.AVATAR_SIZE.DEFAULT]),
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
+
...withLocalizePropTypes,
};
@@ -65,6 +68,7 @@ const defaultProps = {
isUsingDefaultAvatar: false,
isUploading: false,
size: CONST.AVATAR_SIZE.DEFAULT,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
class AvatarWithImagePicker extends React.Component {
@@ -181,6 +185,8 @@ class AvatarWithImagePicker extends React.Component {
containerStyles={styles.avatarLarge}
imageStyles={[styles.avatarLarge, styles.alignSelfCenter]}
source={this.props.avatarURL}
+ fallbackIcon={this.props.fallbackIcon}
+ size={this.props.size}
/>
)
: (
diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index 0db3ff1253ef..aa37769156e3 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -101,6 +101,7 @@ class AvatarWithIndicator extends PureComponent {
diff --git a/src/components/Banner.js b/src/components/Banner.js
index 9c67174f0386..a4ea17eec2c8 100644
--- a/src/components/Banner.js
+++ b/src/components/Banner.js
@@ -5,21 +5,13 @@ import Hoverable from './Hoverable';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import RenderHTML from './RenderHTML';
-import Text from './Text';
import styles from '../styles/styles';
import * as StyleUtils from '../styles/StyleUtils';
import getButtonState from '../libs/getButtonState';
const propTypes = {
- /** Text to display in the banner. */
- text: PropTypes.string.isRequired,
-
- /** Should this component render the text as HTML? */
- shouldRenderHTML: PropTypes.bool,
-};
-
-const defaultProps = {
- shouldRenderHTML: false,
+ /** HTML to display in the banner. */
+ html: PropTypes.string.isRequired,
};
const Banner = props => (
@@ -31,6 +23,7 @@ const Banner = props => (
styles.p5,
styles.borderRadiusNormal,
isHovered ? styles.activeComponentBG : styles.hoveredComponentBG,
+ styles.breakAll,
]}
>
@@ -39,18 +32,13 @@ const Banner = props => (
fill={StyleUtils.getIconFillColor(getButtonState(isHovered))}
/>
- {
- props.shouldRenderHTML
- ?
- : {props.text}
- }
+
)}
);
Banner.propTypes = propTypes;
-Banner.defaultProps = defaultProps;
Banner.displayName = 'Banner';
export default memo(Banner);
diff --git a/src/components/Button.js b/src/components/Button.js
index 85254d4c1e75..4fcc046e0f7c 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -173,14 +173,11 @@ class Button extends Component {
return ;
}
- if (this.props.isLoading) {
- return ;
- }
-
const textComponent = (
@@ -273,6 +270,12 @@ class Button extends Component {
]}
>
{this.renderContent()}
+ {this.props.isLoading && (
+
+ )}
)}
diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js
index 78e01b756a61..164a21ce9a8e 100644
--- a/src/components/ConfirmContent.js
+++ b/src/components/ConfirmContent.js
@@ -76,7 +76,7 @@ const ConfirmContent = props => (
/>
{props.shouldShowCancelButton && (
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js
index 2d557eb75d5c..258a6e21536f 100755
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js
@@ -151,7 +151,6 @@ class EmojiPickerMenu extends Component {
// We allow typing in the search box if any key is pressed apart from Arrow keys.
if (this.searchInput && !this.searchInput.isFocused()) {
this.setState({selectTextOnFocus: false});
- this.searchInput.value = '';
this.searchInput.focus();
// Re-enable selection on the searchInput
diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js
new file mode 100644
index 000000000000..880f3a433aea
--- /dev/null
+++ b/src/components/ExceededCommentLength.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import CONST from '../CONST';
+import Text from './Text';
+import styles from '../styles/styles';
+import stylePropTypes from '../styles/stylePropTypes';
+
+const propTypes = {
+ /** The current length of the comment */
+ commentLength: PropTypes.number.isRequired,
+
+ /** Additional style props */
+ style: stylePropTypes,
+};
+
+const defaultProps = {
+ style: [],
+};
+
+const ExceededCommentLength = (props) => {
+ if (props.commentLength <= CONST.MAX_COMMENT_LENGTH) {
+ return null;
+ }
+
+ const additionalStyles = _.isArray(props.style) ? props.style : [props.style];
+
+ return (
+
+ {`${props.commentLength}/${CONST.MAX_COMMENT_LENGTH}`}
+
+ );
+};
+
+ExceededCommentLength.propTypes = propTypes;
+ExceededCommentLength.defaultProps = defaultProps;
+ExceededCommentLength.displayName = 'ExceededCommentLength';
+
+export default ExceededCommentLength;
diff --git a/src/components/Form.js b/src/components/Form.js
index 41fe3bc1b480..e33d3a959274 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -158,10 +158,16 @@ class Form extends React.Component {
this.setTouchedInput(inputID);
this.validate(this.inputValues);
},
- onInputChange: (value) => {
- this.inputValues[inputID] = value;
+ onInputChange: (value, key) => {
+ const inputKey = key || inputID;
+ this.inputValues[inputKey] = value;
+ const inputRef = this.inputRefs[inputKey];
+
+ if (key && inputRef && _.isFunction(inputRef.setNativeProps)) {
+ inputRef.setNativeProps({value});
+ }
if (child.props.shouldSaveDraft) {
- FormActions.setDraftValues(this.props.formID, {[inputID]: value});
+ FormActions.setDraftValues(this.props.formID, {[inputKey]: value});
}
this.validate(this.inputValues);
},
diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js
index 8647ceecf014..82cff4bf3838 100644
--- a/src/components/FormAlertWithSubmitButton.js
+++ b/src/components/FormAlertWithSubmitButton.js
@@ -6,11 +6,15 @@ import styles from '../styles/styles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import colors from '../styles/colors';
+import compose from '../libs/compose';
import Button from './Button';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import TextLink from './TextLink';
import Text from './Text';
import RenderHTML from './RenderHTML';
+import OfflineIndicator from './OfflineIndicator';
+import networkPropTypes from './networkPropTypes';
+import {withNetwork} from './OnyxProvider';
const propTypes = {
/** Whether to show the alert text */
@@ -41,6 +45,9 @@ const propTypes = {
isLoading: PropTypes.bool,
...withLocalizePropTypes,
+
+ /** Props to detect online status */
+ network: networkPropTypes.isRequired,
};
const defaultProps = {
@@ -95,6 +102,20 @@ const FormAlertWithSubmitButton = (props) => {
);
}
+ if (props.network.isOffline) {
+ return (
+
+
+
+
+ );
+ }
+
return (
{props.isAlertVisible && (
@@ -119,4 +140,7 @@ FormAlertWithSubmitButton.propTypes = propTypes;
FormAlertWithSubmitButton.defaultProps = defaultProps;
FormAlertWithSubmitButton.displayName = 'FormAlertWithSubmitButton';
-export default withLocalize(FormAlertWithSubmitButton);
+export default compose(
+ withLocalize,
+ withNetwork(),
+)(FormAlertWithSubmitButton);
diff --git a/src/components/FullPageOfflineBlockingView.js b/src/components/FullPageOfflineBlockingView.js
new file mode 100644
index 000000000000..5c738bef98cf
--- /dev/null
+++ b/src/components/FullPageOfflineBlockingView.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import Icon from './Icon';
+import networkPropTypes from './networkPropTypes';
+import {withNetwork} from './OnyxProvider';
+import Text from './Text';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import * as Expensicons from './Icon/Expensicons';
+import themeColors from '../styles/themes/default';
+import styles from '../styles/styles';
+import compose from '../libs/compose';
+
+const propTypes = {
+ /** Child elements */
+ children: PropTypes.node.isRequired,
+
+ /** Props to fetch translation features */
+ ...withLocalizePropTypes,
+
+ /** Props to detect online status */
+ network: networkPropTypes.isRequired,
+};
+
+const FullPageOfflineBlockingView = (props) => {
+ if (props.network.isOffline) {
+ return (
+
+
+ {props.translate('common.youAppearToBeOffline')}
+ {props.translate('common.thisFeatureRequiresInternet')}
+
+ );
+ }
+
+ return props.children;
+};
+
+FullPageOfflineBlockingView.propTypes = propTypes;
+FullPageOfflineBlockingView.displayName = 'FullPageOfflineBlockingView';
+
+export default compose(
+ withLocalize,
+ withNetwork(),
+)(FullPageOfflineBlockingView);
diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
index a7e4c910f938..874a7364d084 100755
--- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
+++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
@@ -53,7 +53,6 @@ const BaseHTMLEngineProvider = (props) => {
baseStyle={styles.webViewStyles.baseFontStyle}
tagsStyles={styles.webViewStyles.tagStyles}
enableCSSInlineProcessing={false}
- dangerouslyDisableWhitespaceCollapsing
systemFonts={_.values(fontFamily)}
>
{
const displayName = lodashGet(props.tnode, 'domNode.children[0].data', '');
const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {});
const attrHref = htmlAttribs.href || '';
- const internalExpensifyPath = (attrHref.startsWith(CONST.NEW_EXPENSIFY_URL) && attrHref.replace(CONST.NEW_EXPENSIFY_URL, ''))
+ const internalNewExpensifyPath = (attrHref.startsWith(CONST.NEW_EXPENSIFY_URL) && attrHref.replace(CONST.NEW_EXPENSIFY_URL, ''))
|| (attrHref.startsWith(CONST.STAGING_NEW_EXPENSIFY_URL) && attrHref.replace(CONST.STAGING_NEW_EXPENSIFY_URL, ''));
+ const internalExpensifyPath = attrHref.startsWith(CONFIG.EXPENSIFY.EXPENSIFY_URL) && attrHref.replace(CONFIG.EXPENSIFY.EXPENSIFY_URL, '');
// If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation
// instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag)
- if (internalExpensifyPath) {
+ if (internalNewExpensifyPath) {
return (
Navigation.navigate(internalExpensifyPath)}
+ onPress={() => Navigation.navigate(internalNewExpensifyPath)}
+ >
+
+
+ );
+ }
+
+ // If we are handling an old dot Expensify link we need to open it with openOldDotLink() so we can navigate to it with the user already logged in.
+ // As attachments also use expensify.com we don't want it working the same as links.
+ if (internalExpensifyPath && !isAttachment) {
+ return (
+ Link.openOldDotLink(internalExpensifyPath)}
>
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
index ee3981fc1541..6b911a720aff 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
@@ -31,7 +31,9 @@ class PreRenderer extends React.Component {
const node = this.ref.getScrollableNode();
const horizontalOverflow = node.scrollWidth > node.offsetWidth;
- if ((event.currentTarget === node) && horizontalOverflow) {
+ // Account for vertical scrolling variation when horizontally scrolling via touchpad by checking a large delta.
+ const isVerticalScrolling = Math.abs(event.deltaY) > 3; // This is for touchpads sensitive
+ if ((event.currentTarget === node) && horizontalOverflow && !isVerticalScrolling) {
node.scrollLeft += event.deltaX;
event.preventDefault();
event.stopPropagation();
diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js
index 84de26b310f1..7468e2138ca4 100755
--- a/src/components/IOUConfirmationList.js
+++ b/src/components/IOUConfirmationList.js
@@ -62,8 +62,8 @@ const propTypes = {
phoneNumber: PropTypes.string,
})).isRequired,
- /** Whether this is an IOU split and belongs to a group report */
- isGroupSplit: PropTypes.bool.isRequired,
+ /** Is this IOU associated with existing report */
+ isIOUAttachedToExistingChatReport: PropTypes.bool.isRequired,
...windowDimensionsPropTypes,
@@ -225,6 +225,7 @@ class IOUConfirmationList extends Component {
data: [formattedMyPersonalDetails],
shouldShow: true,
indexOffset: 0,
+ isDisabled: true,
}, {
title: this.props.translate('iOUConfirmationList.whoWasThere'),
data: formattedSelectedParticipants,
@@ -350,6 +351,7 @@ class IOUConfirmationList extends Component {
const shouldDisableButton = selectedParticipants.length === 0 || this.props.network.isOffline;
const isLoading = this.props.iou.loading && !this.props.network.isOffline;
const recipient = this.state.participants[0];
+ const canModifyParticipants = this.props.isIOUAttachedToExistingChatReport && this.props.hasMultipleParticipants;
return (
<>
@@ -362,7 +364,7 @@ class IOUConfirmationList extends Component {
canSelectMultipleOptions={this.props.hasMultipleParticipants}
selectedOptions={this.getSelectedOptions()}
onSelectRow={toggleOption}
- isDisabled={!this.props.isGroupSplit}
+ isDisabled={!canModifyParticipants}
optionHoveredStyle={hoverStyle}
/>
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index 90c47808b188..0e8d598ad703 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -45,6 +45,7 @@ import Monitor from '../../../assets/images/monitor.svg';
import NewWindow from '../../../assets/images/new-window.svg';
import NewWorkspace from '../../../assets/images/new-workspace.svg';
import Offline from '../../../assets/images/offline.svg';
+import OfflineCloud from '../../../assets/images/offline-cloud.svg';
import Paperclip from '../../../assets/images/paperclip.svg';
import Paycheck from '../../../assets/images/paycheck.svg';
import PayPal from '../../../assets/images/paypal.svg';
@@ -76,6 +77,8 @@ import AdminRoomAvatar from '../../../assets/images/avatars/admin-room.svg';
import AnnounceRoomAvatar from '../../../assets/images/avatars/announce-room.svg';
import Connect from '../../../assets/images/connect.svg';
import DomainRoomAvatar from '../../../assets/images/avatars/domain-room.svg';
+import FallbackAvatar from '../../../assets/images/avatars/fallback-avatar.svg';
+import FallbackWorkspaceAvatar from '../../../assets/images/avatars/fallback-workspace-avatar.svg';
export {
ActiveRoomAvatar,
@@ -112,6 +115,8 @@ export {
Eye,
EyeDisabled,
ExpensifyCard,
+ FallbackAvatar,
+ FallbackWorkspaceAvatar,
Gallery,
Gear,
Hashtag,
@@ -131,6 +136,7 @@ export {
NewWindow,
NewWorkspace,
Offline,
+ OfflineCloud,
Paperclip,
Paycheck,
PayPal,
diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js
index 2017962b5e83..45cfe8c6c35a 100644
--- a/src/components/ImageView/index.js
+++ b/src/components/ImageView/index.js
@@ -23,7 +23,6 @@ class ImageView extends PureComponent {
this.onContainerLayoutChanged = this.onContainerLayoutChanged.bind(this);
this.onContainerPressIn = this.onContainerPressIn.bind(this);
this.onContainerPress = this.onContainerPress.bind(this);
- this.onContainerPressOut = this.onContainerPressOut.bind(this);
this.imageLoadingStart = this.imageLoadingStart.bind(this);
this.imageLoadingEnd = this.imageLoadingEnd.bind(this);
this.state = {
@@ -40,10 +39,6 @@ class ImageView extends PureComponent {
imgWidth: 0,
imgHeight: 0,
zoomScale: 0,
- imageLeft: 0,
- imageTop: 0,
- imageRight: 0,
- imageBottom: 0,
};
}
@@ -98,85 +93,51 @@ class ImageView extends PureComponent {
* @param {SyntheticEvent} e
*/
onContainerPress(e) {
- if (this.state.isZoomed && !this.state.isDragging) {
+ let scrollX;
+ let scrollY;
+ if (!this.state.isZoomed && !this.state.isDragging) {
const {offsetX, offsetY} = e.nativeEvent;
- const delta = this.getScrollOffset(offsetX, offsetY);
- const sX = delta.offsetX;
- const sY = delta.offsetY;
- this.scrollableRef.scrollTop = sY * this.state.zoomScale;
- this.scrollableRef.scrollLeft = sX * this.state.zoomScale;
+
+ // Dividing clicked positions by the zoom scale to get coordinates
+ // so that once we zoom we will scroll to the clicked location.
+ const delta = this.getScrollOffset(offsetX / this.state.zoomScale, offsetY / this.state.zoomScale);
+ scrollX = delta.offsetX;
+ scrollY = delta.offsetY;
}
if (this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
this.setState({isDragging: false, isMouseDown: false});
+ } else {
+ // We first zoom and once its done then we scroll to the location the user clicked.
+ this.setState(prevState => ({
+ isZoomed: !prevState.isZoomed,
+ isMouseDown: false,
+ }), () => {
+ this.scrollableRef.scrollTop = scrollY;
+ this.scrollableRef.scrollLeft = scrollX;
+ });
}
}
- onContainerPressOut() {
- if (this.state.isDragging) {
- return;
- }
-
- this.setState(prevState => ({
- isZoomed: !prevState.isZoomed,
- isMouseDown: false,
- }));
- }
-
/**
- * When open image, set image left/right/top/bottom point and width, height
+ * When open image, set image width, height and zoomScale.
* @param {Number} imageWidth
* @param {Number} imageHeight
*/
setImageRegion(imageWidth, imageHeight) {
- let width = imageWidth;
- let height = imageHeight;
- const containerHeight = this.state.containerHeight;
- const containerWidth = this.state.containerWidth;
-
- // return if image not loaded yet
- if (imageHeight <= 0 || containerHeight <= 0) {
+ if (imageHeight <= 0) {
return;
}
-
- // Fit the image to container size if image small than container.
- const aspectRatio = Math.min(containerHeight / imageHeight, containerWidth / imageWidth);
- if (aspectRatio > 1) {
- width *= (aspectRatio);
- height *= (aspectRatio);
- }
- let imgLeft = (this.props.windowWidth - width) / 2;
- let imgRight = ((this.props.windowWidth - width) / 2) + width;
- let imgTop = (this.props.windowHeight - height) / 2;
- let imgBottom = ((this.props.windowHeight - height) / 2) + height;
- const isScreenWiderThanImage = (this.props.windowWidth / width) > 1;
- const isScreenTallerThanImage = (this.props.windowHeight / height) > 1;
- const aspect = width / height;
- if (aspect > 1 && !isScreenWiderThanImage) {
- // In case Width fit Screen width and Height not fit the Screen height
- const fitRate = this.props.windowWidth / width;
- imgLeft = 0;
- imgRight = this.props.windowWidth;
- imgTop = (this.props.windowHeight - (fitRate * height)) / 2;
- imgBottom = imgTop + (fitRate * height);
- } else if (aspect <= 1 && !isScreenTallerThanImage) {
- // In case Height fit Screen height and Width not fit the Screen width
- const fitRate = this.props.windowHeight / height;
- imgTop = 0;
- imgBottom = this.props.windowHeight;
- imgLeft = (this.props.windowWidth - (fitRate * width)) / 2;
- imgRight = imgLeft + (fitRate * width);
- }
-
+ const containerHeight = this.state.containerHeight;
+ const containerWidth = this.state.containerWidth;
+ const width = imageWidth;
+ const height = imageHeight;
const newZoomScale = Math.min(containerWidth / width, containerHeight / height);
+
this.setState({
imgWidth: width,
- zoomScale: newZoomScale,
imgHeight: height,
- imageLeft: imgLeft,
- imageTop: imgTop,
- imageRight: imgRight,
- imageBottom: imgBottom,
+ zoomScale: newZoomScale,
});
}
@@ -187,31 +148,23 @@ class ImageView extends PureComponent {
* @returns {Object} converted touch point
*/
getScrollOffset(x, y) {
- let fitRatio = 1;
- if (this.state.imageTop === 0) {
- // Fit Height
- fitRatio = this.props.windowHeight / this.state.imgHeight;
- } else if (this.state.imageLeft === 0) {
- // Fit Width
- fitRatio = this.props.windowWidth / this.state.imgWidth;
- }
- let sx = (x - this.state.imageLeft) / fitRatio;
- let sy = (y - this.state.imageTop) / fitRatio;
+ let offsetX;
+ let offsetY;
- // White blank touch
- if (x < this.state.imageLeft) {
- sx = 0;
- }
- if (x > this.state.imageRight) {
- sx = this.state.imgWidth;
- }
- if (y < this.state.imageTop) {
- sy = 0;
+ // Container size bigger than clicked position offset
+ if (x <= this.state.containerWidth / 2) {
+ offsetX = 0;
+ } else if (x > this.state.containerWidth / 2) {
+ // Minus half of container size because we want to be center clicked position
+ offsetX = x - (this.state.containerWidth / 2);
}
- if (y > this.state.imageBottom) {
- sy = this.state.imgHeight;
+ if (y <= this.state.containerHeight / 2) {
+ offsetY = 0;
+ } else if (y > this.state.containerHeight / 2) {
+ // Minus half of container size because we want to be center clicked position
+ offsetY = y - (this.state.containerHeight / 2);
}
- return {offsetX: sx, offsetY: sy};
+ return {offsetX, offsetY};
}
trackMovement(e) {
@@ -284,7 +237,6 @@ class ImageView extends PureComponent {
}}
onPressIn={this.onContainerPressIn}
onPress={this.onContainerPress}
- onPressOut={this.onContainerPressOut}
>
{
+ const layout = event.nativeEvent.layout;
+ this.setState({
+ containerHeight: layout.height,
+ });
+ }}
>
{
+ if (props.message.length === 0) {
+ return null;
+ }
+ return (
+
+
+ {props.message}
+
+ );
+};
+
+InlineSystemMessage.propTypes = propTypes;
+InlineSystemMessage.displayName = 'InlineSystemMessage';
+export default InlineSystemMessage;
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index 9e40683893ba..004fd45ad092 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -39,6 +39,7 @@ const defaultProps = {
iconType: 'icon',
onPress: () => {},
interactive: true,
+ fallbackIcon: Expensicons.FallbackAvatar,
};
const MenuItem = props => (
@@ -87,6 +88,7 @@ const MenuItem = props => (
)}
diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index d6f0a7cbc424..3779304830f0 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -7,8 +7,8 @@ import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import themeColors from '../../styles/themes/default';
import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes';
-import getModalStyles from '../../styles/getModalStyles';
import * as Modal from '../../libs/actions/Modal';
+import getModalStyles from '../../styles/getModalStyles';
const propTypes = {
...modalPropTypes,
diff --git a/src/components/OfflineIndicator.js b/src/components/OfflineIndicator.js
new file mode 100644
index 000000000000..37201c61e059
--- /dev/null
+++ b/src/components/OfflineIndicator.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import {View} from 'react-native';
+import _ from 'underscore';
+import {withNetwork} from './OnyxProvider';
+import networkPropTypes from './networkPropTypes';
+import Icon from './Icon';
+import * as Expensicons from './Icon/Expensicons';
+import variables from '../styles/variables';
+import Text from './Text';
+import styles from '../styles/styles';
+import compose from '../libs/compose';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import stylePropTypes from '../styles/stylePropTypes';
+
+const propTypes = {
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
+ /** Additional style props */
+ style: stylePropTypes,
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ style: [],
+};
+
+const OfflineIndicator = (props) => {
+ if (!props.network.isOffline) {
+ return null;
+ }
+
+ const additionalStyles = _.isArray(props.style) ? props.style : [props.style];
+
+ return (
+
+
+
+ {props.translate('common.youAppearToBeOffline')}
+
+
+ );
+};
+
+OfflineIndicator.propTypes = propTypes;
+OfflineIndicator.defaultProps = defaultProps;
+OfflineIndicator.displayName = 'OfflineIndicator';
+
+export default compose(
+ withLocalize,
+ withNetwork(),
+)(OfflineIndicator);
diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js
index f6049a886c5a..1b66eec802dd 100644
--- a/src/components/OptionsList/BaseOptionsList.js
+++ b/src/components/OptionsList/BaseOptionsList.js
@@ -111,7 +111,7 @@ class BaseOptionsList extends Component {
showSelectedState={this.props.canSelectMultipleOptions}
hideAdditionalOptionStates={this.props.hideAdditionalOptionStates}
forceTextUnreadStyle={this.props.forceTextUnreadStyle}
- isDisabled={this.props.isDisabled}
+ isDisabled={this.props.isDisabled || section.isDisabled}
/>
);
}
diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js
index 48c34b1fcb15..81a83cbdf19e 100755
--- a/src/components/OptionsSelector.js
+++ b/src/components/OptionsSelector.js
@@ -30,6 +30,9 @@ const propTypes = {
/** Whether this section should show or not */
shouldShow: PropTypes.bool,
+
+ /** Whether this section items disabled for selection */
+ isDisabled: PropTypes.bool,
})).isRequired,
/** Value in the search input field */
diff --git a/src/components/Picker/BasePicker/basePickerPropTypes.js b/src/components/Picker/BasePicker/basePickerPropTypes.js
index 7af66b59bdf6..a3468ec71fea 100644
--- a/src/components/Picker/BasePicker/basePickerPropTypes.js
+++ b/src/components/Picker/BasePicker/basePickerPropTypes.js
@@ -11,8 +11,8 @@ const propTypes = {
/** Whether or not to show the disabled styles */
disabled: PropTypes.bool,
- /** Should the picker be styled for errors */
- hasError: PropTypes.bool,
+ /** Error text to display */
+ errorText: PropTypes.string,
/** Should the picker be styled for focus state */
focused: PropTypes.bool,
@@ -43,10 +43,19 @@ const propTypes = {
/** Size of a picker component */
size: PropTypes.oneOf(['normal', 'small']),
+
+ /** Callback called when Picker options menu is closed */
+ onClose: PropTypes.func.isRequired,
+
+ /** Callback called when Picker options menu is open */
+ onOpen: PropTypes.func.isRequired,
+
+ /** Callback called when click or tap out of Picker */
+ onBlur: PropTypes.func,
};
const defaultProps = {
disabled: false,
- hasError: false,
+ errorText: '',
focused: false,
placeholder: {},
value: null,
@@ -68,6 +77,7 @@ const defaultProps = {
>
),
size: 'normal',
+ onBlur: () => {},
};
export {
diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js
index 07af4b36d0b1..eb3021ab54a9 100644
--- a/src/components/Picker/BasePicker/index.js
+++ b/src/components/Picker/BasePicker/index.js
@@ -10,16 +10,32 @@ class BasePicker extends React.Component {
constructor(props) {
super(props);
- this.state = {
- selectedValue: this.props.defaultValue,
- };
+ this.pickerValue = this.props.defaultValue;
this.updateSelectedValueAndExecuteOnChange = this.updateSelectedValueAndExecuteOnChange.bind(this);
+ this.executeOnCloseAndOnBlur = this.executeOnCloseAndOnBlur.bind(this);
+ this.setNativeProps = this.setNativeProps.bind(this);
+ }
+
+ /**
+ * This method mimicks RN's setNativeProps method. It's exposed to Picker's ref and can be used by other components
+ * to directly manipulate Picker's value when Picker is used as an uncontrolled input.
+ *
+ * @param {*} value
+ */
+ setNativeProps({value}) {
+ this.pickerValue = value;
}
updateSelectedValueAndExecuteOnChange(value) {
this.props.onInputChange(value);
- this.setState({selectedValue: value});
+ this.pickerValue = value;
+ }
+
+ executeOnCloseAndOnBlur() {
+ // Picker's onClose is not executed on Web and Desktop, so props.onClose has to be called with onBlur callback.
+ this.props.onClose();
+ this.props.onBlur();
}
render() {
@@ -31,7 +47,7 @@ class BasePicker extends React.Component {
style={this.props.size === 'normal' ? basePickerStyles(this.props.disabled, hasError, this.props.focused) : styles.pickerSmall}
useNativeAndroidPickerStyle={false}
placeholder={this.props.placeholder}
- value={this.props.value || this.state.selectedValue}
+ value={this.props.value || this.pickerValue}
Icon={() => this.props.icon(this.props.size)}
disabled={this.props.disabled}
fixAndroidTouchableBug
@@ -39,8 +55,17 @@ class BasePicker extends React.Component {
onClose={this.props.onClose}
pickerProps={{
onFocus: this.props.onOpen,
- onBlur: this.props.onBlur,
- ref: this.props.innerRef,
+ onBlur: this.executeOnCloseAndOnBlur,
+ }}
+ ref={(node) => {
+ if (!node || !_.isFunction(this.props.innerRef)) {
+ return;
+ }
+
+ this.props.innerRef(node);
+
+ // eslint-disable-next-line no-param-reassign
+ node.setNativeProps = this.setNativeProps;
}}
/>
);
diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js
index 599fa482c4b0..c201434d9569 100644
--- a/src/components/ReportTransaction.js
+++ b/src/components/ReportTransaction.js
@@ -1,18 +1,16 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
-import {
- View, Pressable, ActivityIndicator,
-} from 'react-native';
+import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
-import themeColors from '../styles/themes/default';
import * as IOU from '../libs/actions/IOU';
import reportActionPropTypes from '../pages/home/report/reportActionPropTypes';
import ReportActionItemSingle from '../pages/home/report/ReportActionItemSingle';
import Text from './Text';
+import Button from './Button';
const propTypes = {
/** The chatReport which the transaction is associated with */
@@ -88,30 +86,13 @@ class ReportTransaction extends Component {
{this.props.canBeRejected && (
-
- {
- this.isBeingRejected()
- ? (
-
- )
- : (
-
- {this.props.rejectButtonLabelText}
-
- )
- }
-
+ isLoading={this.isBeingRejected()}
+ />
)}
diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js
index 25fc18318c27..4db3eaa6b205 100644
--- a/src/components/ReportWelcomeText.js
+++ b/src/components/ReportWelcomeText.js
@@ -10,6 +10,10 @@ import compose from '../libs/compose';
import * as ReportUtils from '../libs/ReportUtils';
import * as OptionsListUtils from '../libs/OptionsListUtils';
import ONYXKEYS from '../ONYXKEYS';
+import Navigation from '../libs/Navigation/Navigation';
+import ROUTES from '../ROUTES';
+import Tooltip from './Tooltip';
+import RenderHTML from './RenderHTML';
const personalDetailsPropTypes = PropTypes.shape({
/** The login of the person (either email or phone number) */
@@ -29,7 +33,7 @@ const propTypes = {
/* Onyx Props */
- /** All of the personal details for everyone */
+ /** All the personal details for everyone */
personalDetails: PropTypes.objectOf(personalDetailsPropTypes).isRequired,
/** The policies which the user has access to and which the report could be tied to */
@@ -49,6 +53,8 @@ const ReportWelcomeText = (props) => {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const isChatRoom = ReportUtils.isChatRoom(props.report);
const isDefault = !(isChatRoom || isPolicyExpenseChat);
+ const isArchivedRoom = ReportUtils.isArchivedRoom(props.report);
+ const reportArchivedText = ReportUtils.getArchivedText(props.report, props.reportActions, props.personalDetails, props.policies);
const participants = lodashGet(props.report, 'participants', []);
const isMultipleParticipant = participants.length > 1;
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(
@@ -59,25 +65,29 @@ const ReportWelcomeText = (props) => {
return (
{isPolicyExpenseChat && (
- <>
- {/* Add align center style individually because of limited style inheritance in React Native https://reactnative.dev/docs/text#limited-style-inheritance */}
-
- {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartOne')}
-
-
- {/* Use the policyExpenseChat owner's first name or their email if it's undefined or an empty string */}
- {lodashGet(props.personalDetails, [props.report.ownerEmail, 'firstName']) || props.report.ownerEmail}
-
-
- {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartTwo')}
-
-
- {ReportUtils.getPolicyName(props.report, props.policies)}
-
-
- {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartThree')}
-
- >
+ isArchivedRoom
+ ?
+ : (
+ <>
+ {/* Add align center style individually because of limited style inheritance in React Native https://reactnative.dev/docs/text#limited-style-inheritance */}
+
+ {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartOne')}
+
+
+ {/* Use the policyExpenseChat owner's first name or their email if it's undefined or an empty string */}
+ {lodashGet(props.personalDetails, [props.report.ownerEmail, 'firstName']) || props.report.ownerEmail}
+
+
+ {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartTwo')}
+
+
+ {ReportUtils.getPolicyName(props.report, props.policies)}
+
+
+ {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartThree')}
+
+ >
+ )
)}
{isChatRoom && (
<>
@@ -85,7 +95,7 @@ const ReportWelcomeText = (props) => {
{roomWelcomeMessage.phrase1}
-
+ Navigation.navigate(ROUTES.getReportDetailsRoute(props.report.reportID))}>
{props.report.reportName}
@@ -99,11 +109,15 @@ const ReportWelcomeText = (props) => {
{props.translate('reportActionsView.beginningOfChatHistory')}
- {_.map(displayNamesWithTooltips, ({displayName, pronouns}, index) => (
+ {_.map(displayNamesWithTooltips, ({
+ displayName, pronouns, tooltip,
+ }, index) => (
-
- {displayName}
-
+
+ Navigation.navigate(ROUTES.getDetailsRoute(participants[index]))}>
+ {displayName}
+
+
{!_.isEmpty(pronouns) && {` (${pronouns})`}}
{(index === displayNamesWithTooltips.length - 1) && .}
{(index === displayNamesWithTooltips.length - 2) && {` ${props.translate('common.and')} `}}
@@ -129,5 +143,9 @@ export default compose(
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
+ reportActions: {
+ key: props => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${props.report.reportID}`,
+ canEvict: false,
+ },
}),
)(ReportWelcomeText);
diff --git a/src/components/StatePicker.js b/src/components/StatePicker.js
index 0ae82af6baee..a76fd7ba0197 100644
--- a/src/components/StatePicker.js
+++ b/src/components/StatePicker.js
@@ -55,7 +55,8 @@ const StatePicker = forwardRef((props, ref) => (
placeholder={{value: '', label: '-'}}
items={STATES}
onInputChange={props.onInputChange}
- value={props.value ? props.value : props.defaultValue}
+ value={props.value}
+ defaultValue={props.defaultValue}
label={props.label || props.translate('common.state')}
errorText={props.errorText}
onBlur={props.onBlur}
diff --git a/src/components/Tooltip/index.native.js b/src/components/Tooltip/index.native.js
index ea7556928c77..89fb88bd8a39 100644
--- a/src/components/Tooltip/index.native.js
+++ b/src/components/Tooltip/index.native.js
@@ -1,32 +1,21 @@
-import React from 'react';
-import {View} from 'react-native';
import PropTypes from 'prop-types';
// We can't use the common component for the Tooltip as Web implementation uses DOM specific method to
// render the View which is not present on the Mobile.
const propTypes = {
- /** Styles to be assigned to the Tooltip wrapper views */
- containerStyles: PropTypes.arrayOf(PropTypes.object),
-
/** Children to wrap with Tooltip. */
children: PropTypes.node.isRequired,
};
-const defaultProps = {
- containerStyles: [],
-};
-
/**
* @param {propTypes} props
* @returns {ReactNodeLike}
*/
const Tooltip = props => (
-
- {props.children}
-
+ props.children
);
Tooltip.propTypes = propTypes;
-Tooltip.defaultProps = defaultProps;
Tooltip.displayName = 'Tooltip';
+
export default Tooltip;
diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js
index 0ad5a43498af..0dd6b954aa4f 100644
--- a/src/components/menuItemPropTypes.js
+++ b/src/components/menuItemPropTypes.js
@@ -63,6 +63,9 @@ const propTypes = {
/** Whether the menu item should be interactive at all */
interactive: PropTypes.bool,
+
+ /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+ fallbackIcon: PropTypes.func,
};
export default propTypes;
diff --git a/src/languages/en.js b/src/languages/en.js
index ed76c165ee13..f3c028c8a5d8 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -105,6 +105,8 @@ export default {
leaveRoom: 'Leave room',
your: 'your',
conciergeHelp: 'Please reach out to Concierge for help.',
+ youAppearToBeOffline: 'You appear to be offline.',
+ thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera permission required',
@@ -158,7 +160,6 @@ export default {
writeSomething: 'Write something...',
sayHello: 'Say hello!',
blockedFromConcierge: 'Communication is barred',
- youAppearToBeOffline: 'You appear to be offline.',
fileUploadFailed: 'Upload failed. File is not supported.',
localTime: ({user, time}) => `It's ${time} for ${user}`,
edited: '(edited)',
@@ -535,6 +536,7 @@ export default {
buttonConfirm: 'Got it',
error: {
noBankAccountAvailable: 'Sorry, no bank account is available',
+ noBankAccountSelected: 'Please choose an account',
taxID: 'Please enter a valid Tax ID Number',
website: 'Please enter a valid website',
zipCode: 'Please enter a valid zip code',
diff --git a/src/languages/es.js b/src/languages/es.js
index daec24e13146..f968c28ac221 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -105,6 +105,8 @@ export default {
leaveRoom: 'Salir de la sala de chat',
your: 'tu',
conciergeHelp: 'Por favor contacta con Concierge para obtener ayuda.',
+ youAppearToBeOffline: 'Parece que estás desconectado.',
+ thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.',
},
attachmentPicker: {
cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
@@ -158,7 +160,6 @@ export default {
writeSomething: 'Escribe algo...',
sayHello: 'Di hola!',
blockedFromConcierge: 'Comunicación no permitida',
- youAppearToBeOffline: 'Parece que estás desconectado.',
fileUploadFailed: 'Subida fallida. El archivo no es compatible.',
localTime: ({user, time}) => `Son las ${time} para ${user}`,
edited: '(editado)',
@@ -535,6 +536,7 @@ export default {
buttonConfirm: 'OK',
error: {
noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible',
+ noBankAccountSelected: 'Por favor, elige una cuenta bancaria',
taxID: 'Ingresa un número de identificación fiscal válido',
website: 'Ingresa un sitio web válido',
zipCode: 'Ingresa un código postal válido',
diff --git a/src/libs/API.js b/src/libs/API.js
index d0054514ed0a..7bda882e82bd 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -1,997 +1,61 @@
import _ from 'underscore';
-import getPlaidLinkTokenParameters from './getPlaidLinkTokenParameters';
-import isViaExpensifyCashNative from './isViaExpensifyCashNative';
-import requireParameters from './requireParameters';
+import Onyx from 'react-native-onyx';
import * as Request from './Request';
-import * as Network from './Network';
-import * as Middleware from './Middleware';
-import CONST from '../CONST';
+import * as SequentialQueue from './Network/SequentialQueue';
+import {version} from '../../package.json';
-// Setup API middlewares. Each request made will pass through a series of middleware functions that will get called in sequence (each one passing the result of the previous to the next).
-// Note: The ordering here is intentional as we want to Log, Recheck Connection, Reauthenticate, and Retry. Errors thrown in one middleware will bubble to the next e.g. an error thrown in
-// Logging or Reauthenticate logic would be caught by the Retry logic (which is why it is the last one used).
-
-// Logging - Logs request details and errors.
-Request.use(Middleware.Logging);
-
-// RecheckConnection - Sets a timer for a request that will "recheck" if we are connected to the internet if time runs out. Also triggers the connection recheck when we encounter any error.
-Request.use(Middleware.RecheckConnection);
-
-// Reauthentication - Handles jsonCode 407 which indicates an expired authToken. We need to reauthenticate and get a new authToken with our stored credentials.
-Request.use(Middleware.Reauthentication);
-
-// Retry - Handles retrying any failed requests.
-Request.use(Middleware.Retry);
-
-/**
- * @param {Object} parameters
- * @returns {Promise}
- */
-function AddBillingCard(parameters) {
- const commandName = 'User_AddBillingCard';
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
-}
-
-/**
- * @param {{password: String, oldPassword: String}} parameters
- * @param {String} parameters.authToken
- * @param {String} parameters.password
- * @returns {Promise}
- */
-function ChangePassword(parameters) {
- const commandName = 'ChangePassword';
- requireParameters(['password'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {object} parameters
- * @param {string} parameters.emailList
- * @returns {Promise}
- */
-function CreateChatReport(parameters) {
- const commandName = 'CreateChatReport';
- requireParameters(['emailList'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @returns {Promise}
- */
-function User_SignUp(parameters) {
- const commandName = 'User_SignUp';
- requireParameters([
- 'email',
- ], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.authToken
- * @param {String} parameters.partnerName
- * @param {String} parameters.partnerPassword
- * @param {String} parameters.partnerUserID
- * @param {String} parameters.partnerUserSecret
- * @param {Boolean} [parameters.shouldRetry]
- * @param {String} [parameters.email]
- * @returns {Promise}
- */
-function CreateLogin(parameters) {
- const commandName = 'CreateLogin';
- requireParameters([
- 'authToken',
- 'partnerName',
- 'partnerPassword',
- 'partnerUserID',
- 'partnerUserSecret',
- ], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.fundID
- * @returns {Promise}
- */
-function DeleteFund(parameters) {
- const commandName = 'DeleteFund';
- requireParameters(['fundID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.partnerUserID
- * @param {String} parameters.partnerName
- * @param {String} parameters.partnerPassword
- * @param {Boolean} parameters.shouldRetry
- * @returns {Promise}
- */
-function DeleteLogin(parameters) {
- const commandName = 'DeleteLogin';
- requireParameters(['partnerUserID', 'partnerName', 'partnerPassword', 'shouldRetry'],
- parameters, commandName);
-
- // Non-cancellable request: during logout, when requests are cancelled, we don't want to cancel the actual logout request
- return Network.post(commandName, {...parameters, canCancel: false});
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.returnValueList
- * @param {Boolean} shouldUseSecure
- * @returns {Promise}
- */
-function Get(parameters, shouldUseSecure = false) {
- const commandName = 'Get';
- requireParameters(['returnValueList'], parameters, commandName);
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, shouldUseSecure);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @param {Boolean} parameters.forceNetworkRequest
- * @returns {Promise}
- */
-function GetAccountStatus(parameters) {
- const commandName = 'GetAccountStatus';
- requireParameters(['email'], parameters, commandName);
- 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
- * @returns {Promise}
- */
-function GetIOUReport(parameters) {
- const commandName = 'GetIOUReport';
- requireParameters(['debtorEmail'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @returns {Promise}
- * @param {String} policyID
- */
-function GetFullPolicy(policyID) {
- if (!_.isString(policyID)) {
- throw new Error('[API] Must include a single policyID with calls to API.GetFullPolicy');
+function write(command, apiCommandParameters = {}, onyxData = {}) {
+ // Optimistically update Onyx
+ if (onyxData.optimisticData) {
+ Onyx.update(onyxData.optimisticData);
}
- const commandName = 'Get';
- const parameters = {
- returnValueList: 'policyList',
- policyIDList: policyID,
+ // Assemble the data we'll send to the API
+ const data = {
+ ...apiCommandParameters,
+ appversion: version,
};
- return Network.post(commandName, parameters);
-}
-/**
- * @returns {Promise}
- */
-function GetPolicySummaryList() {
- const commandName = 'Get';
- const parameters = {
- returnValueList: 'policySummaryList',
+ // Assemble all the request data we'll be storing in the queue
+ const request = {
+ command,
+ data,
+ ..._.omit(onyxData, 'optimisticData'),
};
- return Network.post(commandName, parameters);
-}
-
-/**
- * @returns {Promise}
- */
-function GetRequestCountryCode() {
- const commandName = 'GetRequestCountryCode';
- return Network.post(commandName);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.name
- * @param {Number} parameters.value
- * @returns {Promise}
- */
-function Graphite_Timer(parameters) {
- const commandName = 'Graphite_Timer';
- requireParameters(['name', 'value'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {String} parameters.paymentMethodType
- * @param {Object} [parameters.newIOUReportDetails]
- * @returns {Promise}
- */
-function PayIOU(parameters) {
- const commandName = 'PayIOU';
- requireParameters(['reportID', 'paymentMethodType'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {Object} [parameters.newIOUReportDetails]
- * @returns {Promise}
- */
-function PayWithWallet(parameters) {
- const commandName = 'PayWithWallet';
- requireParameters(['reportID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.emailList
- * @returns {Promise}
- */
-function PersonalDetails_GetForEmails(parameters) {
- const commandName = 'PersonalDetails_GetForEmails';
- requireParameters(['emailList'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Object} parameters.details
- * @returns {Promise}
- */
-function PersonalDetails_Update(parameters) {
- const commandName = 'PersonalDetails_Update';
- requireParameters(['details'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Object} parameters.name
- * @param {Object} parameters.value
- * @returns {Promise}
- */
-function PreferredLocale_Update(parameters) {
- const commandName = 'PreferredLocale_Update';
- requireParameters(['name', 'value'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.socket_id
- * @param {String} parameters.channel_name
- * @returns {Promise}
- */
-function Push_Authenticate(parameters) {
- const commandName = 'Push_Authenticate';
- requireParameters(['socket_id', 'channel_name'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {String} parameters.transactionID
- * @returns {Promise}
- */
-function RejectTransaction(parameters) {
- const commandName = 'RejectTransaction';
- requireParameters(['reportID', 'transactionID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.reportComment
- * @param {Number} parameters.reportID
- * @param {String} parameters.clientID
- * @param {File|Object} [parameters.file]
- * @returns {Promise}
- */
-function Report_AddComment(parameters) {
- const commandName = 'Report_AddComment';
- requireParameters(['reportComment', 'reportID', 'clientID'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @returns {Promise}
- */
-function Report_GetHistory(parameters) {
- const commandName = 'Report_GetHistory';
- requireParameters(['reportID'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {Boolean} parameters.pinnedValue
- * @returns {Promise}
- */
-function Report_TogglePinned(parameters) {
- const commandName = 'Report_TogglePinned';
- requireParameters(['reportID', 'pinnedValue'],
- parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {Number} parameters.reportActionID
- * @param {String} parameters.reportComment
- * @returns {Promise}
- */
-function Report_EditComment(parameters) {
- const commandName = 'Report_EditComment';
- requireParameters(['reportID', 'reportActionID', 'reportComment'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {Number} parameters.sequenceNumber
- * @returns {Promise}
- */
-function Report_UpdateLastRead(parameters) {
- const commandName = 'Report_UpdateLastRead';
- requireParameters(['reportID', 'sequenceNumber'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {String} parameters.notificationPreference
- * @returns {Promise}
- *
- */
-function Report_UpdateNotificationPreference(parameters) {
- const commandName = 'Report_UpdateNotificationPreference';
- requireParameters(['reportID', 'notificationPreference'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @returns {Promise}
- */
-function ResendValidateCode(parameters) {
- const commandName = 'ResendValidateCode';
- requireParameters(['email'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.name
- * @param {String} parameters.value
- * @returns {Promise}
- */
-function SetNameValuePair(parameters) {
- const commandName = 'SetNameValuePair';
- requireParameters(['name', 'value'], parameters, commandName);
- 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
- * @param {String} parameters.validateCode
- * @param {Number} parameters.accountID
- * @returns {Promise}
- */
-function SetPassword(parameters) {
- const commandName = 'SetPassword';
- requireParameters(['accountID', 'password', 'validateCode'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.password
- * @param {String|null} parameters.bankAccountID
- * @param {String|null} parameters.fundID
- * @returns {Promise}
- */
-function SetWalletLinkedAccount(parameters) {
- const commandName = 'SetWalletLinkedAccount';
- requireParameters(['password'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.subscribed
- * @returns {Promise}
- */
-function UpdateAccount(parameters) {
- const commandName = 'UpdateAccount';
- requireParameters(['subscribed'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.message
- * @returns {Promise}
- */
-function User_Delete(parameters) {
- const commandName = 'User_Delete';
- return Network.post(commandName, parameters);
-}
-/**
- * @returns {Promise}
- */
-function User_GetBetas() {
- return Network.post('User_GetBetas');
+ // Write commands can be saved and retried, so push it to the SequentialQueue
+ SequentialQueue.push(request);
}
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @param {Boolean} [parameters.requireCertainty]
- * @returns {Promise}
- */
-function User_IsFromPublicDomain(parameters) {
- const commandName = 'User_IsFromPublicDomain';
- requireParameters(['email'], parameters, commandName);
- return Network.post(commandName, {
- ...{requireCertainty: true},
- ...parameters,
- });
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @returns {Promise}
- */
-function User_ReopenAccount(parameters) {
- const commandName = 'User_ReopenAccount';
- requireParameters(['email'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.email
- * @param {String} parameters.password
- * @returns {Promise}
- */
-function User_SecondaryLogin_Send(parameters) {
- const commandName = 'User_SecondaryLogin_Send';
- requireParameters(['email', 'password'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {File|Object} parameters.file
- * @returns {Promise}
- */
-function User_UploadAvatar(parameters) {
- const commandName = 'User_UploadAvatar';
- requireParameters(['file'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * Runs command that will fix malformed data in a users account and also run migrations.
- *
- * @returns {Promise}
- */
-function User_FixAccount() {
- const commandName = 'User_FixAccount';
- return Network.post(commandName);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.accountID
- * @param {String} parameters.validateCode
- * @returns {Promise}
- */
-function ValidateEmail(parameters) {
- const commandName = 'ValidateEmail';
- requireParameters(['accountID', 'validateCode'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * Create a new IOUTransaction
- *
- * @param {Object} parameters
- * @param {String} parameters.comment
- * @param {Array} parameters.debtorEmail
- * @param {String} parameters.currency
- * @param {String} parameters.amount
- * @returns {Promise}
- */
-function CreateIOUTransaction(parameters) {
- const commandName = 'CreateIOUTransaction';
- requireParameters(['comment', 'debtorEmail', 'currency', 'amount'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * Create a new IOU Split
- *
- * @param {Object} parameters
- * @param {String} parameters.splits
- * @param {String} parameters.currency
- * @param {String} parameters.reportID
- * @param {String} parameters.amount
- * @param {String} parameters.comment
- * @returns {Promise}
- */
-function CreateIOUSplit(parameters) {
- const commandName = 'CreateIOUSplit';
- requireParameters(['splits', 'currency', 'amount', 'reportID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {String} firstName
- * @param {String} lastName
- * @param {String} dob
- * @returns {Promise}
- */
-function Wallet_GetOnfidoSDKToken(firstName, lastName, dob) {
- return Network.post('Wallet_GetOnfidoSDKToken', {
- // We need to pass this so we can request a token with the correct referrer
- // This value comes from a cross-platform module which returns true for native
- // platforms and false for non-native platforms.
- isViaExpensifyCashNative,
- firstName,
- lastName,
- dob,
- }, CONST.NETWORK.METHOD.POST, true);
-}
-
-/**
- * @returns {Promise}
- */
-function Plaid_GetLinkToken() {
- return Network.post('Plaid_GetLinkToken', getPlaidLinkTokenParameters(), CONST.NETWORK.METHOD.POST, true);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.currentStep
- * @param {String} [parameters.onfidoData] - JSON string
- * @param {String} [parameters.personalDetails] - JSON string
- * @param {String} [parameters.idologyAnswers] - JSON string
- * @param {Boolean} [parameters.hasAcceptedTerms]
- * @returns {Promise}
- */
-function Wallet_Activate(parameters) {
- const commandName = 'Wallet_Activate';
- requireParameters(['currentStep'], parameters, commandName);
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.publicToken
- * @param {Boolean} parameters.allowDebit
- * @param {String} parameters.bank
- * @returns {Promise}
- */
-function BankAccount_Get(parameters) {
- const commandName = 'BankAccount_Get';
- requireParameters(['publicToken', 'allowDebit', 'bank'], parameters, commandName);
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
-}
-
-/**
- * @param {Object} parameters
- * @param {Object[]} parameters.employees
- * @param {String} parameters.welcomeNote
- * @param {String} parameters.policyID
- * @returns {Promise}
- */
-function Policy_Employees_Merge(parameters) {
- const commandName = 'Policy_Employees_Merge';
- requireParameters(['employees', 'welcomeNote', 'policyID'], parameters, commandName);
-
- // Always include returnPersonalDetails to ensure we get the employee's personal details in the response
- return Network.post(commandName, {...parameters, returnPersonalDetails: true});
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.accountNumber
- * @param {String} parameters.addressName
- * @param {Boolean} parameters.allowDebit
- * @param {Boolean} parameters.confirm
- * @param {Boolean} parameters.isSavings
- * @param {String} parameters.password
- * @param {String} parameters.routingNumber
- * @param {String} parameters.setupType
- * @param {String} parameters.additionalData additional JSON data
- * @returns {Promise}
- */
-function BankAccount_Create(parameters) {
- const commandName = 'BankAccount_Create';
- requireParameters([
- 'accountNumber',
- 'addressName',
- 'allowDebit',
- 'confirm',
- 'isSavings',
- 'password',
- 'routingNumber',
- 'setupType',
- 'additionalData',
- ], parameters, commandName);
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
-}
-
-function BankAccount_Validate(parameters) {
- const commandName = 'ValidateBankAccount';
- requireParameters(['bankAccountID', 'validateCode'], parameters, commandName);
- return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST);
-}
-
-/**
- * @param {*} parameters
- * @returns {Promise}
- */
-function BankAccount_SetupWithdrawal(parameters) {
- const commandName = 'BankAccount_SetupWithdrawal';
- let allowedParameters = [
- 'currentStep', 'policyID', 'bankAccountID', 'useOnfido', 'errorAttemptsCount', 'enableCardAfterVerified',
-
- // data from bankAccount step:
- 'setupType', 'routingNumber', 'accountNumber', 'addressName', 'plaidAccountID', 'mask', 'ownershipType', 'isSavings',
- 'acceptTerms', 'bankName', 'plaidAccessToken', 'alternateRoutingNumber',
-
- // data from company step:
- 'companyName', 'companyTaxID', 'addressStreet', 'addressCity', 'addressState', 'addressZipCode',
- 'hasNoConnectionToCannabis', 'incorporationType', 'incorporationState', 'incorporationDate', 'industryCode',
- 'website', 'companyPhone', 'ficticiousBusinessName',
-
- // data from requestor step:
- 'firstName', 'lastName', 'dob', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState',
- 'requestorAddressZipCode', 'isOnfidoSetupComplete', 'onfidoData', 'isControllingOfficer', 'ssnLast4',
-
- // data from ACHContract step (which became the "Beneficial Owners" step, but the key is still ACHContract as
- // it's used in several logic:
- 'ownsMoreThan25Percent', 'beneficialOwners', 'acceptTermsAndConditions', 'certifyTrueInformation',
- ];
-
- if (!parameters.useOnfido) {
- allowedParameters = allowedParameters.concat(['passport', 'answers']);
+function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData = {}) {
+ // Optimistically update Onyx
+ if (onyxData.optimisticData) {
+ Onyx.update(onyxData.optimisticData);
}
- // Only keep allowed parameters in the additionalData object
- const additionalData = _.pick(parameters, allowedParameters);
-
- requireParameters(['currentStep'], parameters, commandName);
- return Network.post(
- commandName, {additionalData: JSON.stringify(additionalData)},
- CONST.NETWORK.METHOD.POST,
- true,
- );
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} parameters.bankAccountID
- * @param {String} parameters.ownerEmail
- * @returns {Promise}
- */
-function DeleteBankAccount(parameters) {
- const commandName = 'DeleteBankAccount';
- requireParameters(['bankAccountID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @returns {Promise}
- */
-function Mobile_GetConstants(parameters) {
- const commandName = 'Mobile_GetConstants';
- requireParameters(['data'], parameters, commandName);
-
- // Stringify the parameters object as we cannot send an object via FormData
- const finalParameters = parameters;
- finalParameters.data = JSON.stringify(parameters.data);
-
- return Network.post(commandName, finalParameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {Number} [parameters.latitude]
- * @param {Number} [parameters.longitude]
- * @returns {Promise}
- */
-function GetLocalCurrency(parameters) {
- const commandName = 'GetLocalCurrency';
- return Network.post(commandName, parameters);
-}
-
-/**
- * @returns {Promise}
- */
-function GetCurrencyList() {
- return Mobile_GetConstants({data: ['currencyList']});
-}
-
-/**
- * @returns {Promise}
- */
-function User_IsUsingExpensifyCard() {
- return Network.post('User_IsUsingExpensifyCard', {});
-}
-
-/**
- * @param {Object} parameters
- * @param {String} [parameters.type]
- * @param {String} [parameters.policyName]
- * @returns {Promise}
- */
-function Policy_Create(parameters) {
- const commandName = 'Policy_Create';
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.value
- * @returns {Promise}
- */
-function Policy_CustomUnit_Update(parameters) {
- const commandName = 'Policy_CustomUnit_Update';
- requireParameters(['policyID', 'customUnit'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.customUnitID
- * @param {String} parameters.value
- * @returns {Promise}
- */
-function Policy_CustomUnitRate_Update(parameters) {
- const commandName = 'Policy_CustomUnitRate_Update';
- requireParameters(['policyID', 'customUnitID', 'customUnitRate'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} [parameters.policyID]
- * @returns {Promise}
- */
-function Policy_Delete(parameters) {
- const commandName = 'Policy_Delete';
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {Array} parameters.emailList
- * @returns {Promise}
- */
-function Policy_Employees_Remove(parameters) {
- const commandName = 'Policy_Employees_Remove';
- requireParameters(['policyID', 'emailList'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.taskID
- * @param {String} parameters.policyID
- * @param {String} parameters.firstName
- * @param {String} parameters.lastName
- * @param {String} parameters.phoneNumber
- * @returns {Promise}
- */
-function Inbox_CallUser(parameters) {
- const commandName = 'Inbox_CallUser';
- requireParameters(['taskID', 'policyID', 'firstName', 'lastName', 'phoneNumber'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * Get the current wait time in minutes for an inbox call
- * @returns {Promise}
- */
-function Inbox_CallUser_WaitTime() {
- const commandName = 'Inbox_CallUser_WaitTime';
- return Network.post(commandName);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.reportIDList
- * @returns {Promise}
- */
-function GetReportSummaryList(parameters) {
- const commandName = 'Get';
- requireParameters(['reportIDList'], parameters, commandName);
- return Network.post(commandName, {...parameters, returnValueList: 'reportSummaryList'});
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.value - Must be a JSON stringified object
- * @returns {Promise}
- */
-function UpdatePolicy(parameters) {
- const commandName = 'UpdatePolicy';
- requireParameters(['policyID', 'value'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.reportName
- * @param {String} parameters.visibility
- * @return {Promise}
- */
-function CreatePolicyRoom(parameters) {
- const commandName = 'CreatePolicyRoom';
- requireParameters(['policyID', 'reportName', 'visibility'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
+ // Assemble the data we'll send to the API
+ const data = {
+ ...apiCommandParameters,
+ appversion: version,
+ };
-/**
- * Renames a user-created policy room
- * @param {Object} parameters
- * @param {String} parameters.reportID
- * @param {String} parameters.reportName
- * @return {Promise}
- */
-function RenameReport(parameters) {
- const commandName = 'RenameReport';
- requireParameters(['reportID', 'reportName'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
+ // Assemble all the request data we'll be storing
+ const request = {
+ command,
+ data,
+ ..._.omit(onyxData, 'optimisticData'),
+ };
-/**
- * Transfer Wallet balance and takes either the bankAccoundID or fundID
- * @param {Object} parameters
- * @param {String} [parameters.bankAccountID]
- * @param {String} [parameters.fundID]
- * @returns {Promise}
- */
-function TransferWalletBalance(parameters) {
- const commandName = 'TransferWalletBalance';
- if (!parameters.bankAccountID && !parameters.fundID) {
- throw new Error('Must pass either bankAccountID or fundID to TransferWalletBalance');
- }
- return Network.post(commandName, parameters);
+ // Return a promise containing the response from HTTPS
+ return Request.processWithMiddleware(request);
}
-/**
- * Fetches the filename of the user's statement
- * @param {Object} parameters
- * @param {String} [parameters.period]
- * @return {Promise}
- */
-function GetStatementPDF(parameters) {
- const commandName = 'GetStatementPDF';
- return Network.post(commandName, parameters);
+function read(command, apiCommandParameters, onyxData) {
+ makeRequestWithSideEffects(command, apiCommandParameters, onyxData);
}
export {
- AddBillingCard,
- BankAccount_Create,
- BankAccount_Get,
- BankAccount_SetupWithdrawal,
- BankAccount_Validate,
- ChangePassword,
- CreateChatReport,
- CreateLogin,
- CreatePolicyRoom,
- RenameReport,
- DeleteFund,
- DeleteLogin,
- DeleteBankAccount,
- Get,
- GetAccountStatus,
- GetShortLivedAuthToken,
- GetStatementPDF,
- GetIOUReport,
- GetFullPolicy,
- GetPolicySummaryList,
- GetReportSummaryList,
- GetRequestCountryCode,
- Graphite_Timer,
- Inbox_CallUser,
- Inbox_CallUser_WaitTime,
- PayIOU,
- PayWithWallet,
- PersonalDetails_GetForEmails,
- PersonalDetails_Update,
- Plaid_GetLinkToken,
- Policy_Employees_Merge,
- Push_Authenticate,
- RejectTransaction,
- Report_AddComment,
- Report_GetHistory,
- Report_TogglePinned,
- Report_EditComment,
- Report_UpdateLastRead,
- Report_UpdateNotificationPreference,
- ResendValidateCode,
- ResetPassword,
- SetNameValuePair,
- SetPassword,
- SetWalletLinkedAccount,
- UpdateAccount,
- UpdatePolicy,
- User_SignUp,
- User_Delete,
- User_GetBetas,
- User_IsFromPublicDomain,
- User_IsUsingExpensifyCard,
- User_ReopenAccount,
- User_SecondaryLogin_Send,
- User_UploadAvatar,
- User_FixAccount,
- CreateIOUTransaction,
- CreateIOUSplit,
- ValidateEmail,
- Wallet_Activate,
- Wallet_GetOnfidoSDKToken,
- TransferWalletBalance,
- GetLocalCurrency,
- GetCurrencyList,
- Policy_Create,
- Policy_CustomUnit_Update,
- Policy_CustomUnitRate_Update,
- Policy_Employees_Remove,
- PreferredLocale_Update,
- Policy_Delete,
+ write,
+ makeRequestWithSideEffects,
+ read,
};
diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js
index 661cc38fb777..7f0d4bf0cd91 100644
--- a/src/libs/ActiveClientManager/index.js
+++ b/src/libs/ActiveClientManager/index.js
@@ -3,22 +3,22 @@ import Onyx from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import ONYXKEYS from '../../ONYXKEYS';
import * as ActiveClients from '../actions/ActiveClients';
-import createOnReadyTask from '../createOnReadyTask';
const clientID = Str.guid();
const maxClients = 20;
let activeClients;
-// Keeps track of the ActiveClientManager's readiness in one place
-// so that multiple calls of isReady resolve the same promise
-const activeClientsReadyTask = createOnReadyTask();
+let resolveIsReadyPromise;
+const isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
/**
* @returns {Promise}
*/
function isReady() {
- return activeClientsReadyTask.isReady();
+ return isReadyPromise;
}
Onyx.connect({
@@ -37,7 +37,7 @@ Onyx.connect({
*/
function init() {
ActiveClients.addClient(clientID)
- .then(activeClientsReadyTask.setIsReady);
+ .then(resolveIsReadyPromise);
}
/**
diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js
index 61684d37d0f9..208dd6b1cf4d 100644
--- a/src/libs/EmojiUtils.js
+++ b/src/libs/EmojiUtils.js
@@ -9,7 +9,7 @@ import * as User from './actions/User';
* @param {String} input
* @returns {String}
*/
-function getEmojiUnicode(input) {
+const getEmojiUnicode = _.memoize((input) => {
if (input.length === 0) {
return '';
}
@@ -40,7 +40,7 @@ function getEmojiUnicode(input) {
}
}
return _.map(pairs, val => parseInt(val, 10).toString(16)).join(' ');
-}
+});
/**
* Function to remove Skin Tone and utf16 surrogates from Emoji
@@ -70,6 +70,34 @@ function isSingleEmoji(message) {
return matchedUnicode === currentMessageUnicode;
}
+/**
+ * Validates that this message contains only emojis
+ *
+ * @param {String} message
+ * @returns {Boolean}
+ */
+function containsOnlyEmojis(message) {
+ const trimmedMessage = message.replace(/ /g, '').replaceAll('\n', '');
+ const match = trimmedMessage.match(CONST.REGEX.EMOJIS);
+
+ if (!match) {
+ return false;
+ }
+
+ const codes = [];
+ _.map(match, emoji => _.map(getEmojiUnicode(emoji).split(' '), (code) => {
+ if (code !== CONST.EMOJI_INVISIBLE_CODEPOINT) {
+ codes.push(code);
+ }
+ return code;
+ }));
+
+ // Emojis are stored as multiple characters, so we're using spread operator
+ // to iterate over the actual emojis, not just characters that compose them
+ const messageCodes = _.filter(_.map([...trimmedMessage], char => getEmojiUnicode(char)), string => string.length > 0 && string !== CONST.EMOJI_INVISIBLE_CODEPOINT);
+ return codes.length === messageCodes.length;
+}
+
/**
* Get the header indices based on the max emojis per row
* @param {Object[]} emojis
@@ -176,4 +204,5 @@ export {
getDynamicHeaderIndices,
mergeEmojisWithFrequentlyUsedEmojis,
addToFrequentlyUsedEmojis,
+ containsOnlyEmojis,
};
diff --git a/src/libs/Growl.js b/src/libs/Growl.js
index 325a4ecd213c..3da2b7543532 100644
--- a/src/libs/Growl.js
+++ b/src/libs/Growl.js
@@ -1,12 +1,14 @@
import React from 'react';
import CONST from '../CONST';
-import createOnReadyTask from './createOnReadyTask';
const growlRef = React.createRef();
-const growlReadyTask = createOnReadyTask();
+let resolveIsReadyPromise;
+const isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
function setIsReady() {
- growlReadyTask.setIsReady();
+ resolveIsReadyPromise();
}
/**
@@ -17,7 +19,7 @@ function setIsReady() {
* @param {Number} [duration]
*/
function show(bodyText, type, duration = CONST.GROWL.DURATION) {
- growlReadyTask.isReady().then(() => growlRef.current.show(bodyText, type, duration));
+ isReadyPromise.then(() => growlRef.current.show(bodyText, type, duration));
}
/**
diff --git a/src/libs/Middleware/Logging.js b/src/libs/Middleware/Logging.js
index 7c76b513eb1f..af58c6d9e0e4 100644
--- a/src/libs/Middleware/Logging.js
+++ b/src/libs/Middleware/Logging.js
@@ -62,7 +62,9 @@ function Logging(response, request) {
if (persisted) {
PersistedRequests.remove(request);
}
- return;
+
+ // Re-throw this error so the next handler can manage it
+ throw error;
}
if (error.message === CONST.ERROR.FAILED_TO_FETCH) {
diff --git a/src/libs/Middleware/Reauthentication.js b/src/libs/Middleware/Reauthentication.js
index d75b3bde86e0..7dc971191b87 100644
--- a/src/libs/Middleware/Reauthentication.js
+++ b/src/libs/Middleware/Reauthentication.js
@@ -18,6 +18,12 @@ import Log from '../Log';
function Reauthentication(response, request, isFromSequentialQueue) {
return response
.then((data) => {
+ // If there is no data for some reason then we cannot reauthenticate
+ if (!data) {
+ Log.hmmm('Undefined data in Reauthentication');
+ return;
+ }
+
if (NetworkStore.isOffline()) {
// If we are offline and somehow handling this response we do not want to reauthenticate
throw new Error('Unable to reauthenticate because we are offline');
@@ -33,7 +39,9 @@ function Reauthentication(response, request, isFromSequentialQueue) {
return data;
}
- request.resolve(data);
+ if (request.resolve) {
+ request.resolve(data);
+ }
return;
}
@@ -75,7 +83,12 @@ function Reauthentication(response, request, isFromSequentialQueue) {
return data;
}
- request.resolve(data);
+ if (request.resolve) {
+ request.resolve(data);
+ }
+
+ // Return response data so we can chain the response with the following middlewares.
+ return data;
});
}
diff --git a/src/libs/Middleware/RecheckConnection.js b/src/libs/Middleware/RecheckConnection.js
index 59f15c628331..58f5cfa601c8 100644
--- a/src/libs/Middleware/RecheckConnection.js
+++ b/src/libs/Middleware/RecheckConnection.js
@@ -19,8 +19,11 @@ function RecheckConnection(response) {
const cancelRequestTimeoutTimer = startRecheckTimeoutTimer();
return response
.catch((error) => {
- // Because we ran into an error we assume we might be offline and do a "connection" health test
- NetworkConnection.recheckNetworkConnection();
+ if (error.name !== CONST.ERROR.REQUEST_CANCELLED) {
+ // Because we ran into an error we assume we might be offline and do a "connection" health test
+ NetworkConnection.recheckNetworkConnection();
+ }
+
throw error;
})
.finally(cancelRequestTimeoutTimer);
diff --git a/src/libs/Middleware/Retry.js b/src/libs/Middleware/Retry.js
index c9090ef36d43..24e9b86b0dbf 100644
--- a/src/libs/Middleware/Retry.js
+++ b/src/libs/Middleware/Retry.js
@@ -1,41 +1,7 @@
-import lodashGet from 'lodash/get';
-import RetryCounter from '../RetryCounter';
import * as PersistedRequests from '../actions/PersistedRequests';
-import * as MainQueue from '../Network/MainQueue';
import Log from '../Log';
import CONST from '../../CONST';
-// Keep track of retries for any non-persisted requests
-const mainQueueRetryCounter = new RetryCounter();
-
-/**
- * @param {Object} queuedRequest
- * @param {*} error
- * @returns {Boolean} true if we were able to retry
- */
-function retryFailedRequest(queuedRequest, error) {
- // When the request did not reach its destination add it back the queue to be retried if we can
- const shouldRetry = lodashGet(queuedRequest, 'data.shouldRetry');
- if (!shouldRetry) {
- return false;
- }
-
- const retryCount = mainQueueRetryCounter.incrementRetries(queuedRequest);
- Log.info('[Network] A retryable request failed', false, {
- retryCount,
- command: queuedRequest.command,
- error: error.message,
- });
-
- if (retryCount < CONST.NETWORK.MAX_REQUEST_RETRIES) {
- MainQueue.push(queuedRequest);
- return true;
- }
-
- Log.info('[Network] Request was retried too many times with no success. No more retries left');
- return false;
-}
-
/**
* @param {Promise} response
* @param {Object} request
@@ -45,6 +11,11 @@ function retryFailedRequest(queuedRequest, error) {
function Retry(response, request, isFromSequentialQueue) {
return response
.catch((error) => {
+ // Do not retry any requests that are cancelled
+ if (error.name === CONST.ERROR.REQUEST_CANCELLED) {
+ return;
+ }
+
if (isFromSequentialQueue) {
const retryCount = PersistedRequests.incrementRetries(request);
Log.info('Persisted request failed', false, {retryCount, command: request.command, error: error.message});
@@ -55,17 +26,15 @@ function Retry(response, request, isFromSequentialQueue) {
return;
}
- if (retryFailedRequest(request, error)) {
- return;
- }
-
if (request.command !== 'Log') {
Log.hmmm('[Network] Handled error when making request', error);
} else {
console.debug('[Network] There was an error in the Log API command, unable to log to server!', error);
}
- request.resolve({jsonCode: CONST.JSON_CODE.UNABLE_TO_RETRY});
+ if (request.resolve) {
+ request.resolve({jsonCode: CONST.JSON_CODE.UNABLE_TO_RETRY});
+ }
});
}
diff --git a/src/libs/Middleware/SaveResponseInOnyx.js b/src/libs/Middleware/SaveResponseInOnyx.js
new file mode 100644
index 000000000000..cf8444c771dd
--- /dev/null
+++ b/src/libs/Middleware/SaveResponseInOnyx.js
@@ -0,0 +1,31 @@
+import Onyx from 'react-native-onyx';
+import _ from 'underscore';
+
+/**
+ * @param {Promise} response
+ * @param {Object} request
+ * @returns {Promise}
+ */
+function SaveResponseInOnyx(response, request) {
+ return response
+ .then((responseData) => {
+ // We'll only save the onyxData, successData and failureData for the refactored commands
+ if (_.has(responseData, 'onyxData')) {
+ const data = [];
+ if (responseData.jsonCode === 200) {
+ if (request.successData) {
+ data.push(...request.successData);
+ }
+ } else if (request.failureData) {
+ data.push(...request.failureData);
+ }
+ if (responseData.onyxData) {
+ data.push(...responseData.onyxData);
+ }
+ Onyx.update(data);
+ }
+ return responseData;
+ });
+}
+
+export default SaveResponseInOnyx;
diff --git a/src/libs/Middleware/index.js b/src/libs/Middleware/index.js
index 8aa09c9e2041..4e270b009c1d 100644
--- a/src/libs/Middleware/index.js
+++ b/src/libs/Middleware/index.js
@@ -2,10 +2,12 @@ import Logging from './Logging';
import Reauthentication from './Reauthentication';
import RecheckConnection from './RecheckConnection';
import Retry from './Retry';
+import SaveResponseInOnyx from './SaveResponseInOnyx';
export {
Logging,
Reauthentication,
RecheckConnection,
Retry,
+ SaveResponseInOnyx,
};
diff --git a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
index e90d35a6cd6c..7114959db546 100644
--- a/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
+++ b/src/libs/Navigation/AppNavigator/BaseDrawerNavigator.js
@@ -53,7 +53,7 @@ class BaseDrawerNavigator extends Component {
const content = (
{
+ resolveIsReadyPromise = resolve;
+});
/**
* This is a hack to workaround the fact that Onyx may not yet have read these values from storage by the time Network starts processing requests.
@@ -23,11 +39,14 @@ function checkRequiredData() {
return;
}
- requiredDataReadyTask.setIsReady();
+ resolveIsReadyPromise();
}
function resetHasReadRequiredDataFromStorage() {
- requiredDataReadyTask.reset();
+ // Create a new promise and a new resolve function
+ isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+ });
}
Onyx.connect({
@@ -58,7 +77,7 @@ Onyx.connect({
// Client becomes online emit connectivity resumed event
if (offline && !network.isOffline) {
- triggerConnectivityResumed();
+ triggerReconnectCallback();
}
offline = network.isOffline;
@@ -104,7 +123,7 @@ function getCurrentUserEmail() {
* @returns {Promise}
*/
function hasReadRequiredDataFromStorage() {
- return requiredDataReadyTask.isReady();
+ return isReadyPromise;
}
/**
@@ -128,7 +147,7 @@ export {
hasReadRequiredDataFromStorage,
resetHasReadRequiredDataFromStorage,
isOffline,
- onConnectivityResumed,
+ onReconnection,
isAuthenticating,
setIsAuthenticating,
getCredentials,
diff --git a/src/libs/Network/SequentialQueue.js b/src/libs/Network/SequentialQueue.js
index 8486bc5b9f71..9de4cbf5b587 100644
--- a/src/libs/Network/SequentialQueue.js
+++ b/src/libs/Network/SequentialQueue.js
@@ -5,11 +5,14 @@ import * as NetworkStore from './NetworkStore';
import ONYXKEYS from '../../ONYXKEYS';
import * as ActiveClientManager from '../ActiveClientManager';
import * as Request from '../Request';
-import createOnReadyTask from '../createOnReadyTask';
-// Create a deferred task to hook into and immediately set it as ready
-const queueProcessTask = createOnReadyTask();
-queueProcessTask.setIsReady();
+let resolveIsReadyPromise;
+let isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
+
+// Resolve the isReadyPromise immediately so that the queue starts working as soon as the page loads
+resolveIsReadyPromise();
let isSequentialQueueRunning = false;
@@ -44,7 +47,11 @@ function flush() {
}
isSequentialQueueRunning = true;
- queueProcessTask.reset();
+
+ // Reset the isReadyPromise so that the queue will be flushed as soon as the request is finished
+ isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+ });
// Ensure persistedRequests are read from storage before proceeding with the queue
const connectionID = Onyx.connect({
@@ -54,7 +61,7 @@ function flush() {
process()
.finally(() => {
isSequentialQueueRunning = false;
- queueProcessTask.setIsReady();
+ resolveIsReadyPromise();
});
},
});
@@ -68,7 +75,7 @@ function isRunning() {
}
// Flush the queue when the connection resumes
-NetworkStore.onConnectivityResumed(flush);
+NetworkStore.onReconnection(flush);
/**
* @param {Object} request
@@ -84,7 +91,7 @@ function push(request) {
// If the queue is running this request will run once it has finished processing the current batch
if (isSequentialQueueRunning) {
- queueProcessTask.isReady().then(flush);
+ isReadyPromise.then(flush);
return;
}
diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js
index 84e8b6fe70db..600d428f0be8 100644
--- a/src/libs/Pusher/pusher.js
+++ b/src/libs/Pusher/pusher.js
@@ -102,11 +102,10 @@ function getChannel(channelName) {
* @param {Pusher.Channel} channel
* @param {String} eventName
* @param {Function} [eventCallback]
- * @param {Boolean} [isChunked] Do we expect this channel to send chunked/separate blocks of data that need recombining?
*
* @private
*/
-function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChunked = false) {
+function bindEventToChannel(channel, eventName, eventCallback = () => {}) {
if (!eventName) {
return;
}
@@ -120,7 +119,7 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChun
Log.alert('[Pusher] Unable to parse JSON response from Pusher', {error: err, eventData});
return;
}
- if (!isChunked) {
+ if (data.id === undefined || data.chunk === undefined || data.final === undefined) {
eventCallback(data);
return;
}
@@ -169,9 +168,6 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChun
* @param {String} channelName
* @param {String} eventName
* @param {Function} [eventCallback]
- * @param {Boolean} [isChunked] This parameters tells us whether or not we expect the result to come in individual
- * pieces/chunks (because it exceeds
- * the 10kB limit that pusher has).
* @param {Function} [onResubscribe] Callback to be called when reconnection happen
*
* @return {Promise}
@@ -182,7 +178,6 @@ function subscribe(
channelName,
eventName,
eventCallback = () => {},
- isChunked = false,
onResubscribe = () => {},
) {
return new Promise((resolve, reject) => {
@@ -201,7 +196,7 @@ function subscribe(
channel.bind('pusher:subscription_succeeded', () => {
// Check so that we do not bind another event with each reconnect attempt
if (!isBound) {
- bindEventToChannel(channel, eventName, eventCallback, isChunked);
+ bindEventToChannel(channel, eventName, eventCallback);
resolve();
isBound = true;
return;
@@ -225,7 +220,7 @@ function subscribe(
reject(error);
});
} else {
- bindEventToChannel(channel, eventName, eventCallback, isChunked);
+ bindEventToChannel(channel, eventName, eventCallback);
resolve();
}
});
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 04692d69780f..4a50afedf598 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -1,6 +1,7 @@
import _ from 'underscore';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
+import lodashFindLast from 'lodash/findLast';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
@@ -8,6 +9,8 @@ import * as Localize from './Localize';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Expensicons from '../components/Icon/Expensicons';
import md5 from './md5';
+import Navigation from './Navigation/Navigation';
+import ROUTES from '../ROUTES';
let sessionEmail;
Onyx.connect({
@@ -481,6 +484,61 @@ function getReportName(report, personalDetailsForParticipants = {}, policies = {
return _.map(displayNamesWithTooltips, ({displayName}) => displayName).join(', ');
}
+/**
+ * Navigate to the details page of a given report
+ *
+ * @param {Object} report
+ */
+function navigateToDetailsPage(report) {
+ const participants = lodashGet(report, 'participants', []);
+
+ if (isChatRoom(report) || isPolicyExpenseChat(report)) {
+ Navigation.navigate(ROUTES.getReportDetailsRoute(report.reportID));
+ return;
+ }
+ if (participants.length === 1) {
+ Navigation.navigate(ROUTES.getDetailsRoute(participants[0]));
+ return;
+ }
+ Navigation.navigate(ROUTES.getReportParticipantsRoute(report.reportID));
+}
+
+/**
+ * Get the text explaining why a report was archived.
+ *
+ * @param {Object} report
+ * @param {Object} reportActions
+ * @param {Object} personalDetails
+ * @param {Object} policies
+ * @returns {String|null}
+ */
+function getArchivedText(report, reportActions, personalDetails, policies) {
+ if (!isArchivedRoom(report)) {
+ return Localize.translateLocal(`reportArchiveReasons.${CONST.REPORT.ARCHIVE_REASON.DEFAULT}`);
+ }
+
+ const reportClosedAction = lodashFindLast(reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
+ if (!reportClosedAction) {
+ return Localize.translateLocal(`reportArchiveReasons.${CONST.REPORT.ARCHIVE_REASON.DEFAULT}`);
+ }
+
+ const archiveReason = lodashGet(reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT);
+ let displayName = lodashGet(personalDetails, `${report.ownerEmail}.displayName`, report.ownerEmail);
+ let oldDisplayName;
+ if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) {
+ const newLogin = reportClosedAction.originalMessage.newLogin;
+ const oldLogin = reportClosedAction.originalMessage.oldLogin;
+ displayName = lodashGet(personalDetails, `${newLogin}.displayName`, newLogin);
+ oldDisplayName = lodashGet(personalDetails, `${oldLogin}.displayName`, oldLogin);
+ }
+
+ return Localize.translateLocal(`reportArchiveReasons.${archiveReason}`, {
+ displayName: `${displayName}`,
+ oldDisplayName: `${oldDisplayName}`,
+ policyName: `${getPolicyName(report, policies)}`,
+ });
+}
+
export {
getReportParticipantsTitle,
isReportMessageAttachment,
@@ -507,4 +565,6 @@ export {
getRoomWelcomeMessage,
getDisplayNamesWithTooltips,
getReportName,
+ navigateToDetailsPage,
+ getArchivedText,
};
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index a5d8918d6404..7806f6fd9cbd 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -2,7 +2,7 @@ import {AppState} from 'react-native';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../ONYXKEYS';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONST from '../../CONST';
import Log from '../Log';
import Performance from '../Performance';
@@ -49,13 +49,13 @@ function setCurrentURL(url) {
*/
function setLocale(locale) {
if (currentUserAccountID) {
- API.PreferredLocale_Update({name: 'preferredLocale', value: locale});
+ DeprecatedAPI.PreferredLocale_Update({name: 'preferredLocale', value: locale});
}
Onyx.merge(ONYXKEYS.NVP_PREFERRED_LOCALE, locale);
}
function getLocale() {
- API.Get({
+ DeprecatedAPI.Get({
returnValueList: 'nameValuePairs',
nvpNames: ONYXKEYS.NVP_PREFERRED_LOCALE,
}).then((response) => {
@@ -125,7 +125,7 @@ function getAppData(shouldSyncPolicyList = true, shouldSyncVBA = true) {
* because some migrations might create new chat reports or their change data.
*/
function fixAccountAndReloadData() {
- API.User_FixAccount()
+ DeprecatedAPI.User_FixAccount()
.then((response) => {
if (!response.changed) {
return;
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 47822a904208..d56db2ae1c65 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import CONST from '../../CONST';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import * as Plaid from './Plaid';
import * as ReimbursementAccount from './ReimbursementAccount';
import ONYXKEYS from '../../ONYXKEYS';
@@ -51,7 +51,7 @@ function addPersonalBankAccount(account, password, plaidLinkToken) {
const unmaskedAccount = _.find(Plaid.getPlaidBankAccounts(), bankAccount => (
bankAccount.plaidAccountID === account.plaidAccountID
));
- API.BankAccount_Create({
+ DeprecatedAPI.BankAccount_Create({
accountNumber: unmaskedAccount.accountNumber,
addressName: unmaskedAccount.addressName,
allowDebit: false,
@@ -103,7 +103,7 @@ function addPersonalBankAccount(account, password, plaidLinkToken) {
* @param {Number} bankAccountID
*/
function deleteBankAccount(bankAccountID) {
- API.DeleteBankAccount({
+ DeprecatedAPI.DeleteBankAccount({
bankAccountID,
}).then((response) => {
if (response.jsonCode === 200) {
diff --git a/src/libs/actions/GeoLocation.js b/src/libs/actions/GeoLocation.js
index 2c88d1ef1752..dcf9e9a459bc 100644
--- a/src/libs/actions/GeoLocation.js
+++ b/src/libs/actions/GeoLocation.js
@@ -1,13 +1,13 @@
/* eslint-disable import/prefer-default-export */
import Onyx from 'react-native-onyx';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import ONYXKEYS from '../../ONYXKEYS';
/**
* Makes a request so that we can get the current country code by the users IP address.
*/
function fetchCountryCodeByRequestIP() {
- API.GetRequestCountryCode()
+ DeprecatedAPI.GetRequestCountryCode()
.then((data) => {
Onyx.merge(ONYXKEYS.COUNTRY_CODE, data.countryCode || 1);
});
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index b7ce3d7c2e34..1a6bb323be82 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -3,7 +3,7 @@ import _ from 'underscore';
import CONST from '../../CONST';
import ONYXKEYS from '../../ONYXKEYS';
import ROUTES from '../../ROUTES';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import * as Report from './Report';
import Navigation from '../Navigation/Navigation';
import Growl from '../Growl';
@@ -19,7 +19,7 @@ import Log from '../Log';
* @param {Number} requestParams.chatReportID the ID of the chat report that the IOU report belongs to
*/
function getIOUReportsForNewTransaction(requestParams) {
- API.Get({
+ DeprecatedAPI.Get({
returnValueList: 'reportStuff',
reportIDList: _.pluck(requestParams, 'reportID').join(','),
shouldLoadOptionalKeys: true,
@@ -109,7 +109,7 @@ function startLoadingAndResetError() {
*/
function createIOUTransaction(params) {
startLoadingAndResetError();
- API.CreateIOUTransaction(params)
+ DeprecatedAPI.CreateIOUTransaction(params)
.then((response) => {
if (response.jsonCode !== 200) {
processIOUErrorResponse(response);
@@ -134,7 +134,7 @@ function createIOUSplit(params) {
startLoadingAndResetError();
let chatReportID;
- API.CreateChatReport({
+ DeprecatedAPI.CreateChatReport({
emailList: _.map(params.splits, participant => participant.email).join(','),
})
.then((response) => {
@@ -143,7 +143,7 @@ function createIOUSplit(params) {
}
chatReportID = response.reportID;
- return API.CreateIOUSplit({
+ return DeprecatedAPI.CreateIOUSplit({
...params,
splits: JSON.stringify(params.splits),
reportID: response.reportID,
@@ -186,7 +186,7 @@ function createIOUSplit(params) {
function createIOUSplitGroup(params) {
startLoadingAndResetError();
- API.CreateIOUSplit({
+ DeprecatedAPI.CreateIOUSplit({
...params,
splits: JSON.stringify(params.splits),
})
@@ -215,7 +215,7 @@ function rejectTransaction({
Onyx.merge(ONYXKEYS.TRANSACTIONS_BEING_REJECTED, {
[transactionID]: true,
});
- API.RejectTransaction({
+ DeprecatedAPI.RejectTransaction({
reportID,
transactionID,
comment,
@@ -295,8 +295,8 @@ function payIOUReport({
Onyx.merge(ONYXKEYS.IOU, {loading: true, error: false});
const payIOUPromise = paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY
- ? API.PayWithWallet({reportID, newIOUReportDetails})
- : API.PayIOU({reportID, paymentMethodType, newIOUReportDetails});
+ ? DeprecatedAPI.PayWithWallet({reportID, newIOUReportDetails})
+ : DeprecatedAPI.PayIOU({reportID, paymentMethodType, newIOUReportDetails});
// Build the url for the user's platform of choice if they have selected something other than a manual settlement or Expensify Wallet e.g. Venmo or PayPal.me
let url;
diff --git a/src/libs/actions/Inbox.js b/src/libs/actions/Inbox.js
index 8d9e7a4bea25..4bd92abdf0bf 100644
--- a/src/libs/actions/Inbox.js
+++ b/src/libs/actions/Inbox.js
@@ -1,6 +1,7 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import Growl from '../Growl';
import * as Localize from '../Localize';
import Navigation from '../Navigation/Navigation';
@@ -18,7 +19,7 @@ function requestInboxCall({
taskID, policyID, firstName, lastName, phoneNumber, phoneNumberExtension,
}) {
Onyx.merge(ONYXKEYS.REQUEST_CALL_FORM, {loading: true});
- API.Inbox_CallUser({
+ DeprecatedAPI.Inbox_CallUser({
policyID,
firstName,
lastName,
@@ -46,14 +47,11 @@ function requestInboxCall({
});
}
-function getInboxCallWaitTime() {
- API.Inbox_CallUser_WaitTime()
- .then((data) => {
- Onyx.set(ONYXKEYS.INBOX_CALL_USER_WAIT_TIME, data.waitTime);
- });
+function openRequestCallPage() {
+ API.read('OpenRequestCallPage');
}
export {
requestInboxCall,
- getInboxCallWaitTime,
+ openRequestCallPage,
};
diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js
index 32bf84da877a..83503f10372b 100644
--- a/src/libs/actions/Link.js
+++ b/src/libs/actions/Link.js
@@ -4,7 +4,7 @@ import ONYXKEYS from '../../ONYXKEYS';
import Growl from '../Growl';
import * as Localize from '../Localize';
import CONST from '../../CONST';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONFIG from '../../CONFIG';
import asyncOpenURL from '../asyncOpenURL';
@@ -42,7 +42,7 @@ function openOldDotLink(url) {
return `${CONFIG.EXPENSIFY.EXPENSIFY_URL}${url}${url.indexOf('?') === -1 ? '?' : '&'}authToken=${shortLivedAuthToken}&email=${encodeURIComponent(currentUserEmail)}`;
}
- asyncOpenURL(API.GetShortLivedAuthToken(), buildOldDotURL);
+ asyncOpenURL(DeprecatedAPI.GetShortLivedAuthToken(), buildOldDotURL);
}
/**
diff --git a/src/libs/actions/NameValuePair.js b/src/libs/actions/NameValuePair.js
index 2b0740558311..17563e8a8b27 100644
--- a/src/libs/actions/NameValuePair.js
+++ b/src/libs/actions/NameValuePair.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
/**
* Gets the value of an NVP
@@ -11,7 +11,7 @@ import * as API from '../API';
* @param {*} [defaultValue]
*/
function get(name, onyxKey, defaultValue) {
- API.Get({
+ DeprecatedAPI.Get({
returnValueList: 'nameValuePairs',
name,
})
@@ -29,7 +29,7 @@ function get(name, onyxKey, defaultValue) {
* @param {String} [onyxKeyName]
*/
function set(name, value, onyxKeyName) {
- API.SetNameValuePair({name, value: _.isObject(value) ? JSON.stringify(value) : value});
+ DeprecatedAPI.SetNameValuePair({name, value: _.isObject(value) ? JSON.stringify(value) : value});
// Update the associated onyx key if we've passed the associated key name
if (onyxKeyName) {
diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js
index fe7405dfd388..4fa0da7d5369 100644
--- a/src/libs/actions/PaymentMethods.js
+++ b/src/libs/actions/PaymentMethods.js
@@ -3,7 +3,7 @@ import {createRef} from 'react';
import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONST from '../../CONST';
import Growl from '../Growl';
import * as Localize from '../Localize';
@@ -20,7 +20,7 @@ import NameValuePair from './NameValuePair';
* @returns {Promise}
*/
function deleteDebitCard(fundID) {
- return API.DeleteFund({fundID})
+ return DeprecatedAPI.DeleteFund({fundID})
.then((response) => {
if (response.jsonCode === 200) {
Growl.show(Localize.translateLocal('paymentsPage.deleteDebitCardSuccess'), CONST.GROWL.SUCCESS, 3000);
@@ -66,7 +66,7 @@ function continueSetup() {
*/
function getPaymentMethods() {
Onyx.set(ONYXKEYS.IS_LOADING_PAYMENT_METHODS, true);
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'bankAccountList, fundList, userWallet, nameValuePairs',
name: 'paypalMeAddress',
includeDeleted: false,
@@ -98,7 +98,7 @@ function getPaymentMethods() {
* @returns {Promise}
*/
function setWalletLinkedAccount(password, bankAccountID, fundID) {
- return API.SetWalletLinkedAccount({
+ return DeprecatedAPI.SetWalletLinkedAccount({
password,
bankAccountID,
fundID,
@@ -141,7 +141,7 @@ function addBillingCard(params) {
const cardYear = CardUtils.getYearFromExpirationDateString(params.expirationDate);
Onyx.merge(ONYXKEYS.ADD_DEBIT_CARD_FORM, {submitting: true});
- API.AddBillingCard({
+ DeprecatedAPI.AddBillingCard({
cardNumber: params.cardNumber,
cardYear,
cardMonth,
@@ -208,7 +208,7 @@ function transferWalletBalance(paymentMethod) {
};
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: true});
- API.TransferWalletBalance(parameters)
+ DeprecatedAPI.TransferWalletBalance(parameters)
.then((response) => {
if (response.jsonCode !== 200) {
throw new Error(response.message);
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index fd33f1677d35..cc7add84320e 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -5,7 +5,7 @@ import Onyx from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import ONYXKEYS from '../../ONYXKEYS';
import CONST from '../../CONST';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import NameValuePair from './NameValuePair';
import * as LoginUtils from '../LoginUtils';
import * as ReportUtils from '../ReportUtils';
@@ -121,7 +121,7 @@ function formatPersonalDetails(personalDetailsList) {
* @returns {Promise}
*/
function fetchPersonalDetails() {
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'personalDetailsList',
})
.then((data) => {
@@ -201,7 +201,7 @@ function getFromReportParticipants(reports) {
return;
}
- API.PersonalDetails_GetForEmails({emailList: participantEmails.join(',')})
+ DeprecatedAPI.PersonalDetails_GetForEmails({emailList: participantEmails.join(',')})
.then((data) => {
const existingDetails = _.pick(data, participantEmails);
@@ -274,7 +274,7 @@ function mergeLocalPersonalDetails(details) {
* @param {boolean} shouldGrowl
*/
function setPersonalDetails(details, shouldGrowl) {
- API.PersonalDetails_Update({details: JSON.stringify(details)})
+ DeprecatedAPI.PersonalDetails_Update({details: JSON.stringify(details)})
.then((response) => {
if (response.jsonCode === 200) {
if (details.timezone) {
@@ -289,9 +289,9 @@ function setPersonalDetails(details, shouldGrowl) {
Growl.error(Localize.translateLocal('personalDetails.error.firstNameLength'), 3000);
} else if (response.jsonCode === 401) {
Growl.error(Localize.translateLocal('personalDetails.error.lastNameLength'), 3000);
+ } else {
+ console.debug('Error while setting personal details', response);
}
- }).catch((error) => {
- console.debug('Error while setting personal details', error);
});
}
@@ -300,7 +300,7 @@ function setPersonalDetails(details, shouldGrowl) {
* @returns {Object}
*/
function getCurrencyList() {
- return API.GetCurrencyList()
+ return DeprecatedAPI.GetCurrencyList()
.then((data) => {
const currencyListObject = JSON.parse(data.currencyList);
Onyx.merge(ONYXKEYS.CURRENCY_LIST, currencyListObject);
@@ -319,7 +319,7 @@ function fetchLocalCurrency() {
isRetrievingCurrency: true,
});
- API.GetLocalCurrency({...coords})
+ DeprecatedAPI.GetLocalCurrency({...coords})
.then((data) => {
currency = data.currency;
})
@@ -341,7 +341,7 @@ function fetchLocalCurrency() {
*/
function setAvatar(file) {
setPersonalDetails({avatarUploading: true});
- API.User_UploadAvatar({file})
+ DeprecatedAPI.User_UploadAvatar({file})
.then((response) => {
// Once we get the s3url back, update the personal details for the user with the new avatar URL
if (response.jsonCode !== 200) {
@@ -366,7 +366,7 @@ function setAvatar(file) {
function deleteAvatar(defaultAvatarURL) {
// We don't want to save the default avatar URL in the backend since we don't want to allow
// users the option of removing the default avatar, instead we'll save an empty string
- API.PersonalDetails_Update({details: JSON.stringify({avatar: ''})});
+ DeprecatedAPI.PersonalDetails_Update({details: JSON.stringify({avatar: ''})});
mergeLocalPersonalDetails({avatar: defaultAvatarURL});
}
diff --git a/src/libs/actions/Plaid.js b/src/libs/actions/Plaid.js
index cd8e7cb6ef60..8fceb8187748 100644
--- a/src/libs/actions/Plaid.js
+++ b/src/libs/actions/Plaid.js
@@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str';
import _ from 'underscore';
import ONYXKEYS from '../../ONYXKEYS';
import CONST from '../../CONST';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import Growl from '../Growl';
import * as Localize from '../Localize';
@@ -30,7 +30,7 @@ function clearPlaidBankAccountsAndToken() {
* Gets the Plaid Link token used to initialize the Plaid SDK
*/
function fetchPlaidLinkToken() {
- API.Plaid_GetLinkToken()
+ DeprecatedAPI.Plaid_GetLinkToken()
.then((response) => {
if (response.jsonCode !== 200) {
return;
@@ -48,7 +48,7 @@ function fetchPlaidBankAccounts(publicToken, bank) {
bankName = bank;
Onyx.merge(ONYXKEYS.PLAID_BANK_ACCOUNTS, {loading: true});
- API.BankAccount_Get({
+ DeprecatedAPI.BankAccount_Get({
publicToken,
allowDebit: false,
bank,
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 4f9137355a6f..76f52becc26f 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import ONYXKEYS from '../../ONYXKEYS';
import * as PersonalDetails from './PersonalDetails';
import Growl from '../Growl';
@@ -127,7 +127,7 @@ function updateAllPolicies(policyCollection) {
function create(name = '') {
Onyx.set(ONYXKEYS.IS_CREATING_WORKSPACE, true);
let res = null;
- return API.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name})
+ return DeprecatedAPI.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name})
.then((response) => {
Onyx.set(ONYXKEYS.IS_CREATING_WORKSPACE, false);
if (response.jsonCode !== 200) {
@@ -176,7 +176,7 @@ function createAndNavigate(name = '') {
* @returns {Promise}
*/
function deletePolicy(policyID) {
- return API.Policy_Delete({policyID})
+ return DeprecatedAPI.Policy_Delete({policyID})
.then((response) => {
if (response.jsonCode !== 200) {
// Show the user feedback
@@ -211,7 +211,7 @@ function deletePolicy(policyID) {
*/
function getPolicyList() {
Onyx.set(ONYXKEYS.IS_LOADING_POLICY_DATA, true);
- API.GetPolicySummaryList()
+ DeprecatedAPI.GetPolicySummaryList()
.then((data) => {
if (data.jsonCode !== 200) {
Onyx.set(ONYXKEYS.IS_LOADING_POLICY_DATA, false);
@@ -248,7 +248,7 @@ function createAndGetPolicyList() {
* @param {String} policyID
*/
function loadFullPolicy(policyID) {
- API.GetFullPolicy(policyID)
+ DeprecatedAPI.GetFullPolicy(policyID)
.then((data) => {
if (data.jsonCode !== 200) {
return;
@@ -301,7 +301,7 @@ function removeMembers(members, policyID) {
Onyx.set(key, policy);
// Make the API call to remove a login from the policy
- API.Policy_Employees_Remove({
+ DeprecatedAPI.Policy_Employees_Remove({
emailList: members.join(','),
policyID,
})
@@ -339,7 +339,7 @@ function invite(logins, welcomeNote, policyID) {
Onyx.merge(key, policy);
// Make the API call to merge the login into the policy
- API.Policy_Employees_Merge({
+ DeprecatedAPI.Policy_Employees_Merge({
employees: JSON.stringify(_.map(logins, login => ({email: login}))),
welcomeNote,
policyID,
@@ -387,7 +387,7 @@ function updateLocalPolicyValues(policyID, values) {
*/
function update(policyID, values, shouldGrowl = false) {
updateLocalPolicyValues(policyID, {isPolicyUpdating: true});
- API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null})
+ DeprecatedAPI.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null})
.then((policyResponse) => {
if (policyResponse.jsonCode !== 200) {
throw new Error();
@@ -414,7 +414,7 @@ function update(policyID, values, shouldGrowl = false) {
*/
function uploadAvatar(policyID, file) {
updateLocalPolicyValues(policyID, {isAvatarUploading: true});
- API.User_UploadAvatar({file})
+ DeprecatedAPI.User_UploadAvatar({file})
.then((response) => {
if (response.jsonCode === 200) {
// Update the policy with the new avatarURL as soon as we get it
@@ -450,7 +450,7 @@ function hideWorkspaceAlertMessage(policyID) {
* @param {Object} values
*/
function setCustomUnit(policyID, values) {
- API.Policy_CustomUnit_Update({
+ DeprecatedAPI.Policy_CustomUnit_Update({
policyID: policyID.toString(),
customUnit: JSON.stringify(values),
lastModified: null,
@@ -479,7 +479,7 @@ function setCustomUnit(policyID, values) {
* @param {Object} values
*/
function setCustomUnitRate(policyID, customUnitID, values) {
- API.Policy_CustomUnitRate_Update({
+ DeprecatedAPI.Policy_CustomUnitRate_Update({
policyID: policyID.toString(),
customUnitID: customUnitID.toString(),
customUnitRate: JSON.stringify(values),
@@ -526,9 +526,12 @@ function subscribeToPolicyEvents() {
// Remove the members from the policy
Onyx.set(key, policyWithoutEmployee);
- // Refetch the policy expense chats to update their state
+ // Refetch the policy expense chats to update their state and their actions to get the archive reason
if (!_.isEmpty(policyExpenseChatIDs)) {
Report.fetchChatReportsByIDs(policyExpenseChatIDs);
+ _.each(policyExpenseChatIDs, (reportID) => {
+ Report.fetchActions(reportID);
+ });
}
// Remove the default chats if we are one of the users getting removed
diff --git a/src/libs/actions/ReimbursementAccount/fetchFreePlanVerifiedBankAccount.js b/src/libs/actions/ReimbursementAccount/fetchFreePlanVerifiedBankAccount.js
index 6fcbb3703012..927340ea6204 100644
--- a/src/libs/actions/ReimbursementAccount/fetchFreePlanVerifiedBankAccount.js
+++ b/src/libs/actions/ReimbursementAccount/fetchFreePlanVerifiedBankAccount.js
@@ -1,7 +1,7 @@
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import lodashGet from 'lodash/get';
-import * as API from '../../API';
+import * as DeprecatedAPI from '../../deprecatedAPI';
import CONST from '../../../CONST';
import ONYXKEYS from '../../../ONYXKEYS';
import * as navigation from './navigation';
@@ -30,7 +30,7 @@ function fetchNameValuePairsAndBankAccount() {
let bankAccountID;
let failedValidationAttemptsName;
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'nameValuePairs',
name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
})
@@ -40,7 +40,7 @@ function fetchNameValuePairsAndBankAccount() {
failedValidationAttemptsName = CONST.NVP.FAILED_BANK_ACCOUNT_VALIDATIONS_PREFIX + bankAccountID;
// Now that we have the bank account. Lets grab the rest of the bank info we need
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'nameValuePairs, bankAccountList',
nvpNames: [
failedValidationAttemptsName,
diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
index dc16d35970cb..4f54352169ff 100644
--- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
+++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js
@@ -1,7 +1,7 @@
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../../ONYXKEYS';
-import * as API from '../../API';
+import * as DeprecatedAPI from '../../deprecatedAPI';
import CONST from '../../../CONST';
import * as store from './store';
import Growl from '../../Growl';
@@ -24,7 +24,7 @@ function resetFreePlanBankAccount() {
// 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});
- API.DeleteBankAccount({bankAccountID, ownerEmail: store.getCredentials().login})
+ 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
@@ -46,7 +46,7 @@ function resetFreePlanBankAccount() {
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
- API.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: ''});
+ DeprecatedAPI.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: ''});
Navigation.navigate(ROUTES.getBankAccountRoute());
});
}
diff --git a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js
index c3c713c512c6..b67e4c319482 100644
--- a/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js
+++ b/src/libs/actions/ReimbursementAccount/setupWithdrawalAccount.js
@@ -7,7 +7,7 @@ import * as Plaid from '../Plaid';
import CONST from '../../../CONST';
import ONYXKEYS from '../../../ONYXKEYS';
import * as store from './store';
-import * as API from '../../API';
+import * as DeprecatedAPI from '../../deprecatedAPI';
import * as errors from './errors';
import * as Localize from '../../Localize';
import * as navigation from './navigation';
@@ -17,7 +17,7 @@ import * as navigation from './navigation';
* @param {Number} bankAccountID
*/
function setFreePlanVerifiedBankAccountID(bankAccountID) {
- API.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: bankAccountID});
+ DeprecatedAPI.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: bankAccountID});
}
/**
@@ -26,7 +26,7 @@ function setFreePlanVerifiedBankAccountID(bankAccountID) {
function getBankAccountListAndGoToValidateStep(updatedACHData) {
// Get an up-to-date bank account list so that we can allow the user to validate their newly
// generated bank account
- API.Get({returnValueList: 'bankAccountList'})
+ DeprecatedAPI.Get({returnValueList: 'bankAccountList'})
.then((bankAccountListResponse) => {
const bankAccountJSON = _.findWhere(bankAccountListResponse.bankAccountList, {
bankAccountID: updatedACHData.bankAccountID,
@@ -206,7 +206,7 @@ function mergeParamsWithLocalACHData(data) {
function setupWithdrawalAccount(params) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true, errorModalMessage: '', errors: null});
const updatedACHData = mergeParamsWithLocalACHData(params);
- API.BankAccount_SetupWithdrawal(updatedACHData)
+ DeprecatedAPI.BankAccount_SetupWithdrawal(updatedACHData)
.then((response) => {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...updatedACHData}});
const currentStep = updatedACHData.currentStep;
diff --git a/src/libs/actions/ReimbursementAccount/validateBankAccount.js b/src/libs/actions/ReimbursementAccount/validateBankAccount.js
index dd313dbb33ad..ace044899289 100644
--- a/src/libs/actions/ReimbursementAccount/validateBankAccount.js
+++ b/src/libs/actions/ReimbursementAccount/validateBankAccount.js
@@ -1,6 +1,6 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../../ONYXKEYS';
-import * as API from '../../API';
+import * as DeprecatedAPI from '../../deprecatedAPI';
import BankAccount from '../../models/BankAccount';
import CONST from '../../../CONST';
import * as Localize from '../../Localize';
@@ -13,11 +13,11 @@ import * as errors from './errors';
function validateBankAccount(bankAccountID, validateCode) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
- API.BankAccount_Validate({bankAccountID, validateCode})
+ DeprecatedAPI.BankAccount_Validate({bankAccountID, validateCode})
.then((response) => {
if (response.jsonCode === 200) {
Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, null);
- API.User_IsUsingExpensifyCard()
+ DeprecatedAPI.User_IsUsingExpensifyCard()
.then(({isUsingExpensifyCard}) => {
const reimbursementAccount = {
loading: false,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index ebe19e79bdb1..79438cb60f8a 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -17,7 +17,7 @@ import Visibility from '../Visibility';
import ROUTES from '../../ROUTES';
import NetworkConnection from '../NetworkConnection';
import Timing from './Timing';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
import Log from '../Log';
@@ -283,14 +283,14 @@ function getSimplifiedIOUReport(reportData, chatReportID) {
}
/**
- * Given IOU and chat report ID fetches most recent IOU data from API.
+ * Given IOU and chat report ID fetches most recent IOU data from DeprecatedAPI.
*
* @param {Number} iouReportID
* @param {Number} chatReportID
* @returns {Promise}
*/
function fetchIOUReport(iouReportID, chatReportID) {
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'reportStuff',
reportIDList: iouReportID,
shouldLoadOptionalKeys: true,
@@ -322,7 +322,7 @@ function fetchIOUReport(iouReportID, chatReportID) {
* @returns {Promise}
*/
function fetchIOUReportID(debtorEmail) {
- return API.GetIOUReport({
+ return DeprecatedAPI.GetIOUReport({
debtorEmail,
}).then((response) => {
const iouReportID = response.reportID || 0;
@@ -352,7 +352,7 @@ function fetchIOUReportID(debtorEmail) {
function fetchChatReportsByIDs(chatList, shouldRedirectIfInaccessible = false) {
let fetchedReports;
const simplifiedReports = {};
- return API.GetReportSummaryList({reportIDList: chatList.join(',')})
+ return DeprecatedAPI.GetReportSummaryList({reportIDList: chatList.join(',')})
.then(({reportSummaryList, jsonCode}) => {
Log.info('[Report] successfully fetched report data', false, {chatList});
fetchedReports = reportSummaryList;
@@ -728,9 +728,8 @@ function getReportChannelName(reportID) {
*
* @param {String} eventName
* @param {Function} onEvent
- * @param {Boolean} isChunked
*/
-function subscribeToPrivateUserChannelEvent(eventName, onEvent, isChunked = false) {
+function subscribeToPrivateUserChannelEvent(eventName, onEvent) {
const pusherChannelName = `private-encrypted-user-accountID-${currentUserAccountID}${CONFIG.PUSHER.SUFFIX}`;
/**
@@ -759,7 +758,7 @@ function subscribeToPrivateUserChannelEvent(eventName, onEvent, isChunked = fals
Log.hmmm('[Report] Failed to subscribe to Pusher channel', false, {error, pusherChannelName, eventName});
}
- Pusher.subscribe(pusherChannelName, eventName, onEventPush, isChunked, onPusherResubscribeToPrivateUserChannel)
+ Pusher.subscribe(pusherChannelName, eventName, onEventPush, onPusherResubscribeToPrivateUserChannel)
.catch(onSubscriptionFailed);
}
@@ -781,17 +780,13 @@ function subscribeToUserEvents() {
subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT, pushJSON => updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction, pushJSON.notificationPreference));
// Live-update a report's actions when a 'chunked report comment' event is received.
- subscribeToPrivateUserChannelEvent(
- Pusher.TYPE.REPORT_COMMENT_CHUNK,
- pushJSON => updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction, pushJSON.notificationPreference),
- true,
- );
+ subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_CHUNK, pushJSON => updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction, pushJSON.notificationPreference));
// Live-update a report's actions when an 'edit comment' event is received.
subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT, pushJSON => updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message));
// Live-update a report's actions when an 'edit comment chunk' event is received.
- subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT_CHUNK, pushJSON => updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message), true);
+ subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT_CHUNK, pushJSON => updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message));
// Live-update a report's pinned state when a 'report toggle pinned' event is received.
subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_TOGGLE_PINNED, pushJSON => updateReportPinnedState(pushJSON.reportID, pushJSON.isPinned));
@@ -916,7 +911,7 @@ function fetchOrCreateChatReport(participants, shouldNavigate = true) {
throw new Error('fetchOrCreateChatReport() must have at least two participants.');
}
- return API.CreateChatReport({
+ return DeprecatedAPI.CreateChatReport({
emailList: participants.join(','),
})
.then((data) => {
@@ -959,7 +954,7 @@ function fetchActions(reportID, offset) {
return;
}
- return API.Report_GetHistory({
+ return DeprecatedAPI.Report_GetHistory({
reportID,
reportActionsOffset,
reportActionsLimit: CONST.REPORT.ACTIONS.LIMIT,
@@ -998,7 +993,7 @@ function fetchAllReports(
shouldDelayActionsFetch = false,
) {
Onyx.set(ONYXKEYS.IS_LOADING_REPORT_DATA, true);
- return API.Get({
+ return DeprecatedAPI.Get({
returnValueList: 'chatList',
})
.then((response) => {
@@ -1159,7 +1154,7 @@ function addAction(reportID, text, file) {
},
});
- API.Report_AddComment({
+ DeprecatedAPI.Report_AddComment({
reportID,
file,
reportComment: commentText,
@@ -1227,7 +1222,7 @@ function deleteReportComment(reportID, reportAction) {
});
// Try to delete the comment by calling the API
- API.Report_EditComment({
+ DeprecatedAPI.Report_EditComment({
reportID,
reportActionID: reportAction.reportActionID,
reportComment: '',
@@ -1282,7 +1277,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false
setLocalLastRead(reportID, lastReadSequenceNumber);
// Mark the report as not having any unread items
- API.Report_UpdateLastRead({
+ DeprecatedAPI.Report_UpdateLastRead({
reportID,
sequenceNumber: lastReadSequenceNumber,
markAsUnread: manuallyMarked,
@@ -1297,7 +1292,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false
function togglePinnedState(report) {
const pinnedValue = !report.isPinned;
updateReportPinnedState(report.reportID, pinnedValue);
- API.Report_TogglePinned({
+ DeprecatedAPI.Report_TogglePinned({
reportID: report.reportID,
pinnedValue,
});
@@ -1414,7 +1409,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge);
// Persist the updated report comment
- API.Report_EditComment({
+ DeprecatedAPI.Report_EditComment({
reportID,
reportActionID: originalReportAction.reportActionID,
reportComment: htmlForNewComment,
@@ -1484,7 +1479,7 @@ function syncChatAndIOUReports(chatReport, iouReport) {
*/
function updateNotificationPreference(reportID, notificationPreference) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {notificationPreference});
- API.Report_UpdateNotificationPreference({reportID, notificationPreference});
+ DeprecatedAPI.Report_UpdateNotificationPreference({reportID, notificationPreference});
}
/**
@@ -1517,7 +1512,7 @@ function handleInaccessibleReport() {
*/
function createPolicyRoom(policyID, reportName, visibility) {
Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, true);
- return API.CreatePolicyRoom({policyID, reportName, visibility})
+ return DeprecatedAPI.CreatePolicyRoom({policyID, reportName, visibility})
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
Growl.error(Localize.translateLocal('newRoomPage.growlMessageOnError'));
@@ -1549,7 +1544,7 @@ function createPolicyRoom(policyID, reportName, visibility) {
*/
function renameReport(reportID, reportName) {
Onyx.set(ONYXKEYS.IS_LOADING_RENAME_POLICY_ROOM, true);
- API.RenameReport({reportID, reportName})
+ DeprecatedAPI.RenameReport({reportID, reportName})
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
Growl.error(Localize.translateLocal('newRoomPage.growlMessageOnRenameError'));
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index 498d11c563c4..629f2efb6a83 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str';
import _ from 'underscore';
import ONYXKEYS from '../../../ONYXKEYS';
import redirectToSignIn from '../SignInRedirect';
-import * as API from '../../API';
+import * as DeprecatedAPI from '../../deprecatedAPI';
import CONFIG from '../../../CONFIG';
import Log from '../../Log';
import PushNotification from '../../Notification/PushNotification';
@@ -53,7 +53,7 @@ function setSuccessfulSignInData(data) {
function createAccount(login) {
Onyx.merge(ONYXKEYS.ACCOUNT, {error: ''});
- API.User_SignUp({
+ DeprecatedAPI.User_SignUp({
email: login,
}).then((response) => {
// A 405 means that the account needs to be validated. We should let the user proceed to the ResendValidationForm view.
@@ -73,7 +73,7 @@ function signOut() {
Log.info('Flushing logs before signing out', true, {}, true);
if (credentials && credentials.autoGeneratedLogin) {
// Clean up the login that we created
- API.DeleteLogin({
+ DeprecatedAPI.DeleteLogin({
partnerUserID: credentials.autoGeneratedLogin,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
@@ -105,7 +105,7 @@ function signOutAndRedirectToSignIn() {
*/
function reopenAccount(login = credentials.login) {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true});
- API.User_ReopenAccount({email: login})
+ DeprecatedAPI.User_ReopenAccount({email: login})
.finally(() => {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
@@ -118,7 +118,7 @@ function reopenAccount(login = credentials.login) {
*/
function resendValidationLink(login = credentials.login) {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true});
- API.ResendValidateCode({email: login})
+ DeprecatedAPI.ResendValidateCode({email: login})
.finally(() => {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
@@ -132,7 +132,7 @@ function resendValidationLink(login = credentials.login) {
function fetchAccountDetails(login) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
- API.GetAccountStatus({email: login, forceNetworkRequest: true})
+ DeprecatedAPI.GetAccountStatus({email: login, forceNetworkRequest: true})
.then((response) => {
if (response.jsonCode === 200) {
Onyx.merge(ONYXKEYS.CREDENTIALS, {
@@ -183,7 +183,7 @@ function createTemporaryLogin(authToken, email) {
const autoGeneratedLogin = Str.guid('expensify.cash-');
const autoGeneratedPassword = Str.guid();
- return API.CreateLogin({
+ return DeprecatedAPI.CreateLogin({
authToken,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
@@ -205,7 +205,7 @@ function createTemporaryLogin(authToken, email) {
// If we have an old generated login for some reason
// we should delete it before storing the new details
if (credentials && credentials.autoGeneratedLogin) {
- API.DeleteLogin({
+ DeprecatedAPI.DeleteLogin({
partnerUserID: credentials.autoGeneratedLogin,
partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
@@ -299,7 +299,7 @@ function signInWithShortLivedToken(accountID, email, shortLivedToken) {
*/
function resetPassword() {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true, forgotPassword: true});
- API.ResetPassword({email: credentials.login})
+ DeprecatedAPI.ResetPassword({email: credentials.login})
.finally(() => {
Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false, validateCodeExpired: false});
});
@@ -316,7 +316,7 @@ function resetPassword() {
*/
function setPassword(password, validateCode, accountID) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true, validateCodeExpired: false});
- API.SetPassword({
+ DeprecatedAPI.SetPassword({
password,
validateCode,
accountID,
@@ -375,7 +375,7 @@ function clearAccountMessages() {
*/
function changePasswordAndSignIn(authToken, password) {
Onyx.merge(ONYXKEYS.ACCOUNT, {validateSessionExpired: false});
- API.ChangePassword({
+ DeprecatedAPI.ChangePassword({
authToken,
password,
})
@@ -411,7 +411,7 @@ function changePasswordAndSignIn(authToken, password) {
function validateEmail(accountID, validateCode) {
Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: true});
Onyx.merge(ONYXKEYS.SESSION, {error: ''});
- API.ValidateEmail({
+ DeprecatedAPI.ValidateEmail({
accountID,
validateCode,
})
@@ -455,7 +455,7 @@ const reauthenticatePusher = _.throttle(() => {
function authenticatePusher(socketID, channelName, callback) {
Log.info('[PusherAuthorizer] Attempting to authorize Pusher', false, {channelName});
- API.Push_Authenticate({
+ DeprecatedAPI.Push_Authenticate({
socket_id: socketID,
channel_name: channelName,
shouldRetry: false,
diff --git a/src/libs/actions/Timing.js b/src/libs/actions/Timing.js
index fbcb287cd959..ce4e0b5f8b3d 100644
--- a/src/libs/actions/Timing.js
+++ b/src/libs/actions/Timing.js
@@ -1,5 +1,5 @@
import getPlatform from '../getPlatform';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import * as Environment from '../Environment/Environment';
import Firebase from '../Firebase';
@@ -51,7 +51,7 @@ function end(eventName, secondaryName = '') {
return;
}
- API.Graphite_Timer({
+ DeprecatedAPI.Graphite_Timer({
name: grafanaEventName,
value: eventTime,
platform: `${getPlatform()}`,
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index e9f02897f0c9..16220acb7c80 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str';
import {PUBLIC_DOMAINS as COMMON_PUBLIC_DOMAINS} from 'expensify-common/lib/CONST';
import moment from 'moment';
import ONYXKEYS from '../../ONYXKEYS';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
import Navigation from '../Navigation/Navigation';
@@ -49,7 +49,7 @@ Onyx.connect({
function changePasswordAndNavigate(oldPassword, password) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
- return API.ChangePassword({oldPassword, password})
+ return DeprecatedAPI.ChangePassword({oldPassword, password})
.then((response) => {
if (response.jsonCode !== 200) {
const error = lodashGet(response, 'message', 'Unable to change password. Please try again.');
@@ -71,7 +71,7 @@ function changePasswordAndNavigate(oldPassword, password) {
* @param {String} message optional reason for closing account
*/
function closeAccount(message) {
- API.User_Delete({message}).then((response) => {
+ DeprecatedAPI.User_Delete({message}).then((response) => {
console.debug('User_Delete: ', JSON.stringify(response));
if (response.jsonCode === 200) {
@@ -86,7 +86,7 @@ function closeAccount(message) {
}
function getBetas() {
- API.User_GetBetas().then((response) => {
+ DeprecatedAPI.User_GetBetas().then((response) => {
if (response.jsonCode !== 200) {
return;
}
@@ -99,7 +99,7 @@ function getBetas() {
* Fetches the data needed for user settings
*/
function getUserDetails() {
- API.Get({
+ DeprecatedAPI.Get({
returnValueList: 'account, loginList, nameValuePairs',
nvpNames: [
CONST.NVP.PAYPAL_ME_ADDRESS,
@@ -139,7 +139,7 @@ function getUserDetails() {
* @param {String} login
*/
function resendValidateCode(login) {
- API.ResendValidateCode({email: login});
+ DeprecatedAPI.ResendValidateCode({email: login});
}
/**
@@ -150,7 +150,7 @@ function resendValidateCode(login) {
function setExpensifyNewsStatus(subscribed) {
Onyx.merge(ONYXKEYS.USER, {expensifyNewsStatus: subscribed});
- API.UpdateAccount({subscribed})
+ DeprecatedAPI.UpdateAccount({subscribed})
.then((response) => {
if (response.jsonCode === 200) {
return;
@@ -173,7 +173,7 @@ function setExpensifyNewsStatus(subscribed) {
function setSecondaryLoginAndNavigate(login, password) {
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
- return API.User_SecondaryLogin_Send({
+ return DeprecatedAPI.User_SecondaryLogin_Send({
email: login,
password,
}).then((response) => {
@@ -211,7 +211,7 @@ function validateLogin(accountID, validateCode) {
const redirectRoute = isLoggedIn ? ROUTES.getReportRoute(currentlyViewedReportID) : ROUTES.HOME;
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
- API.ValidateEmail({
+ DeprecatedAPI.ValidateEmail({
accountID,
validateCode,
}).then((response) => {
@@ -273,13 +273,13 @@ function getDomainInfo() {
}
// If it is not a common public domain, check the API
- API.User_IsFromPublicDomain({email: sessionEmail})
+ DeprecatedAPI.User_IsFromPublicDomain({email: sessionEmail})
.then((response) => {
if (response.jsonCode === 200) {
const {isFromPublicDomain} = response;
Onyx.merge(ONYXKEYS.USER, {isFromPublicDomain});
- API.User_IsUsingExpensifyCard()
+ DeprecatedAPI.User_IsUsingExpensifyCard()
.then(({isUsingExpensifyCard}) => {
Onyx.merge(ONYXKEYS.USER, {isUsingExpensifyCard});
});
@@ -305,7 +305,7 @@ function subscribeToUserEvents() {
// Live-update an user's preferred locale
Pusher.subscribe(pusherChannelName, Pusher.TYPE.PREFERRED_LOCALE, (pushJSON) => {
Onyx.merge(ONYXKEYS.NVP_PREFERRED_LOCALE, pushJSON.preferredLocale);
- }, false,
+ },
() => {
NetworkConnection.triggerReconnectionCallbacks('pusher re-subscribed to private user channel');
})
@@ -320,7 +320,7 @@ function subscribeToUserEvents() {
// Subscribe to screen share requests sent by GuidesPlus agents
Pusher.subscribe(pusherChannelName, Pusher.TYPE.SCREEN_SHARE_REQUEST, (pushJSON) => {
Onyx.merge(ONYXKEYS.SCREEN_SHARE_REQUEST, pushJSON);
- }, false,
+ },
() => {
NetworkConnection.triggerReconnectionCallbacks('pusher re-subscribed to private user channel');
})
@@ -351,7 +351,7 @@ function subscribeToExpensifyCardUpdates() {
} else {
Onyx.merge(ONYXKEYS.USER, {isCheckingDomain: pushJSON.isCheckingDomain});
}
- }, false,
+ },
() => {
NetworkConnection.triggerReconnectionCallbacks('pusher re-subscribed to private user channel');
})
@@ -415,7 +415,7 @@ function joinScreenShare(accessToken, roomName) {
*/
function generateStatementPDF(period) {
Onyx.merge(ONYXKEYS.WALLET_STATEMENT, {isGenerating: true});
- return API.GetStatementPDF({period})
+ return DeprecatedAPI.GetStatementPDF({period})
.then((response) => {
if (response.jsonCode !== 200 || !response.filename) {
Log.info('[User] Failed to generate statement PDF', false, {response});
diff --git a/src/libs/actions/Wallet.js b/src/libs/actions/Wallet.js
index fa6735659c26..6f2e02cd06a1 100644
--- a/src/libs/actions/Wallet.js
+++ b/src/libs/actions/Wallet.js
@@ -3,7 +3,7 @@ import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
-import * as API from '../API';
+import * as DeprecatedAPI from '../deprecatedAPI';
import CONST from '../../CONST';
import * as PaymentMethods from './PaymentMethods';
import * as Localize from '../Localize';
@@ -20,7 +20,7 @@ import * as Localize from '../Localize';
function fetchOnfidoToken(firstName, lastName, dob) {
// Use Onyx.set() since we are resetting the Onfido flow completely.
Onyx.set(ONYXKEYS.WALLET_ONFIDO, {loading: true});
- API.Wallet_GetOnfidoSDKToken(firstName, lastName, dob)
+ DeprecatedAPI.Wallet_GetOnfidoSDKToken(firstName, lastName, dob)
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.SUCCESS) {
const apiResult = lodashGet(response, ['requestorIdentityOnfido', 'apiResult'], {});
@@ -201,7 +201,7 @@ function activateWallet(currentStep, parameters) {
Onyx.merge(ONYXKEYS.WALLET_TERMS, {loading: true});
}
- API.Wallet_Activate({
+ DeprecatedAPI.Wallet_Activate({
currentStep,
personalDetails,
idologyAnswers,
@@ -326,7 +326,7 @@ function activateWallet(currentStep, parameters) {
* @property {('SILVER'|'GOLD')} tierName - will be GOLD when fully activated. SILVER is able to recieve funds only.
*/
function fetchUserWallet() {
- API.Get({returnValueList: 'userWallet'})
+ DeprecatedAPI.Get({returnValueList: 'userWallet'})
.then((response) => {
if (response.jsonCode !== 200) {
return;
diff --git a/src/libs/actions/Welcome.js b/src/libs/actions/Welcome.js
index e8e09131e315..375d89adff7f 100644
--- a/src/libs/actions/Welcome.js
+++ b/src/libs/actions/Welcome.js
@@ -8,9 +8,11 @@ import * as Policy from './Policy';
import ONYXKEYS from '../../ONYXKEYS';
import NameValuePair from './NameValuePair';
import CONST from '../../CONST';
-import createOnReadyTask from '../createOnReadyTask';
-const readyTask = createOnReadyTask();
+let resolveIsReadyPromise;
+let isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+});
let isFirstTimeNewExpensifyUser;
let isLoadingReportData = true;
@@ -28,7 +30,7 @@ function checkOnReady() {
return;
}
- readyTask.setIsReady();
+ resolveIsReadyPromise();
}
Onyx.connect({
@@ -91,7 +93,7 @@ Onyx.connect({
* @param {Function} params.showCreateMenu
*/
function show({routes, showCreateMenu}) {
- readyTask.isReady().then(() => {
+ isReadyPromise.then(() => {
if (!isFirstTimeNewExpensifyUser) {
return;
}
@@ -122,7 +124,9 @@ function show({routes, showCreateMenu}) {
}
function resetReadyCheck() {
- readyTask.reset();
+ isReadyPromise = new Promise((resolve) => {
+ resolveIsReadyPromise = resolve;
+ });
}
export {
diff --git a/src/libs/createCallback.js b/src/libs/createCallback.js
deleted file mode 100644
index 61402a6edc33..000000000000
--- a/src/libs/createCallback.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import _ from 'underscore';
-
-/**
- * Utility for registering a single callback. Use to inject dependencies in places where you would normally "register" a method
- * that you want to call later. Can be overwritten or cleared and returns value to caller. This is intented to be used with array
- * destructuring.
- *
- * @example
- *
- * const [onResponse, handleResponse, clearHandler] = createCallback();
- *
- * @returns {Array}
- */
-function createCallback() {
- let callback = null;
-
- function clear() {
- callback = null;
- }
-
- function set(newCallback) {
- callback = newCallback;
- }
-
- function run(...args) {
- if (!_.isFunction(callback)) {
- return;
- }
-
- return callback(...args);
- }
-
- return [run, set, clear];
-}
-
-export default createCallback;
diff --git a/src/libs/createOnReadyTask.js b/src/libs/createOnReadyTask.js
deleted file mode 100644
index 564a668dad09..000000000000
--- a/src/libs/createOnReadyTask.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Helper method to create a task to track the "readiness" of something and defer any actions until after something is "ready".
- *
- * @example
- *
- * const task = createOnReadyTask();
- * task.isReady().then(() => doIt());
- * task.setIsReady(); // -> doIt() will now execute
- * task.reset() // -> will let us reset the task (useful for testing)
- * @returns {Object}
- */
-export default function createOnReadyTask() {
- let resolveIsReadyPromise;
- let isReadyPromise;
- function reset() {
- isReadyPromise = (new Promise((resolve) => {
- resolveIsReadyPromise = resolve;
- }));
- }
- reset();
- return {
- isReady: () => isReadyPromise,
- setIsReady: () => resolveIsReadyPromise(),
- reset,
- };
-}
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
new file mode 100644
index 000000000000..67333741f0a7
--- /dev/null
+++ b/src/libs/deprecatedAPI.js
@@ -0,0 +1,990 @@
+import _ from 'underscore';
+import getPlaidLinkTokenParameters from './getPlaidLinkTokenParameters';
+import isViaExpensifyCashNative from './isViaExpensifyCashNative';
+import requireParameters from './requireParameters';
+import * as Request from './Request';
+import * as Network from './Network';
+import * as Middleware from './Middleware';
+import CONST from '../CONST';
+
+// Setup API middlewares. Each request made will pass through a series of middleware functions that will get called in sequence (each one passing the result of the previous to the next).
+// Note: The ordering here is intentional as we want to Log, Recheck Connection, Reauthenticate, and Retry. Errors thrown in one middleware will bubble to the next e.g. an error thrown in
+// Logging or Reauthenticate logic would be caught by the Retry logic (which is why it is the last one used).
+
+// Logging - Logs request details and errors.
+Request.use(Middleware.Logging);
+
+// RecheckConnection - Sets a timer for a request that will "recheck" if we are connected to the internet if time runs out. Also triggers the connection recheck when we encounter any error.
+Request.use(Middleware.RecheckConnection);
+
+// Reauthentication - Handles jsonCode 407 which indicates an expired authToken. We need to reauthenticate and get a new authToken with our stored credentials.
+Request.use(Middleware.Reauthentication);
+
+// Retry - Handles retrying any failed requests.
+Request.use(Middleware.Retry);
+
+// SaveResponseInOnyx - Merges either the successData or failureData into Onyx depending on if the call was successful or not
+Request.use(Middleware.SaveResponseInOnyx);
+
+/**
+ * @param {Object} parameters
+ * @returns {Promise}
+ */
+function AddBillingCard(parameters) {
+ const commandName = 'User_AddBillingCard';
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
+}
+
+/**
+ * @param {{password: String, oldPassword: String}} parameters
+ * @param {String} parameters.authToken
+ * @param {String} parameters.password
+ * @returns {Promise}
+ */
+function ChangePassword(parameters) {
+ const commandName = 'ChangePassword';
+ requireParameters(['password'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {object} parameters
+ * @param {string} parameters.emailList
+ * @returns {Promise}
+ */
+function CreateChatReport(parameters) {
+ const commandName = 'CreateChatReport';
+ requireParameters(['emailList'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @returns {Promise}
+ */
+function User_SignUp(parameters) {
+ const commandName = 'User_SignUp';
+ requireParameters([
+ 'email',
+ ], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.authToken
+ * @param {String} parameters.partnerName
+ * @param {String} parameters.partnerPassword
+ * @param {String} parameters.partnerUserID
+ * @param {String} parameters.partnerUserSecret
+ * @param {Boolean} [parameters.shouldRetry]
+ * @param {String} [parameters.email]
+ * @returns {Promise}
+ */
+function CreateLogin(parameters) {
+ const commandName = 'CreateLogin';
+ requireParameters([
+ 'authToken',
+ 'partnerName',
+ 'partnerPassword',
+ 'partnerUserID',
+ 'partnerUserSecret',
+ ], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.fundID
+ * @returns {Promise}
+ */
+function DeleteFund(parameters) {
+ const commandName = 'DeleteFund';
+ requireParameters(['fundID'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.partnerUserID
+ * @param {String} parameters.partnerName
+ * @param {String} parameters.partnerPassword
+ * @param {Boolean} parameters.shouldRetry
+ * @returns {Promise}
+ */
+function DeleteLogin(parameters) {
+ const commandName = 'DeleteLogin';
+ requireParameters(['partnerUserID', 'partnerName', 'partnerPassword', 'shouldRetry'],
+ parameters, commandName);
+
+ // Non-cancellable request: during logout, when requests are cancelled, we don't want to cancel the actual logout request
+ return Network.post(commandName, {...parameters, canCancel: false});
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.returnValueList
+ * @param {Boolean} shouldUseSecure
+ * @returns {Promise}
+ */
+function Get(parameters, shouldUseSecure = false) {
+ const commandName = 'Get';
+ requireParameters(['returnValueList'], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, shouldUseSecure);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @param {Boolean} parameters.forceNetworkRequest
+ * @returns {Promise}
+ */
+function GetAccountStatus(parameters) {
+ const commandName = 'GetAccountStatus';
+ requireParameters(['email'], parameters, commandName);
+ 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
+ * @returns {Promise}
+ */
+function GetIOUReport(parameters) {
+ const commandName = 'GetIOUReport';
+ requireParameters(['debtorEmail'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ * @param {String} policyID
+ */
+function GetFullPolicy(policyID) {
+ if (!_.isString(policyID)) {
+ throw new Error('[API] Must include a single policyID with calls to API.GetFullPolicy');
+ }
+
+ const commandName = 'Get';
+ const parameters = {
+ returnValueList: 'policyList',
+ policyIDList: policyID,
+ };
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ */
+function GetPolicySummaryList() {
+ const commandName = 'Get';
+ const parameters = {
+ returnValueList: 'policySummaryList',
+ };
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ */
+function GetRequestCountryCode() {
+ const commandName = 'GetRequestCountryCode';
+ return Network.post(commandName);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.name
+ * @param {Number} parameters.value
+ * @returns {Promise}
+ */
+function Graphite_Timer(parameters) {
+ const commandName = 'Graphite_Timer';
+ requireParameters(['name', 'value'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {String} parameters.paymentMethodType
+ * @param {Object} [parameters.newIOUReportDetails]
+ * @returns {Promise}
+ */
+function PayIOU(parameters) {
+ const commandName = 'PayIOU';
+ requireParameters(['reportID', 'paymentMethodType'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {Object} [parameters.newIOUReportDetails]
+ * @returns {Promise}
+ */
+function PayWithWallet(parameters) {
+ const commandName = 'PayWithWallet';
+ requireParameters(['reportID'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.emailList
+ * @returns {Promise}
+ */
+function PersonalDetails_GetForEmails(parameters) {
+ const commandName = 'PersonalDetails_GetForEmails';
+ requireParameters(['emailList'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Object} parameters.details
+ * @returns {Promise}
+ */
+function PersonalDetails_Update(parameters) {
+ const commandName = 'PersonalDetails_Update';
+ requireParameters(['details'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Object} parameters.name
+ * @param {Object} parameters.value
+ * @returns {Promise}
+ */
+function PreferredLocale_Update(parameters) {
+ const commandName = 'PreferredLocale_Update';
+ requireParameters(['name', 'value'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.socket_id
+ * @param {String} parameters.channel_name
+ * @returns {Promise}
+ */
+function Push_Authenticate(parameters) {
+ const commandName = 'Push_Authenticate';
+ requireParameters(['socket_id', 'channel_name'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {String} parameters.transactionID
+ * @returns {Promise}
+ */
+function RejectTransaction(parameters) {
+ const commandName = 'RejectTransaction';
+ requireParameters(['reportID', 'transactionID'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.reportComment
+ * @param {Number} parameters.reportID
+ * @param {String} parameters.clientID
+ * @param {File|Object} [parameters.file]
+ * @returns {Promise}
+ */
+function Report_AddComment(parameters) {
+ const commandName = 'Report_AddComment';
+ requireParameters(['reportComment', 'reportID', 'clientID'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @returns {Promise}
+ */
+function Report_GetHistory(parameters) {
+ const commandName = 'Report_GetHistory';
+ requireParameters(['reportID'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {Boolean} parameters.pinnedValue
+ * @returns {Promise}
+ */
+function Report_TogglePinned(parameters) {
+ const commandName = 'Report_TogglePinned';
+ requireParameters(['reportID', 'pinnedValue'],
+ parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {Number} parameters.reportActionID
+ * @param {String} parameters.reportComment
+ * @returns {Promise}
+ */
+function Report_EditComment(parameters) {
+ const commandName = 'Report_EditComment';
+ requireParameters(['reportID', 'reportActionID', 'reportComment'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {Number} parameters.sequenceNumber
+ * @returns {Promise}
+ */
+function Report_UpdateLastRead(parameters) {
+ const commandName = 'Report_UpdateLastRead';
+ requireParameters(['reportID', 'sequenceNumber'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.reportID
+ * @param {String} parameters.notificationPreference
+ * @returns {Promise}
+ *
+ */
+function Report_UpdateNotificationPreference(parameters) {
+ const commandName = 'Report_UpdateNotificationPreference';
+ requireParameters(['reportID', 'notificationPreference'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @returns {Promise}
+ */
+function ResendValidateCode(parameters) {
+ const commandName = 'ResendValidateCode';
+ requireParameters(['email'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.name
+ * @param {String} parameters.value
+ * @returns {Promise}
+ */
+function SetNameValuePair(parameters) {
+ const commandName = 'SetNameValuePair';
+ requireParameters(['name', 'value'], parameters, commandName);
+ 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
+ * @param {String} parameters.validateCode
+ * @param {Number} parameters.accountID
+ * @returns {Promise}
+ */
+function SetPassword(parameters) {
+ const commandName = 'SetPassword';
+ requireParameters(['accountID', 'password', 'validateCode'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.password
+ * @param {String|null} parameters.bankAccountID
+ * @param {String|null} parameters.fundID
+ * @returns {Promise}
+ */
+function SetWalletLinkedAccount(parameters) {
+ const commandName = 'SetWalletLinkedAccount';
+ requireParameters(['password'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.subscribed
+ * @returns {Promise}
+ */
+function UpdateAccount(parameters) {
+ const commandName = 'UpdateAccount';
+ requireParameters(['subscribed'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.message
+ * @returns {Promise}
+ */
+function User_Delete(parameters) {
+ const commandName = 'User_Delete';
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ */
+function User_GetBetas() {
+ return Network.post('User_GetBetas');
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @param {Boolean} [parameters.requireCertainty]
+ * @returns {Promise}
+ */
+function User_IsFromPublicDomain(parameters) {
+ const commandName = 'User_IsFromPublicDomain';
+ requireParameters(['email'], parameters, commandName);
+ return Network.post(commandName, {
+ ...{requireCertainty: true},
+ ...parameters,
+ });
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @returns {Promise}
+ */
+function User_ReopenAccount(parameters) {
+ const commandName = 'User_ReopenAccount';
+ requireParameters(['email'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @param {String} parameters.password
+ * @returns {Promise}
+ */
+function User_SecondaryLogin_Send(parameters) {
+ const commandName = 'User_SecondaryLogin_Send';
+ requireParameters(['email', 'password'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {File|Object} parameters.file
+ * @returns {Promise}
+ */
+function User_UploadAvatar(parameters) {
+ const commandName = 'User_UploadAvatar';
+ requireParameters(['file'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Runs command that will fix malformed data in a users account and also run migrations.
+ *
+ * @returns {Promise}
+ */
+function User_FixAccount() {
+ const commandName = 'User_FixAccount';
+ return Network.post(commandName);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.accountID
+ * @param {String} parameters.validateCode
+ * @returns {Promise}
+ */
+function ValidateEmail(parameters) {
+ const commandName = 'ValidateEmail';
+ requireParameters(['accountID', 'validateCode'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Create a new IOUTransaction
+ *
+ * @param {Object} parameters
+ * @param {String} parameters.comment
+ * @param {Array} parameters.debtorEmail
+ * @param {String} parameters.currency
+ * @param {String} parameters.amount
+ * @returns {Promise}
+ */
+function CreateIOUTransaction(parameters) {
+ const commandName = 'CreateIOUTransaction';
+ requireParameters(['comment', 'debtorEmail', 'currency', 'amount'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Create a new IOU Split
+ *
+ * @param {Object} parameters
+ * @param {String} parameters.splits
+ * @param {String} parameters.currency
+ * @param {String} parameters.reportID
+ * @param {String} parameters.amount
+ * @param {String} parameters.comment
+ * @returns {Promise}
+ */
+function CreateIOUSplit(parameters) {
+ const commandName = 'CreateIOUSplit';
+ requireParameters(['splits', 'currency', 'amount', 'reportID'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {String} firstName
+ * @param {String} lastName
+ * @param {String} dob
+ * @returns {Promise}
+ */
+function Wallet_GetOnfidoSDKToken(firstName, lastName, dob) {
+ return Network.post('Wallet_GetOnfidoSDKToken', {
+ // We need to pass this so we can request a token with the correct referrer
+ // This value comes from a cross-platform module which returns true for native
+ // platforms and false for non-native platforms.
+ isViaExpensifyCashNative,
+ firstName,
+ lastName,
+ dob,
+ }, CONST.NETWORK.METHOD.POST, true);
+}
+
+/**
+ * @returns {Promise}
+ */
+function Plaid_GetLinkToken() {
+ return Network.post('Plaid_GetLinkToken', getPlaidLinkTokenParameters(), CONST.NETWORK.METHOD.POST, true);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.currentStep
+ * @param {String} [parameters.onfidoData] - JSON string
+ * @param {String} [parameters.personalDetails] - JSON string
+ * @param {String} [parameters.idologyAnswers] - JSON string
+ * @param {Boolean} [parameters.hasAcceptedTerms]
+ * @returns {Promise}
+ */
+function Wallet_Activate(parameters) {
+ const commandName = 'Wallet_Activate';
+ requireParameters(['currentStep'], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.publicToken
+ * @param {Boolean} parameters.allowDebit
+ * @param {String} parameters.bank
+ * @returns {Promise}
+ */
+function BankAccount_Get(parameters) {
+ const commandName = 'BankAccount_Get';
+ requireParameters(['publicToken', 'allowDebit', 'bank'], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Object[]} parameters.employees
+ * @param {String} parameters.welcomeNote
+ * @param {String} parameters.policyID
+ * @returns {Promise}
+ */
+function Policy_Employees_Merge(parameters) {
+ const commandName = 'Policy_Employees_Merge';
+ requireParameters(['employees', 'welcomeNote', 'policyID'], parameters, commandName);
+
+ // Always include returnPersonalDetails to ensure we get the employee's personal details in the response
+ return Network.post(commandName, {...parameters, returnPersonalDetails: true});
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.accountNumber
+ * @param {String} parameters.addressName
+ * @param {Boolean} parameters.allowDebit
+ * @param {Boolean} parameters.confirm
+ * @param {Boolean} parameters.isSavings
+ * @param {String} parameters.password
+ * @param {String} parameters.routingNumber
+ * @param {String} parameters.setupType
+ * @param {String} parameters.additionalData additional JSON data
+ * @returns {Promise}
+ */
+function BankAccount_Create(parameters) {
+ const commandName = 'BankAccount_Create';
+ requireParameters([
+ 'accountNumber',
+ 'addressName',
+ 'allowDebit',
+ 'confirm',
+ 'isSavings',
+ 'password',
+ 'routingNumber',
+ 'setupType',
+ 'additionalData',
+ ], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
+}
+
+function BankAccount_Validate(parameters) {
+ const commandName = 'ValidateBankAccount';
+ requireParameters(['bankAccountID', 'validateCode'], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST);
+}
+
+/**
+ * @param {*} parameters
+ * @returns {Promise}
+ */
+function BankAccount_SetupWithdrawal(parameters) {
+ const commandName = 'BankAccount_SetupWithdrawal';
+ let allowedParameters = [
+ 'currentStep', 'policyID', 'bankAccountID', 'useOnfido', 'errorAttemptsCount', 'enableCardAfterVerified',
+
+ // data from bankAccount step:
+ 'setupType', 'routingNumber', 'accountNumber', 'addressName', 'plaidAccountID', 'mask', 'ownershipType', 'isSavings',
+ 'acceptTerms', 'bankName', 'plaidAccessToken', 'alternateRoutingNumber',
+
+ // data from company step:
+ 'companyName', 'companyTaxID', 'addressStreet', 'addressCity', 'addressState', 'addressZipCode',
+ 'hasNoConnectionToCannabis', 'incorporationType', 'incorporationState', 'incorporationDate', 'industryCode',
+ 'website', 'companyPhone', 'ficticiousBusinessName',
+
+ // data from requestor step:
+ 'firstName', 'lastName', 'dob', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState',
+ 'requestorAddressZipCode', 'isOnfidoSetupComplete', 'onfidoData', 'isControllingOfficer', 'ssnLast4',
+
+ // data from ACHContract step (which became the "Beneficial Owners" step, but the key is still ACHContract as
+ // it's used in several logic:
+ 'ownsMoreThan25Percent', 'beneficialOwners', 'acceptTermsAndConditions', 'certifyTrueInformation',
+ ];
+
+ if (!parameters.useOnfido) {
+ allowedParameters = allowedParameters.concat(['passport', 'answers']);
+ }
+
+ // Only keep allowed parameters in the additionalData object
+ const additionalData = _.pick(parameters, allowedParameters);
+
+ requireParameters(['currentStep'], parameters, commandName);
+ return Network.post(
+ commandName, {additionalData: JSON.stringify(additionalData)},
+ CONST.NETWORK.METHOD.POST,
+ true,
+ );
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} parameters.bankAccountID
+ * @param {String} parameters.ownerEmail
+ * @returns {Promise}
+ */
+function DeleteBankAccount(parameters) {
+ const commandName = 'DeleteBankAccount';
+ requireParameters(['bankAccountID'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @returns {Promise}
+ */
+function Mobile_GetConstants(parameters) {
+ const commandName = 'Mobile_GetConstants';
+ requireParameters(['data'], parameters, commandName);
+
+ // Stringify the parameters object as we cannot send an object via FormData
+ const finalParameters = parameters;
+ finalParameters.data = JSON.stringify(parameters.data);
+
+ return Network.post(commandName, finalParameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {Number} [parameters.latitude]
+ * @param {Number} [parameters.longitude]
+ * @returns {Promise}
+ */
+function GetLocalCurrency(parameters) {
+ const commandName = 'GetLocalCurrency';
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ */
+function GetCurrencyList() {
+ return Mobile_GetConstants({data: ['currencyList']});
+}
+
+/**
+ * @returns {Promise}
+ */
+function User_IsUsingExpensifyCard() {
+ return Network.post('User_IsUsingExpensifyCard', {});
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} [parameters.type]
+ * @param {String} [parameters.policyName]
+ * @returns {Promise}
+ */
+function Policy_Create(parameters) {
+ const commandName = 'Policy_Create';
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.policyID
+ * @param {String} parameters.value
+ * @returns {Promise}
+ */
+function Policy_CustomUnit_Update(parameters) {
+ const commandName = 'Policy_CustomUnit_Update';
+ requireParameters(['policyID', 'customUnit'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.policyID
+ * @param {String} parameters.customUnitID
+ * @param {String} parameters.value
+ * @returns {Promise}
+ */
+function Policy_CustomUnitRate_Update(parameters) {
+ const commandName = 'Policy_CustomUnitRate_Update';
+ requireParameters(['policyID', 'customUnitID', 'customUnitRate'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} [parameters.policyID]
+ * @returns {Promise}
+ */
+function Policy_Delete(parameters) {
+ const commandName = 'Policy_Delete';
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.policyID
+ * @param {Array} parameters.emailList
+ * @returns {Promise}
+ */
+function Policy_Employees_Remove(parameters) {
+ const commandName = 'Policy_Employees_Remove';
+ requireParameters(['policyID', 'emailList'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.taskID
+ * @param {String} parameters.policyID
+ * @param {String} parameters.firstName
+ * @param {String} parameters.lastName
+ * @param {String} parameters.phoneNumber
+ * @returns {Promise}
+ */
+function Inbox_CallUser(parameters) {
+ const commandName = 'Inbox_CallUser';
+ requireParameters(['taskID', 'policyID', 'firstName', 'lastName', 'phoneNumber'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.reportIDList
+ * @returns {Promise}
+ */
+function GetReportSummaryList(parameters) {
+ const commandName = 'Get';
+ requireParameters(['reportIDList'], parameters, commandName);
+ return Network.post(commandName, {...parameters, returnValueList: 'reportSummaryList'});
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.policyID
+ * @param {String} parameters.value - Must be a JSON stringified object
+ * @returns {Promise}
+ */
+function UpdatePolicy(parameters) {
+ const commandName = 'UpdatePolicy';
+ requireParameters(['policyID', 'value'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.policyID
+ * @param {String} parameters.reportName
+ * @param {String} parameters.visibility
+ * @return {Promise}
+ */
+function CreatePolicyRoom(parameters) {
+ const commandName = 'CreatePolicyRoom';
+ requireParameters(['policyID', 'reportName', 'visibility'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Renames a user-created policy room
+ * @param {Object} parameters
+ * @param {String} parameters.reportID
+ * @param {String} parameters.reportName
+ * @return {Promise}
+ */
+function RenameReport(parameters) {
+ const commandName = 'RenameReport';
+ requireParameters(['reportID', 'reportName'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Transfer Wallet balance and takes either the bankAccoundID or fundID
+ * @param {Object} parameters
+ * @param {String} [parameters.bankAccountID]
+ * @param {String} [parameters.fundID]
+ * @returns {Promise}
+ */
+function TransferWalletBalance(parameters) {
+ const commandName = 'TransferWalletBalance';
+ if (!parameters.bankAccountID && !parameters.fundID) {
+ throw new Error('Must pass either bankAccountID or fundID to TransferWalletBalance');
+ }
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * Fetches the filename of the user's statement
+ * @param {Object} parameters
+ * @param {String} [parameters.period]
+ * @return {Promise}
+ */
+function GetStatementPDF(parameters) {
+ const commandName = 'GetStatementPDF';
+ return Network.post(commandName, parameters);
+}
+
+export {
+ AddBillingCard,
+ BankAccount_Create,
+ BankAccount_Get,
+ BankAccount_SetupWithdrawal,
+ BankAccount_Validate,
+ ChangePassword,
+ CreateChatReport,
+ CreateLogin,
+ CreatePolicyRoom,
+ RenameReport,
+ DeleteFund,
+ DeleteLogin,
+ DeleteBankAccount,
+ Get,
+ GetAccountStatus,
+ GetShortLivedAuthToken,
+ GetStatementPDF,
+ GetIOUReport,
+ GetFullPolicy,
+ GetPolicySummaryList,
+ GetReportSummaryList,
+ GetRequestCountryCode,
+ Graphite_Timer,
+ Inbox_CallUser,
+ PayIOU,
+ PayWithWallet,
+ PersonalDetails_GetForEmails,
+ PersonalDetails_Update,
+ Plaid_GetLinkToken,
+ Policy_Employees_Merge,
+ Push_Authenticate,
+ RejectTransaction,
+ Report_AddComment,
+ Report_GetHistory,
+ Report_TogglePinned,
+ Report_EditComment,
+ Report_UpdateLastRead,
+ Report_UpdateNotificationPreference,
+ ResendValidateCode,
+ ResetPassword,
+ SetNameValuePair,
+ SetPassword,
+ SetWalletLinkedAccount,
+ UpdateAccount,
+ UpdatePolicy,
+ User_SignUp,
+ User_Delete,
+ User_GetBetas,
+ User_IsFromPublicDomain,
+ User_IsUsingExpensifyCard,
+ User_ReopenAccount,
+ User_SecondaryLogin_Send,
+ User_UploadAvatar,
+ User_FixAccount,
+ CreateIOUTransaction,
+ CreateIOUSplit,
+ ValidateEmail,
+ Wallet_Activate,
+ Wallet_GetOnfidoSDKToken,
+ TransferWalletBalance,
+ GetLocalCurrency,
+ GetCurrencyList,
+ Policy_Create,
+ Policy_CustomUnit_Update,
+ Policy_CustomUnitRate_Update,
+ Policy_Employees_Remove,
+ PreferredLocale_Update,
+ Policy_Delete,
+};
diff --git a/src/libs/fileDownload/getAttachmentDetails.js b/src/libs/fileDownload/getAttachmentDetails.js
index 8b8e777ab911..8a465403f8a8 100644
--- a/src/libs/fileDownload/getAttachmentDetails.js
+++ b/src/libs/fileDownload/getAttachmentDetails.js
@@ -6,11 +6,12 @@ import Config from '../../CONFIG';
* @param {String} html
* @returns {Object}
*/
-const PREVIEW_SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_PREVIEW_ATTRIBUTE}*=*"(.+?)"`, 'i');
-const SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}*=*"(.+?)"`, 'i');
-const ORIGINAL_FILENAME_REGEX = new RegExp(`${CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE}*=*"(.+?)"`, 'i');
-
export default function getAttachmentName(html) {
+ // Files can be rendered either as anchor tag or as an image so based on that we have to form regex.
+ const IS_IMAGE_TAG = /
/i.test(html);
+ const PREVIEW_SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_PREVIEW_ATTRIBUTE}*=*"(.+?)"`, 'i');
+ const SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}*=*"(.+?)"`, 'i');
+ const ORIGINAL_FILENAME_REGEX = IS_IMAGE_TAG ? new RegExp(`${CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE}*=*"(.+?)"`, 'i') : new RegExp(']*>([^<]+)', 'i');
if (!html) {
return {
previewSourceURL: null,
@@ -18,11 +19,11 @@ export default function getAttachmentName(html) {
originalFileName: null,
};
}
- const previewSourceURL = html.match(PREVIEW_SOURCE_REGEX)[1].replace(
+ const sourceURL = html.match(SOURCE_REGEX)[1].replace(
Config.EXPENSIFY.EXPENSIFY_URL,
Config.EXPENSIFY.URL_API_ROOT,
);
- const sourceURL = html.match(SOURCE_REGEX)[1].replace(
+ const previewSourceURL = (IS_IMAGE_TAG ? html.match(PREVIEW_SOURCE_REGEX)[1] : sourceURL).replace(
Config.EXPENSIFY.EXPENSIFY_URL,
Config.EXPENSIFY.URL_API_ROOT,
);
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index c0b3eb0827b9..b46e6dd68dfc 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -114,6 +114,7 @@ const DetailsPage = (props) => {
containerStyles={[styles.avatarLarge, styles.mb3]}
imageStyles={[styles.avatarLarge]}
source={details.avatar}
+ size={CONST.AVATAR_SIZE.LARGE}
/>
)}
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index f25d6cb36a4d..be29bef4f19d 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -275,7 +275,7 @@ class CompanyStep extends React.Component {
onInputChange={value => this.clearErrorAndSetValue('incorporationType', value)}
value={this.state.incorporationType}
placeholder={{value: '', label: '-'}}
- hasError={this.getErrors().incorporationType}
+ errorText={this.getErrorText('incorporationType')}
/>
diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js
index a96e495cf8d4..7c0b58bf0157 100644
--- a/src/pages/ReimbursementAccount/EnableStep.js
+++ b/src/pages/ReimbursementAccount/EnableStep.js
@@ -24,11 +24,16 @@ import * as Illustrations from '../../components/Icon/Illustrations';
import * as BankAccounts from '../../libs/actions/BankAccounts';
import * as Link from '../../libs/actions/Link';
import * as User from '../../libs/actions/User';
+import {withNetwork} from '../../components/OnyxProvider';
+import networkPropTypes from '../../components/networkPropTypes';
const propTypes = {
/** Are we loading payment methods? */
isLoadingPaymentMethods: PropTypes.bool,
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
/** List of bank accounts */
bankAccountList: PropTypes.objectOf(bankAccountPropTypes),
@@ -42,6 +47,18 @@ const defaultProps = {
class EnableStep extends React.Component {
componentDidMount() {
+ this.fetchData();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.network.isOffline || this.props.network.isOffline) {
+ return;
+ }
+
+ this.fetchData();
+ }
+
+ fetchData() {
PaymentMethods.getPaymentMethods();
}
@@ -133,6 +150,7 @@ EnableStep.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
isLoadingPaymentMethods: {
key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index 60107f7926fa..f62c16b7fd19 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -17,6 +17,8 @@ import styles from '../../styles/styles';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import getPlaidOAuthReceivedRedirectURI from '../../libs/getPlaidOAuthReceivedRedirectURI';
import Text from '../../components/Text';
+import {withNetwork} from '../../components/OnyxProvider';
+import networkPropTypes from '../../components/networkPropTypes';
// Steps
import BankAccountStep from './BankAccountStep';
@@ -34,6 +36,9 @@ const propTypes = {
/** ACH data for the withdrawal account actively being set up */
reimbursementAccount: reimbursementAccountPropTypes,
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
/** Current session for the user */
session: PropTypes.shape({
/** User login */
@@ -65,14 +70,13 @@ const defaultProps = {
class ReimbursementAccountPage extends React.Component {
componentDidMount() {
- // We can specify a step to navigate to by using route params when the component mounts.
- const stepToOpen = this.getStepToOpenFromRouteParams();
-
- // If we are trying to navigate to `/bank-account/new` and we already have a bank account then don't allow returning to `/new`
- BankAccounts.fetchFreePlanVerifiedBankAccount(stepToOpen !== CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT ? stepToOpen : '');
+ this.fetchData();
}
componentDidUpdate(prevProps) {
+ if (prevProps.network.isOffline && !this.props.network.isOffline) {
+ this.fetchData();
+ }
const currentStep = lodashGet(
this.props,
'reimbursementAccount.achData.currentStep',
@@ -139,6 +143,15 @@ class ReimbursementAccountPage extends React.Component {
}
}
+ fetchData() {
+ // We can specify a step to navigate to by using route params when the component mounts.
+ // We want to use the same stepToOpen variable when the network state changes because we can be redirected to a different step when the account refreshes.
+ const stepToOpen = this.getStepToOpenFromRouteParams();
+
+ // If we are trying to navigate to `/bank-account/new` and we already have a bank account then don't allow returning to `/new`
+ BankAccounts.fetchFreePlanVerifiedBankAccount(stepToOpen !== CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT ? stepToOpen : '');
+ }
+
render() {
// The SetupWithdrawalAccount flow allows us to continue the flow from various points depending on where the
// user left off. This view will refer to the achData as the single source of truth to determine which route to
@@ -232,6 +245,7 @@ ReimbursementAccountPage.propTypes = propTypes;
ReimbursementAccountPage.defaultProps = defaultProps;
export default compose(
+ withNetwork(),
withOnyx({
reimbursementAccount: {
key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
diff --git a/src/pages/RequestCallPage.js b/src/pages/RequestCallPage.js
index b42ecfc57332..84617412d833 100644
--- a/src/pages/RequestCallPage.js
+++ b/src/pages/RequestCallPage.js
@@ -234,7 +234,7 @@ class RequestCallPage extends Component {
return;
}
- Inbox.getInboxCallWaitTime();
+ Inbox.openRequestCallPage();
}
validatePhoneInput() {
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index a8f6209d1190..88a88b7d10b2 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -14,8 +14,6 @@ import * as Report from '../../libs/actions/Report';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
import MultipleAvatars from '../../components/MultipleAvatars';
import SubscriptAvatar from '../../components/SubscriptAvatar';
-import Navigation from '../../libs/Navigation/Navigation';
-import ROUTES from '../../ROUTES';
import DisplayNames from '../../components/DisplayNames';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import participantPropTypes from '../../components/participantPropTypes';
@@ -28,9 +26,6 @@ import Text from '../../components/Text';
import Tooltip from '../../components/Tooltip';
const propTypes = {
- /** The ID of the report */
- reportID: PropTypes.number.isRequired,
-
/** Toggles the navigationMenu open and closed */
onNavigationMenuButtonClicked: PropTypes.func.isRequired,
@@ -113,15 +108,7 @@ const HeaderView = (props) => {
]}
>
{
- if (isChatRoom || isPolicyExpenseChat) {
- return Navigation.navigate(ROUTES.getReportDetailsRoute(props.reportID));
- }
- if (participants.length === 1) {
- return Navigation.navigate(ROUTES.getDetailsRoute(participants[0]));
- }
- Navigation.navigate(ROUTES.getReportParticipantsRoute(props.reportID));
- }}
+ onPress={() => ReportUtils.navigateToDetailsPage(props.report)}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
>
{shouldShowSubscript ? (
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index c1e7de21137f..e8ebd17d4110 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -3,7 +3,6 @@ import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import {Keyboard, View} from 'react-native';
import _ from 'underscore';
-import lodashFindLast from 'lodash/findLast';
import styles from '../../styles/styles';
import ScreenWrapper from '../../components/ScreenWrapper';
import HeaderView from './HeaderView';
@@ -165,13 +164,7 @@ class ReportScreen extends React.Component {
}
const reportID = getReportID(this.props.route);
-
const isArchivedRoom = ReportUtils.isArchivedRoom(this.props.report);
- let reportClosedAction;
- if (isArchivedRoom) {
- reportClosedAction = lodashFindLast(this.props.reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
- }
-
return (
) : (
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index 7196a4ce1b2e..c06d0fea408b 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -51,6 +51,9 @@ export default [
let {sourceURL} = attachmentDetails;
sourceURL = addEncryptedAuthTokenToURL(sourceURL);
fileDownload(sourceURL, originalFileName);
+ if (closePopover) {
+ hideContextMenu(true, ReportActionComposeFocusManager.focus);
+ }
},
getDescription: () => {},
},
diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js
index 25a14689bf9a..409f17f8e15a 100644
--- a/src/pages/home/report/ParticipantLocalTime.js
+++ b/src/pages/home/report/ParticipantLocalTime.js
@@ -1,9 +1,7 @@
import React, {PureComponent} from 'react';
-import {
- View,
-} from 'react-native';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
+import _ from 'underscore';
import styles from '../../../styles/styles';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import participantPropTypes from '../../../components/participantPropTypes';
@@ -11,14 +9,22 @@ import Text from '../../../components/Text';
import Timers from '../../../libs/Timers';
import CONST from '../../../CONST';
import DateUtils from '../../../libs/DateUtils';
+import stylePropTypes from '../../../styles/stylePropTypes';
const propTypes = {
/** Personal details of the participant */
participant: participantPropTypes.isRequired,
+ /** Additional style props */
+ style: stylePropTypes,
+
...withLocalizePropTypes,
};
+const defaultProps = {
+ style: [],
+};
+
class ParticipantLocalTime extends PureComponent {
constructor(props) {
super(props);
@@ -54,33 +60,29 @@ class ParticipantLocalTime extends PureComponent {
}
render() {
+ const additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style];
const reportRecipientDisplayName = this.props.participant.firstName
|| (Str.isSMSLogin(this.props.participant.login)
? this.props.toLocalPhone(this.props.participant.displayName)
: this.props.participant.displayName);
return (
-
-
- {this.props.translate(
- 'reportActionCompose.localTime',
- {
- user: reportRecipientDisplayName,
- time: this.state.localTime,
- },
- )}
-
-
+
+ {this.props.translate(
+ 'reportActionCompose.localTime',
+ {
+ user: reportRecipientDisplayName,
+ time: this.state.localTime,
+ },
+ )}
+
);
}
}
ParticipantLocalTime.propTypes = propTypes;
-
+ParticipantLocalTime.defaultProps = defaultProps;
export default withLocalize(ParticipantLocalTime);
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index a3f2c17ab409..918f80d82d15 100755
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -26,7 +26,6 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen
import withDrawerState from '../../../components/withDrawerState';
import CONST from '../../../CONST';
import canFocusInputOnScreenFocus from '../../../libs/canFocusInputOnScreenFocus';
-import variables from '../../../styles/variables';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import Permissions from '../../../libs/Permissions';
import Navigation from '../../../libs/Navigation/Navigation';
@@ -34,17 +33,17 @@ import ROUTES from '../../../ROUTES';
import reportActionPropTypes from './reportActionPropTypes';
import * as ReportUtils from '../../../libs/ReportUtils';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
-import Text from '../../../components/Text';
import participantPropTypes from '../../../components/participantPropTypes';
import ParticipantLocalTime from './ParticipantLocalTime';
-import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider';
+import {withPersonalDetails} from '../../../components/OnyxProvider';
import DateUtils from '../../../libs/DateUtils';
import * as User from '../../../libs/actions/User';
import Tooltip from '../../../components/Tooltip';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
import VirtualKeyboard from '../../../libs/VirtualKeyboard';
import canUseTouchScreen from '../../../libs/canUseTouchscreen';
-import networkPropTypes from '../../../components/networkPropTypes';
+import OfflineIndicator from '../../../components/OfflineIndicator';
+import ExceededCommentLength from '../../../components/ExceededCommentLength';
const propTypes = {
/** Beta features list */
@@ -87,9 +86,6 @@ const propTypes = {
/** Is composer screen focused */
isFocused: PropTypes.bool.isRequired,
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
// The NVP describing a user's block status
blockedFromConcierge: PropTypes.shape({
// The date that the user will be unblocked
@@ -447,9 +443,16 @@ class ReportActionCompose extends React.Component {
const hasExceededMaxCommentLength = this.comment.length > CONST.MAX_COMMENT_LENGTH;
return (
-
- {shouldShowReportRecipientLocalTime
- && }
+
+
+ {shouldShowReportRecipientLocalTime && (
+
+ )}
+
+
+
+
+
-
-
- {this.props.network.isOffline ? (
-
-
-
- {this.props.translate('reportActionCompose.youAppearToBeOffline')}
-
-
- ) : }
-
- {hasExceededMaxCommentLength && (
-
- {`${this.comment.length}/${CONST.MAX_COMMENT_LENGTH}`}
-
- )}
-
);
}
@@ -628,7 +606,6 @@ export default compose(
withNavigationFocus,
withLocalize,
withPersonalDetails(),
- withNetwork(),
withOnyx({
betas: {
key: ONYXKEYS.BETAS,
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index ca4baf304652..ac04ba8a5e5b 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -173,6 +173,7 @@ class ReportActionItem extends Component {
hovered
|| this.state.isContextMenuActive
|| this.props.draftMessage,
+ this.props.action.isPending || this.props.action.error,
)}
>
{!this.props.displayAsGroup
diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js
index 00d3c0c62652..f0a2a3a6e09d 100644
--- a/src/pages/home/report/ReportActionItemCreated.js
+++ b/src/pages/home/report/ReportActionItemCreated.js
@@ -1,5 +1,5 @@
import React from 'react';
-import {View} from 'react-native';
+import {Pressable, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import ONYXKEYS from '../../../ONYXKEYS';
@@ -17,7 +17,6 @@ const propTypes = {
/** Whether the user is not an admin of policyExpenseChat chat */
isOwnPolicyExpenseChat: PropTypes.bool,
-
}),
/** Personal details of all the users */
@@ -38,6 +37,7 @@ const defaultProps = {
const ReportActionItemCreated = (props) => {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies);
+
return (
{
]}
>
-
+ ReportUtils.navigateToDetailsPage(props.report)}>
+
+
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index 194a865636f7..8cfcfe9aac73 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -68,7 +68,7 @@ const defaultProps = {
const ReportActionItemFragment = (props) => {
switch (props.fragment.type) {
- case 'COMMENT':
+ case 'COMMENT': {
// If this is an attachment placeholder, return the placeholder component
if (props.isAttachment && props.loading) {
return (
@@ -97,6 +97,16 @@ const ReportActionItemFragment = (props) => {
);
}
+ // If the only difference between fragment.text and fragment.html is
tags
+ // we replace them with line breaks and render it as text, not as html.
+ // This is done to render emojis with line breaks between them as text.
+ const differByLineBreaksOnly = props.fragment.html.replaceAll('
', ' ') === props.fragment.text;
+ if (differByLineBreaksOnly) {
+ const textWithLineBreaks = props.fragment.html.replaceAll('
', '\n');
+ // eslint-disable-next-line no-param-reassign
+ props.fragment = {...props.fragment, text: textWithLineBreaks, html: textWithLineBreaks};
+ }
+
// Only render HTML if we have html in the fragment
return props.fragment.html !== props.fragment.text
? (
@@ -106,7 +116,7 @@ const ReportActionItemFragment = (props) => {
) : (
{Str.htmlDecode(props.fragment.text)}
{props.fragment.isEdited && (
@@ -119,6 +129,7 @@ const ReportActionItemFragment = (props) => {
)}
);
+ }
case 'TEXT':
return (
diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js
index ee43a3a004ff..c7c34ec58db7 100755
--- a/src/pages/home/report/ReportTypingIndicator.js
+++ b/src/pages/home/report/ReportTypingIndicator.js
@@ -49,24 +49,21 @@ class ReportTypingIndicator extends React.Component {
// Decide on the Text element that will hold the display based on the number of users that are typing.
switch (numUsersTyping) {
case 0:
- return <>>;
+ return null;
+
case 1:
return (
);
+
default:
return (
{this.props.translate('reportTypingIndicator.multipleUsers')}
diff --git a/src/pages/home/report/reportActionPropTypes.js b/src/pages/home/report/reportActionPropTypes.js
index 54df5df52392..c4400d36c3ab 100644
--- a/src/pages/home/report/reportActionPropTypes.js
+++ b/src/pages/home/report/reportActionPropTypes.js
@@ -3,24 +3,31 @@ import PropTypes from 'prop-types';
import reportActionFragmentPropTypes from './reportActionFragmentPropTypes';
export default {
- // Name of the action e.g. ADDCOMMENT
+ /** Name of the action e.g. ADDCOMMENT */
actionName: PropTypes.string,
- // Person who created the action
+ /** Person who created the action */
person: PropTypes.arrayOf(reportActionFragmentPropTypes),
- // ID of the report action
+ /** ID of the report action */
sequenceNumber: PropTypes.number,
- // Unix timestamp
+ /** Unix timestamp */
timestamp: PropTypes.number,
- // report action message
+ /** report action message */
message: PropTypes.arrayOf(reportActionFragmentPropTypes),
- // Original message associated with this action
+ /** Original message associated with this action */
originalMessage: PropTypes.shape({
// The ID of the iou transaction
IOUTransactionID: PropTypes.string,
}),
+
+ /** If the reportAction is pending, that means we have not yet sent the Report_AddComment command to the server.
+ This should most often occur when the client is offline. */
+ isPending: PropTypes.bool,
+
+ /** Error message that's come back from the server. */
+ error: PropTypes.string,
};
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index c0d8cf70449f..f590679492bb 100755
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -26,6 +26,8 @@ import CONST from '../../CONST';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import * as PersonalDetails from '../../libs/actions/PersonalDetails';
import ROUTES from '../../ROUTES';
+import networkPropTypes from '../../components/networkPropTypes';
+import {withNetwork} from '../../components/OnyxProvider';
/**
* IOU modal for requesting money and splitting bills.
@@ -50,6 +52,9 @@ const propTypes = {
localCurrencyCode: PropTypes.string,
}),
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
// Holds data related to IOU view state, rather than the underlying IOU data.
iou: PropTypes.shape({
/** Whether or not transaction creation has started */
@@ -138,11 +143,15 @@ class IOUModal extends Component {
}
componentDidMount() {
- PersonalDetails.fetchLocalCurrency();
+ this.fetchData();
IOU.setIOUSelectedCurrency(this.props.myPersonalDetails.localCurrencyCode);
}
componentDidUpdate(prevProps) {
+ if (prevProps.network.isOffline && !this.props.network.isOffline) {
+ this.fetchData();
+ }
+
// Successfully close the modal if transaction creation has ended and there is no error
if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) {
Navigation.dismissModal();
@@ -215,6 +224,10 @@ class IOUModal extends Component {
return this.props.translate(this.steps[currentStepIndex]) || '';
}
+ fetchData() {
+ PersonalDetails.fetchLocalCurrency();
+ }
+
/**
* Update comment whenever user enters any new text
*
@@ -434,7 +447,7 @@ class IOUModal extends Component {
comment={this.state.comment}
onUpdateComment={this.updateComment}
iouType={this.props.iouType}
- isGroupSplit={this.steps.length === 2}
+ isIOUAttachedToExistingChatReport={!_.isEmpty(reportID)}
/>
)}
@@ -453,13 +466,11 @@ IOUModal.defaultProps = defaultProps;
export default compose(
withLocalize,
+ withNetwork(),
withOnyx({
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`,
},
- iousReport: {
- key: ONYXKEYS.COLLECTION.REPORT_IOUS,
- },
iou: {
key: ONYXKEYS.IOU,
},
diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js
index 6d59ed01c7ce..5352ccfdcf83 100644
--- a/src/pages/iou/steps/IOUConfirmPage.js
+++ b/src/pages/iou/steps/IOUConfirmPage.js
@@ -42,8 +42,8 @@ const propTypes = {
/** IOU type */
iouType: PropTypes.string,
- /** Whether this is an IOU split and belongs to a group report */
- isGroupSplit: PropTypes.bool.isRequired,
+ /** Is this IOU associated with existing report */
+ isIOUAttachedToExistingChatReport: PropTypes.bool.isRequired,
};
const defaultProps = {
@@ -62,7 +62,7 @@ const IOUConfirmPage = props => (
onConfirm={props.onConfirm}
onSendMoney={props.onSendMoney}
iouType={props.iouType}
- isGroupSplit={props.isGroupSplit}
+ isIOUAttachedToExistingChatReport={props.isIOUAttachedToExistingChatReport}
/>
);
diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js
index d395d375f304..a724fe0ddf74 100644
--- a/src/pages/settings/AppDownloadLinks.js
+++ b/src/pages/settings/AppDownloadLinks.js
@@ -7,15 +7,25 @@ import CONST from '../../CONST';
import * as Expensicons from '../../components/Icon/Expensicons';
import ScreenWrapper from '../../components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import compose from '../../libs/compose';
import MenuItem from '../../components/MenuItem';
import styles from '../../styles/styles';
import * as Link from '../../libs/actions/Link';
+import PressableWithSecondaryInteraction from '../../components/PressableWithSecondaryInteraction';
+import ControlSelection from '../../libs/ControlSelection';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
+import canUseTouchScreen from '../../libs/canUseTouchscreen';
+import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu';
+import * as ContextMenuActions from '../home/report/ContextMenu/ContextMenuActions';
const propTypes = {
...withLocalizePropTypes,
+ ...windowDimensionsPropTypes,
};
const AppDownloadLinksPage = (props) => {
+ let popoverAnchor;
+
const menuItems = [
{
translationKey: 'initialSettingsPage.appDownloadLinks.android.label',
@@ -24,6 +34,7 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.ANDROID);
},
+ link: CONST.APP_DOWNLOAD_LINKS.ANDROID,
},
{
translationKey: 'initialSettingsPage.appDownloadLinks.ios.label',
@@ -32,6 +43,7 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.IOS);
},
+ link: CONST.APP_DOWNLOAD_LINKS.IOS,
},
{
translationKey: 'initialSettingsPage.appDownloadLinks.desktop.label',
@@ -40,9 +52,25 @@ const AppDownloadLinksPage = (props) => {
action: () => {
Link.openExternalLink(CONST.APP_DOWNLOAD_LINKS.DESKTOP);
},
+ link: CONST.APP_DOWNLOAD_LINKS.DESKTOP,
},
];
+ /**
+ * Show the ReportActionContextMenu modal popover.
+ *
+ * @param {Object} [event] - A press event.
+ * @param {string} [selection] - A copy text.
+ */
+ const showPopover = (event, selection) => {
+ ReportActionContextMenu.showContextMenu(
+ ContextMenuActions.CONTEXT_MENU_TYPES.LINK,
+ event,
+ selection,
+ popoverAnchor,
+ );
+ };
+
return (
{
/>
{_.map(menuItems, item => (
-
@@ -70,4 +108,7 @@ const AppDownloadLinksPage = (props) => {
AppDownloadLinksPage.propTypes = propTypes;
AppDownloadLinksPage.displayName = 'AppDownloadLinksPage';
-export default withLocalize(AppDownloadLinksPage);
+export default compose(
+ withWindowDimensions,
+ withLocalize,
+)(AppDownloadLinksPage);
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 2bb4544fabb2..e9374be7f609 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -141,6 +141,7 @@ const InitialSettingsPage = (props) => {
action: () => Navigation.navigate(ROUTES.getWorkspaceInitialRoute(policy.id)),
iconStyles: [styles.popoverMenuIconEmphasized],
iconFill: themeColors.iconReversed,
+ fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
}))
.value();
menuItems.push(...defaultMenuItems);
@@ -195,6 +196,7 @@ const InitialSettingsPage = (props) => {
iconFill={item.iconFill}
shouldShowRightIcon
badgeText={(isPaymentItem && Permissions.canUseWallet(props.betas)) ? walletBalance : undefined}
+ fallbackIcon={item.fallbackIcon}
/>
);
})}
diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
index f04d34ba6caf..e0b6b61d4668 100644
--- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
+++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js
@@ -30,6 +30,7 @@ import * as Expensicons from '../../../../components/Icon/Expensicons';
import ConfirmModal from '../../../../components/ConfirmModal';
import KYCWall from '../../../../components/KYCWall';
import {propTypes, defaultProps} from './paymentsPagePropTypes';
+import {withNetwork} from '../../../../components/OnyxProvider';
class BasePaymentsPage extends React.Component {
constructor(props) {
@@ -60,12 +61,20 @@ class BasePaymentsPage extends React.Component {
}
componentDidMount() {
- PaymentMethods.getPaymentMethods();
+ this.fetchData();
if (this.props.shouldListenForResize) {
this.dimensionsSubscription = Dimensions.addEventListener('change', this.setMenuPosition);
}
}
+ componentDidUpdate(prevProps) {
+ if (!prevProps.network.isOffline || this.props.network.isOffline) {
+ return;
+ }
+
+ this.fetchData();
+ }
+
componentWillUnmount() {
if (!this.props.shouldListenForResize || !this.dimensionsSubscription) {
return;
@@ -186,6 +195,10 @@ class BasePaymentsPage extends React.Component {
throw new Error('Invalid payment method type selected');
}
+ fetchData() {
+ PaymentMethods.getPaymentMethods();
+ }
+
/**
* Hide the add payment modal
*/
@@ -432,6 +445,7 @@ BasePaymentsPage.defaultProps = defaultProps;
export default compose(
withWindowDimensions,
withLocalize,
+ withNetwork(),
withOnyx({
betas: {
key: ONYXKEYS.BETAS,
diff --git a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
index 5e9da7e3e19a..92158d06204c 100644
--- a/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
+++ b/src/pages/settings/Payments/PaymentsPage/paymentsPagePropTypes.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import walletTransferPropTypes from '../walletTransferPropTypes';
import {withLocalizePropTypes} from '../../../../components/withLocalize';
import {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
+import networkPropTypes from '../../../../components/networkPropTypes';
const propTypes = {
/** Wallet balance transfer props */
@@ -22,6 +23,9 @@ const propTypes = {
currentBalance: PropTypes.number,
}),
+ /** Information about the network */
+ network: networkPropTypes.isRequired,
+
...withLocalizePropTypes,
...windowDimensionsPropTypes,
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index 1aaf232db0bf..fbeb7b69b16c 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -231,7 +231,7 @@ class ProfilePage extends Component {
>
diff --git a/src/pages/signin/SignInPageLayout/SignInPageContent.js b/src/pages/signin/SignInPageLayout/SignInPageContent.js
index f787d197fe7d..4006d65a0093 100755
--- a/src/pages/signin/SignInPageLayout/SignInPageContent.js
+++ b/src/pages/signin/SignInPageLayout/SignInPageContent.js
@@ -55,7 +55,8 @@ const SignInPageContent = props => (
{
);
+ const hasRedirect = !_.isEmpty(backgroundStyle.redirectUri);
+
+ const graphicLayout = (
+ {
+ Link.openExternalLink(backgroundStyle.redirectUri);
+ }}
+ disabled={!hasRedirect}
+ >
+
+
+ );
+
if (props.isSmallScreenWidth) {
return content;
}
- const hasRedirect = !_.isEmpty(backgroundStyle.redirectUri);
+ if (props.isMediumScreenWidth && props.windowHeight >= variables.minHeightToShowGraphics) {
+ return (
+
+ {graphicLayout}
+
+ {content}
+
+
+ );
+ }
return (
{content}
- {
- Link.openExternalLink(backgroundStyle.redirectUri);
- }}
- disabled={!hasRedirect}
- >
-
-
+ {graphicLayout}
);
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 3e43faf56c14..9cb442edb586 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -156,6 +156,8 @@ class WorkspaceInitialPage extends React.Component {
containerStyles={styles.avatarLarge}
imageStyles={[styles.avatarLarge, styles.alignSelfCenter]}
source={this.props.policy.avatarURL}
+ fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
+ size={CONST.AVATAR_SIZE.LARGE}
/>
)
: (
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index a4ae23252d78..764da2999e21 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -141,14 +141,24 @@ class WorkspaceMembersPage extends React.Component {
* @returns {Boolean} Return true if the tooltip was displayed so we can use the state of it in other functions.
*/
willTooltipShowForLogin(login, wasHovered = false) {
+ const isSmallOrMediumScreen = this.props.isSmallScreenWidth || this.props.isMediumScreenWidth;
+
// Small screens only show the tooltip on press, so ignore hovered event on those cases.
- if (wasHovered && (this.props.isSmallScreenWidth || this.props.isMediumScreenWidth)) {
+ if (wasHovered && isSmallOrMediumScreen) {
return false;
}
const canBeRemoved = this.props.policy.owner !== login && this.props.session.email !== login;
if (!canBeRemoved) {
- this.setState({showTooltipForLogin: login});
+ this.setState({
+ showTooltipForLogin: login,
+ }, () => {
+ // Immediately reset the login to deactivate the tooltip trigger, otherwise, the tooltip will not open again on further interactions on small screens.
+ if (!isSmallOrMediumScreen) {
+ return;
+ }
+ this.setState({showTooltipForLogin: ''});
+ });
}
return !canBeRemoved;
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
index 3b2ce0417fe3..6aec1b883eb3 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.js
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -171,7 +171,6 @@ class WorkspaceNewRoomPage extends React.Component {
placeholder={{value: '', label: this.props.translate('newRoomPage.selectAWorkspace')}}
items={this.state.workspaceOptions}
errorText={this.state.errors.policyID}
- hasError={Boolean(this.state.errors.policyID)}
onInputChange={policyID => this.clearErrorAndSetValue('policyID', policyID)}
/>
diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.js
index f5fa19fc6700..5cdafcac70ab 100644
--- a/src/pages/workspace/WorkspacePageWithSections.js
+++ b/src/pages/workspace/WorkspacePageWithSections.js
@@ -17,8 +17,13 @@ import reimbursementAccountPropTypes from '../ReimbursementAccount/reimbursement
import userPropTypes from '../settings/userPropTypes';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import withFullPolicy from './withFullPolicy';
+import {withNetwork} from '../../components/OnyxProvider';
+import networkPropTypes from '../../components/networkPropTypes';
const propTypes = {
+ /** Information about the network from Onyx */
+ network: networkPropTypes.isRequired,
+
/** The text to display in the header */
headerText: PropTypes.string.isRequired,
@@ -65,12 +70,25 @@ const defaultProps = {
class WorkspacePageWithSections extends React.Component {
componentDidMount() {
+ this.fetchData();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.network.isOffline || this.props.network.isOffline) {
+ return;
+ }
+
+ this.fetchData();
+ }
+
+ fetchData() {
const achState = lodashGet(this.props.reimbursementAccount, 'achData.state', '');
BankAccounts.fetchFreePlanVerifiedBankAccount('', achState);
}
render() {
const achState = lodashGet(this.props.reimbursementAccount, 'achData.state', '');
+ const isLoadingVBA = this.props.reimbursementAccount.loading;
const hasVBA = achState === BankAccount.STATE.OPEN;
const isUsingECard = lodashGet(this.props.user, 'isUsingExpensifyCard', false);
const policyID = lodashGet(this.props.route, 'params.policyID');
@@ -93,7 +111,7 @@ class WorkspacePageWithSections extends React.Component {
>
- {this.props.children(hasVBA, policyID, isUsingECard)}
+ {this.props.children(isLoadingVBA, hasVBA, policyID, isUsingECard)}
@@ -118,4 +136,5 @@ export default compose(
},
}),
withFullPolicy,
+ withNetwork(),
)(WorkspacePageWithSections);
diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js
index 9f84eaf14b94..4f97da3b3039 100644
--- a/src/pages/workspace/WorkspaceSettingsPage.js
+++ b/src/pages/workspace/WorkspaceSettingsPage.js
@@ -139,7 +139,7 @@ class WorkspaceSettingsPage extends React.Component {
)}
>
- {hasVBA => (
+ {(isLoadingVBA, hasVBA) => (
)}
+ fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
style={[styles.mb3]}
anchorPosition={{top: 172, right: 18}}
isUsingDefaultAvatar={!this.state.previewAvatarURL}
@@ -175,7 +176,7 @@ class WorkspaceSettingsPage extends React.Component {
onInputChange={currency => this.setState({currency})}
items={this.getCurrencyItems()}
value={this.state.currency}
- isDisabled={hasVBA}
+ isDisabled={isLoadingVBA || hasVBA}
/>
diff --git a/src/pages/workspace/bills/WorkspaceBillsPage.js b/src/pages/workspace/bills/WorkspaceBillsPage.js
index 96ac0cc5ca47..bb85b9c64cbc 100644
--- a/src/pages/workspace/bills/WorkspaceBillsPage.js
+++ b/src/pages/workspace/bills/WorkspaceBillsPage.js
@@ -25,11 +25,12 @@ const WorkspaceBillsPage = props => (
route={props.route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BILLS}
>
- {(hasVBA, policyID) => (
+ {(isLoadingVBA, hasVBA, policyID) => (
<>
- {!hasVBA ? (
+ {!isLoadingVBA && !hasVBA && (
- ) : (
+ )}
+ {!isLoadingVBA && hasVBA && (
)}
>
diff --git a/src/pages/workspace/card/WorkspaceCardPage.js b/src/pages/workspace/card/WorkspaceCardPage.js
index 5dba60e04a00..9f7bf30a5bf8 100644
--- a/src/pages/workspace/card/WorkspaceCardPage.js
+++ b/src/pages/workspace/card/WorkspaceCardPage.js
@@ -26,17 +26,17 @@ const WorkspaceCardPage = props => (
route={props.route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_CARD}
>
- {(hasVBA, policyID, isUsingECard) => (
+ {(isLoadingVBA, hasVBA, policyID, isUsingECard) => (
<>
- {!hasVBA && (
+ {!isLoadingVBA && !hasVBA && (
)}
- {hasVBA && !isUsingECard && (
+ {!isLoadingVBA && hasVBA && !isUsingECard && (
)}
- {hasVBA && isUsingECard && (
+ {!isLoadingVBA && hasVBA && isUsingECard && (
)}
>
diff --git a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js
index edece8912aa7..73b2ef6dad28 100644
--- a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js
+++ b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js
@@ -7,7 +7,6 @@ import * as Expensicons from '../../../components/Icon/Expensicons';
import * as Illustrations from '../../../components/Icon/Illustrations';
import UnorderedList from '../../../components/UnorderedList';
import Section from '../../../components/Section';
-import Navigation from '../../../libs/Navigation/Navigation';
import * as Link from '../../../libs/actions/Link';
import * as User from '../../../libs/actions/User';
import ONYXKEYS from '../../../ONYXKEYS';
@@ -27,7 +26,6 @@ const WorkspaceCardVBANoECardView = props => (
{
title: props.translate('workspace.card.addWorkEmail'),
onPress: () => {
- Navigation.dismissModal();
Link.openOldDotLink(CONST.ADD_SECONDARY_LOGIN_URL);
User.subscribeToExpensifyCardUpdates();
},
diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.js b/src/pages/workspace/invoices/WorkspaceInvoicesPage.js
index f2f2df6da823..4b7333c3e866 100644
--- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.js
+++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.js
@@ -25,11 +25,12 @@ const WorkspaceInvoicesPage = props => (
route={props.route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_INVOICES}
>
- {(hasVBA, policyID) => (
+ {(isLoadingVBA, hasVBA, policyID) => (
<>
- {!hasVBA ? (
+ {!isLoadingVBA && !hasVBA && (
- ) : (
+ )}
+ {!isLoadingVBA && hasVBA && (
)}
>
diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js b/src/pages/workspace/reimburse/WorkspaceReimbursePage.js
index 2f0e8a9e3816..75e1a5f29fc0 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.js
@@ -24,8 +24,8 @@ const WorkspaceReimbursePage = props => (
route={props.route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE}
>
- {(hasVBA, policyID) => (
-
+ {(isLoadingVBA, hasVBA, policyID) => (
+
)}
);
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
index 927401c46522..fee13dfa76e3 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js
@@ -27,7 +27,10 @@ const propTypes = {
/** The policy ID currently being configured */
policyID: PropTypes.string.isRequired,
- /** Does the user has a VBA into its account? */
+ /** Whether VBA data is loading */
+ isLoadingVBA: PropTypes.bool.isRequired,
+
+ /** Does the user have a VBA in their account? */
hasVBA: PropTypes.bool.isRequired,
/** Policy values needed in the component */
@@ -191,7 +194,7 @@ class WorkspaceReimburseView extends React.Component {
- {!this.props.hasVBA && (
+ {!this.props.isLoadingVBA && !this.props.hasVBA && (
)}
- {Boolean(this.props.hasVBA) && (
+ {!this.props.isLoadingVBA && this.props.hasVBA && (
(
route={props.route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_TRAVEL}
>
- {(hasVBA, policyID) => (
+ {(isLoadingVBA, hasVBA, policyID) => (
<>
- {!hasVBA ? (
+ {!isLoadingVBA && !hasVBA && (
- ) : (
+ )}
+ {!isLoadingVBA && hasVBA && (
)}
>
diff --git a/src/stories/Banner.stories.js b/src/stories/Banner.stories.js
index 247489d49711..99de89c94490 100644
--- a/src/stories/Banner.stories.js
+++ b/src/stories/Banner.stories.js
@@ -18,13 +18,12 @@ const Template = args => ;
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const InfoBanner = Template.bind({});
InfoBanner.args = {
- text: 'This is an informational banner',
+ html: 'This is an informational banner',
};
const HTMLBanner = Template.bind({});
HTMLBanner.args = {
- text: 'This is a informational banner containing HTML',
- shouldRenderHTML: true,
+ html: 'This is a informational banner containing HTML',
};
export default story;
diff --git a/src/stories/Form.stories.js b/src/stories/Form.stories.js
index 6473750dac6d..926aa40fddc2 100644
--- a/src/stories/Form.stories.js
+++ b/src/stories/Form.stories.js
@@ -104,7 +104,7 @@ const Template = (args) => {
/>
@@ -179,8 +179,8 @@ const defaultArgs = {
if (!values.pickAnotherFruit) {
errors.pickAnotherFruit = 'Please select a fruit';
}
- if (!values.pickState) {
- errors.pickState = 'Please select a state';
+ if (!values.state) {
+ errors.state = 'Please select a state';
}
if (!values.checkbox) {
errors.checkbox = 'You must accept the Terms of Service to continue';
@@ -204,7 +204,7 @@ const defaultArgs = {
dob: '1990-01-01',
pickFruit: 'orange',
pickAnotherFruit: 'apple',
- pickState: 'AL',
+ state: 'AL',
checkbox: false,
},
};
@@ -221,7 +221,7 @@ InputError.args = {
pickFruit: '',
dob: '',
pickAnotherFruit: '',
- pickState: '',
+ state: '',
checkbox: false,
},
};
diff --git a/src/stories/FormAlertWithSubmitButton.stories.js b/src/stories/FormAlertWithSubmitButton.stories.js
new file mode 100644
index 000000000000..9747c447cf0d
--- /dev/null
+++ b/src/stories/FormAlertWithSubmitButton.stories.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import FormAlertWithSubmitButton from '../components/FormAlertWithSubmitButton';
+
+/**
+ * We use the Component Story Format for writing stories. Follow the docs here:
+ *
+ * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
+ */
+const story = {
+ title: 'Components/FormAlertWithSubmitButton',
+ component: FormAlertWithSubmitButton,
+};
+
+// eslint-disable-next-line react/jsx-props-no-spreading
+const Template = args => ;
+
+// Arguments can be passed to the component by binding
+// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
+const Default = Template.bind({});
+Default.args = {
+ isAlertVisible: true,
+ onSubmit: () => {},
+ buttonText: 'Submit',
+ network: {
+ isOffline: true,
+ },
+};
+
+export default story;
+export {
+ Default,
+};
diff --git a/src/stories/InlineSystemMessage.stories.js b/src/stories/InlineSystemMessage.stories.js
new file mode 100644
index 000000000000..5d966d49ee22
--- /dev/null
+++ b/src/stories/InlineSystemMessage.stories.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import InlineSystemMessage from '../components/InlineSystemMessage';
+
+/**
+ * We use the Component Story Format for writing stories. Follow the docs here:
+ *
+ * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
+ */
+const story = {
+ title: 'Components/InlineSystemMessage',
+ component: InlineSystemMessage,
+};
+
+// eslint-disable-next-line react/jsx-props-no-spreading
+const Template = args => ;
+
+// Arguments can be passed to the component by binding
+// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
+const Default = Template.bind({});
+Default.args = {
+ message: 'This is an error message',
+};
+
+export default story;
+export {
+ Default,
+};
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 7b1ced1f7ec6..ab37cbe68b72 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -36,6 +36,7 @@ function getAvatarStyle(size) {
height: avatarSize,
width: avatarSize,
borderRadius: avatarSize,
+ backgroundColor: themeColors.offline,
};
}
@@ -386,9 +387,10 @@ function getLoginPagePromoStyle() {
* Generate the styles for the ReportActionItem wrapper view.
*
* @param {Boolean} [isHovered]
+ * @param {Boolean} [isPending]
* @returns {Object}
*/
-function getReportActionItemStyle(isHovered = false) {
+function getReportActionItemStyle(isHovered = false, isPending = false) {
return {
display: 'flex',
justifyContent: 'space-between',
@@ -397,6 +399,7 @@ function getReportActionItemStyle(isHovered = false) {
// Warning: Setting this to a non-transparent color will cause unread indicator to break on Android
: colors.transparent,
+ opacity: isPending ? 0.5 : 1,
cursor: 'default',
};
}
diff --git a/src/styles/getModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js
similarity index 98%
rename from src/styles/getModalStyles.js
rename to src/styles/getModalStyles/getBaseModalStyles.js
index 32ca24fa9c84..f61d7968644f 100644
--- a/src/styles/getModalStyles.js
+++ b/src/styles/getModalStyles/getBaseModalStyles.js
@@ -1,7 +1,7 @@
-import CONST from '../CONST';
-import colors from './colors';
-import variables from './variables';
-import themeColors from './themes/default';
+import CONST from '../../CONST';
+import colors from '../colors';
+import variables from '../variables';
+import themeColors from '../themes/default';
export default (type, windowDimensions, popoverAnchorPosition = {}, containerStyle = {}) => {
const {isSmallScreenWidth, windowWidth} = windowDimensions;
diff --git a/src/styles/getModalStyles/index.android.js b/src/styles/getModalStyles/index.android.js
new file mode 100644
index 000000000000..e7e3531171ae
--- /dev/null
+++ b/src/styles/getModalStyles/index.android.js
@@ -0,0 +1,7 @@
+import getBaseModalStyles from './getBaseModalStyles';
+
+// Only apply top padding on iOS since it's the only platform using SafeAreaView
+export default (type, windowDimensions, popoverAnchorPosition = {}, containerStyle = {}) => ({
+ ...getBaseModalStyles(type, windowDimensions, popoverAnchorPosition, containerStyle),
+ shouldAddTopSafeAreaPadding: false,
+});
diff --git a/src/styles/getModalStyles/index.js b/src/styles/getModalStyles/index.js
new file mode 100644
index 000000000000..4e35fa589119
--- /dev/null
+++ b/src/styles/getModalStyles/index.js
@@ -0,0 +1,3 @@
+import getBaseModalStyles from './getBaseModalStyles';
+
+export default getBaseModalStyles;
diff --git a/src/styles/styles.js b/src/styles/styles.js
index f222d4f56246..d46612af0886 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -135,6 +135,7 @@ const webViewStyles = {
fontSize: variables.fontSizeNormal,
fontFamily: fontFamily.GTA,
flex: 1,
+ whiteSpace: 'pre',
},
};
@@ -319,6 +320,10 @@ const styles = {
backgroundColor: 'transparent',
},
+ opacity0: {
+ opacity: 0,
+ },
+
opacity1: {
opacity: 1,
},
@@ -643,7 +648,7 @@ const styles = {
},
chatItemComposeSecondaryRow: {
- height: 15,
+ minHeight: 15,
marginBottom: 5,
marginTop: 5,
},
@@ -951,10 +956,6 @@ const styles = {
height: '100%',
},
- resendLinkButton: {
- minWidth: 124,
- },
-
sidebarFooter: {
alignItems: 'center',
display: 'flex',
@@ -1042,9 +1043,9 @@ const styles = {
textDecorationLine: 'none',
},
- singleEmojiText: {
- fontSize: variables.fontSizeSingleEmoji,
- lineHeight: variables.fontSizeSingleEmojiHeight,
+ onlyEmojisText: {
+ fontSize: variables.fontSizeOnlyEmojis,
+ lineHeight: variables.fontSizeOnlyEmojisHeight,
},
createMenuPositionSidebar: {
@@ -2189,6 +2190,11 @@ const styles = {
cursor: 'not-allowed',
},
+ noSelect: {
+ boxShadow: 'none',
+ outline: 'none',
+ },
+
cursorPointer: {
cursor: 'pointer',
},
@@ -2519,6 +2525,13 @@ const styles = {
marginBottom: 40,
padding: 16,
},
+
+ inlineSystemMessage: {
+ color: themeColors.textSupporting,
+ fontSize: variables.fontSizeLabel,
+ fontFamily: fontFamily.GTA,
+ marginLeft: 4,
+ },
};
export default styles;
diff --git a/src/styles/utilities/positioning.js b/src/styles/utilities/positioning.js
index 455566b4655d..f25c757fbfc3 100644
--- a/src/styles/utilities/positioning.js
+++ b/src/styles/utilities/positioning.js
@@ -15,6 +15,12 @@ export default {
tn8: {
top: -32,
},
+ l0: {
+ left: 0,
+ },
+ r0: {
+ right: 0,
+ },
r4: {
right: 16,
},
diff --git a/src/styles/variables.js b/src/styles/variables.js
index 4302947ac879..7259a678443f 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -14,8 +14,8 @@ export default {
avatarSizeSmall: 28,
avatarSizeSubscript: 20,
avatarSizeSmallSubscript: 14,
- fontSizeSingleEmoji: 30,
- fontSizeSingleEmojiHeight: 35,
+ fontSizeOnlyEmojis: 30,
+ fontSizeOnlyEmojisHeight: 35,
fontSizeSmall: 11,
fontSizeExtraSmall: 9,
fontSizeLabel: 13,
@@ -46,4 +46,5 @@ export default {
tooltipzIndex: 10050,
gutterWidth: 16,
popoverMenuShadow: '0px 4px 12px 0px rgba(0, 0, 0, 0.06)',
+ minHeightToShowGraphics: 854, // Login form layout breaks below this height due to insufficient space to show the form and graphics
};
diff --git a/tests/actions/SessionTest.js b/tests/actions/SessionTest.js
index cf03d8611405..41f1e3379a61 100644
--- a/tests/actions/SessionTest.js
+++ b/tests/actions/SessionTest.js
@@ -1,6 +1,6 @@
import Onyx from 'react-native-onyx';
import {beforeEach, jest, test} from '@jest/globals';
-import * as API from '../../src/libs/API';
+import * as DeprecatedAPI from '../../src/libs/deprecatedAPI';
import HttpUtils from '../../src/libs/HttpUtils';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import ONYXKEYS from '../../src/ONYXKEYS';
@@ -56,7 +56,7 @@ test('Authenticate is called with saved credentials when a session expires', ()
// data.
HttpUtils.xhr
- // This will make the call to API.Get() below return with an expired session code
+ // This will make the call to DeprecatedAPI.Get() below return with an expired session code
.mockImplementationOnce(() => Promise.resolve({
jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED,
}))
@@ -70,7 +70,7 @@ test('Authenticate is called with saved credentials when a session expires', ()
}));
// When we attempt to fetch the chatList via the API
- API.Get({returnValueList: 'chatList'});
+ DeprecatedAPI.Get({returnValueList: 'chatList'});
return waitForPromisesToResolve();
})
.then(() => {
diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js
index d8eade984048..1e4208f5e770 100644
--- a/tests/unit/NetworkTest.js
+++ b/tests/unit/NetworkTest.js
@@ -4,7 +4,7 @@ import Onyx from 'react-native-onyx';
import {
beforeEach, jest, test, expect, afterEach,
} from '@jest/globals';
-import * as API from '../../src/libs/API';
+import * as DeprecatedAPI from '../../src/libs/deprecatedAPI';
import * as TestHelper from '../utils/TestHelper';
import HttpUtils from '../../src/libs/HttpUtils';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
@@ -102,7 +102,7 @@ test('failing to reauthenticate while offline should not log out user', () => {
}));
// This should first trigger re-authentication and then a Failed to fetch
- API.Get({returnValueList: 'chatList'});
+ DeprecatedAPI.Get({returnValueList: 'chatList'});
return waitForPromisesToResolve()
.then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}))
.then(() => {
@@ -174,7 +174,7 @@ test('consecutive API calls eventually succeed when authToken is expired', () =>
HttpUtils.xhr = jest.fn();
HttpUtils.xhr
- // This will make the first call to API.Get() return with an expired session code
+ // This will make the first call to DeprecatedAPI.Get() return with an expired session code
.mockImplementationOnce(() => Promise.resolve({
jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED,
}))
@@ -213,15 +213,15 @@ test('consecutive API calls eventually succeed when authToken is expired', () =>
}));
// And then make 3 API requests in quick succession with an expired authToken and handle the response
- API.Get({returnValueList: 'chatList'})
+ DeprecatedAPI.Get({returnValueList: 'chatList'})
.then((response) => {
Onyx.merge('test_chatList', response.chatList);
});
- API.Get({returnValueList: 'personalDetailsList'})
+ DeprecatedAPI.Get({returnValueList: 'personalDetailsList'})
.then((response) => {
Onyx.merge('test_personalDetailsList', response.personalDetailsList);
});
- API.Get({returnValueList: 'account'})
+ DeprecatedAPI.Get({returnValueList: 'account'})
.then((response) => {
Onyx.merge('test_account', response.account);
});
@@ -274,16 +274,15 @@ test('Request will not run until credentials are read from Onyx', () => {
});
});
-test('retry network request if connection is lost while request is running', () => {
+test('Non-retryable request will not be retried if connection is lost in flight', () => {
// Given a xhr mock that will fail as if network connection dropped
const xhr = jest.spyOn(HttpUtils, 'xhr')
.mockImplementationOnce(() => {
Onyx.merge(ONYXKEYS.NETWORK, {isOffline: true});
return Promise.reject(new Error(CONST.ERROR.FAILED_TO_FETCH));
- })
- .mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS, fromRetriedResult: true});
+ });
- // Given a regular "retryable" request (that is bound to fail)
+ // Given a non-retryable request (that is bound to fail)
const promise = Network.post('Get');
return waitForPromisesToResolve()
@@ -298,15 +297,15 @@ test('retry network request if connection is lost while request is running', ()
return waitForPromisesToResolve();
})
.then(() => {
- // Then the request should be attempted again
- expect(xhr).toHaveBeenCalledTimes(2);
+ // Then the request should only have been attempted once and we should get an unable to retry
+ expect(xhr).toHaveBeenCalledTimes(1);
- // And the promise should be resolved with the 2nd call that succeeded
- return expect(promise).resolves.toEqual({jsonCode: CONST.JSON_CODE.SUCCESS, fromRetriedResult: true});
+ // And the promise should be resolved with the special offline jsonCode
+ return expect(promise).resolves.toEqual({jsonCode: CONST.JSON_CODE.UNABLE_TO_RETRY});
});
});
-test('requests should be persisted while offline', () => {
+test('Retryable requests should be persisted while offline', () => {
// We don't expect calls `xhr` so we make the test fail if such call is made
const xhr = jest.spyOn(HttpUtils, 'xhr').mockRejectedValue(new Error('Unexpected xhr call'));
@@ -328,15 +327,25 @@ test('requests should be persisted while offline', () => {
expect.objectContaining({command: 'mock command', data: expect.objectContaining({param1: 'value1'})}),
expect.objectContaining({command: 'mock command', data: expect.objectContaining({param3: 'value3'})}),
]);
+
+ PersistedRequests.clear();
+ return waitForPromisesToResolve();
+ })
+ .then(() => {
+ expect(PersistedRequests.getAll()).toEqual([]);
});
});
-test('requests should resume when we are online', () => {
+test('Retryable requests should resume when we are online', () => {
// We're setting up a basic case where all requests succeed when we resume connectivity
const xhr = jest.spyOn(HttpUtils, 'xhr').mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS});
// Given we have some requests made while we're offline
- return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})
+ return Onyx.multiSet({
+ [ONYXKEYS.NETWORK]: {isOffline: true},
+ [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'},
+ [ONYXKEYS.SESSION]: {authToken: 'testToken'},
+ })
.then(() => {
// When network calls with `persist` are made
Network.post('mock command', {param1: 'value1', persist: true});
@@ -352,6 +361,9 @@ test('requests should resume when we are online', () => {
.then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}))
.then(waitForPromisesToResolve)
.then(() => {
+ expect(NetworkStore.isOffline()).toBe(false);
+ expect(SequentialQueue.isRunning()).toBe(false);
+
// Then `xhr` should be called with expected data, and the persisted queue should be empty
expect(xhr).toHaveBeenCalledTimes(2);
expect(xhr.mock.calls).toEqual([
@@ -466,19 +478,19 @@ test('test bad response will log alert', () => {
});
});
-test('test Failed to fetch error for requests not flagged with shouldRetry will resolve with an offline jsonCode', () => {
- // Setup xhr handler that rejects once with a 502 Bad Gateway
+test('test Failed to fetch error for non-retryable requests resolve with unable to retry jsonCode', () => {
+ // Setup xhr handler that rejects once with a Failed to Fetch
global.fetch = jest.fn().mockRejectedValue(new Error(CONST.ERROR.FAILED_TO_FETCH));
-
const onResolved = jest.fn();
// Given we have a request made while online
return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})
.then(() => {
+ expect(NetworkStore.isOffline()).toBe(false);
+
// When network calls with are made
- Network.post('MockCommand', {param1: 'value1', shouldRetry: false})
+ Network.post('mock command', {param1: 'value1'})
.then(onResolved);
-
return waitForPromisesToResolve();
})
.then(() => {
@@ -497,7 +509,7 @@ test('persisted request can trigger reauthentication for anything retryable', ()
.mockResolvedValueOnce({jsonCode: CONST.JSON_CODE.SUCCESS}); // Original command return 200
// Given we have a request made while we're offline and we have credentials available to reauthenticate
- Onyx.merge(ONYXKEYS.CREDENTIALS, {autoGeneratedLogin: 'caca', autoGeneratedPassword: 'caca'});
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'});
return waitForPromisesToResolve()
.then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}))
.then(() => {
@@ -527,14 +539,13 @@ test('several actions made while offline will get added in the order they are cr
// Given offline state where all requests will eventualy succeed without issue
const xhr = jest.spyOn(HttpUtils, 'xhr')
.mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS});
-
return Onyx.multiSet({
[ONYXKEYS.SESSION]: {authToken: 'anyToken'},
[ONYXKEYS.NETWORK]: {isOffline: true},
[ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test_user', autoGeneratedPassword: 'psswd'},
})
.then(() => {
- // When we queue 6 persistable commands
+ // When we queue 6 persistable commands and one not persistable
Network.post('MockCommand', {content: 'value1', persist: true});
Network.post('MockCommand', {content: 'value2', persist: true});
Network.post('MockCommand', {content: 'value3', persist: true});
@@ -557,13 +568,6 @@ test('several actions made while offline will get added in the order they are cr
expect(xhr.mock.calls[3][1].content).toBe('value4');
expect(xhr.mock.calls[4][1].content).toBe('value5');
expect(xhr.mock.calls[5][1].content).toBe('value6');
-
- // Move main queue forward so it processes the "read" request
- jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS);
- return waitForPromisesToResolve();
- })
- .then(() => {
- expect(xhr.mock.calls[6][1].content).toBe('not-persisted');
});
});
@@ -575,7 +579,8 @@ test('several actions made while offline will get added in the order they are cr
return Onyx.multiSet({
[ONYXKEYS.NETWORK]: {isOffline: true},
- [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'caca', autoGeneratedPassword: 'caca'},
+ [ONYXKEYS.SESSION]: {authToken: 'test'},
+ [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'},
})
.then(() => {
// When we queue 6 persistable commands
@@ -765,3 +770,27 @@ test('persistable request will move directly to the SequentialQueue when we are
expect(thirdRequestCommandName).toBe('MockCommandTwo');
});
});
+
+test('cancelled requests should not be retried', () => {
+ const xhr = jest.spyOn(HttpUtils, 'xhr');
+
+ // GIVEN a mock that will return a "cancelled" request error
+ global.fetch = jest.fn()
+ .mockRejectedValue(new DOMException('Aborted', CONST.ERROR.REQUEST_CANCELLED));
+
+ return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})
+ .then(() => {
+ // WHEN we make a few requests and then cancel them
+ Network.post('MockCommandOne');
+ Network.post('MockCommandTwo');
+ Network.post('MockCommandThree');
+
+ // WHEN we wait for the requests to all cancel
+ return waitForPromisesToResolve();
+ })
+ .then(() => {
+ // THEN expect our queue to be empty and for no requests to have been retried
+ expect(MainQueue.getAll().length).toBe(0);
+ expect(xhr.mock.calls.length).toBe(3);
+ });
+});
diff --git a/tests/unit/createCallbackTest.js b/tests/unit/createCallbackTest.js
deleted file mode 100644
index 7180452bf9f7..000000000000
--- a/tests/unit/createCallbackTest.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import createCallback from '../../src/libs/createCallback';
-
-test('Callback utility works', () => {
- // GIVEN a generic callback setup
- const [doSomething, registerDoSomething, clear] = createCallback();
- const mockCallback = jest.fn();
-
- // WHEN we register a callback
- registerDoSomething(mockCallback);
-
- // THEN call the callback
- doSomething();
-
- // THEN our callback will be called
- expect(mockCallback).toHaveBeenCalledTimes(1);
-
- // WHEN we clear the callback
- clear();
-
- // and call again
- doSomething();
-
- // THEN expect mock callback to not have been called again
- expect(mockCallback).toHaveBeenCalledTimes(1);
-});
diff --git a/tests/unit/createOnReadyTaskTest.js b/tests/unit/createOnReadyTaskTest.js
deleted file mode 100644
index e1280e4d9f1c..000000000000
--- a/tests/unit/createOnReadyTaskTest.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import createOnReadyTask from '../../src/libs/createOnReadyTask';
-import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
-
-test('createOnReadyTask', () => {
- // Given a generic onReady task and a mock callback executed when we are ready
- const readyTask = createOnReadyTask();
- const mock = jest.fn();
- readyTask.isReady().then(mock);
- return waitForPromisesToResolve()
- .then(() => {
- expect(mock).toHaveBeenCalledTimes(0);
-
- // When we set ready
- readyTask.setIsReady();
- return waitForPromisesToResolve();
- })
- .then(() => {
- // Then we should expect mock to be called
- expect(mock).toHaveBeenCalledTimes(1);
-
- // When we reset the task and wait for it again
- readyTask.reset();
- readyTask.isReady().then(mock);
- return waitForPromisesToResolve();
- })
- .then(() => {
- // Then we should not expect mock to be called again
- expect(mock).toHaveBeenCalledTimes(1);
-
- // When we set it to ready again
- readyTask.setIsReady();
- return waitForPromisesToResolve();
- })
- .then(() => {
- // Then we should expect the mock to get called twice
- expect(mock).toHaveBeenCalledTimes(2);
- });
-});