diff --git a/.eslintrc.js b/.eslintrc.js index f852c970f85c..281f8269804e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,13 @@ const restrictedImportPaths = [ { name: 'react-native', - importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable'], + importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', 'Text'], message: [ '', "For 'useWindowDimensions', please use 'src/hooks/useWindowDimensions' instead.", "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", "For 'StatusBar', please use 'src/libs/StatusBar' instead.", + "For 'Text', please use '@components/Text' instead.", ].join('\n'), }, { diff --git a/README.md b/README.md index 3b9010695760..f6629af8604d 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ In order to have more consistent builds, we use a strict `node` and `npm` versio ## Configuring HTTPS The webpack development server now uses https. If you're using a mac, you can simply run `npm run setup-https`. -If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `new.expesify.com.dev` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`. +If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `dev.new.expensify.com` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`. ## Running the web app 🕸 * To run the **development web app**: `npm run web` diff --git a/android/app/build.gradle b/android/app/build.gradle index 162147aeff0c..47f19acfe6ae 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001042407 - versionName "1.4.24-7" + versionCode 1001042601 + versionName "1.4.26-1" } flavorDimensions "default" diff --git a/android/app/src/main/res/drawable/ic_launcher_monochrome.png b/android/app/src/main/res/drawable/ic_launcher_monochrome.png index b1a286b6f8dd..0af99b087923 100644 Binary files a/android/app/src/main/res/drawable/ic_launcher_monochrome.png and b/android/app/src/main/res/drawable/ic_launcher_monochrome.png differ diff --git a/assets/images/expensify-logo--adhoc.svg b/assets/images/expensify-logo--adhoc.svg index 273002deca9b..52b381dc4b78 100644 --- a/assets/images/expensify-logo--adhoc.svg +++ b/assets/images/expensify-logo--adhoc.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/assets/images/expensify-logo--dev.svg b/assets/images/expensify-logo--dev.svg index e8e3fb5033d9..2c9ae142e283 100644 --- a/assets/images/expensify-logo--dev.svg +++ b/assets/images/expensify-logo--dev.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/assets/images/expensify-logo--staging.svg b/assets/images/expensify-logo--staging.svg index 78dcc1581f99..a1e7482c133b 100644 --- a/assets/images/expensify-logo--staging.svg +++ b/assets/images/expensify-logo--staging.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/assets/images/home-background--mobile-new.svg b/assets/images/home-background--mobile-new.svg index 0da937cae059..d81f2a18cc78 100644 --- a/assets/images/home-background--mobile-new.svg +++ b/assets/images/home-background--mobile-new.svg @@ -1,8835 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/new-expensify.svg b/assets/images/new-expensify.svg index 38276ecd9385..7bfef1fd38b4 100644 --- a/assets/images/new-expensify.svg +++ b/assets/images/new-expensify.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/product-illustrations/payment-hands.svg b/assets/images/product-illustrations/payment-hands.svg index bf76b528ee76..2dbebd24994b 100644 --- a/assets/images/product-illustrations/payment-hands.svg +++ b/assets/images/product-illustrations/payment-hands.svg @@ -1 +1,140 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/product-illustrations/telescope.svg b/assets/images/product-illustrations/telescope.svg index 95617c801789..1830dff0fe3c 100644 --- a/assets/images/product-illustrations/telescope.svg +++ b/assets/images/product-illustrations/telescope.svg @@ -1,79 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/signIn/google-logo.svg b/assets/images/signIn/google-logo.svg index 4fbdc804a0a2..169ea34b23ee 100644 --- a/assets/images/signIn/google-logo.svg +++ b/assets/images/signIn/google-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__bigrocket.svg b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg index 1afd5f66b6ea..64d6dc2200f0 100644 --- a/assets/images/simple-illustrations/simple-illustration__bigrocket.svg +++ b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg @@ -1,100 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg index 829d3ee2e3fe..ab9d3ae4db70 100644 --- a/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg +++ b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg @@ -1,22 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__handcard.svg b/assets/images/simple-illustrations/simple-illustration__handcard.svg index 7419b33d425c..a49e0ee5b77f 100644 --- a/assets/images/simple-illustrations/simple-illustration__handcard.svg +++ b/assets/images/simple-illustrations/simple-illustration__handcard.svg @@ -1,41 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg index 471b978bb97e..5b5e12a99a9b 100644 --- a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg +++ b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg @@ -1,98 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__hourglass.svg b/assets/images/simple-illustrations/simple-illustration__hourglass.svg index 539e1e45b795..683e74a657e8 100644 --- a/assets/images/simple-illustrations/simple-illustration__hourglass.svg +++ b/assets/images/simple-illustrations/simple-illustration__hourglass.svg @@ -1,56 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__mailbox.svg b/assets/images/simple-illustrations/simple-illustration__mailbox.svg index 81b1f508fb52..7af7c71e24f3 100644 --- a/assets/images/simple-illustrations/simple-illustration__mailbox.svg +++ b/assets/images/simple-illustrations/simple-illustration__mailbox.svg @@ -1,71 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__smallrocket.svg b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg index 0f8f166c849f..388bb968a762 100644 --- a/assets/images/simple-illustrations/simple-illustration__smallrocket.svg +++ b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg @@ -1,45 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__trashcan.svg b/assets/images/simple-illustrations/simple-illustration__trashcan.svg index 4e66efa0a67e..66cc9ee27550 100644 --- a/assets/images/simple-illustrations/simple-illustration__trashcan.svg +++ b/assets/images/simple-illustrations/simple-illustration__trashcan.svg @@ -1,52 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/thumbs-up.svg b/assets/images/thumbs-up.svg index ef81c88fc854..3e2a4a5125b6 100644 --- a/assets/images/thumbs-up.svg +++ b/assets/images/thumbs-up.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/docs/assets/images/send-money.svg b/docs/assets/images/send-money.svg index e858f0d5c327..7abce818f09e 100644 --- a/docs/assets/images/send-money.svg +++ b/docs/assets/images/send-money.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/assets/images/subscription-annual.svg b/docs/assets/images/subscription-annual.svg index a4b99a43b16e..f74ce086b2c7 100644 --- a/docs/assets/images/subscription-annual.svg +++ b/docs/assets/images/subscription-annual.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7081805db569..bb67f5840fad 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.24 + 1.4.26 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.24.7 + 1.4.26.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 20d4ea1a4820..8d5fb7867c37 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.24 + 1.4.26 CFBundleSignature ???? CFBundleVersion - 1.4.24.7 + 1.4.26.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f941edc1100e..85f148305fde 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -3,9 +3,9 @@ CFBundleShortVersionString - 1.4.24 + 1.4.26 CFBundleVersion - 1.4.24.7 + 1.4.26.1 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index acc8720dafce..f433c4f1e1e2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1176,11 +1176,11 @@ PODS: - React-Core - react-native-key-command (1.0.6): - React-Core - - react-native-netinfo (11.1.0): + - react-native-netinfo (11.2.1): - React-Core - react-native-pager-view (6.2.2): - React-Core - - react-native-pdf (6.7.4): + - react-native-pdf (6.7.3): - React-Core - react-native-performance (5.1.0): - React-Core @@ -1909,9 +1909,9 @@ SPEC CHECKSUMS: react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56 react-native-image-picker: c33d4e79f0a14a2b66e5065e14946ae63749660b react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5 - react-native-netinfo: 3aa5637c18834966e0c932de8ae1ae56fea20a97 + react-native-netinfo: 8a7fd3f7130ef4ad2fb4276d5c9f8d3f28d2df3d react-native-pager-view: 02a5c4962530f7efc10dd51ee9cdabeff5e6c631 - react-native-pdf: 79aa75e39a80c1d45ffe58aa500f3cf08f267a2e + react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886 react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1 react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4 @@ -1967,7 +1967,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7 - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 diff --git a/package-lock.json b/package-lock.json index ac012bea728f..ab98b21fca69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.24-7", + "version": "1.4.26-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.24-7", + "version": "1.4.26-1", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -27,7 +27,7 @@ "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.0.6", - "@react-native-community/netinfo": "11.1.0", + "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", "@react-native-firebase/crashlytics": "^12.3.0", @@ -94,9 +94,9 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.126", + "react-native-onyx": "1.0.118", "react-native-pager-view": "6.2.2", - "react-native-pdf": "^6.7.4", + "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", @@ -9608,9 +9608,9 @@ } }, "node_modules/@react-native-community/netinfo": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.1.0.tgz", - "integrity": "sha512-pIbCuqgrY7SkngAcjUs9fMzNh1h4soQMVw1IeGp1HN5//wox3fUVOuvyIubTscUbdLFKiltJAiuQek7Nhx1bqA==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.2.1.tgz", + "integrity": "sha512-n9kgmH7vLaU7Cdo8vGfJGGwhrlgppaOSq5zKj9I7H4k5iRM3aNtwURw83mgrc22Ip7nSye2afZV2xDiIyvHttQ==", "peerDependencies": { "react-native": ">=0.59" } @@ -47034,17 +47034,17 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.126", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.126.tgz", - "integrity": "sha512-tUJI1mQaWXLfyBFYQQWM6mm9GiCqIXGvjbqJkH1fLY3OqbGW6DyH4CxC+qJrqfi4bKZgZHp5xlBHhkPV4pKK2A==", + "version": "1.0.118", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz", + "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", "underscore": "^1.13.6" }, "engines": { - "node": "20.9.0", - "npm": "10.1.0" + "node": ">=16.15.1 <=20.9.0", + "npm": ">=8.11.0 <=10.1.0" }, "peerDependencies": { "idb-keyval": "^6.2.1", @@ -47079,9 +47079,9 @@ } }, "node_modules/react-native-pdf": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz", - "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", + "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", "dependencies": { "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" @@ -62630,9 +62630,9 @@ "requires": {} }, "@react-native-community/netinfo": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.1.0.tgz", - "integrity": "sha512-pIbCuqgrY7SkngAcjUs9fMzNh1h4soQMVw1IeGp1HN5//wox3fUVOuvyIubTscUbdLFKiltJAiuQek7Nhx1bqA==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.2.1.tgz", + "integrity": "sha512-n9kgmH7vLaU7Cdo8vGfJGGwhrlgppaOSq5zKj9I7H4k5iRM3aNtwURw83mgrc22Ip7nSye2afZV2xDiIyvHttQ==", "requires": {} }, "@react-native-firebase/analytics": { @@ -89702,9 +89702,9 @@ } }, "react-native-onyx": { - "version": "1.0.126", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.126.tgz", - "integrity": "sha512-tUJI1mQaWXLfyBFYQQWM6mm9GiCqIXGvjbqJkH1fLY3OqbGW6DyH4CxC+qJrqfi4bKZgZHp5xlBHhkPV4pKK2A==", + "version": "1.0.118", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz", + "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -89718,9 +89718,9 @@ "requires": {} }, "react-native-pdf": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz", - "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", + "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", "requires": { "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" diff --git a/package.json b/package.json index 4a28617f649d..7d792cae8cc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.24-7", + "version": "1.4.26-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -75,7 +75,7 @@ "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.0.6", - "@react-native-community/netinfo": "11.1.0", + "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", "@react-native-firebase/crashlytics": "^12.3.0", @@ -142,9 +142,9 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.126", + "react-native-onyx": "1.0.118", "react-native-pager-view": "6.2.2", - "react-native-pdf": "^6.7.4", + "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", diff --git a/patches/react-native-blob-util+0.17.3.patch b/patches/react-native-blob-util+0.17.3.patch new file mode 100644 index 000000000000..2ade175a7b30 --- /dev/null +++ b/patches/react-native-blob-util+0.17.3.patch @@ -0,0 +1,17 @@ +diff --git a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java +index 4b41402..4f07fc6 100644 +--- a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java ++++ b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java +@@ -279,7 +279,11 @@ public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnabl + DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE); + downloadManagerId = dm.enqueue(req); + androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId)); +- appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); ++ if(Build.VERSION.SDK_INT >= 34 ){ ++ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED); ++ }else{ ++ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); ++ } + future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { diff --git a/src/CONST.ts b/src/CONST.ts index 8b5c0f5a88ca..d6f3d3cdcef6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -479,7 +479,9 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', - EXPENSIFY_INBOX_URL: 'https://www.expensify.com/inbox', + OLDDOT_URLS: { + INBOX: 'inbox', + }, SIGN_IN_FORM_WIDTH: 300, @@ -601,9 +603,11 @@ const CONST = { }, THREAD_DISABLED: ['CREATED'], }, + CANCEL_PAYMENT_REASONS: { + ADMIN: 'CANCEL_REASON_ADMIN', + }, ACTIONABLE_MENTION_WHISPER_RESOLUTION: { INVITE: 'invited', - NOTHING: 'nothing', }, ARCHIVE_REASON: { DEFAULT: 'default', @@ -635,18 +639,13 @@ const CONST = { ANNOUNCE: '#announce', ADMINS: '#admins', }, - STATE: { - OPEN: 'OPEN', - SUBMITTED: 'SUBMITTED', - PROCESSING: 'PROCESSING', - }, STATE_NUM: { OPEN: 0, - PROCESSING: 1, - SUBMITTED: 2, + SUBMITTED: 1, + APPROVED: 2, BILLING: 3, }, - STATUS: { + STATUS_NUM: { OPEN: 0, SUBMITTED: 1, CLOSED: 2, @@ -1445,6 +1444,8 @@ const CONST = { INVISIBLE_CHARACTERS_GROUPS: /[\p{C}\p{Z}]/gu, OTHER_INVISIBLE_CHARACTERS: /[\u3164]/g, + + REPORT_FIELD_TITLE: /{report:([a-zA-Z]+)}/g, }, PRONOUNS: { @@ -2730,7 +2731,7 @@ const CONST = { EXPECTED_OUTPUT: 'FCFA 123,457', }, - PATHS_TO_TREAT_AS_EXTERNAL: ['NewExpensify.dmg'], + PATHS_TO_TREAT_AS_EXTERNAL: ['NewExpensify.dmg', 'docs/index.html'], // Test tool menu parameters TEST_TOOL: { diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.tsx similarity index 72% rename from src/components/AddressSearch/CurrentLocationButton.js rename to src/components/AddressSearch/CurrentLocationButton.tsx index 06541565f567..11bd0a64eba5 100644 --- a/src/components/AddressSearch/CurrentLocationButton.js +++ b/src/components/AddressSearch/CurrentLocationButton.tsx @@ -1,29 +1,16 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import {Text} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import colors from '@styles/theme/colors'; +import type {CurrentLocationButtonProps} from './types'; -const propTypes = { - /** Callback that runs when location button is clicked */ - onPress: PropTypes.func, - - /** Boolean to indicate if the button is clickable */ - isDisabled: PropTypes.bool, -}; - -const defaultProps = { - isDisabled: false, - onPress: () => {}, -}; - -function CurrentLocationButton({onPress, isDisabled}) { +function CurrentLocationButton({onPress, isDisabled = false}: CurrentLocationButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -32,7 +19,7 @@ function CurrentLocationButton({onPress, isDisabled}) { onPress?.()} accessibilityLabel={translate('location.useCurrent')} disabled={isDisabled} onMouseDown={(e) => e.preventDefault()} @@ -48,7 +35,5 @@ function CurrentLocationButton({onPress, isDisabled}) { } CurrentLocationButton.displayName = 'CurrentLocationButton'; -CurrentLocationButton.propTypes = propTypes; -CurrentLocationButton.defaultProps = defaultProps; export default CurrentLocationButton; diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.tsx similarity index 63% rename from src/components/AddressSearch/index.js rename to src/components/AddressSearch/index.tsx index 357f5af8cb58..89e87eeebe54 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.tsx @@ -1,184 +1,79 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; +import {ActivityIndicator, Keyboard, LogBox, ScrollView, View} from 'react-native'; +import type {LayoutChangeEvent} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; -import _ from 'underscore'; +import type {GooglePlaceData, GooglePlaceDetail} from 'react-native-google-places-autocomplete'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import LocationErrorMessage from '@components/LocationErrorMessage'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; +import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; -import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; +import type {GeolocationErrorCodeType} from '@libs/getCurrentPosition/getCurrentPosition.types'; import * as GooglePlacesUtils from '@libs/GooglePlacesUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import CurrentLocationButton from './CurrentLocationButton'; import isCurrentTargetInsideContainer from './isCurrentTargetInsideContainer'; +import type {AddressSearchProps, RenamedInputKeysProps} from './types'; // The error that's being thrown below will be ignored until we fork the // react-native-google-places-autocomplete repo and replace the // VirtualizedList component with a VirtualizedList-backed instead LogBox.ignoreLogs(['VirtualizedLists should never be nested']); -const propTypes = { - /** The ID used to uniquely identify the input in a Form */ - inputID: PropTypes.string, - - /** Saves a draft of the input value when used in a form */ - shouldSaveDraft: PropTypes.bool, - - /** Callback that is called when the text input is blurred */ - onBlur: PropTypes.func, - - /** Error text to display */ - errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), - - /** Hint text to display */ - hint: PropTypes.string, - - /** The label to display for the field */ - label: PropTypes.string.isRequired, - - /** The value to set the field to initially */ - value: PropTypes.string, - - /** The value to set the field to initially */ - defaultValue: PropTypes.string, - - /** A callback function when the value of this field has changed */ - onInputChange: PropTypes.func.isRequired, - - /** A callback function when an address has been auto-selected */ - onPress: PropTypes.func, - - /** Customize the TextInput container */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /** Should address search be limited to results in the USA */ - isLimitedToUSA: PropTypes.bool, - - /** Shows a current location button in suggestion list */ - canUseCurrentLocation: PropTypes.bool, - - /** A list of predefined places that can be shown when the user isn't searching for something */ - predefinedPlaces: PropTypes.arrayOf( - PropTypes.shape({ - /** A description of the location (usually the address) */ - description: PropTypes.string, - - /** The name of the location */ - name: PropTypes.string, - - /** Data required by the google auto complete plugin to know where to put the markers on the map */ - geometry: PropTypes.shape({ - /** Data about the location */ - location: PropTypes.shape({ - /** Lattitude of the location */ - lat: PropTypes.number, - - /** Longitude of the location */ - lng: PropTypes.number, - }), - }), - }), - ), - - /** A map of inputID key names */ - renamedInputKeys: PropTypes.shape({ - street: PropTypes.string, - street2: PropTypes.string, - city: PropTypes.string, - state: PropTypes.string, - lat: PropTypes.string, - lng: PropTypes.string, - zipCode: PropTypes.string, - }), - - /** Maximum number of characters allowed in search input */ - maxInputLength: PropTypes.number, - - /** The result types to return from the Google Places Autocomplete request */ - resultTypes: PropTypes.string, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** Location bias for querying search results. */ - locationBias: PropTypes.string, - - ...withLocalizePropTypes, -}; - -const defaultProps = { - inputID: undefined, - shouldSaveDraft: false, - onBlur: () => {}, - onPress: () => {}, - errorText: '', - hint: '', - value: undefined, - defaultValue: undefined, - containerStyles: [], - isLimitedToUSA: false, - canUseCurrentLocation: false, - renamedInputKeys: { - street: 'addressStreet', - street2: 'addressStreet2', - city: 'addressCity', - state: 'addressState', - zipCode: 'addressZipCode', - lat: 'addressLat', - lng: 'addressLng', - }, - maxInputLength: undefined, - predefinedPlaces: [], - resultTypes: 'address', - locationBias: undefined, -}; - -function AddressSearch({ - canUseCurrentLocation, - containerStyles, - defaultValue, - errorText, - hint, - innerRef, - inputID, - isLimitedToUSA, - label, - maxInputLength, - network, - onBlur, - onInputChange, - onPress, - predefinedPlaces, - preferredLocale, - renamedInputKeys, - resultTypes, - shouldSaveDraft, - translate, - value, - locationBias, -}) { +function AddressSearch( + { + canUseCurrentLocation = false, + containerStyles, + defaultValue, + errorText = '', + hint = '', + inputID, + isLimitedToUSA = false, + label, + maxInputLength, + onBlur, + onInputChange, + onPress, + predefinedPlaces = [], + preferredLocale, + renamedInputKeys = { + street: 'addressStreet', + street2: 'addressStreet2', + city: 'addressCity', + state: 'addressState', + zipCode: 'addressZipCode', + lat: 'addressLat', + lng: 'addressLng', + }, + resultTypes = 'address', + shouldSaveDraft = false, + value, + locationBias, + }: AddressSearchProps, + ref: ForwardedRef, +) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); const [displayListViewBorder, setDisplayListViewBorder] = useState(false); const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [searchValue, setSearchValue] = useState(value || defaultValue || ''); - const [locationErrorCode, setLocationErrorCode] = useState(null); + const [locationErrorCode, setLocationErrorCode] = useState(null); const [isFetchingCurrentLocation, setIsFetchingCurrentLocation] = useState(false); const shouldTriggerGeolocationCallbacks = useRef(true); - const containerRef = useRef(); + const containerRef = useRef(null); const query = useMemo( () => ({ language: preferredLocale, @@ -189,18 +84,18 @@ function AddressSearch({ [preferredLocale, resultTypes, isLimitedToUSA, locationBias], ); const shouldShowCurrentLocationButton = canUseCurrentLocation && searchValue.trim().length === 0 && isFocused; - const saveLocationDetails = (autocompleteData, details) => { - const addressComponents = details.address_components; + const saveLocationDetails = (autocompleteData: GooglePlaceData, details: GooglePlaceDetail | null) => { + const addressComponents = details?.address_components; if (!addressComponents) { // When there are details, but no address_components, this indicates that some predefined options have been passed // to this component which don't match the usual properties coming from auto-complete. In that case, only a limited // amount of data massaging needs to happen for what the parent expects to get from this function. - if (_.size(details)) { - onPress({ - address: autocompleteData.description || lodashGet(details, 'description', ''), - lat: lodashGet(details, 'geometry.location.lat', 0), - lng: lodashGet(details, 'geometry.location.lng', 0), - name: lodashGet(details, 'name'), + if (details) { + onPress?.({ + address: autocompleteData.description ?? '', + lat: details.geometry.location.lat ?? 0, + lng: details.geometry.location.lng ?? 0, + name: details.name, }); } return; @@ -219,14 +114,19 @@ function AddressSearch({ administrative_area_level_2: stateFallback, country: countryPrimary, } = GooglePlacesUtils.getAddressComponents(addressComponents, { + // eslint-disable-next-line @typescript-eslint/naming-convention street_number: 'long_name', route: 'long_name', subpremise: 'long_name', locality: 'long_name', sublocality: 'long_name', + // eslint-disable-next-line @typescript-eslint/naming-convention postal_town: 'long_name', + // eslint-disable-next-line @typescript-eslint/naming-convention postal_code: 'long_name', + // eslint-disable-next-line @typescript-eslint/naming-convention administrative_area_level_1: 'short_name', + // eslint-disable-next-line @typescript-eslint/naming-convention administrative_area_level_2: 'long_name', country: 'short_name', }); @@ -234,6 +134,7 @@ function AddressSearch({ // The state's iso code (short_name) is needed for the StatePicker component but we also // need the state's full name (long_name) when we render the state in a TextInput. const {administrative_area_level_1: longStateName} = GooglePlacesUtils.getAddressComponents(addressComponents, { + // eslint-disable-next-line @typescript-eslint/naming-convention administrative_area_level_1: 'long_name', }); @@ -243,15 +144,16 @@ function AddressSearch({ country: countryFallbackLongName = '', state: stateAutoCompleteFallback = '', city: cityAutocompleteFallback = '', - } = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData.terms); + } = GooglePlacesUtils.getPlaceAutocompleteTerms(autocompleteData?.terms ?? []); - const countryFallback = _.findKey(CONST.ALL_COUNTRIES, (country) => country === countryFallbackLongName); + const countryFallback = Object.keys(CONST.ALL_COUNTRIES).find((country) => country === countryFallbackLongName); - const country = countryPrimary || countryFallback; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const country = countryPrimary || countryFallback || ''; const values = { street: `${streetNumber} ${streetName}`.trim(), - name: lodashGet(details, 'name', ''), + name: details.name ?? '', // Autocomplete returns any additional valid address fragments (e.g. Apt #) as subpremise. street2: subpremise, // Make sure country is updated first, since city and state will be reset if the country changes @@ -264,9 +166,9 @@ function AddressSearch({ city: locality || postalTown || sublocality || cityAutocompleteFallback, zipCode, - lat: lodashGet(details, 'geometry.location.lat', 0), - lng: lodashGet(details, 'geometry.location.lng', 0), - address: autocompleteData.description || lodashGet(details, 'formatted_address', ''), + lat: details.geometry.location.lat ?? 0, + lng: details.geometry.location.lng ?? 0, + address: autocompleteData.description || details.formatted_address || '', }; // If the address is not in the US, use the full length state name since we're displaying the address's @@ -282,7 +184,7 @@ function AddressSearch({ } // Set the state to be the same as the city in case the state is empty. - if (_.isEmpty(values.state)) { + if (!values.state) { values.state = values.city; } @@ -290,8 +192,8 @@ function AddressSearch({ // We are setting up a fallback to ensure "values.street" is populated with a relevant value if (!values.street && details.adr_address) { const streetAddressRegex = /([^<]*)<\/span>/; - const adr_address = details.adr_address.match(streetAddressRegex); - const streetAddressFallback = lodashGet(adr_address, [1], null); + const adrAddress = details.adr_address.match(streetAddressRegex); + const streetAddressFallback = adrAddress ? adrAddress?.[1] : null; if (streetAddressFallback) { values.street = streetAddressFallback; } @@ -299,28 +201,28 @@ function AddressSearch({ // Not all pages define the Address Line 2 field, so in that case we append any additional address details // (e.g. Apt #) to Address Line 1 - if (subpremise && typeof renamedInputKeys.street2 === 'undefined') { + if (subpremise && typeof renamedInputKeys?.street2 === 'undefined') { values.street += `, ${subpremise}`; } - const isValidCountryCode = lodashGet(CONST.ALL_COUNTRIES, country); + const isValidCountryCode = !!Object.keys(CONST.ALL_COUNTRIES).find((foundCountry) => foundCountry === country); if (isValidCountryCode) { values.country = country; } if (inputID) { - _.each(values, (inputValue, key) => { - const inputKey = lodashGet(renamedInputKeys, key, key); + Object.entries(values).forEach(([key, inputValue]) => { + const inputKey = renamedInputKeys?.[key as keyof RenamedInputKeysProps] ?? key; if (!inputKey) { return; } - onInputChange(inputValue, inputKey); + onInputChange?.(inputValue, inputKey); }); } else { - onInputChange(values); + onInputChange?.(values); } - onPress(values); + onPress?.(values); }; /** Gets the user's current location and registers success/error callbacks */ @@ -351,7 +253,7 @@ function AddressSearch({ address: CONST.YOUR_LOCATION_TEXT, name: CONST.YOUR_LOCATION_TEXT, }; - onPress(location); + onPress?.(location); }, (errorData) => { if (!shouldTriggerGeolocationCallbacks.current) { @@ -368,19 +270,22 @@ function AddressSearch({ ); }; - const renderHeaderComponent = () => - predefinedPlaces.length > 0 && ( - <> - {/* This will show current location button in list if there are some recent destinations */} - {shouldShowCurrentLocationButton && ( - - )} - {!value && {translate('common.recentDestinations')}} - - ); + const renderHeaderComponent = () => ( + <> + {predefinedPlaces.length > 0 && ( + <> + {/* This will show current location button in list if there are some recent destinations */} + {shouldShowCurrentLocationButton && ( + + )} + {!value && {translate('common.recentDestinations')}} + + )} + + ); // eslint-disable-next-line arrow-body-style useEffect(() => { @@ -392,10 +297,8 @@ function AddressSearch({ const listEmptyComponent = useCallback( () => - network.isOffline || !isTyping ? null : ( - {translate('common.noResultsFound')} - ), - [network.isOffline, isTyping, styles, translate], + !!isOffline || !isTyping ? null : {translate('common.noResultsFound')}, + [isOffline, isTyping, styles, translate], ); const listLoader = useCallback( @@ -464,27 +367,15 @@ function AddressSearch({ query={query} requestUrl={{ useOnPlatform: 'all', - url: network.isOffline ? null : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), + url: isOffline ? '' : ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}), }} textInputProps={{ InputComp: TextInput, - ref: (node) => { - if (!innerRef) { - return; - } - - if (_.isFunction(innerRef)) { - innerRef(node); - return; - } - - // eslint-disable-next-line no-param-reassign - innerRef.current = node; - }, + ref, label, containerStyles, errorText, - hint: displayListViewBorder || (predefinedPlaces.length === 0 && shouldShowCurrentLocationButton) || (canUseCurrentLocation && isTyping) ? undefined : hint, + hint: displayListViewBorder || (predefinedPlaces?.length === 0 && shouldShowCurrentLocationButton) || (canUseCurrentLocation && isTyping) ? undefined : hint, value, defaultValue, inputID, @@ -498,20 +389,19 @@ function AddressSearch({ setIsFocused(false); setIsTyping(false); } - onBlur(); + onBlur?.(); }, autoComplete: 'off', - onInputChange: (text) => { + onInputChange: (text: string) => { setSearchValue(text); setIsTyping(true); if (inputID) { - onInputChange(text); + onInputChange?.(text); } else { onInputChange({street: text}); } - // If the text is empty and we have no predefined places, we set displayListViewBorder to false to prevent UI flickering - if (_.isEmpty(text) && _.isEmpty(predefinedPlaces)) { + if (!text && !predefinedPlaces.length) { setDisplayListViewBorder(false); } }, @@ -530,22 +420,21 @@ function AddressSearch({ isRowScrollable={false} listHoverColor={theme.border} listUnderlayColor={theme.buttonPressedBG} - onLayout={(event) => { + onLayout={(event: LayoutChangeEvent) => { // We use the height of the element to determine if we should hide the border of the listView dropdown // to prevent a lingering border when there are no address suggestions. setDisplayListViewBorder(event.nativeEvent.layout.height > variables.googleEmptyListViewHeight); }} inbetweenCompo={ // We want to show the current location button even if there are no recent destinations - predefinedPlaces.length === 0 && shouldShowCurrentLocationButton ? ( + predefinedPlaces?.length === 0 && + shouldShowCurrentLocationButton && ( - ) : ( - <> ) } placeholder="" @@ -561,18 +450,6 @@ function AddressSearch({ ); } -AddressSearch.propTypes = propTypes; -AddressSearch.defaultProps = defaultProps; -AddressSearch.displayName = 'AddressSearch'; - -const AddressSearchWithRef = React.forwardRef((props, ref) => ( - -)); - -AddressSearchWithRef.displayName = 'AddressSearchWithRef'; +AddressSearch.displayName = 'AddressSearchWithRef'; -export default compose(withNetwork(), withLocalize)(AddressSearchWithRef); +export default forwardRef(AddressSearch); diff --git a/src/components/AddressSearch/isCurrentTargetInsideContainer.js b/src/components/AddressSearch/isCurrentTargetInsideContainer.js deleted file mode 100644 index 18bfc10a8dcb..000000000000 --- a/src/components/AddressSearch/isCurrentTargetInsideContainer.js +++ /dev/null @@ -1,8 +0,0 @@ -function isCurrentTargetInsideContainer(event, containerRef) { - // The related target check is required here - // because without it when we select an option, the onBlur will still trigger setting displayListViewBorder to false - // it will make the auto complete component re-render before onPress is called making selecting an option not working. - return containerRef.current && event.target && containerRef.current.contains(event.relatedTarget); -} - -export default isCurrentTargetInsideContainer; diff --git a/src/components/AddressSearch/isCurrentTargetInsideContainer.native.js b/src/components/AddressSearch/isCurrentTargetInsideContainer.native.js deleted file mode 100644 index dbf0004b08d9..000000000000 --- a/src/components/AddressSearch/isCurrentTargetInsideContainer.native.js +++ /dev/null @@ -1,6 +0,0 @@ -function isCurrentTargetInsideContainer() { - // The related target check is not required here because in native there is no race condition rendering like on the web - return false; -} - -export default isCurrentTargetInsideContainer; diff --git a/src/components/AddressSearch/isCurrentTargetInsideContainer.native.ts b/src/components/AddressSearch/isCurrentTargetInsideContainer.native.ts new file mode 100644 index 000000000000..b53b9e3ddec0 --- /dev/null +++ b/src/components/AddressSearch/isCurrentTargetInsideContainer.native.ts @@ -0,0 +1,6 @@ +import type {IsCurrentTargetInsideContainerType} from './types'; + +// The related target check is not required here because in native there is no race condition rendering like on the web +const isCurrentTargetInsideContainer: IsCurrentTargetInsideContainerType = () => false; + +export default isCurrentTargetInsideContainer; diff --git a/src/components/AddressSearch/isCurrentTargetInsideContainer.ts b/src/components/AddressSearch/isCurrentTargetInsideContainer.ts new file mode 100644 index 000000000000..a50eb747b400 --- /dev/null +++ b/src/components/AddressSearch/isCurrentTargetInsideContainer.ts @@ -0,0 +1,14 @@ +import type {IsCurrentTargetInsideContainerType} from './types'; + +const isCurrentTargetInsideContainer: IsCurrentTargetInsideContainerType = (event, containerRef) => { + // The related target check is required here + // because without it when we select an option, the onBlur will still trigger setting displayListViewBorder to false + // it will make the auto complete component re-render before onPress is called making selecting an option not working. + if (!containerRef.current || !event.target || !('relatedTarget' in event) || !('contains' in containerRef.current)) { + return false; + } + + return !!containerRef.current.contains(event.relatedTarget as Node); +}; + +export default isCurrentTargetInsideContainer; diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts new file mode 100644 index 000000000000..8016f1b2ea39 --- /dev/null +++ b/src/components/AddressSearch/types.ts @@ -0,0 +1,96 @@ +import type {RefObject} from 'react'; +import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, View, ViewStyle} from 'react-native'; +import type {Place} from 'react-native-google-places-autocomplete'; +import type Locale from '@src/types/onyx/Locale'; + +type CurrentLocationButtonProps = { + /** Callback that is called when the button is clicked */ + onPress?: () => void; + + /** Boolean to indicate if the button is clickable */ + isDisabled?: boolean; +}; + +type RenamedInputKeysProps = { + street: string; + street2: string; + city: string; + state: string; + lat: string; + lng: string; + zipCode: string; +}; + +type OnPressProps = { + address: string; + lat: number; + lng: number; + name: string; +}; + +type StreetValue = { + street: string; +}; + +type AddressSearchProps = { + /** The ID used to uniquely identify the input in a Form */ + inputID?: string; + + /** Saves a draft of the input value when used in a form */ + shouldSaveDraft?: boolean; + + /** Callback that is called when the text input is blurred */ + onBlur?: () => void; + + /** Error text to display */ + errorText?: string; + + /** Hint text to display */ + hint?: string; + + /** The label to display for the field */ + label: string; + + /** The value to set the field to initially */ + value?: string; + + /** The value to set the field to initially */ + defaultValue?: string; + + /** A callback function when the value of this field has changed */ + onInputChange: (value: string | number | RenamedInputKeysProps | StreetValue, key?: string) => void; + + /** A callback function when an address has been auto-selected */ + onPress?: (props: OnPressProps) => void; + + /** Customize the TextInput container */ + containerStyles?: StyleProp; + + /** Should address search be limited to results in the USA */ + isLimitedToUSA?: boolean; + + /** Shows a current location button in suggestion list */ + canUseCurrentLocation?: boolean; + + /** A list of predefined places that can be shown when the user isn't searching for something */ + predefinedPlaces?: Place[]; + + /** A map of inputID key names */ + renamedInputKeys: RenamedInputKeysProps; + + /** Maximum number of characters allowed in search input */ + maxInputLength?: number; + + /** The result types to return from the Google Places Autocomplete request */ + resultTypes?: string; + + /** Location bias for querying search results. */ + locationBias?: string; + + /** The user's preferred locale e.g. 'en', 'es-ES' */ + preferredLocale?: Locale; +}; + +type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean; + +export type {CurrentLocationButtonProps, AddressSearchProps, RenamedInputKeysProps, IsCurrentTargetInsideContainerType}; diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx index bb3792f59d9f..99a0ee3bf683 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import React, {useEffect, useRef} from 'react'; +// eslint-disable-next-line no-restricted-imports import type {Text as RNText} from 'react-native'; import {StyleSheet} from 'react-native'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index ad79e316baf3..04e8a5f8d55b 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Text, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx/lib/types'; import useLocalize from '@hooks/useLocalize'; @@ -9,6 +9,7 @@ import type {PersonalDetails, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; import Button from './Button'; import ExpensifyWordmark from './ExpensifyWordmark'; +import Text from './Text'; type AnonymousReportFooterProps = { /** The report currently being looked at */ diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js index 92cbe3a4da04..f69fe7eb5ecb 100644 --- a/src/components/AvatarCropModal/ImageCropView.js +++ b/src/components/AvatarCropModal/ImageCropView.js @@ -6,7 +6,6 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; import gestureHandlerPropTypes from './gestureHandlerPropTypes'; @@ -51,7 +50,6 @@ const defaultProps = { }; function ImageCropView(props) { - const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize); @@ -90,7 +88,8 @@ function ImageCropView(props) { diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index 5993e60861f5..807029addf5e 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -33,10 +33,10 @@ type FullPageNotFoundViewProps = { linkKey?: TranslationPaths; /** Method to trigger when pressing the back button of the header */ - onBackButtonPress: () => void; + onBackButtonPress?: () => void; /** Function to call when pressing the navigation link */ - onLinkPress: () => void; + onLinkPress?: () => void; }; // eslint-disable-next-line rulesdir/no-negated-variables diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.tsx similarity index 56% rename from src/components/ButtonWithDropdownMenu.js rename to src/components/ButtonWithDropdownMenu.tsx index 4d3ec8796a31..466c68229a32 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -1,89 +1,89 @@ -import PropTypes from 'prop-types'; +import type {RefObject} from 'react'; import React, {useEffect, useRef, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import _ from 'underscore'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; +import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import sourcePropTypes from './Image/sourcePropTypes'; +import type {AnchorAlignment} from './Popover/types'; import PopoverMenu from './PopoverMenu'; -const propTypes = { +type DropdownOption = { + value: string; + text: string; + icon: IconAsset; + iconWidth?: number; + iconHeight?: number; + iconDescription?: string; +}; + +type ButtonWithDropdownMenuProps = { /** Text to display for the menu header */ - menuHeaderText: PropTypes.string, + menuHeaderText?: string; /** Callback to execute when the main button is pressed */ - onPress: PropTypes.func.isRequired, + onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void; /** Call the onPress function on main button when Enter key is pressed */ - pressOnEnter: PropTypes.bool, + pressOnEnter?: boolean; /** Whether we should show a loading state for the main button */ - isLoading: PropTypes.bool, + isLoading?: boolean; /** The size of button size */ - buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)), + buttonSize: ValueOf; /** Should the confirmation button be disabled? */ - isDisabled: PropTypes.bool, + isDisabled?: boolean; /** Additional styles to add to the component */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + style?: StyleProp; /** Menu options to display */ /** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */ - options: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - icon: sourcePropTypes, - iconWidth: PropTypes.number, - iconHeight: PropTypes.number, - iconDescription: PropTypes.string, - }), - ).isRequired, + options: DropdownOption[]; /** The anchor alignment of the popover menu */ - anchorAlignment: PropTypes.shape({ - horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), - vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), - }), + anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + buttonRef: RefObject; }; -const defaultProps = { - isLoading: false, - isDisabled: false, - pressOnEnter: false, - menuHeaderText: '', - style: [], - buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, - anchorAlignment: { +function ButtonWithDropdownMenu({ + isLoading = false, + isDisabled = false, + pressOnEnter = false, + menuHeaderText = '', + style, + buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, + anchorAlignment = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, - buttonRef: () => {}, -}; - -function ButtonWithDropdownMenu(props) { + buttonRef, + onPress, + options, +}: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedItemIndex, setSelectedItemIndex] = useState(0); const [isMenuVisible, setIsMenuVisible] = useState(false); - const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {windowWidth, windowHeight} = useWindowDimensions(); - const caretButton = useRef(null); - const selectedItem = props.options[selectedItemIndex] || _.first(props.options); - const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(props.buttonSize); - const isButtonSizeLarge = props.buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; + const caretButton = useRef(null); + const selectedItem = options[selectedItemIndex] || options[0]; + const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize); + const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; useEffect(() => { if (!caretButton.current) { @@ -92,29 +92,31 @@ function ButtonWithDropdownMenu(props) { if (!isMenuVisible) { return; } - caretButton.current.measureInWindow((x, y, w, h) => { - setPopoverAnchorPosition({ - horizontal: x + w, - vertical: - props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP - ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding - : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + if ('measureInWindow' in caretButton.current) { + caretButton.current.measureInWindow((x, y, w, h) => { + setPopoverAnchorPosition({ + horizontal: x + w, + vertical: + anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP + ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding + : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + }); }); - }); - }, [windowWidth, windowHeight, isMenuVisible, props.anchorAlignment.vertical]); + } + }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]); return ( - {props.options.length > 1 ? ( - + {options.length > 1 ? ( +