diff --git a/.github/ISSUE_TEMPLATE/Standard.md b/.github/ISSUE_TEMPLATE/Standard.md index 514d25f8b29f7..f728a9761a362 100644 --- a/.github/ISSUE_TEMPLATE/Standard.md +++ b/.github/ISSUE_TEMPLATE/Standard.md @@ -20,13 +20,16 @@ Describe what actually happened Can the user still use Expensify without this being fixed? Have you informed them of the workaround? ## Platform: + Where is this issue occurring? -- [ ] Web -- [ ] iOS -- [ ] Android -- [ ] Desktop App -- [ ] Mobile Web +- Web +- iOS +- Android +- Desktop App +- Mobile Web **Version Number:** **Logs:** https://stackoverflow.com/c/expensify/questions/4856 diff --git a/android/app/build.gradle b/android/app/build.gradle index 985385d9aa7e2..b44828db2e7d1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008104 - versionName "1.0.81-4" + versionCode 1001008105 + versionName "1.0.81-5" } splits { abi { diff --git a/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java b/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java index 2bbba01ba11d9..ae2d14ce9b67c 100644 --- a/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java +++ b/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java @@ -4,7 +4,7 @@ *

This source code is licensed under the MIT license found in the LICENSE file in the root * directory of this source tree. */ -package com.expensifyreactnative.chat; +package com.reactnativechat; import android.content.Context; import com.facebook.flipper.android.AndroidFlipperClient; diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java index 0a01f65adc6ca..1caabc78617dc 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.java +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java @@ -20,7 +20,6 @@ import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ReactModuleRegistryProvider; -import org.unimodules.core.interfaces.SingletonModule; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; @@ -107,7 +106,7 @@ private static void initializeFlipper( We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ - Class aClass = Class.forName("com.expensify.chat.ReactNativeFlipper"); + Class aClass = Class.forName("com.reactnativechat.ReactNativeFlipper"); aClass .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) .invoke(null, context, reactInstanceManager); diff --git a/android/gradle.properties b/android/gradle.properties index 75c02e4558b36..87a5a217a8a43 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -29,7 +29,7 @@ AsyncStorage_db_size_in_MB=10 AsyncStorage_useNextStorage=true # Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.54.0 +FLIPPER_VERSION=0.100.0 # Key Store Information MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index cf17a6eb03011..2c30175257a7d 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.4 + 1.0.81.5 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 194967d37a2a2..cba2c4e516da9 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.4 + 1.0.81.5 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3be3a461ff597..766985a36464c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -412,6 +412,8 @@ PODS: - React-Core - react-native-document-picker (5.1.0): - React-Core + - react-native-flipper (0.100.0): + - React-Core - react-native-image-picker (4.0.3): - React-Core - react-native-netinfo (5.9.10): @@ -632,6 +634,7 @@ DEPENDENCIES: - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - 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`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pdf (from `../node_modules/react-native-pdf`) @@ -761,6 +764,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-config" react-native-document-picker: :path: "../node_modules/react-native-document-picker" + react-native-flipper: + :path: "../node_modules/react-native-flipper" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-netinfo: @@ -913,6 +918,7 @@ SPEC CHECKSUMS: React-jsinspector: 500a59626037be5b3b3d89c5151bc3baa9abf1a9 react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed react-native-document-picker: 0e3602a4064da040321bafad6848d8b0edcb1d55 + react-native-flipper: 1943b82f2e494c77b741eb1ed257b6734a334b83 react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f diff --git a/package-lock.json b/package-lock.json index 39d49d969ce84..6ea8dfcbe9fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-4", + "version": "1.0.81-5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2624,9 +2624,9 @@ }, "dependencies": { "@babel/compat-data": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", - "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==" + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==" }, "@babel/core": { "version": "7.9.0", @@ -2667,11 +2667,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2687,11 +2687,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2716,13 +2716,13 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", - "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.8.tgz", + "integrity": "sha512-bpYvH8zJBWzeqi1o+co8qOrw+EXzQ/0c74gVmY205AWXy9nifHrOg77y+1zwxX5lXE7Icq4sPlSQ4O2kWBrteQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.14.5", "@babel/helper-function-name": "^7.14.5", - "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.7", "@babel/helper-optimise-call-expression": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5" @@ -2746,11 +2746,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2775,9 +2775,9 @@ } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -2790,11 +2790,11 @@ } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2809,11 +2809,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2828,30 +2828,30 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-member-expression-to-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", - "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "requires": { "@babel/types": "^7.14.5" }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2866,11 +2866,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2885,11 +2885,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2911,11 +2911,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2941,61 +2941,61 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.14.8" }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3010,11 +3010,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3029,20 +3029,20 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", + "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -3069,19 +3069,19 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3094,27 +3094,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3131,9 +3131,9 @@ } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", - "integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", "requires": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5", @@ -3204,11 +3204,11 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", - "integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", "requires": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", @@ -3333,9 +3333,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", - "integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -3418,34 +3418,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3458,27 +3458,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3504,34 +3504,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3544,27 +3544,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3591,34 +3591,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3631,27 +3631,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3675,34 +3675,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3715,36 +3715,36 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", - "integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", "requires": { "@babel/helper-create-regexp-features-plugin": "^7.14.5" } @@ -3940,11 +3940,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } }, @@ -3986,9 +3986,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001237", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz", - "integrity": "sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==" + "version": "1.0.30001248", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz", + "integrity": "sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw==" }, "chalk": { "version": "2.4.2", @@ -4019,9 +4019,9 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" }, "core-js-compat": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", - "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.2.tgz", + "integrity": "sha512-Wp+BJVvwopjI+A1EFqm2dwUmWYXrvucmtIB2LgXn/Rb+gWPKYxtmb4GKHGKG/KGF1eK9jfjzT38DITbTOCX/SQ==", "requires": { "browserslist": "^4.16.6", "semver": "7.0.0" @@ -4035,9 +4035,9 @@ } }, "electron-to-chromium": { - "version": "1.3.752", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", - "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==" + "version": "1.3.791", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.791.tgz", + "integrity": "sha512-Tdx7w1fZpeWOOBluK+kXTAKCXyc79K65RB6Zp0+sPSZZhDjXlrxfGlXrlMGVVQUrKCyEZFQs1UBBLNz5IdbF0g==" }, "fs-extra": { "version": "9.0.0", @@ -15482,9 +15482,9 @@ } }, "@unimodules/core": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.0.tgz", - "integrity": "sha512-oLRT4Bkah3GEopkxmTgpHsRTRp+NJ1907ZjE9y/HLh32q7O/3mcbpY77Uvm+EXW0Vh14gOlU+bmkpC0hz3we0w==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.1.tgz", + "integrity": "sha512-Sa7X+WkrhZzcckavjuQu4mq6BTPWsio7OITfoNNzjL0CEmfHfo3DNWQWoVyj+wCgMnjGUT1l3+q3AQlI+CaCLA==", "requires": { "compare-versions": "^3.4.0" } @@ -16350,6 +16350,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "ascii-table": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ascii-table/-/ascii-table-0.0.9.tgz", + "integrity": "sha1-BqZgTWpV1L9BqaR9mHLXp42jHnM=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -23070,8 +23075,8 @@ } }, "expensify-common": { - "version": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", - "from": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", + "version": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", + "from": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -23146,9 +23151,9 @@ } }, "expo-asset": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-8.3.2.tgz", - "integrity": "sha512-MKOwkkN0lnQRcOdn5moqkHPmLgFoUSIYyrvMAJ767vTXvLvZgoQgvBwqCAXsXitIwEitG0Az3XZ23SfKJpFbFg==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-8.3.3.tgz", + "integrity": "sha512-qCm5d14tzswY8DcmRJ+0WkY9tc3OiVikBAiw2hCMC+bFpK/bEdqy4Zwfd69MFIAJ0taJpHWhdUoBRO0byQLlfg==", "requires": { "blueimp-md5": "^2.10.0", "invariant": "^2.2.4", @@ -23837,6 +23842,12 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "flipper-plugin-bridgespy-client": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/flipper-plugin-bridgespy-client/-/flipper-plugin-bridgespy-client-0.1.9.tgz", + "integrity": "sha512-KdJcKbgnFfkdhQ8+dOLQtpeeQDFkLjKC21fM20iZ1lpZg+XJ5EHRZWhqzwJMgt0pDFLA7Ws2itmZESYtjyyvKA==", + "dev": true + }, "flow-parser": { "version": "0.121.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", @@ -35997,6 +36008,12 @@ "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-5.1.0.tgz", "integrity": "sha512-XMSDibp1GX0UMlVdsmAgjmf4/FJ+TCvVLWdKjV4QkTIO3TbDKsWSAS1+9jgUYcqIwQpO87SFBkvJ5cjOwx9vNA==" }, + "react-native-flipper": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.100.0.tgz", + "integrity": "sha512-UsRlT9UAPHs9K+w1gVi/QEZgYem+W1Z2YXwr5NToAwNMsmTCfl0OpfWS0Xw9FiNQIOcpT2MTlPDHzL3sAQH44A==", + "dev": true + }, "react-native-gesture-handler": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.9.0.tgz", @@ -36063,12 +36080,12 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", - "from": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", + "version": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", + "from": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", "requires": { + "ascii-table": "0.0.9", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", "lodash": "4.17.21", - "react": "^16.13.1", "underscore": "^1.13.1" }, "dependencies": { @@ -36089,16 +36106,6 @@ "underscore": "1.9.1" }, "dependencies": { - "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" - } - }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -36107,9 +36114,9 @@ } }, "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "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", @@ -37772,6 +37779,12 @@ "inherits": "^2.0.1" } }, + "rn-async-storage-flipper": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/rn-async-storage-flipper/-/rn-async-storage-flipper-0.0.10.tgz", + "integrity": "sha512-Bp8B3oWufAHhc1okewR6EXZL7gRkMQMODYepTpsTgvFq7Iae57Ow4ZIDjumb6BxoCfipFSFBnxvfPhy3PDDBRQ==", + "dev": true + }, "rn-fetch-blob": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz", @@ -38375,9 +38388,9 @@ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "slugify": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.5.3.tgz", - "integrity": "sha512-/HkjRdwPY3yHJReXu38NiusZw2+LLE2SrhkWJtmlPDB1fqFSvioYj62NkPcrKiNCgRLeGcGK7QBvr1iQwybeXw==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz", + "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==" }, "snapdragon": { "version": "0.8.2", diff --git a/package.json b/package.json index 92ecd8be5afb2..73d60c3113de5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-4", + "version": "1.0.81-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -60,7 +60,7 @@ "electron-log": "^4.3.5", "electron-serve": "^1.0.0", "electron-updater": "^4.3.4", - "expensify-common": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", + "expensify-common": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", "expo-haptics": "^10.0.0", "file-loader": "^6.0.0", "html-entities": "^1.3.1", @@ -82,7 +82,7 @@ "react-native-image-picker": "^4.0.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.10.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", "react-native-pdf": "^6.2.2", "react-native-permissions": "^3.0.1", "react-native-picker-select": "8.0.4", @@ -145,6 +145,7 @@ "eslint-loader": "^4.0.2", "eslint-plugin-detox": "^1.0.0", "eslint-plugin-jest": "^24.1.0", + "flipper-plugin-bridgespy-client": "^0.1.9", "html-webpack-plugin": "^4.3.0", "jest": "^26.5.2", "jest-circus": "^26.5.2", @@ -154,8 +155,10 @@ "portfinder": "^1.0.28", "pusher-js-mock": "^0.3.3", "react-hot-loader": "^4.12.21", + "react-native-flipper": "^0.100.0", "react-native-svg-transformer": "^0.14.3", "react-test-renderer": "16.13.1", + "rn-async-storage-flipper": "0.0.10", "semver": "^7.3.4", "style-loader": "^2.0.0", "wait-port": "^0.2.9", diff --git a/src/CONST.js b/src/CONST.js index 303f4f04611b7..e822f35cc55de 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,6 +1,8 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; const CONST = { + // 50 megabytes in bytes + API_MAX_ATTACHMENT_SIZE: 52428800, APP_DOWNLOAD_LINKS: { ANDROID: 'https://play.google.com/store/apps/details?id=com.expensify.chat', IOS: 'https://apps.apple.com/us/app/expensify-cash/id1530278510', diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index dfd81620d98ab..181c718c9f6e2 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,6 +2,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; +import lodashGet from 'lodash/get'; import CONST from '../CONST'; import Modal from './Modal'; import AttachmentView from './AttachmentView'; @@ -14,6 +15,7 @@ import Button from './Button'; import HeaderWithCloseButton from './HeaderWithCloseButton'; import fileDownload from '../libs/fileDownload'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import ConfirmModal from './ConfirmModal'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -58,11 +60,14 @@ class AttachmentModal extends PureComponent { this.state = { isModalOpen: false, + isConfirmModalOpen: false, file: null, sourceURL: props.sourceURL, }; this.submitAndClose = this.submitAndClose.bind(this); + this.closeConfirmModal = this.closeConfirmModal.bind(this); + this.isValidSize = this.isValidSize.bind(this); } /** @@ -81,6 +86,22 @@ class AttachmentModal extends PureComponent { this.setState({isModalOpen: false}); } + /** + * Close the confirm modal. + */ + closeConfirmModal() { + this.setState({isConfirmModalOpen: false}); + } + + /** + * Check if the attachment size is less than the API size limit. + * @param {Object} file + * @returns {Boolean} + */ + isValidSize(file) { + return !file || lodashGet(file, 'size', 0) < CONST.API_MAX_ATTACHMENT_SIZE; + } + render() { const sourceURL = this.props.isAuthTokenRequired ? addEncryptedAuthTokenToURL(this.state.sourceURL) @@ -134,8 +155,20 @@ class AttachmentModal extends PureComponent { /> )} + {this.props.children({ displayFileInModal: ({file}) => { + if (!this.isValidSize(file)) { + this.setState({isConfirmModalOpen: true}); + return; + } if (file instanceof File) { const source = URL.createObjectURL(file); this.setState({isModalOpen: true, sourceURL: source, file}); diff --git a/src/components/Badge.js b/src/components/Badge.js new file mode 100644 index 0000000000000..84399afce0b92 --- /dev/null +++ b/src/components/Badge.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {Pressable, View} from 'react-native'; +import PropTypes from 'prop-types'; +import styles, {getBadgeColorStyle} from '../styles/styles'; +import Text from './Text'; + +const propTypes = { + /** Is Success type */ + success: PropTypes.bool, + + /** Is Error type */ + error: PropTypes.bool, + + /** Whether badge is clickable */ + pressable: PropTypes.bool, + + /** Text to display in the Badge */ + text: PropTypes.string.isRequired, + + /** Styles for Badge */ + badgeStyles: PropTypes.arrayOf(PropTypes.object), + + /** Callback to be called on onPress */ + onPress: PropTypes.func, +}; + +const defaultProps = { + success: false, + error: false, + pressable: false, + badgeStyles: [], + onPress: undefined, +}; + +const Badge = (props) => { + const textStyles = props.success || props.error ? styles.textWhite : undefined; + const Wrapper = props.pressable ? Pressable : View; + const wrapperStyles = ({pressed}) => ([ + styles.badge, + styles.ml2, + getBadgeColorStyle(props.success, props.error, pressed), + ...props.badgeStyles, + ]); + + return ( + + + {props.text} + + + ); +}; + +Badge.displayName = 'Badge'; +Badge.propTypes = propTypes; +Badge.defaultProps = defaultProps; +export default Badge; diff --git a/src/components/EnvironmentBadge.js b/src/components/EnvironmentBadge.js index f7bde09733a56..122a0e47d87a4 100644 --- a/src/components/EnvironmentBadge.js +++ b/src/components/EnvironmentBadge.js @@ -1,9 +1,7 @@ import React from 'react'; -import {View} from 'react-native'; -import styles from '../styles/styles'; import CONST from '../CONST'; -import Text from './Text'; import withEnvironment, {environmentPropTypes} from './withEnvironment'; +import Badge from './Badge'; const EnvironmentBadge = (props) => { // If we are on production, don't show any badge @@ -11,16 +9,12 @@ const EnvironmentBadge = (props) => { return null; } - const backgroundColorStyle = props.environment === CONST.ENVIRONMENT.STAGING - ? styles.badgeSuccess - : styles.badgeDanger; - return ( - - - {props.environment} - - + ); }; diff --git a/src/components/IOUBadge.js b/src/components/IOUBadge.js index ebc9852d54019..2eb516b427293 100644 --- a/src/components/IOUBadge.js +++ b/src/components/IOUBadge.js @@ -1,15 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Pressable} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; -import styles, {getBadgeColorStyle} from '../styles/styles'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import CONST from '../CONST'; -import Text from './Text'; +import Badge from './Badge'; const propTypes = { /** IOU Report data object */ @@ -53,24 +51,16 @@ const IOUBadge = (props) => { Navigation.navigate(ROUTES.getIouDetailsRoute(props.iouReport.chatReportID, props.iouReport.reportID)); }; return ( - ([ - styles.badge, - styles.ml2, - getBadgeColorStyle(props.session.email === props.iouReport.ownerEmail, pressed), - ])} - > - - {props.numberFormat( - props.iouReport.total / 100, - {style: 'currency', currency: props.iouReport.currency}, - )} - - + ); }; diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 1a28310396ed4..f046068643e1a 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -8,8 +8,12 @@ import styles, {getButtonBackgroundColorStyle, getIconFillColor} from '../styles import Icon from './Icon'; import {ArrowRight} from './Icon/Expensicons'; import getButtonState from '../libs/getButtonState'; +import Badge from './Badge'; const propTypes = { + /** Text to be shown as badge near the right end. */ + badgeText: PropTypes.string, + /** Any additional styles to apply */ // eslint-disable-next-line react/forbid-prop-types wrapperStyle: PropTypes.object, @@ -58,6 +62,7 @@ const propTypes = { }; const defaultProps = { + badgeText: undefined, shouldShowRightIcon: false, wrapperStyle: {}, success: false, @@ -74,6 +79,7 @@ const defaultProps = { }; const MenuItem = ({ + badgeText, onPress, icon, iconRight, @@ -141,6 +147,7 @@ const MenuItem = ({ + {badgeText && } {subtitle && ( diff --git a/src/languages/en.js b/src/languages/en.js index 0d38c1bcf1025..595e8e249a16f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -43,8 +43,8 @@ export default { here: 'here', dob: 'Date of Birth', ssnLast4: 'Last 4 Digits of SSN', - addressNoPO: 'Address (no P.O. boxes)', - companyAddressNoPO: 'Company Address (no P.O. boxes)', + addressNoPO: 'Personal Address (PO Boxes and mail drop addresses are NOT allowed)', + companyAddressNoPO: 'Company Address (PO Boxes and mail drop addresses are NOT allowed)', city: 'City', state: 'State', zip: 'Zip Code', @@ -60,6 +60,10 @@ export default { noResultsFound: 'No results found', timePrefix: 'It\'s', conjunctionFor: 'for', + todayAt: 'Today at', + tomorrowAt: 'Tomorrow at', + yesterdayAt: 'Yesterday at', + conjunctionAt: 'at', }, attachmentPicker: { cameraPermissionRequired: 'Camera Permission Required', @@ -70,6 +74,8 @@ export default { takePhoto: 'Take Photo', chooseFromGallery: 'Choose from Gallery', chooseDocument: 'Choose Document', + attachmentTooLarge: 'Attachment too large', + sizeExceeded: 'Attachment size is larger than 50 MB limit.', }, textInputFocusable: { noExtentionFoundForMimeType: 'No extension found for mime type', @@ -110,6 +116,7 @@ export default { youAppearToBeOffline: 'You appear to be offline.', fileUploadFailed: 'Upload Failed. File is not supported.', roomIsArchived: 'This chat room has been deleted', + localTime: ({user, time}) => `It's ${time} for ${user}`, }, contextMenuItem: { copyToClipboard: 'Copy to Clipboard', @@ -240,6 +247,7 @@ export default { payPalMe: 'PayPal.me/', yourPayPalUsername: 'Your PayPal username', addPayPalAccount: 'Add PayPal Account', + editPayPalAccount: 'Update PayPal Account', growlMessageOnSave: 'Your PayPal username was successfully added', }, paymentsPage: { diff --git a/src/languages/es.js b/src/languages/es.js index 6742589880661..2965419569fb9 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -4,6 +4,7 @@ export default { cancel: 'Cancelar', yes: 'Si', no: 'No', + ok: 'OK', attachment: 'Archivo Adjunto', to: 'A', optional: 'Opcional', @@ -21,10 +22,14 @@ export default { not: 'No', signIn: 'Conectarse', continue: 'Continuar', + firstName: 'Primer nombre', + lastName: 'Apellido', phoneNumber: 'Número de teléfono', email: 'Email', and: 'y', details: 'Detalles', + privacy: 'Intimidad', + privacyPolicy: 'Política de privacidad', delete: 'Eliminar', deleted: 'eliminado', contacts: 'Contactos', @@ -38,8 +43,8 @@ export default { here: 'aquí', dob: 'Fecha de Nacimiento', ssnLast4: 'Últimos 4 dígitos de su SSN', - addressNoPO: 'Dirección (sin Apartado Postal)', - companyAddressNoPO: 'Dirección de la Empresa (sin Apartado Postal)', + addressNoPO: 'Dirección física personal (no se aceptan apartados ni direcciones postales)', + companyAddressNoPO: 'Dirección física de la empresa (no se aceptan apartados ni direcciones postales)', city: 'Ciudad', state: 'Estado', zip: 'Código Postal', @@ -55,6 +60,10 @@ export default { noResultsFound: 'No se han encontrado resultados', timePrefix: 'Son las', conjunctionFor: 'para', + todayAt: 'Hoy a las', + tomorrowAt: 'Mañana a las', + yesterdayAt: 'Ayer a las', + conjunctionAt: 'a', }, attachmentPicker: { cameraPermissionRequired: 'Se necesita permiso para usar la cámara', @@ -65,6 +74,8 @@ export default { takePhoto: 'Hacer una Foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir Documento', + attachmentTooLarge: 'Archivo adjunto demasiado grande', + sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.', }, textInputFocusable: { noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido', @@ -103,11 +114,15 @@ export default { writeSomething: 'Escribe algo...', blockedFromConcierge: 'Comunicación no permitida', youAppearToBeOffline: 'Parece que estás desconectado.', + fileUploadFailed: 'Subida fallida. El archivo no es compatible.', roomIsArchived: 'Esta sala de chat ha sido eliminada', + localTime: ({user, time}) => `Son las ${time} para ${user}`, }, - reportActionContextMenu: { + contextMenuItem: { copyToClipboard: 'Copiar al Portapapeles', copied: '¡Copiado!', + }, + reportActionContextMenu: { copyLink: 'Copiar Enlace', markAsUnread: 'Marcar como no leído', editComment: 'Editar Commentario', @@ -136,6 +151,7 @@ export default { confirm: 'Confirmar', splitBill: 'Dividir Factura', requestMoney: 'Pedir Dinero', + sendMoney: 'Enviar Dinero', pay: 'Pagar', viewDetails: 'Ver detalles', settleExpensify: 'Pagar con Expensify', @@ -147,6 +163,7 @@ export default { owes: ({manager, owner}) => `${manager} debe a ${owner}`, paid: ({owner, manager}) => `${manager} pagó a ${owner}`, split: ({amount}) => `Dividir ${amount}`, + send: ({amount}) => `Enviar ${amount}`, choosePaymentMethod: 'Elige el método de pago:', noReimbursableExpenses: 'El monto de este informe es inválido', }, @@ -170,9 +187,7 @@ export default { profilePage: { profile: 'Perfil', tellUsAboutYourself: '¡Cuéntanos algo sobre tí, nos encantaría conocerte!', - firstName: 'Nombre', john: 'Juan', - lastName: 'Apellidos', doe: 'Nadie', preferredPronouns: 'Pronombres preferidos', selectYourPronouns: 'Selecciona tus pronombres', @@ -231,7 +246,9 @@ export default { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', yourPayPalUsername: 'Tu usuario de PayPal', - addPayPalAccount: 'Agregar Cuenta de Paypal', + addPayPalAccount: 'Agregar Cuenta de PayPal', + growlMessageOnSave: 'Su nombre de usuario de PayPal se agregó correctamente', + editPayPalAccount: 'Actualizar cuenta de PayPal', }, paymentsPage: { paymentMethodsTitle: 'Métodos de pago', @@ -260,6 +277,14 @@ export default { expensifyIsOpenSource: 'Expensify.cash es open source', theCode: 'el código', openJobs: 'trabajos disponibles', + heroHeading: 'Dividir cuentas\ny chatear con amigos.', + heroDescription: { + phrase1: 'El dinero habla. Y ahora que el chat y los pagos están en un solo lugar, también es fácil. Sus pagos le llegan tan rápido como puede transmitir su punto.', + phrase2: 'New Expensify es de código abierto. Vista', + phrase3: 'el código', + phrase4: 'Vista', + phrase5: 'vacantes', + }, }, termsOfUse: { phrase1: 'Al usar Expensify.cash, estás aceptando los', @@ -268,9 +293,11 @@ export default { phrase4: 'política de privacidad', phrase5: 'El envío de dinero es brindado por Expensify Payments LLC (NMLS ID:2017010) de conformidad con sus', phrase6: 'licencias', + phrase7: 'licenses', }, passwordForm: { pleaseFillOutAllFields: 'Por favor completa todos los campos', + enterYourTwoFactorAuthenticationCodeToContinue: 'Ingrese su código de autenticación de dos factores para continuar', forgot: '¿Te has olvidado?', twoFactorCode: 'Autenticación de 2 factores', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', @@ -306,7 +333,10 @@ export default { }, setPasswordPage: { enterPassword: 'Escribe una contraseña', + confirmNewPassword: 'Confirma la contraseña', setPassword: 'Configura tu Contraseña', + passwordsDontMatch: 'Las contraseñas deben coincidir', + newPasswordPrompt: 'Su contraseña debe tener al menos 8 caracteres, \n1 letra mayúscula, 1 letra minúscula, 1 número.', }, bankAccount: { accountNumber: 'Número de cuenta', @@ -369,6 +399,68 @@ export default { noPhoneNumber: 'Por favor escribe un número de teléfono que incluya el código de país e.g +447814266907', maxParticipantsReached: 'Has llegado al número máximo de participantes para un grupo.', }, + onfidoStep: { + acceptTerms: 'Al continuar con la solicitud para activar su billetera Expensify, confirma que ha leído, comprende y acepta ', + facialScan: 'Política y lanzamiento de la exploración facial de Onfido', + tryAgain: 'Intentar otra vez', + verifyIdentity: 'Verificar identidad', + genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', + }, + additionalDetailsStep: { + headerTitle: 'Detalles adicionales', + helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar este pago.', + helpLink: 'Obtenga más información sobre por qué necesitamos esto.', + legalFirstNameLabel: 'Primer nombre legal', + legalMiddleNameLabel: 'Segundo nombre legal', + legalLastNameLabel: 'Apellido legal', + }, + termsStep: { + headerTitle: 'Condiciones y tarifas', + haveReadAndAgree: 'He leído y acepto recibir ', + electronicDisclosures: 'divulgaciones electrónicas', + agreeToThe: 'Estoy de acuerdo con la ', + walletAgreement: 'Acuerdo de billetera', + enablePayments: 'Habilitar pagos', + termsMustBeAccepted: 'Se deben aceptar los términos', + }, + activateStep: { + headerTitle: 'Habilitar pagos', + activated: 'Su billetera Expensify está lista para usar.', + checkBackLater: 'Todavía estamos revisando tu información. Por favor, vuelva más tarde.', + }, + companyStep: { + headerTitle: 'Información de la Empresa', + subtitle: 'Dé más información sobre su empresa.', + legalBusinessName: 'Nombre Comercial Legal', + companyWebsite: 'Company Website', + taxIDNumber: 'Tax ID Number', + companyType: 'Página Web de la Empresa', + incorporationDate: 'Fecha de Incorporación', + industryClassificationCode: 'Código de Clasificación Industrial', + confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', + listOfRestrictedBusinesses: 'lista de negocios restringidos', + incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', + companyPhonePlaceholder: '10 dígitos, sin guiones', + }, + requestorStep: { + headerTitle: 'Información del solicitante', + financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', + learnMore: 'Más información', + isMyDataSafe: '¿Están seguros mis datos?', + onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', + onFidoFacialScan: 'Onfido’s Facial Scan Policy and Release', + isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', + isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', + }, + validationStep: { + headerTitle: 'Validar', + buttonText: 'Finalizar Configuración', + maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', + description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', + descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', + reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', + forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', + }, beneficialOwnersStep: { beneficialOwners: 'Beneficial Owners', additionalInformation: 'Additional Information', @@ -430,39 +522,6 @@ export default { welcomeNote: ({workspaceName}) => `¡Has sido invitado a la ${workspaceName} Espacio de trabajo! Descargue la aplicación móvil Expensify para comenzar a rastrear sus gastos.`, }, }, - companyStep: { - headerTitle: 'Información de la Empresa', - subtitle: 'Dé más información sobre su empresa.', - legalBusinessName: 'Nombre Comercial Legal', - companyWebsite: 'Company Website', - taxIDNumber: 'Tax ID Number', - companyType: 'Página Web de la Empresa', - incorporationDate: 'Fecha de Incorporación', - industryClassificationCode: 'Código de Clasificación Industrial', - confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', - listOfRestrictedBusinesses: 'lista de negocios restringidos', - incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', - companyPhonePlaceholder: '10 dígitos, sin guiones', - }, - validationStep: { - headerTitle: 'Validar', - buttonText: 'Finalizar Configuración', - maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', - description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', - descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', - reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', - forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', - }, - requestorStep: { - headerTitle: 'Información del solicitante', - financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', - learnMore: 'Más información', - isMyDataSafe: '¿Están seguros mis datos?', - onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', - facialScan: 'la política de reconocimiento facial y la exención de Onfido', - isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', - isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', - }, requestCallPage: { requestACall: 'Llámame por teléfono', description: '¿Necesitas ayuda configurando tu cuenta? Nuestro equipo de guías puede ayudarte.', diff --git a/src/libs/API.js b/src/libs/API.js index c885298411b0e..5fa63cf9dbf4f 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -221,6 +221,11 @@ function Authenticate(parameters) { case 401: throw new Error('passwordForm.error.incorrectLoginOrPassword'); case 402: + // If too few characters are passed as the password, the WAF will pass it to the API as an empty + // string, which results in a 402 error from Auth. + if (response.message === '402 Missing partnerUserSecret') { + throw new Error('passwordForm.error.incorrectLoginOrPassword'); + } throw new Error('passwordForm.error.twoFactorAuthenticationEnabled'); case 403: throw new Error('passwordForm.error.invalidLoginOrPassword'); diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 7b9e77d9c9196..b2107c8213101 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; +import {translate} from './translate'; let timezone; Onyx.connect({ @@ -57,13 +58,18 @@ function timestampToDateTime(locale, timestamp, includeTimeZone = false) { const date = getLocalMomentFromTimestamp(locale, timestamp); const tz = includeTimeZone ? ' [UTC]Z' : ''; + const todayAt = translate(locale, 'common.todayAt'); + const tomorrowAt = translate(locale, 'common.tomorrowAt'); + const yesterdayAt = translate(locale, 'common.yesterdayAt'); + const at = translate(locale, 'common.conjunctionAt'); + return moment(date).calendar({ - sameDay: `[Today at] LT${tz}`, - nextDay: `[Tomorrow at] LT${tz}`, - nextWeek: `MMM D [at] LT${tz}`, - lastDay: `[Yesterday at] LT${tz}`, - lastWeek: `MMM D [at] LT${tz}`, - sameElse: `MMM D, YYYY [at] LT${tz}`, + sameDay: `[${todayAt}] LT${tz}`, + nextDay: `[${tomorrowAt}] LT${tz}`, + lastDay: `[${yesterdayAt}] LT${tz}`, + nextWeek: `MMM D [${at}] LT${tz}`, + lastWeek: `MMM D [${at}] LT${tz}`, + sameElse: `MMM D, YYYY [${at}] LT${tz}`, }); } diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 53434116618ba..168bac43e8877 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -618,18 +618,12 @@ function setupWithdrawalAccount(data) { // Show warning if another account already set up this bank account and promote share if (response.existingOwners) { console.error('Cannot set up withdrawal account due to existing owners', response); - const existingOwnersList = response.existingOwners.reduce((ownersStr, owner, i, ownersArr) => { - let separator = ',\n'; - if (i === 0) { - separator = '\n'; - } else if (i === ownersArr.length - 1) { - separator = ' and\n'; - } - return `${ownersStr}${separator}${owner}`; - }, ''); Onyx.merge( ONYXKEYS.REIMBURSEMENT_ACCOUNT, - {existingOwnersList, error: CONST.BANK_ACCOUNT.ERROR.EXISTING_OWNERS}, + { + existingOwners: response.existingOwners, + error: CONST.BANK_ACCOUNT.ERROR.EXISTING_OWNERS, + }, ); return; } diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 7c96603338999..4965508c8b166 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -114,9 +114,9 @@ function removeMembers(members, policyID) { // Optimistically remove the members from the policy Onyx.set(key, policy); - // Make the API call to merge the login into the policy + // Make the API call to remove a login from the policy API.Policy_Employees_Remove({ - emailList: members, + emailList: members.join(','), policyID, }) .then((data) => { diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 6e7c895b6552a..e2428a124d04a 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -69,7 +69,7 @@ function getBetas() { function getUserDetails() { API.Get({ returnValueList: 'account, loginList, nameValuePairs', - nvpNames: [CONST.NVP.BLOCKED_FROM_CONCIERGE, CONST.NVP.PAYPAL_ME_ADDRESS].join(','), + nvpNames: CONST.NVP.PAYPAL_ME_ADDRESS, }) .then((response) => { // Update the User onyx key diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index db8cce8eaee63..b38563acdef00 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -36,8 +36,8 @@ const propTypes = { /** Error set when handling the API response */ error: PropTypes.string, - /** A list of existing owners, set if the bank account being added is already owned */ - existingOwnersList: PropTypes.string, + /** The existing owners for if the bank account is already owned */ + existingOwners: PropTypes.arrayOf(PropTypes.string), }).isRequired, ...withLocalizePropTypes, @@ -129,8 +129,9 @@ class BankAccountStep extends React.Component { // Disable bank account fields once they've been added in db so they can't be changed const isFromPlaid = this.props.achData.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; const shouldDisableInputs = Boolean(this.props.achData.bankAccountID) || isFromPlaid; + const existingOwners = this.props.reimbursementAccount.existingOwners; const isExistingOwnersErrorVisible = Boolean(this.props.reimbursementAccount.error - && this.props.reimbursementAccount.existingOwnersList); + && existingOwners); return ( + {this.props.translate('bankAccount.error.existingOwners.alreadyInUse')} - - {this.props.reimbursementAccount.existingOwnersList} - + {existingOwners && existingOwners.map((existingOwner, i) => { + let separator = ', '; + if (i === 0) { + separator = ''; + } else if (i === existingOwners.length - 1) { + separator = ` ${this.props.translate('common.and')} `; + } + return ( + <> + {separator} + {existingOwner} + {i === existingOwners.length - 1 && .} + + ); + })} {this.props.translate('bankAccount.error.existingOwners.pleaseAskThemToShare')} diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 5f8732b881efa..c0fc39effba9b 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -45,6 +45,20 @@ class CompanyStep extends React.Component { hasNoConnectionToCannabis: lodashGet(props, ['achData', 'hasNoConnectionToCannabis'], false), password: '', }; + + // These fields need to be filled out in order to submit the form + this.requiredFields = [ + 'companyName', + 'addressStreet', + 'addressCity', + 'addressState', + 'addressZipCode', + 'website', + 'companyTaxID', + 'incorporationDate', + 'industryCode', + 'password', + ]; } /** @@ -111,6 +125,8 @@ class CompanyStep extends React.Component { render() { const shouldDisableCompanyName = Boolean(this.props.achData.bankAccountID && this.props.achData.companyName); const shouldDisableCompanyTaxID = Boolean(this.props.achData.bankAccountID && this.props.achData.companyTaxID); + const shouldDisableSubmitButton = this.requiredFields + .reduce((acc, curr) => acc || !this.state[curr].trim(), false); return ( <> diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 7244c9460a34c..03b6679deab41 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -66,7 +66,7 @@ class SearchPage extends Component { this.selectReport = this.selectReport.bind(this); this.onChangeText = this.onChangeText.bind(this); - this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 300); + this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75); const { recentReports, diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js new file mode 100755 index 0000000000000..4f6b0dcf5ae60 --- /dev/null +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -0,0 +1,54 @@ +import React from 'react'; +import {View} from 'react-native'; +import _ from 'underscore'; +import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles'; +import ContextMenuItem from '../../../../components/ContextMenuItem'; +import { + propTypes as GenericReportActionContextMenuPropTypes, + defaultProps, +} from './GenericReportActionContextMenuPropTypes'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import ContextMenuActions from './ContextMenuActions'; + +const propTypes = { + ...GenericReportActionContextMenuPropTypes, + ...withLocalizePropTypes, +}; + +class BaseReportActionContextMenu extends React.Component { + constructor(props) { + super(props); + + this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); + } + + render() { + return this.props.isVisible && ( + + {_.map(ContextMenuActions, contextAction => contextAction.shouldShow(this.props.reportAction) && ( + contextAction.onPress(!this.props.isMini, { + reportAction: this.props.reportAction, + reportID: this.props.reportID, + draftMessage: this.props.draftMessage, + selection: this.props.selection, + })} + /> + ))} + + ); + } +} + +BaseReportActionContextMenu.propTypes = propTypes; +BaseReportActionContextMenu.defaultProps = defaultProps; + +export default withLocalize(BaseReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js new file mode 100644 index 0000000000000..1123e54f2623a --- /dev/null +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -0,0 +1,116 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import Str from 'expensify-common/lib/str'; +import { + Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, +} from '../../../../components/Icon/Expensicons'; +import { + setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, +} from '../../../../libs/actions/Report'; +import Clipboard from '../../../../libs/Clipboard'; +import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; +import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; +import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; + +/** + * Gets the HTML version of the message in an action. + * @param {Object} reportAction + * @return {String} + */ +function getActionText(reportAction) { + const message = _.last(lodashGet(reportAction, 'message', null)); + return lodashGet(message, 'html', ''); +} + +// A list of all the context actions in this menu. +export default [ + // Copy to clipboard + { + textTranslateKey: 'contextMenuItem.copyToClipboard', + icon: ClipboardIcon, + successTextTranslateKey: 'contextMenuItem.copied', + successIcon: Checkmark, + shouldShow: () => true, + + // If return value is true, we switch the `text` and `icon` on + // `ContextMenuItem` with `successText` and `successIcon` which will fallback to + // the `text` and `icon` + onPress: (closePopover, {reportAction, selection}) => { + const message = _.last(lodashGet(reportAction, 'message', null)); + const html = lodashGet(message, 'html', ''); + const text = Str.htmlDecode(selection || lodashGet(message, 'text', '')); + const isAttachment = _.has(reportAction, 'isAttachment') + ? reportAction.isAttachment + : isReportMessageAttachment(text); + if (!isAttachment) { + Clipboard.setString(text); + } else { + Clipboard.setString(html); + } + if (closePopover) { + hideContextMenu(true, ReportActionComposeFocusManager.focus); + } + }, + }, + + { + textTranslateKey: 'reportActionContextMenu.copyLink', + icon: LinkCopy, + shouldShow: () => false, + onPress: () => {}, + }, + + { + textTranslateKey: 'reportActionContextMenu.markAsUnread', + icon: Mail, + successIcon: Checkmark, + shouldShow: () => true, + onPress: (closePopover, {reportAction, reportID}) => { + updateLastReadActionID(reportID, reportAction.sequenceNumber); + setNewMarkerPosition(reportID, reportAction.sequenceNumber); + if (closePopover) { + hideContextMenu(true, ReportActionComposeFocusManager.focus); + } + }, + }, + + { + textTranslateKey: 'reportActionContextMenu.editComment', + icon: Pencil, + shouldShow: reportAction => canEditReportAction(reportAction), + onPress: (closePopover, {reportID, reportAction, draftMessage}) => { + const editAction = () => saveReportActionDraft( + reportID, + reportAction.reportActionID, + _.isEmpty(draftMessage) ? getActionText(reportAction) : '', + ); + + if (closePopover) { + // Hide popover, then call editAction + hideContextMenu(false, editAction); + return; + } + + // No popover to hide, call editAction immediately + editAction(); + }, + }, + { + textTranslateKey: 'reportActionContextMenu.deleteComment', + icon: Trashcan, + shouldShow: reportAction => canDeleteReportAction(reportAction), + onPress: (closePopover, {reportID, reportAction}) => { + if (closePopover) { + // Hide popover, then call showDeleteConfirmModal + hideContextMenu( + false, + () => showDeleteModal(reportID, reportAction), + ); + return; + } + + // No popover to hide, call showDeleteConfirmModal immediately + showDeleteModal(reportID, reportAction); + }, + }, +]; diff --git a/src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js b/src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js new file mode 100644 index 0000000000000..ab21ecf8b7da8 --- /dev/null +++ b/src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js @@ -0,0 +1,32 @@ +import PropTypes from 'prop-types'; +import ReportActionPropTypes from '../ReportActionPropTypes'; + +const propTypes = { + /** The ID of the report this report action is attached to. */ + reportID: PropTypes.number.isRequired, + + /** The report action this context menu is attached to. */ + reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, + + /** If true, this component will be a small, row-oriented menu that displays icons but not text. + If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ + isMini: PropTypes.bool, + + /** Controls the visibility of this component. */ + isVisible: PropTypes.bool, + + /** The copy selection of text. */ + selection: PropTypes.string, + + /** Draft message - if this is set the comment is in 'edit' mode */ + draftMessage: PropTypes.string, +}; + +const defaultProps = { + isMini: false, + isVisible: false, + selection: '', + draftMessage: '', +}; + +export {propTypes, defaultProps}; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js new file mode 100644 index 0000000000000..a57825716bb77 --- /dev/null +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -0,0 +1,36 @@ +import _ from 'underscore'; +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import { + propTypes as GenericReportActionContextMenuPropTypes, + defaultProps as GenericReportActionContextMenuDefaultProps, +} from '../GenericReportActionContextMenuPropTypes'; +import {getMiniReportActionContextMenuWrapperStyle} from '../../../../../styles/getReportActionItemStyles'; +import BaseReportActionContextMenu from '../BaseReportActionContextMenu'; + +const propTypes = { + ..._.omit(GenericReportActionContextMenuPropTypes, ['isMini']), + + /** Should the reportAction this menu is attached to have the appearance of being + * grouped with the previous reportAction? */ + displayAsGroup: PropTypes.bool, +}; + +const defaultProps = { + ..._.omit(GenericReportActionContextMenuDefaultProps, ['isMini']), + displayAsGroup: false, +}; + +const MiniReportActionContextMenu = props => ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + +); + +MiniReportActionContextMenu.propTypes = propTypes; +MiniReportActionContextMenu.defaultProps = defaultProps; +MiniReportActionContextMenu.displayName = 'MiniReportActionContextMenu'; + +export default MiniReportActionContextMenu; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js new file mode 100644 index 0000000000000..461f67a0a4bcb --- /dev/null +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js @@ -0,0 +1 @@ +export default () => null; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js new file mode 100644 index 0000000000000..096645e532c41 --- /dev/null +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -0,0 +1,261 @@ +import React from 'react'; +import { + Dimensions, +} from 'react-native'; +import _ from 'underscore'; +import { + deleteReportComment, +} from '../../../../libs/actions/Report'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; +import BaseReportActionContextMenu from './BaseReportActionContextMenu'; +import ConfirmModal from '../../../../components/ConfirmModal'; + +const propTypes = { + ...withLocalizePropTypes, +}; + +class PopoverReportActionContextMenu extends React.Component { + constructor(props) { + super(props); + + this.state = { + reportID: 0, + reportAction: {}, + selection: '', + reportActionDraftMessage: '', + isPopoverVisible: false, + isDeleteCommentConfirmModalVisible: false, + cursorRelativePosition: { + horizontal: 0, + vertical: 0, + }, + + // The horizontal and vertical position (relative to the screen) where the popover will display. + popoverAnchorPosition: { + horizontal: 0, + vertical: 0, + }, + }; + this.onPopoverHide = () => {}; + this.contextMenuAnchor = undefined; + this.showContextMenu = this.showContextMenu.bind(this); + this.hideContextMenu = this.hideContextMenu.bind(this); + this.measureContent = this.measureContent.bind(this); + this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); + this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); + this.hideDeleteModal = this.hideDeleteModal.bind(this); + this.showDeleteModal = this.showDeleteModal.bind(this); + this.runAndResetOnPopoverHide = this.runAndResetOnPopoverHide.bind(this); + this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); + this.isActiveReportAction = this.isActiveReportAction.bind(this); + } + + componentDidMount() { + Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); + } + + shouldComponentUpdate(nextProps, nextState) { + return this.state.isPopoverVisible !== nextState.isPopoverVisible + || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition + || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible; + } + + componentWillUnmount() { + Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); + } + + /** + * Get the Context menu anchor position + * We calculate the achor coordinates from measureInWindow async method + * + * @returns {Promise} + */ + getContextMenuMeasuredLocation() { + return new Promise((resolve) => { + if (this.contextMenuAnchor) { + this.contextMenuAnchor.measureInWindow((x, y) => resolve({x, y})); + } else { + resolve({x: 0, y: 0}); + } + }); + } + + /** + * Whether Context Menu is active for the Report Action. + * + * @param {Number|String} actionID + * @return {Boolean} + */ + isActiveReportAction(actionID) { + return this.state.reportAction.reportActionID === actionID; + } + + /** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {string} [selection] - A copy text. + * @param {Element} contextMenuAnchor - popoverAnchor + * @param {Number} reportID - Active Report Id + * @param {Object} reportAction - ReportAction for ContextMenu + * @param {String} draftMessage - ReportAction Draftmessage + * @param {Function} [onShow] - Run a callback when Menu is shown + * @param {Function} [onHide] - Run a callback when Menu is hidden + */ + showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShow = () => {}, + onHide = () => {}, + ) { + const nativeEvent = event.nativeEvent || {}; + this.contextMenuAnchor = contextMenuAnchor; + this.onPopoverHide = onHide; + this.getContextMenuMeasuredLocation().then(({x, y}) => { + this.setState({ + cursorRelativePosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }, + reportID, + reportAction, + selection, + isPopoverVisible: true, + reportActionDraftMessage: draftMessage, + }, onShow); + }); + } + + /** + * This gets called on Dimensions change to find the anchor coordinates for the action context menu. + */ + measureContextMenuAnchorPosition() { + if (!this.state.isPopoverVisible) { + return; + } + this.getContextMenuMeasuredLocation().then(({x, y}) => { + if (!x || !y) { + return; + } + this.setState(prev => ({ + popoverAnchorPosition: { + horizontal: prev.cursorRelativePosition.horizontal + x, + vertical: prev.cursorRelativePosition.vertical + y, + }, + })); + }); + } + + /** + * After Popover hides, call the registered onPopoverHide callback and reset it + */ + runAndResetOnPopoverHide() { + this.onPopoverHide(); + + // After we have called the action, reset it. + this.onPopoverHide = () => {}; + } + + /** + * Hide the ReportActionContextMenu modal popover. + * @param {Function} onHideCallback Callback to be called after popover is completely hidden + */ + hideContextMenu(onHideCallback) { + if (_.isFunction(onHideCallback)) { + this.onPopoverHide = onHideCallback; + } + this.setState({ + reportID: 0, + reportAction: {}, + selection: '', + reportActionDraftMessage: '', + isPopoverVisible: false, + }); + } + + /** + * Used to calculate the Context Menu Dimensions + * + * @returns {JSX} + */ + measureContent() { + return ( + + ); + } + + confirmDeleteAndHideModal() { + deleteReportComment(this.state.reportID, this.state.reportAction); + this.setState({isDeleteCommentConfirmModalVisible: false}); + } + + hideDeleteModal() { + this.setState({ + reportID: 0, + reportAction: {}, + isDeleteCommentConfirmModalVisible: false, + }); + } + + /** + * Opens the Confirm delete action modal + * @param {Number} reportID + * @param {Object} reportAction + */ + showDeleteModal(reportID, reportAction) { + this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); + } + + render() { + return ( + <> + + + + + + ); + } +} + +PopoverReportActionContextMenu.propTypes = propTypes; +PopoverReportActionContextMenu.displayName = 'PopoverReportActionContextMenu'; + +export default withLocalize(PopoverReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js new file mode 100644 index 0000000000000..7f438d4b28526 --- /dev/null +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -0,0 +1,99 @@ +import React from 'react'; + +const contextMenuRef = React.createRef(); + +/** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {string} [selection] - A copy text. + * @param {Element} contextMenuAnchor - popoverAnchor + * @param {Number} reportID - Active Report Id + * @param {Object} reportAction - ReportAction for ContextMenu + * @param {String} draftMessage - ReportAction Draftmessage + * @param {Function} [onShow=() => {}] - Run a callback when Menu is shown + * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden + */ +function showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShow = () => {}, + onHide = () => {}, +) { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShow, + onHide, + ); +} + +/** + * Hide the ReportActionContextMenu modal popover. + * Hides the popover menu with an optional delay + * @param {Boolean} shouldDelay - whether the menu should close after a delay + * @param {Function} [onHideCallback=() => {}] - Callback to be called after Context Menu is completely hidden + */ +function hideContextMenu(shouldDelay, onHideCallback = () => {}) { + if (!contextMenuRef.current) { + return; + } + if (!shouldDelay) { + contextMenuRef.current.hideContextMenu(onHideCallback); + + return; + } + setTimeout(() => contextMenuRef.current.hideContextMenu(onHideCallback), 800); +} + +function hideDeleteModal() { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.hideDeleteModal(); +} + +/** + * Opens the Confirm delete action modal + * @param {Number} reportID + * @param {Object} reportAction + */ +function showDeleteModal(reportID, reportAction) { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.showDeleteModal(reportID, reportAction); +} + +/** + * Whether Context Menu is active for the Report Action. + * + * @param {Number|String} actionID + * @return {Boolean} + */ +function isActiveReportAction(actionID) { + if (!contextMenuRef.current) { + return; + } + return contextMenuRef.current.isActiveReportAction(actionID); +} + +export { + contextMenuRef, + showContextMenu, + hideContextMenu, + isActiveReportAction, + showDeleteModal, + hideDeleteModal, +}; diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js index cfd902da46e4a..010edcf25ce26 100644 --- a/src/pages/home/report/ParticipantLocalTime.js +++ b/src/pages/home/report/ParticipantLocalTime.js @@ -35,6 +35,10 @@ class ParticipantLocalTime extends React.Component { }, 1000)); } + shouldComponentUpdate(nextProps, nextState) { + return nextState.localTime !== this.state.localTime; + } + componentWillUnmount() { clearInterval(this.timer); clearInterval(this.readyTimer); @@ -42,6 +46,7 @@ class ParticipantLocalTime extends React.Component { getParticipantLocalTime() { const reportRecipientTimezone = lodashGet(this.props.participant, 'timezone', {}); + moment.locale(this.props.preferredLocale); const reportRecipientDay = moment().tz(reportRecipientTimezone.selected).format('dddd'); const currentUserDay = moment().tz(this.props.currentUserTimezone.selected).format('dddd'); @@ -52,38 +57,29 @@ class ParticipantLocalTime extends React.Component { } render() { - // Moment.format does not return AM or PM values immediately. - // So we have to wait until we are ready before showing the time to the user - const isReportRecipientLocalTimeReady = this.state.localTime.toString().match(/(A|P)M/ig); const reportRecipientDisplayName = this.props.participant.firstName || (Str.isSMSLogin(this.props.participant.login) ? this.props.toLocalPhone(this.props.participant.displayName) : this.props.participant.displayName); return ( - isReportRecipientLocalTimeReady ? ( - - + - - {this.props.translate('common.timePrefix')} - - - {this.state.localTime} - - - {this.props.translate('common.conjunctionFor')} - - - {reportRecipientDisplayName} - - - - ) - : + ]} + numberOfLines={1} + > + {this.props.translate( + 'reportActionCompose.localTime', + { + user: reportRecipientDisplayName, + time: this.state.localTime, + }, + )} + + ); } } diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js deleted file mode 100755 index f7e0b7f63d715..0000000000000 --- a/src/pages/home/report/ReportActionContextMenu.js +++ /dev/null @@ -1,199 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; -import { - Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, -} from '../../../components/Icon/Expensicons'; -import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles'; -import { - setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, -} from '../../../libs/actions/Report'; -import ContextMenuItem from '../../../components/ContextMenuItem'; -import ReportActionPropTypes from './ReportActionPropTypes'; -import Clipboard from '../../../libs/Clipboard'; -import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../libs/reportUtils'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; - -const propTypes = { - /** The ID of the report this report action is attached to. */ - // eslint-disable-next-line react/no-unused-prop-types - reportID: PropTypes.number.isRequired, - - /** The report action this context menu is attached to. */ - reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, - - /** If true, this component will be a small, row-oriented menu that displays icons but not text. - If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ - isMini: PropTypes.bool, - - /** Controls the visibility of this component. */ - isVisible: PropTypes.bool, - - /** The copy selection of text. */ - selection: PropTypes.string, - - /** Draft message - if this is set the comment is in 'edit' mode */ - draftMessage: PropTypes.string, - - /** Function to dismiss the popover containing this menu */ - hidePopover: PropTypes.func.isRequired, - - /** Function to show the delete Action confirmation modal */ - showDeleteConfirmModal: PropTypes.func.isRequired, - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isMini: false, - isVisible: false, - selection: '', - draftMessage: '', -}; - -class ReportActionContextMenu extends React.Component { - constructor(props) { - super(props); - - this.getActionText = this.getActionText.bind(this); - this.hidePopover = this.hidePopover.bind(this); - - // A list of all the context actions in this menu. - this.contextActions = [ - // Copy to clipboard - { - text: this.props.translate('contextMenuItem.copyToClipboard'), - icon: ClipboardIcon, - successText: this.props.translate('contextMenuItem.copied'), - successIcon: Checkmark, - shouldShow: true, - - // If return value is true, we switch the `text` and `icon` on - // `ContextMenuItem` with `successText` and `successIcon` which will fallback to - // the `text` and `icon` - onPress: () => { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - const html = lodashGet(message, 'html', ''); - const text = Str.htmlDecode(props.selection || lodashGet(message, 'text', '')); - const isAttachment = _.has(this.props.reportAction, 'isAttachment') - ? this.props.reportAction.isAttachment - : isReportMessageAttachment(text); - if (!isAttachment) { - Clipboard.setString(text); - } else { - Clipboard.setString(html); - } - this.hidePopover(true, ReportActionComposeFocusManager.focus); - }, - }, - - { - text: this.props.translate('reportActionContextMenu.copyLink'), - icon: LinkCopy, - shouldShow: false, - onPress: () => {}, - }, - - { - text: this.props.translate('reportActionContextMenu.markAsUnread'), - icon: Mail, - successIcon: Checkmark, - shouldShow: true, - onPress: () => { - updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); - setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - this.hidePopover(true, ReportActionComposeFocusManager.focus); - }, - }, - - { - text: this.props.translate('reportActionContextMenu.editComment'), - icon: Pencil, - shouldShow: () => canEditReportAction(this.props.reportAction), - onPress: () => { - const editAction = () => saveReportActionDraft( - this.props.reportID, - this.props.reportAction.reportActionID, - _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', - ); - - if (this.props.isMini) { - // No popover to hide, call editAction immediately - editAction(); - } else { - // Hide popover, then call editAction - this.hidePopover(false, editAction); - } - }, - }, - { - text: this.props.translate('reportActionContextMenu.deleteComment'), - icon: Trashcan, - shouldShow: () => canDeleteReportAction(this.props.reportAction), - onPress: () => { - if (this.props.isMini) { - // No popover to hide, call showDeleteConfirmModal immediately - this.props.showDeleteConfirmModal(); - } else { - // Hide popover, then call showDeleteConfirmModal - this.hidePopover(false, this.props.showDeleteConfirmModal); - } - }, - }, - ]; - - this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); - } - - /** - * Gets the markdown version of the message in an action. - * - * @return {String} - */ - getActionText() { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - return lodashGet(message, 'html', ''); - } - - /** - * Hides the popover menu with an optional delay - * - * @param {Boolean} shouldDelay whether the menu should close after a delay - * @param {Function} [onHideCallback=() => {}] Callback to be called after Popover Menu is hidden - * @memberof ReportActionContextMenu - */ - hidePopover(shouldDelay, onHideCallback = () => {}) { - if (!shouldDelay) { - this.props.hidePopover(onHideCallback); - return; - } - setTimeout(() => this.props.hidePopover(onHideCallback), 800); - } - - render() { - return this.props.isVisible && ( - - {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( - contextAction.onPress(this.props.reportAction)} - /> - ))} - - ); - } -} - -ReportActionContextMenu.propTypes = propTypes; -ReportActionContextMenu.defaultProps = defaultProps; - -export default withLocalize(ReportActionContextMenu); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 64b3a9fa4929e..010f9e2998172 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {Component} from 'react'; -import {Dimensions, View} from 'react-native'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import CONST from '../../../CONST'; @@ -8,25 +8,21 @@ import ONYXKEYS from '../../../ONYXKEYS'; import ReportActionPropTypes from './ReportActionPropTypes'; import { getReportActionItemStyle, - getMiniReportActionContextMenuWrapperStyle, } from '../../../styles/getReportActionItemStyles'; import PressableWithSecondaryInteraction from '../../../components/PressableWithSecondaryInteraction'; import Hoverable from '../../../components/Hoverable'; -import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredContent'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemGrouped from './ReportActionItemGrouped'; -import ReportActionContextMenu from './ReportActionContextMenu'; import IOUAction from '../../../components/ReportActionItem/IOUAction'; import ReportActionItemMessage from './ReportActionItemMessage'; import UnreadActionIndicator from '../../../components/UnreadActionIndicator'; import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; -import ConfirmModal from '../../../components/ConfirmModal'; import compose from '../../../libs/compose'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import {deleteReportComment} from '../../../libs/actions/Report'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; +import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; +import {isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu'; const propTypes = { /** The ID of the report this action is on. */ @@ -50,12 +46,9 @@ const propTypes = { /** Position index of the report action in the overall report FlatList view */ index: PropTypes.number.isRequired, - /* Onyx Props */ - /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, - ...withLocalizePropTypes, ...windowDimensionsPropTypes, }; @@ -67,115 +60,22 @@ const defaultProps = { class ReportActionItem extends Component { constructor(props) { super(props); - - this.onPopoverHide = () => {}; + this.popoverAnchor = undefined; this.state = { - isPopoverVisible: false, - isDeleteCommentConfirmModalVisible: false, - cursorPosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, + isContextMenuActive: isActiveReportAction(props.action.reportActionID), }; - - this.popoverAnchor = undefined; + this.checkIfContextMenuActive = this.checkIfContextMenuActive.bind(this); this.showPopover = this.showPopover.bind(this); - this.hidePopover = this.hidePopover.bind(this); - this.measureContent = this.measureContent.bind(this); - this.selection = ''; - this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); - this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); - this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); - this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); - this.contextMenuHide = this.contextMenuHide.bind(this); - } - - componentDidMount() { - Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); } shouldComponentUpdate(nextProps, nextState) { - return this.state.isPopoverVisible !== nextState.isPopoverVisible - || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition - || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible - || this.props.displayAsGroup !== nextProps.displayAsGroup + return this.props.displayAsGroup !== nextProps.displayAsGroup || this.props.draftMessage !== nextProps.draftMessage || this.props.isMostRecentIOUReportAction !== nextProps.isMostRecentIOUReportAction || this.props.hasOutstandingIOU !== nextProps.hasOutstandingIOU || this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator - || !_.isEqual(this.props.action, nextProps.action); - } - - componentWillUnmount() { - Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); - } - - /** - * Get the Context menu anchor position - * We calculate the achor coordinates from measureInWindow async method - * - * @returns {Promise} - * @memberof ReportActionItem - */ - getMeasureLocation() { - return new Promise((res) => { - if (this.popoverAnchor) { - this.popoverAnchor.measureInWindow((x, y) => res({x, y})); - } else { - res({x: 0, y: 0}); - } - }); - } - - /** - * Save the location of a native press event & set the Initial Context menu anchor coordinates - * - * @param {Object} nativeEvent - * @returns {Promise} - */ - capturePressLocation(nativeEvent) { - return this.getMeasureLocation().then(({x, y}) => { - this.setState({ - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - }); - }); - } - - contextMenuHide() { - this.onPopoverHide(); - - // After we have called the action, reset it. - this.onPopoverHide = () => {}; - } - - /** - * This gets called on Dimensions change to find the anchor coordinates for the action context menu. - */ - measureContextMenuAnchorPosition() { - if (!this.state.isPopoverVisible) { - return; - } - this.getMeasureLocation().then(({x, y}) => { - this.setState(prev => ({ - popoverAnchorPosition: { - horizontal: prev.cursorPosition.horizontal + x, - vertical: prev.cursorPosition.vertical + y, - }, - })); - }); + || !_.isEqual(this.props.action, nextProps.action) + || this.state.isContextMenuActive !== nextState.isContextMenuActive; } /** @@ -189,59 +89,20 @@ class ReportActionItem extends Component { if (this.props.draftMessage) { return; } - const nativeEvent = event.nativeEvent || {}; - this.selection = selection; - this.capturePressLocation(nativeEvent).then(() => { - this.setState({isPopoverVisible: true}); - }); - } - - /** - * Hide the ReportActionContextMenu modal popover. - * @param {Function} onHideCallback Callback to be called after popover is completely hidden - */ - hidePopover(onHideCallback) { - if (_.isFunction(onHideCallback)) { - this.onPopoverHide = onHideCallback; - } - this.setState({isPopoverVisible: false}); - } - - /** - * Used to calculate the Context Menu Dimensions - * - * @returns {JSX} - * @memberof ReportActionItem - */ - measureContent() { - return ( - + showContextMenu( + event, + selection, + this.popoverAnchor, + this.props.reportID, + this.props.action, + this.props.draftMessage, + this.checkIfContextMenuActive, + this.checkIfContextMenuActive, ); } - confirmDeleteAndHideModal() { - deleteReportComment(this.props.reportID, this.props.action); - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - hideDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - /** - * Opens the Confirm delete action modal - * - * @memberof ReportActionItem - */ - showDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: true}); + checkIfContextMenuActive() { + this.setState({isContextMenuActive: isActiveReportAction(this.props.action.reportActionID)}); } render() { @@ -267,98 +128,63 @@ class ReportActionItem extends Component { ); } return ( - <> - this.popoverAnchor = el} - onPressIn={() => this.props.isSmallScreenWidth && canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={this.showPopover} - preventDefaultContentMenu={!this.props.draftMessage} - > - - {hovered => ( - - {this.props.shouldDisplayNewIndicator && ( - + this.popoverAnchor = el} + onPressIn={() => this.props.isSmallScreenWidth && canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onSecondaryInteraction={this.showPopover} + preventDefaultContentMenu={!this.props.draftMessage} + + > + + {hovered => ( + + {this.props.shouldDisplayNewIndicator && ( + + )} + + {!this.props.displayAsGroup + ? ( + + {children} + + ) + : ( + + {children} + )} - > - {!this.props.displayAsGroup - ? ( - - {children} - - ) - : ( - - {children} - - )} - - - - - )} - - - - - - - + + + )} + + ); } } - ReportActionItem.propTypes = propTypes; ReportActionItem.defaultProps = defaultProps; export default compose( withWindowDimensions, - withLocalize, withOnyx({ draftMessage: { key: ({ diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 9db04402951c0..10b1c94214322 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -68,9 +68,8 @@ class ReportActionItemMessageEdit extends React.Component { */ updateDraft(newDraft) { this.textInput.setNativeProps({text: newDraft}); - const trimmedNewDraft = newDraft.trim(); - this.setState({draft: trimmedNewDraft}); - this.debouncedSaveDraft(trimmedNewDraft); + this.setState({draft: newDraft}); + this.debouncedSaveDraft(newDraft); } /** @@ -95,7 +94,8 @@ class ReportActionItemMessageEdit extends React.Component { * the new content. */ publishDraft() { - editReportComment(this.props.reportID, this.props.action, this.state.draft); + const trimmedNewDraft = this.state.draft.trim(); + editReportComment(this.props.reportID, this.props.action, trimmedNewDraft); this.deleteDraft(); } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index f8bea1b7c2d8f..7f5f73b205855 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -34,6 +34,8 @@ import withDrawerState, {withDrawerPropTypes} from '../../../components/withDraw import {flatListRef, scrollToBottom} from '../../../libs/ReportScrollManager'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; +import {contextMenuRef} from './ContextMenu/ReportActionContextMenu'; +import PopoverReportActionContextMenu from './ContextMenu/PopoverReportActionContextMenu'; const propTypes = { /** The ID of the report actions will be created for */ @@ -100,6 +102,7 @@ class ReportActionsView extends React.Component { this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); + this.keyExtractor = this.keyExtractor.bind(this); } componentDidMount() { @@ -216,6 +219,17 @@ class ReportActionsView extends React.Component { } } + /** + * Create a unique key for Each Action in the FlatList. + * We use a combination of sequenceNumber and clientID in case the clientID are the same - which + * shouldn't happen, but might be possible in some rare cases. + * @param {Object} item + * @return {String} + */ + keyExtractor(item) { + return `${item.action.sequenceNumber}${item.action.clientID}`; + } + /** * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently * displaying. @@ -365,7 +379,7 @@ class ReportActionsView extends React.Component { index, }) { const shouldDisplayNewIndicator = this.props.report.newMarkerSequenceNumber > 0 - && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber; + && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber; return ( `${item.action.sequenceNumber}${item.action.clientID}`} - initialRowHeight={32} - onEndReached={this.loadMoreChats} - onEndReachedThreshold={0.75} - ListFooterComponent={this.state.isLoadingMoreChats - ? - : null} - keyboardShouldPersistTaps="handled" - onLayout={this.recordTimeToMeasureItemLayout} - /> + <> + + : null} + keyboardShouldPersistTaps="handled" + onLayout={this.recordTimeToMeasureItemLayout} + /> + + ); } } diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js index d12fdd35d032a..9e25d5627483d 100755 --- a/src/pages/home/report/ReportTypingIndicator.js +++ b/src/pages/home/report/ReportTypingIndicator.js @@ -54,14 +54,14 @@ class ReportTypingIndicator extends React.Component { case 1: return ( - - - {getDisplayName(this.state.usersTyping[0])} - + {getDisplayName(this.state.usersTyping[0])} {` ${this.props.translate('reportTypingIndicator.isTyping')}`} @@ -69,18 +69,16 @@ class ReportTypingIndicator extends React.Component { case 2: return ( - - - {getDisplayName(this.state.usersTyping[0])} - + {getDisplayName(this.state.usersTyping[0])} {` ${this.props.translate('common.and')} `} - - {getDisplayName(this.state.usersTyping[1])} - + {getDisplayName(this.state.usersTyping[1])} {` ${this.props.translate('reportTypingIndicator.areTyping')}`} @@ -88,14 +86,14 @@ class ReportTypingIndicator extends React.Component { default: return ( - - - {this.props.translate('reportTypingIndicator.multipleUsers')} - + {this.props.translate('reportTypingIndicator.multipleUsers')} {` ${this.props.translate('reportTypingIndicator.areTyping')}`} diff --git a/src/pages/settings/InitialPage.js b/src/pages/settings/InitialPage.js index b7dcab7ad978d..064691876484a 100755 --- a/src/pages/settings/InitialPage.js +++ b/src/pages/settings/InitialPage.js @@ -67,6 +67,12 @@ const propTypes = { role: PropTypes.string, })), + /** The user's wallet account */ + userWallet: PropTypes.shape({ + /** The user's current wallet balance */ + availableBalance: PropTypes.number, + }), + ...withLocalizePropTypes, }; @@ -75,6 +81,9 @@ const defaultProps = { network: {}, session: {}, policies: {}, + userWallet: { + availableBalance: 0, + }, }; const defaultMenuItems = [ @@ -113,10 +122,17 @@ const defaultMenuItems = [ const InitialSettingsPage = ({ myPersonalDetails, network, + numberFormat, session, policies, translate, + userWallet, }) => { + const walletBalance = numberFormat( + userWallet.availableBalance, + {style: 'currency', currency: 'USD'}, + ); + // On the very first sign in or after clearing storage these // details will not be present on the first render so we'll just // return nothing for now. @@ -169,6 +185,7 @@ const InitialSettingsPage = ({ {_.map(menuItems, (item, index) => { const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; + const isPaymentItem = item.translationKey === 'common.payments'; return ( ); })} @@ -206,5 +224,8 @@ export default compose( policies: { key: ONYXKEYS.COLLECTION.POLICY, }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, }), )(InitialSettingsPage); diff --git a/src/pages/settings/Payments/AddPayPalMePage.js b/src/pages/settings/Payments/AddPayPalMePage.js index 46928e99cd7b0..97edbe4237731 100644 --- a/src/pages/settings/Payments/AddPayPalMePage.js +++ b/src/pages/settings/Payments/AddPayPalMePage.js @@ -9,7 +9,7 @@ import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; import Text from '../../../components/Text'; import ScreenWrapper from '../../../components/ScreenWrapper'; import NameValuePair from '../../../libs/actions/NameValuePair'; -import {getUserDetails} from '../../../libs/actions/User'; +import getPaymentMethods from '../../../libs/actions/PaymentMethods'; import Navigation from '../../../libs/Navigation/Navigation'; import styles from '../../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; @@ -42,7 +42,7 @@ class AddPayPalMePage extends React.Component { } componentDidMount() { - getUserDetails(); + getPaymentMethods(); } componentDidUpdate(prevProps) { @@ -59,6 +59,7 @@ class AddPayPalMePage extends React.Component { setPayPalMeUsername() { NameValuePair.set(CONST.NVP.PAYPAL_ME_ADDRESS, this.state.payPalMeUsername, ONYXKEYS.NVP_PAYPAL_ME_ADDRESS); Growl.show(this.props.translate('addPayPalMePage.growlMessageOnSave'), CONST.GROWL.SUCCESS, 3000); + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); } render() { @@ -102,7 +103,9 @@ class AddPayPalMePage extends React.Component { onPress={this.setPayPalMeUsername} pressOnEnter style={[styles.mt3]} - text={this.props.translate('addPayPalMePage.addPayPalAccount')} + text={this.props.payPalMeUsername + ? this.props.translate('addPayPalMePage.editPayPalAccount') + : this.props.translate('addPayPalMePage.addPayPalAccount')} /> diff --git a/src/pages/settings/Payments/PaymentMethodList.js b/src/pages/settings/Payments/PaymentMethodList.js index 7e62429e1f38e..5d1b3bd3ceef5 100644 --- a/src/pages/settings/Payments/PaymentMethodList.js +++ b/src/pages/settings/Payments/PaymentMethodList.js @@ -138,19 +138,14 @@ class PaymentMethodList extends Component { }); } - // Don't show Add Payment Method button if user provided details for all possible payment methods. - // Right now only available method is Paypal.me - // When there is a new payment method, it needs to be added to following if condition. - if (!this.props.payPalMeUsername) { - combinedPaymentMethods.push({ - type: MENU_ITEM, - title: this.props.translate('paymentMethodList.addPaymentMethod'), - icon: Plus, - onPress: e => this.props.onPress(e), - key: 'addPaymentMethodButton', - disabled: this.props.isLoadingPayments, - }); - } + combinedPaymentMethods.push({ + type: MENU_ITEM, + title: this.props.translate('paymentMethodList.addPaymentMethod'), + icon: Plus, + onPress: e => this.props.onPress(e), + key: 'addPaymentMethodButton', + disabled: this.props.isLoadingPayments, + }); return combinedPaymentMethods; } diff --git a/src/pages/settings/Payments/PaymentsPage.js b/src/pages/settings/Payments/PaymentsPage.js index 3d266d1cfa489..70296695c1112 100644 --- a/src/pages/settings/Payments/PaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage.js @@ -1,8 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../ONYXKEYS'; import PaymentMethodList from './PaymentMethodList'; import ROUTES from '../../../ROUTES'; import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; @@ -23,9 +20,6 @@ import CurrentWalletBalance from '../../../components/CurrentWalletBalance'; const PAYPAL = 'payPalMe'; const propTypes = { - /** User's paypal.me username if they have one */ - payPalMeUsername: PropTypes.string, - ...withLocalizePropTypes, }; @@ -129,13 +123,11 @@ class PaymentsPage extends React.Component { left: this.state.anchorPositionLeft, }} > - {!this.props.payPalMeUsername && ( - this.addPaymentMethodTypePressed(PAYPAL)} - /> - )} + this.addPaymentMethodTypePressed(PAYPAL)} + /> @@ -149,9 +141,4 @@ PaymentsPage.displayName = 'PaymentsPage'; export default compose( withLocalize, - withOnyx({ - payPalMeUsername: { - key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, - }, - }), )(PaymentsPage); diff --git a/src/setup/index.native.js b/src/setup/index.native.js index 20469e3101ecd..3e6e8990599d5 100644 --- a/src/setup/index.native.js +++ b/src/setup/index.native.js @@ -1,4 +1,10 @@ -// No additional setup required for native platforms +// Setup Flipper plugins when on dev export default function () { - return null; + // eslint-disable-next-line no-undef + if (__DEV__) { + require('flipper-plugin-bridgespy-client'); + const RNAsyncStorageFlipper = require('rn-async-storage-flipper').default; + const AsyncStorage = require('@react-native-async-storage/async-storage').default; + RNAsyncStorageFlipper(AsyncStorage); + } } diff --git a/src/styles/addOutlineWidth/index.js b/src/styles/addOutlineWidth/index.js index 96868ba9d9420..566a1ef34df4d 100644 --- a/src/styles/addOutlineWidth/index.js +++ b/src/styles/addOutlineWidth/index.js @@ -3,6 +3,8 @@ * can be added to the object */ +import themeDefault from '../themes/default'; + /** * Adds the addOutlineWidth property to an object to be used when styling * @@ -14,6 +16,7 @@ function withOutlineWidth(obj, val) { return { ...obj, outlineWidth: val, + boxShadow: `0px 0px 0px ${val}px ${themeDefault.borderFocus}`, }; } diff --git a/src/styles/styles.js b/src/styles/styles.js index 7311fe1818e75..b63b2dd771c61 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -88,14 +88,6 @@ const styles = { lineHeight: 14, }, - textMicroSupportingBold: { - color: themeColors.textSupporting, - fontFamily: fontFamily.GTA_BOLD, - fontWeight: fontWeightBold, - fontSize: variables.fontSizeSmall, - lineHeight: 14, - }, - textLarge: { fontSize: variables.fontSizeLarge, }, @@ -424,7 +416,7 @@ const styles = { }, badgeText: { - color: themeColors.textReversed, + color: themeColors.text, fontSize: variables.fontSizeSmall, lineHeight: 16, ...whiteSpace.noWrap, @@ -1033,10 +1025,12 @@ const styles = { paddingLeft: 20, paddingRight: 20, display: 'flex', + backgroundColor: themeColors.appBG, }, chatItemComposeWithFirstRow: { minHeight: 90, + marginTop: -16, }, chatItemComposeBoxColor: { @@ -2068,17 +2062,21 @@ function getBackgroundColorStyle(backgroundColor) { } /** - * Generate a style for the background color of the IOU badge + * Generate a style for the background color of the Badge * - * @param {Boolean} isOwner - * @param {Boolean} [isPressed] - * @returns {Object} + * @param {Boolean} success + * @param {Boolean} error + * @param {boolean} [isPressed=false] + * @return {Object} */ -function getBadgeColorStyle(isOwner, isPressed = false) { - if (isOwner) { +function getBadgeColorStyle(success, error, isPressed = false) { + if (success) { return isPressed ? styles.badgeSuccessPressed : styles.badgeSuccess; } - return isPressed ? styles.badgeDangerPressed : styles.badgeDanger; + if (error) { + return isPressed ? styles.badgeDangerPressed : styles.badgeDanger; + } + return {}; } /** diff --git a/src/styles/utilities/sizing.js b/src/styles/utilities/sizing.js index 9724e9acf7989..1dbb4bd4c4500 100644 --- a/src/styles/utilities/sizing.js +++ b/src/styles/utilities/sizing.js @@ -16,10 +16,6 @@ export default { width: '50%', }, - w20: { - width: '20%', - }, - mwn: { maxWidth: 'auto', }, diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js index b168a82df0fc7..ac0e411bd8b7f 100644 --- a/tests/unit/NetworkTest.js +++ b/tests/unit/NetworkTest.js @@ -15,6 +15,13 @@ jest.mock('../../src/libs/Notification/PushNotification', () => ({ jest.useFakeTimers(); +Onyx.init({ + keys: ONYXKEYS, + registerStorageEventListener: () => {}, +}); + +beforeEach(() => Onyx.clear().then(waitForPromisesToResolve)); + test('failing to reauthenticate while offline should not log out user', () => { // Given a test user login and account ID const TEST_USER_LOGIN = 'test@testguy.com'; diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index f46a93bc3ad56..5d85bcd3f333c 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,7 +1,10 @@ +const _ = require('underscore'); +const {error: AnnotationError} = require('@actions/core'); const translate = require('../../src/libs/translate'); -const translations = require('../../src/languages/translations'); const CONFIG = require('../../src/CONFIG'); +const translations = require('../../src/languages/translations'); +const originalTranslations = _.clone(translations); translations.default = { en: { testKey1: 'English', @@ -52,3 +55,44 @@ describe('translate', () => { expect(translate.translate('en', ['testKeyGroup', 'testFunction'], {testVariable})).toBe(expectedValue); }); }); + +describe('Translation Keys', () => { + function traverseKeyPath(source, path, keyPaths) { + const pathArray = keyPaths || []; + const keyPath = path ? `${path}.` : ''; + _.each(_.keys(source), (key) => { + if (_.isObject(source[key]) && !_.isFunction(source[key])) { + traverseKeyPath(source[key], keyPath + key, pathArray); + } else { + pathArray.push(keyPath + key); + } + }); + return pathArray; + } + const excludeLanguages = ['en', 'es-ES']; + const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const mainLanguage = originalTranslations.default.en; + const mainLanguageKeys = traverseKeyPath(mainLanguage); + + _.each(languages, (ln) => { + const languageKeys = traverseKeyPath(originalTranslations.default[ln]); + + it(`Does ${ln} locale has all the keys`, () => { + const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + } + expect(hasAllKeys).toEqual([]); + }); + + it(`Does ${ln} locale has unused keys`, () => { + const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + } + expect(hasAllKeys).toEqual([]); + }); + }); +}); diff --git a/web/index.html b/web/index.html index df4c09c794445..a96e576510c2b 100644 --- a/web/index.html +++ b/web/index.html @@ -32,6 +32,14 @@ -webkit-user-select: none !important; -webkit-touch-callout: none !important; } + :focus-visible { + outline: 0; + box-shadow: 0px 0px 0px 1px #0185ff; + } + :focus[data-focusvisible-polyfill] { + outline: 0; + box-shadow: 0px 0px 0px 1px #0185ff; + }