diff --git a/android/app/build.gradle b/android/app/build.gradle index 72cf2bbc9b6d..abdbe75c326c 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 1001044102 - versionName "1.4.41-2" + versionCode 1001044103 + versionName "1.4.41-3" } flavorDimensions "default" diff --git a/assets/emojis/common.ts b/assets/emojis/common.ts index b23383590c51..c19d958812d1 100644 --- a/assets/emojis/common.ts +++ b/assets/emojis/common.ts @@ -2174,62 +2174,62 @@ const emojis: PickerEmojis = [ { name: 'people_holding_hands', code: '🧑‍🤝‍🧑', - types: ['🧑🏿‍🤝‍🧑🏿', '🧑🏿‍🤝‍🧑🏾', '🧑🏿‍🤝‍🧑🏽', '🧑🏿‍🤝‍🧑🏼', '🧑🏿‍🤝‍🧑🏻', '🧑🏾‍🤝‍🧑🏿', '🧑🏾‍🤝‍🧑🏾', '🧑🏾‍🤝‍🧑🏽', '🧑🏾‍🤝‍🧑🏼', '🧑🏾‍🤝‍🧑🏻', '🧑🏽‍🤝‍🧑🏿', '🧑🏽‍🤝‍🧑🏾', '🧑🏽‍🤝‍🧑🏽', '🧑🏽‍🤝‍🧑🏼', '🧑🏽‍🤝‍🧑🏻', '🧑🏼‍🤝‍🧑🏿', '🧑🏼‍🤝‍🧑🏾', '🧑🏼‍🤝‍🧑🏽', '🧑🏼‍🤝‍🧑🏼', '🧑🏼‍🤝‍🧑🏻', '🧑🏻‍🤝‍🧑🏿', '🧑🏻‍🤝‍🧑🏾', '🧑🏻‍🤝‍🧑🏽', '🧑🏻‍🤝‍🧑🏼', '🧑🏻‍🤝‍🧑🏻'], + types: ['🧑🏿‍🤝‍🧑🏿', '🧑🏾‍🤝‍🧑🏾', '🧑🏽‍🤝‍🧑🏽', '🧑🏼‍🤝‍🧑🏼', '🧑🏻‍🤝‍🧑🏻'], }, { name: 'two_women_holding_hands', code: '👭', - types: ['👩🏿‍🤝‍👩🏾', '👩🏿‍🤝‍👩🏽', '👩🏿‍🤝‍👩🏼', '👩🏿‍🤝‍👩🏻', '👩🏾‍🤝‍👩🏿', '👩🏾‍🤝‍👩🏽', '👩🏾‍🤝‍👩🏼', '👩🏾‍🤝‍👩🏻', '👩🏽‍🤝‍👩🏿', '👩🏽‍🤝‍👩🏾', '👩🏽‍🤝‍👩🏼', '👩🏽‍🤝‍👩🏻', '👩🏼‍🤝‍👩🏿', '👩🏼‍🤝‍👩🏾', '👩🏼‍🤝‍👩🏽', '👩🏼‍🤝‍👩🏻', '👩🏻‍🤝‍👩🏿', '👩🏻‍🤝‍👩🏾', '👩🏻‍🤝‍👩🏽', '👩🏻‍🤝‍👩🏼', '👭🏿', '👭🏾', '👭🏽', '👭🏼', '👭🏻'], + types: ['👭🏿', '👭🏾', '👭🏽', '👭🏼', '👭🏻'], }, { name: 'couple', code: '👫', - types: ['👩🏿‍🤝‍👨🏾', '👩🏿‍🤝‍👨🏽', '👩🏿‍🤝‍👨🏼', '👩🏿‍🤝‍👨🏻', '👩🏾‍🤝‍👨🏿', '👩🏾‍🤝‍👨🏽', '👩🏾‍🤝‍👨🏼', '👩🏾‍🤝‍👨🏻', '👩🏽‍🤝‍👨🏿', '👩🏽‍🤝‍👨🏾', '👩🏽‍🤝‍👨🏼', '👩🏽‍🤝‍👨🏻', '👩🏼‍🤝‍👨🏿', '👩🏼‍🤝‍👨🏾', '👩🏼‍🤝‍👨🏽', '👩🏼‍🤝‍👨🏻', '👩🏻‍🤝‍👨🏿', '👩🏻‍🤝‍👨🏾', '👩🏻‍🤝‍👨🏽', '👩🏻‍🤝‍👨🏼', '👫🏿', '👫🏾', '👫🏽', '👫🏼', '👫🏻'], + types: ['👫🏿', '👫🏾', '👫🏽', '👫🏼', '👫🏻'], }, { name: 'two_men_holding_hands', code: '👬', - types: ['👨🏿‍🤝‍👨🏾', '👨🏿‍🤝‍👨🏽', '👨🏿‍🤝‍👨🏼', '👨🏿‍🤝‍👨🏻', '👨🏾‍🤝‍👨🏿', '👨🏾‍🤝‍👨🏽', '👨🏾‍🤝‍👨🏼', '👨🏾‍🤝‍👨🏻', '👨🏽‍🤝‍👨🏿', '👨🏽‍🤝‍👨🏾', '👨🏽‍🤝‍👨🏼', '👨🏽‍🤝‍👨🏻', '👨🏼‍🤝‍👨🏿', '👨🏼‍🤝‍👨🏾', '👨🏼‍🤝‍👨🏽', '👨🏼‍🤝‍👨🏻', '👨🏻‍🤝‍👨🏿', '👨🏻‍🤝‍👨🏾', '👨🏻‍🤝‍👨🏽', '👨🏻‍🤝‍👨🏼', '👬🏿', '👬🏾', '👬🏽', '👬🏼', '👬🏻'], + types: ['👬🏿', '👬🏾', '👬🏽', '👬🏼', '👬🏻'], }, { name: 'couplekiss', code: '💏', - types: ['🧑🏿‍❤️‍💋‍🧑🏾', '🧑🏿‍❤️‍💋‍🧑🏽', '🧑🏿‍❤️‍💋‍🧑🏼', '🧑🏿‍❤️‍💋‍🧑🏻', '🧑🏾‍❤️‍💋‍🧑🏿', '🧑🏾‍❤️‍💋‍🧑🏽', '🧑🏾‍❤️‍💋‍🧑🏼', '🧑🏾‍❤️‍💋‍🧑🏻', '🧑🏽‍❤️‍💋‍🧑🏿', '🧑🏽‍❤️‍💋‍🧑🏾', '🧑🏽‍❤️‍💋‍🧑🏼', '🧑🏽‍❤️‍💋‍🧑🏻', '🧑🏼‍❤️‍💋‍🧑🏿', '🧑🏼‍❤️‍💋‍🧑🏾', '🧑🏼‍❤️‍💋‍🧑🏽', '🧑🏼‍❤️‍💋‍🧑🏻', '🧑🏻‍❤️‍💋‍🧑🏿', '🧑🏻‍❤️‍💋‍🧑🏾', '🧑🏻‍❤️‍💋‍🧑🏽', '🧑🏻‍❤️‍💋‍🧑🏼', '💏🏿', '💏🏾', '💏🏽', '💏🏼', '💏🏻'], + types: ['💏🏿', '💏🏾', '💏🏽', '💏🏼', '💏🏻'], }, { name: 'couplekiss_man_woman', code: '👩‍❤️‍💋‍👨', - types: ['👩🏿‍❤️‍💋‍👨🏿', '👩🏿‍❤️‍💋‍👨🏾', '👩🏿‍❤️‍💋‍👨🏽', '👩🏿‍❤️‍💋‍👨🏼', '👩🏿‍❤️‍💋‍👨🏻', '👩🏾‍❤️‍💋‍👨🏿', '👩🏾‍❤️‍💋‍👨🏾', '👩🏾‍❤️‍💋‍👨🏽', '👩🏾‍❤️‍💋‍👨🏼', '👩🏾‍❤️‍💋‍👨🏻', '👩🏽‍❤️‍💋‍👨🏿', '👩🏽‍❤️‍💋‍👨🏾', '👩🏽‍❤️‍💋‍👨🏽', '👩🏽‍❤️‍💋‍👨🏼', '👩🏽‍❤️‍💋‍👨🏻', '👩🏼‍❤️‍💋‍👨🏿', '👩🏼‍❤️‍💋‍👨🏾', '👩🏼‍❤️‍💋‍👨🏽', '👩🏼‍❤️‍💋‍👨🏼', '👩🏼‍❤️‍💋‍👨🏻', '👩🏻‍❤️‍💋‍👨🏿', '👩🏻‍❤️‍💋‍👨🏾', '👩🏻‍❤️‍💋‍👨🏽', '👩🏻‍❤️‍💋‍👨🏼', '👩🏻‍❤️‍💋‍👨🏻'], + types: ['👩🏿‍❤️‍💋‍👨🏿', '👩🏾‍❤️‍💋‍👨🏾', '👩🏽‍❤️‍💋‍👨🏽', '👩🏼‍❤️‍💋‍👨🏼', '👩🏻‍❤️‍💋‍👨🏻'], }, { name: 'couplekiss_man_man', code: '👨‍❤️‍💋‍👨', - types: ['👨🏿‍❤️‍💋‍👨🏿', '👨🏿‍❤️‍💋‍👨🏾', '👨🏿‍❤️‍💋‍👨🏽', '👨🏿‍❤️‍💋‍👨🏼', '👨🏿‍❤️‍💋‍👨🏻', '👨🏾‍❤️‍💋‍👨🏿', '👨🏾‍❤️‍💋‍👨🏾', '👨🏾‍❤️‍💋‍👨🏽', '👨🏾‍❤️‍💋‍👨🏼', '👨🏾‍❤️‍💋‍👨🏻', '👨🏽‍❤️‍💋‍👨🏿', '👨🏽‍❤️‍💋‍👨🏾', '👨🏽‍❤️‍💋‍👨🏽', '👨🏽‍❤️‍💋‍👨🏼', '👨🏽‍❤️‍💋‍👨🏻', '👨🏼‍❤️‍💋‍👨🏿', '👨🏼‍❤️‍💋‍👨🏾', '👨🏼‍❤️‍💋‍👨🏽', '👨🏼‍❤️‍💋‍👨🏼', '👨🏼‍❤️‍💋‍👨🏻', '👨🏻‍❤️‍💋‍👨🏿', '👨🏻‍❤️‍💋‍👨🏾', '👨🏻‍❤️‍💋‍👨🏽', '👨🏻‍❤️‍💋‍👨🏼', '👨🏻‍❤️‍💋‍👨🏻'], + types: ['👨🏿‍❤️‍💋‍👨🏿', '👨🏾‍❤️‍💋‍👨🏾', '👨🏽‍❤️‍💋‍👨🏽', '👨🏼‍❤️‍💋‍👨🏼', '👨🏻‍❤️‍💋‍👨🏻'], }, { name: 'couplekiss_woman_woman', code: '👩‍❤️‍💋‍👩', - types: ['👩🏿‍❤️‍💋‍👩🏿', '👩🏿‍❤️‍💋‍👩🏾', '👩🏿‍❤️‍💋‍👩🏽', '👩🏿‍❤️‍💋‍👩🏼', '👩🏿‍❤️‍💋‍👩🏻', '👩🏾‍❤️‍💋‍👩🏿', '👩🏾‍❤️‍💋‍👩🏾', '👩🏾‍❤️‍💋‍👩🏽', '👩🏾‍❤️‍💋‍👩🏼', '👩🏾‍❤️‍💋‍👩🏻', '👩🏽‍❤️‍💋‍👩🏿', '👩🏽‍❤️‍💋‍👩🏾', '👩🏽‍❤️‍💋‍👩🏽', '👩🏽‍❤️‍💋‍👩🏼', '👩🏽‍❤️‍💋‍👩🏻', '👩🏼‍❤️‍💋‍👩🏿', '👩🏼‍❤️‍💋‍👩🏾', '👩🏼‍❤️‍💋‍👩🏽', '👩🏼‍❤️‍💋‍👩🏼', '👩🏼‍❤️‍💋‍👩🏻', '👩🏻‍❤️‍💋‍👩🏿', '👩🏻‍❤️‍💋‍👩🏾', '👩🏻‍❤️‍💋‍👩🏽', '👩🏻‍❤️‍💋‍👩🏼', '👩🏻‍❤️‍💋‍👩🏻'], + types: ['👩🏿‍❤️‍💋‍👩🏿', '👩🏾‍❤️‍💋‍👩🏾', '👩🏽‍❤️‍💋‍👩🏽', '👩🏼‍❤️‍💋‍👩🏼', '👩🏻‍❤️‍💋‍👩🏻'], }, { name: 'couple_with_heart', code: '💑', - types: ['🧑🏿‍❤️‍🧑🏾', '🧑🏿‍❤️‍🧑🏽', '🧑🏿‍❤️‍🧑🏼', '🧑🏿‍❤️‍🧑🏻', '🧑🏾‍❤️‍🧑🏿', '🧑🏾‍❤️‍🧑🏽', '🧑🏾‍❤️‍🧑🏼', '🧑🏾‍❤️‍🧑🏻', '🧑🏽‍❤️‍🧑🏿', '🧑🏽‍❤️‍🧑🏾', '🧑🏽‍❤️‍🧑🏼', '🧑🏽‍❤️‍🧑🏻', '🧑🏼‍❤️‍🧑🏿', '🧑🏼‍❤️‍🧑🏾', '🧑🏼‍❤️‍🧑🏽', '🧑🏼‍❤️‍🧑🏻', '🧑🏻‍❤️‍🧑🏿', '🧑🏻‍❤️‍🧑🏾', '🧑🏻‍❤️‍🧑🏽', '🧑🏻‍❤️‍🧑🏼', '💑🏿', '💑🏾', '💑🏽', '💑🏼', '💑🏻'], + types: ['💑🏿', '💑🏾', '💑🏽', '💑🏼', '💑🏻'], }, { name: 'couple_with_heart_woman_man', code: '👩‍❤️‍👨', - types: ['👩🏿‍❤️‍👨🏿', '👩🏿‍❤️‍👨🏾', '👩🏿‍❤️‍👨🏽', '👩🏿‍❤️‍👨🏼', '👩🏿‍❤️‍👨🏻', '👩🏾‍❤️‍👨🏿', '👩🏾‍❤️‍👨🏾', '👩🏾‍❤️‍👨🏽', '👩🏾‍❤️‍👨🏼', '👩🏾‍❤️‍👨🏻', '👩🏽‍❤️‍👨🏿', '👩🏽‍❤️‍👨🏾', '👩🏽‍❤️‍👨🏽', '👩🏽‍❤️‍👨🏼', '👩🏽‍❤️‍👨🏻', '👩🏼‍❤️‍👨🏿', '👩🏼‍❤️‍👨🏾', '👩🏼‍❤️‍👨🏽', '👩🏼‍❤️‍👨🏼', '👩🏼‍❤️‍👨🏻', '👩🏻‍❤️‍👨🏿', '👩🏻‍❤️‍👨🏾', '👩🏻‍❤️‍👨🏽', '👩🏻‍❤️‍👨🏼', '👩🏻‍❤️‍👨🏻'], + types: ['👩🏿‍❤️‍👨🏿', '👩🏾‍❤️‍👨🏾', '👩🏽‍❤️‍👨🏽', '👩🏼‍❤️‍👨🏼', '👩🏻‍❤️‍👨🏻'], }, { name: 'couple_with_heart_man_man', code: '👨‍❤️‍👨', - types: ['👨🏿‍❤️‍👨🏿', '👨🏿‍❤️‍👨🏾', '👨🏿‍❤️‍👨🏽', '👨🏿‍❤️‍👨🏼', '👨🏿‍❤️‍👨🏻', '👨🏾‍❤️‍👨🏿', '👨🏾‍❤️‍👨🏾', '👨🏾‍❤️‍👨🏽', '👨🏾‍❤️‍👨🏼', '👨🏾‍❤️‍👨🏻', '👨🏽‍❤️‍👨🏿', '👨🏽‍❤️‍👨🏾', '👨🏽‍❤️‍👨🏽', '👨🏽‍❤️‍👨🏼', '👨🏽‍❤️‍👨🏻', '👨🏼‍❤️‍👨🏿', '👨🏼‍❤️‍👨🏾', '👨🏼‍❤️‍👨🏽', '👨🏼‍❤️‍👨🏼', '👨🏼‍❤️‍👨🏻', '👨🏻‍❤️‍👨🏿', '👨🏻‍❤️‍👨🏾', '👨🏻‍❤️‍👨🏽', '👨🏻‍❤️‍👨🏼', '👨🏻‍❤️‍👨🏻'], + types: ['👨🏿‍❤️‍👨🏿', '👨🏾‍❤️‍👨🏾', '👨🏽‍❤️‍👨🏽', '👨🏼‍❤️‍👨🏼', '👨🏻‍❤️‍👨🏻'], }, { name: 'couple_with_heart_woman_woman', code: '👩‍❤️‍👩', - types: ['👩🏿‍❤️‍👩🏿', '👩🏿‍❤️‍👩🏾', '👩🏿‍❤️‍👩🏽', '👩🏿‍❤️‍👩🏼', '👩🏿‍❤️‍👩🏻', '👩🏾‍❤️‍👩🏿', '👩🏾‍❤️‍👩🏾', '👩🏾‍❤️‍👩🏽', '👩🏾‍❤️‍👩🏼', '👩🏾‍❤️‍👩🏻', '👩🏽‍❤️‍👩🏿', '👩🏽‍❤️‍👩🏾', '👩🏽‍❤️‍👩🏽', '👩🏽‍❤️‍👩🏼', '👩🏽‍❤️‍👩🏻', '👩🏼‍❤️‍👩🏿', '👩🏼‍❤️‍👩🏾', '👩🏼‍❤️‍👩🏽', '👩🏼‍❤️‍👩🏼', '👩🏼‍❤️‍👩🏻', '👩🏻‍❤️‍👩🏿', '👩🏻‍❤️‍👩🏾', '👩🏻‍❤️‍👩🏽', '👩🏻‍❤️‍👩🏼', '👩🏻‍❤️‍👩🏻'], + types: ['👩🏿‍❤️‍👩🏿', '👩🏾‍❤️‍👩🏾', '👩🏽‍❤️‍👩🏽', '👩🏼‍❤️‍👩🏼', '👩🏻‍❤️‍👩🏻'], }, { name: 'family', diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index 8ad574d3b2e0..cc859f220608 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -97,6 +97,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ {from: 'web/apple-touch-icon.png'}, {from: 'assets/images/expensify-app-icon.svg'}, {from: 'web/manifest.json'}, + {from: 'web/gtm.js'}, {from: 'assets/css', to: 'css'}, {from: 'assets/fonts/web', to: 'fonts'}, {from: 'assets/sounds', to: 'sounds'}, diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ffc0769a9571..adc1a821ad03 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.41.2 + 1.4.41.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 86825c7cc4a3..0e64efddfc07 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.41.2 + 1.4.41.3 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 06238fb247c9..c97cce9ed42e 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.41 CFBundleVersion - 1.4.41.2 + 1.4.41.3 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 5db1d6cfe86d..6b0757301477 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.41-2", + "version": "1.4.41-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.41-2", + "version": "1.4.41-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -76,7 +76,7 @@ "react-error-boundary": "^4.0.11", "react-map-gl": "^7.1.3", "react-native": "0.73.2", - "react-native-android-location-enabler": "^1.2.2", + "react-native-android-location-enabler": "^2.0.1", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.1", "react-native-config": "^1.4.5", @@ -44811,11 +44811,15 @@ } }, "node_modules/react-native-android-location-enabler": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/react-native-android-location-enabler/-/react-native-android-location-enabler-1.2.2.tgz", - "integrity": "sha512-CC5ghRoK3jkGNK8jdIiYIc3l0XZuQuMt2KEfldDpnMCkNz2aAfUWyLCoOniFLqtdD9poA3az+kCmUzTvLAyTiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-native-android-location-enabler/-/react-native-android-location-enabler-2.0.1.tgz", + "integrity": "sha512-hDNfQL4gImrmc6K6J44kd2iKrpPHc23V4stujNIg3I1LvYLT+kwWmfTGapeY0hl6EXACfaFdm/wBb4ggtNcnPA==", + "engines": { + "node": ">= 16.0.0" + }, "peerDependencies": { - "react-native": ">= 0.60.0" + "react": ">= 18.2.0", + "react-native": ">= 0.71.0" } }, "node_modules/react-native-animatable": { diff --git a/package.json b/package.json index be8e65d59b70..bb1df02fe98a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.41-2", + "version": "1.4.41-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -124,7 +124,7 @@ "react-error-boundary": "^4.0.11", "react-map-gl": "^7.1.3", "react-native": "0.73.2", - "react-native-android-location-enabler": "^1.2.2", + "react-native-android-location-enabler": "^2.0.1", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.1", "react-native-config": "^1.4.5", diff --git a/patches/react-native-config+1.4.6.patch b/patches/react-native-config+1.4.6.patch new file mode 100644 index 000000000000..880c723ddc37 --- /dev/null +++ b/patches/react-native-config+1.4.6.patch @@ -0,0 +1,44 @@ +diff --git a/node_modules/react-native-config/android/dotenv.gradle b/node_modules/react-native-config/android/dotenv.gradle +index 2225375..48f94ca 100644 +--- a/node_modules/react-native-config/android/dotenv.gradle ++++ b/node_modules/react-native-config/android/dotenv.gradle +@@ -41,7 +41,8 @@ def loadDotEnv(flavor = getCurrentFlavor()) { + def env = [:] + println("Reading env from: $envFile") + +- File f = new File("$project.rootDir/../$envFile"); ++ def reactNativeProjectRoot = project.hasProperty('reactNativeProject') ? project.reactNativeProject : ".." ++ File f = new File("$project.rootDir/$reactNativeProjectRoot/$envFile"); + if (!f.exists()) { + f = new File("$envFile"); + } +diff --git a/node_modules/react-native-config/react-native-config.podspec b/node_modules/react-native-config/react-native-config.podspec +index 54985dd..c394ec7 100644 +--- a/node_modules/react-native-config/react-native-config.podspec ++++ b/node_modules/react-native-config/react-native-config.podspec +@@ -3,6 +3,7 @@ + require 'json' + + package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) ++REACT_NATIVE_DIR = ENV["REACT_NATIVE_DIR"] || ".." + + Pod::Spec.new do |s| + s.name = 'react-native-config' +@@ -21,7 +22,7 @@ Pod::Spec.new do |s| + name: 'Config codegen', + script: %( + set -ex +-HOST_PATH="$SRCROOT/../.." ++HOST_PATH="$SRCROOT/../#{REACT_NATIVE_DIR}" + "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig" + ), + execution_position: :before_compile, +@@ -43,7 +44,7 @@ HOST_PATH="$SRCROOT/../.." + name: 'Config codegen', + script: %( + set -ex +- HOST_PATH="$SRCROOT/../.." ++ HOST_PATH="$SRCROOT/../#{REACT_NATIVE_DIR}" + "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig" + ), + execution_position: :before_compile, diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index f55db3dd0620..26d41ea82e00 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -100,6 +100,9 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + /** Allows to open an image without Attachment Picker. */ + enablePreview: PropTypes.bool, }; const defaultProps = { @@ -127,6 +130,7 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }, + enablePreview: false, }; function AvatarWithImagePicker({ @@ -152,6 +156,7 @@ function AvatarWithImagePicker({ avatarStyle, disabled, onViewPhotoPress, + enablePreview, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -330,10 +335,16 @@ function AvatarWithImagePicker({ text={translate('avatarWithImagePicker.editImage')} > setIsMenuVisible((prev) => !prev)} + onPress={() => { + if (disabled && enablePreview && onViewPhotoPress) { + onViewPhotoPress(); + return; + } + setIsMenuVisible((prev) => !prev); + }} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('avatarWithImagePicker.editImage')} - disabled={isAvatarCropModalOpen || disabled} + disabled={isAvatarCropModalOpen || (disabled && !enablePreview)} disabledStyle={disabledStyle} ref={anchorRef} > diff --git a/src/components/ImageSVG/index.android.tsx b/src/components/ImageSVG/index.android.tsx new file mode 100644 index 000000000000..9c8ae8f8801a --- /dev/null +++ b/src/components/ImageSVG/index.android.tsx @@ -0,0 +1,32 @@ +import {Image} from 'expo-image'; +import React, {useEffect} from 'react'; +import type {ImageSourcePropType} from 'react-native'; +import type ImageSVGProps from './types'; + +function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cover', style}: ImageSVGProps) { + const tintColorProp = fill ? {tintColor: fill} : {}; + + // Clear memory cache when unmounting images to avoid memory overload + useEffect(() => { + const clearMemoryCache = () => Image.clearMemoryCache(); + return () => { + clearMemoryCache(); + }; + }, []); + + return ( + + ); +} + +ImageSVG.displayName = 'ImageSVG'; +export default ImageSVG; diff --git a/src/components/ImageSVG/index.native.tsx b/src/components/ImageSVG/index.ios.tsx similarity index 100% rename from src/components/ImageSVG/index.native.tsx rename to src/components/ImageSVG/index.ios.tsx diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 2c41864564a3..ba9ce9858d03 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -24,7 +24,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction?: OnyxCommon.PendingAction; + pendingAction?: OnyxCommon.PendingAction | null; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 3766ed71a149..647f25c450e5 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; import * as Network from '@userActions/Network'; @@ -13,6 +14,7 @@ import Button from './Button'; import {withNetwork} from './OnyxProvider'; import Switch from './Switch'; import TestToolRow from './TestToolRow'; +import Text from './Text'; type TestToolMenuOnyxProps = { /** User object in Onyx */ @@ -27,9 +29,16 @@ const USER_DEFAULT: UserOnyx = {shouldUseStagingServer: undefined, isSubscribedT function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { const shouldUseStagingServer = user?.shouldUseStagingServer ?? ApiUtils.isUsingStagingApi(); + const styles = useThemeStyles(); return ( <> + + Test Preferences + {/* Option to switch between staging and default api endpoints. This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. This toggle is not rendered for internal devs as they make environment changes directly to the .env file. */} diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b3f6194f0055..3e4d4be0ecaf 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -339,7 +339,12 @@ type SplitDetailsNavigatorParamList = { [SCREENS.SPLIT_DETAILS.ROOT]: { reportActionID: string; }; - [SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: undefined; + [SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: { + field: string; + reportID: string; + reportActionID: string; + currency: string; + }; [SCREENS.SPLIT_DETAILS.EDIT_CURRENCY]: undefined; }; @@ -463,6 +468,7 @@ type PublicScreensParamList = { [SCREENS.VALIDATE_LOGIN]: { accountID: string; validateCode: string; + exitTo?: Routes | HybridAppRoute; }; [SCREENS.UNLINK_LOGIN]: { accountID?: string; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 454b85cc3152..504b2ac27965 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -601,3 +601,5 @@ export { getRecentTransactions, hasViolation, }; + +export type {TransactionChanges}; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 52c3ecef156c..55a6c81f0417 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -184,7 +184,7 @@ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number) * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. * This removes that part of the URL so the full version of the image can load. */ -function getFullSizeAvatar(avatarSource: AvatarSource, accountID: number): AvatarSource { +function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index d000d5ebfbec..7416b4f07e5e 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -1,7 +1,7 @@ import throttle from 'lodash/throttle'; import type {ChannelAuthorizationData} from 'pusher-js/types/src/core/auth/options'; import type {ChannelAuthorizationCallback} from 'pusher-js/with-encryption'; -import {Linking} from 'react-native'; +import {InteractionManager, Linking, NativeModules} from 'react-native'; import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -45,6 +45,7 @@ import * as Welcome from '@userActions/Welcome'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {HybridAppRoute, Route as Routes} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type Credentials from '@src/types/onyx/Credentials'; @@ -504,11 +505,6 @@ function signInWithValidateCode(accountID: number, code: string, twoFactorAuthCo }); } -function signInWithValidateCodeAndNavigate(accountID: number, validateCode: string, twoFactorAuthCode = '') { - signInWithValidateCode(accountID, validateCode, twoFactorAuthCode); - Navigation.navigate(ROUTES.HOME); -} - /** * Initializes the state of the automatic authentication when the user clicks on a magic link. * @@ -855,6 +851,26 @@ function waitForUserSignIn(): Promise { }); } +function handleExitToNavigation(exitTo: Routes | HybridAppRoute) { + InteractionManager.runAfterInteractions(() => { + waitForUserSignIn().then(() => { + Navigation.waitForProtectedRoutes().then(() => { + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + Navigation.navigate(url, CONST.NAVIGATION.TYPE.FORCED_UP); + }); + }); + }); +} + +function signInWithValidateCodeAndNavigate(accountID: number, validateCode: string, twoFactorAuthCode = '', exitTo?: Routes | HybridAppRoute) { + signInWithValidateCode(accountID, validateCode, twoFactorAuthCode); + if (exitTo) { + handleExitToNavigation(exitTo); + } else { + Navigation.navigate(ROUTES.HOME); + } +} + /** * check if the route can be accessed by anonymous user * @@ -890,6 +906,7 @@ export { checkIfActionIsAllowed, signIn, signInWithValidateCode, + handleExitToNavigation, signInWithValidateCodeAndNavigate, initAutoAuthState, signInWithShortLivedAuthToken, diff --git a/src/libs/getCurrentPosition/index.android.ts b/src/libs/getCurrentPosition/index.android.ts index bd27944b8830..8b0d1c76d25f 100644 --- a/src/libs/getCurrentPosition/index.android.ts +++ b/src/libs/getCurrentPosition/index.android.ts @@ -1,7 +1,7 @@ // https://github.com/Richou/react-native-android-location-enabler/issues/40 // If we update our react native version, we need to test this file again import Geolocation from '@react-native-community/geolocation'; -import RNAndroidLocationEnabler from 'react-native-android-location-enabler'; +import {promptForEnableLocationIfNeeded} from 'react-native-android-location-enabler'; import type {GetCurrentPosition} from './getCurrentPosition.types'; import {GeolocationErrorCode} from './getCurrentPosition.types'; @@ -15,9 +15,8 @@ const getCurrentPosition: GetCurrentPosition = (success, error, config) => { // Prompt's the user to enable geolocation permission with yes/no options // If the user selects yes, then this module would enable the native system location // Otherwise if user selects no, or we have an issue displaying the prompt, it will return an error - RNAndroidLocationEnabler.promptForEnableLocationIfNeeded({ + promptForEnableLocationIfNeeded({ interval: 2000, // This updates location after every 2 seconds (required prop). We don't depend on this as we only use the location once. - fastInterval: 1, // The shortest time (1 ms) our app is willing to wait for location update. Passing 0 ms short's the internal ternary condition of library to default value. }) .then((permissionState) => { if (permissionState === 'enabled') { diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.tsx similarity index 72% rename from src/pages/DetailsPage.js rename to src/pages/DetailsPage.tsx index a4cafd59cb73..76733fbf25e6 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.tsx @@ -1,10 +1,9 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import Str from 'expensify-common/lib/str'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; import AutoUpdateTime from '@components/AutoUpdateTime'; import Avatar from '@components/Avatar'; @@ -18,77 +17,51 @@ import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; +import type {DetailsNavigatorParamList} from '@navigation/types'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import personalDetailsPropType from './personalDetailsPropType'; - -const matchType = PropTypes.shape({ - params: PropTypes.shape({ - /** login passed via route /details/:login */ - login: PropTypes.string, - - /** report ID passed */ - reportID: PropTypes.string, - }), -}); - -const propTypes = { - /* Onyx Props */ +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails, PersonalDetailsList, Session} from '@src/types/onyx'; +type DetailsPageOnyxProps = { /** The personal details of the person who is logged in */ - personalDetails: personalDetailsPropType, - - /** Route params */ - route: matchType.isRequired, + personalDetails: OnyxEntry; /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), - - ...withLocalizePropTypes, + session: OnyxEntry; }; -const defaultProps = { - // When opening someone else's profile (via deep link) before login, this is empty - personalDetails: {}, - session: { - accountID: 0, - }, -}; +type DetailsPageProps = DetailsPageOnyxProps & StackScreenProps; /** * Gets the phone number to display for SMS logins - * - * @param {Object} details - * @param {String} details.login - * @param {String} details.displayName - * @returns {String} */ -const getPhoneNumber = (details) => { +const getPhoneNumber = ({login = '', displayName = ''}: PersonalDetails): string | undefined => { // If the user hasn't set a displayName, it is set to their phone number, so use that - const parsedPhoneNumber = parsePhoneNumber(details.displayName); + const parsedPhoneNumber = parsePhoneNumber(displayName); if (parsedPhoneNumber.possible) { - return parsedPhoneNumber.number.e164; + return parsedPhoneNumber?.number?.e164; } // If the user has set a displayName, get the phone number from the SMS login - return details.login ? Str.removeSMSDomain(details.login) : ''; + return login ? Str.removeSMSDomain(login) : ''; }; -function DetailsPage(props) { +function DetailsPage({personalDetails, route, session}: DetailsPageProps) { const styles = useThemeStyles(); - const login = lodashGet(props.route.params, 'login', ''); - let details = _.find(props.personalDetails, (detail) => detail.login === login.toLowerCase()); + const {translate, formatPhoneNumber} = useLocalize(); + const login = route.params?.login ?? ''; + const sessionAccountID = session?.accountID ?? 0; + + let details = Object.values(personalDetails ?? {}).find((personalDetail) => personalDetail?.login === login.toLowerCase()); if (!details) { if (login === CONST.EMAIL.CONCIERGE) { @@ -116,44 +89,44 @@ function DetailsPage(props) { if (pronouns && pronouns.startsWith(CONST.PRONOUNS.PREFIX)) { const localeKey = pronouns.replace(CONST.PRONOUNS.PREFIX, ''); - pronouns = props.translate(`pronouns.${localeKey}`); + pronouns = translate(`pronouns.${localeKey}` as TranslationPaths); } const phoneNumber = getPhoneNumber(details); const phoneOrEmail = isSMSLogin ? getPhoneNumber(details) : details.login; const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, '', false); - const isCurrentUser = props.session.accountID === details.accountID; + const isCurrentUser = sessionAccountID === details.accountID; return ( - - + + {details ? ( {({show}) => ( - + @@ -173,11 +146,11 @@ function DetailsPage(props) { style={[styles.textLabelSupporting, styles.mb1]} numberOfLines={1} > - {props.translate(isSMSLogin ? 'common.phoneNumber' : 'common.email')} + {translate(isSMSLogin ? 'common.phoneNumber' : 'common.email')} - + - {isSMSLogin ? props.formatPhoneNumber(phoneNumber) : details.login} + {isSMSLogin ? formatPhoneNumber(phoneNumber ?? '') : details.login} @@ -188,16 +161,16 @@ function DetailsPage(props) { style={[styles.textLabelSupporting, styles.mb1]} numberOfLines={1} > - {props.translate('profilePage.preferredPronouns')} + {translate('profilePage.preferredPronouns')} {pronouns} ) : null} - {shouldShowLocalTime && } + {shouldShowLocalTime && } {!isCurrentUser && ( Report.navigateToAndOpenReport([login])} @@ -213,18 +186,13 @@ function DetailsPage(props) { ); } -DetailsPage.propTypes = propTypes; -DetailsPage.defaultProps = defaultProps; DetailsPage.displayName = 'DetailsPage'; -export default compose( - withLocalize, - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(DetailsPage); +export default withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + session: { + key: ONYXKEYS.SESSION, + }, +})(DetailsPage); diff --git a/src/pages/EditRequestMerchantPage.js b/src/pages/EditRequestMerchantPage.js index 420bcbc35630..33c04df39e3e 100644 --- a/src/pages/EditRequestMerchantPage.js +++ b/src/pages/EditRequestMerchantPage.js @@ -21,7 +21,11 @@ const propTypes = { onSubmit: PropTypes.func.isRequired, /** Boolean to enable validation */ - isPolicyExpenseChat: PropTypes.bool.isRequired, + isPolicyExpenseChat: PropTypes.bool, +}; + +const defaultProps = { + isPolicyExpenseChat: false, }; function EditRequestMerchantPage({defaultMerchant, onSubmit, isPolicyExpenseChat}) { @@ -75,6 +79,7 @@ function EditRequestMerchantPage({defaultMerchant, onSubmit, isPolicyExpenseChat } EditRequestMerchantPage.propTypes = propTypes; +EditRequestMerchantPage.defaultProps = defaultProps; EditRequestMerchantPage.displayName = 'EditRequestMerchantPage'; export default EditRequestMerchantPage; diff --git a/src/pages/EditRequestTagPage.js b/src/pages/EditRequestTagPage.js index 8ecc4a953065..b2f576c499a6 100644 --- a/src/pages/EditRequestTagPage.js +++ b/src/pages/EditRequestTagPage.js @@ -16,12 +16,16 @@ const propTypes = { policyID: PropTypes.string.isRequired, /** The tag name to which the default tag belongs to */ - tagName: PropTypes.string.isRequired, + tagName: PropTypes.string, /** Callback to fire when the Save button is pressed */ onSubmit: PropTypes.func.isRequired, }; +const defaultProps = { + tagName: '', +}; + function EditRequestTagPage({defaultTag, policyID, tagName, onSubmit}) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -58,6 +62,7 @@ function EditRequestTagPage({defaultTag, policyID, tagName, onSubmit}) { } EditRequestTagPage.propTypes = propTypes; +EditRequestTagPage.defaultProps = defaultProps; EditRequestTagPage.displayName = 'EditRequestTagPage'; export default EditRequestTagPage; diff --git a/src/pages/EditSplitBillPage.js b/src/pages/EditSplitBillPage.tsx similarity index 50% rename from src/pages/EditSplitBillPage.js rename to src/pages/EditSplitBillPage.tsx index 543ef664568f..c859df7c407a 100644 --- a/src/pages/EditSplitBillPage.js +++ b/src/pages/EditSplitBillPage.tsx @@ -1,57 +1,45 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import transactionPropTypes from '@components/transactionPropTypes'; -import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; +import type {SplitDetailsNavigatorParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; +import type {TransactionChanges} from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {Report, ReportActions, Transaction} from '@src/types/onyx'; +import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import EditRequestAmountPage from './EditRequestAmountPage'; import EditRequestCategoryPage from './EditRequestCategoryPage'; import EditRequestMerchantPage from './EditRequestMerchantPage'; import EditRequestTagPage from './EditRequestTagPage'; -import reportPropTypes from './reportPropTypes'; -const propTypes = { - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The transaction field we are editing */ - field: PropTypes.string, - - /** The chat reportID of the split */ - reportID: PropTypes.string, +type EditSplitBillOnyxProps = { + /** The report currently being used */ + report: OnyxEntry; - /** reportActionID of the split action */ - reportActionID: PropTypes.string, - }), - }).isRequired, + /** The report action for currently used report */ + // Used in withOnyx + // eslint-disable-next-line react/no-unused-prop-types + reportActions: OnyxEntry; /** The current transaction */ - transaction: transactionPropTypes.isRequired, + transaction: OnyxEntry; /** The draft transaction that holds data to be persisted on the current transaction */ - draftTransaction: transactionPropTypes, - - /** The report currently being used */ - report: reportPropTypes.isRequired, + draftTransaction: OnyxEntry; }; -const defaultProps = { - draftTransaction: undefined, -}; +type EditSplitBillProps = EditSplitBillOnyxProps & StackScreenProps; -function EditSplitBillPage({route, transaction, draftTransaction, report}) { - const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const reportID = lodashGet(route, ['params', 'reportID'], ''); - const reportActionID = lodashGet(route, ['params', 'reportActionID'], ''); +function EditSplitBillPage({route, transaction, draftTransaction, report}: EditSplitBillProps) { + const {field: fieldToEdit, reportID, reportActionID, currency} = route.params; const { amount: transactionAmount, @@ -59,26 +47,25 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}) { merchant: transactionMerchant, category: transactionCategory, tag: transactionTag, - } = draftTransaction ? ReportUtils.getTransactionDetails(draftTransaction) : ReportUtils.getTransactionDetails(transaction); - - const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; + } = ReportUtils.getTransactionDetails(draftTransaction ?? transaction) ?? {}; + const defaultCurrency = currency ?? transactionCurrency; function navigateBackToSplitDetails() { Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(reportID, reportActionID)); } - const setDraftSplitTransaction = (transactionChanges) => { - IOU.setDraftSplitTransaction(transaction.transactionID, transactionChanges); + const setDraftSplitTransaction = (transactionChanges: TransactionChanges) => { + if (transaction) { + IOU.setDraftSplitTransaction(transaction.transactionID, transactionChanges); + } navigateBackToSplitDetails(); }; if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT) { return ( { const amount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount)); @@ -98,7 +85,7 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}) { if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.MERCHANT) { return ( { setDraftSplitTransaction({merchant: transactionChanges.merchant.trim()}); }} @@ -109,8 +96,8 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}) { if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.CATEGORY) { return ( { setDraftSplitTransaction({category: transactionChanges.category.trim()}); }} @@ -121,8 +108,8 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}) { if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAG) { return ( { setDraftSplitTransaction({tag: transactionChanges.tag.trim()}); }} @@ -134,31 +121,27 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}) { } EditSplitBillPage.displayName = 'EditSplitBillPage'; -EditSplitBillPage.propTypes = propTypes; -EditSplitBillPage.defaultProps = defaultProps; -export default compose( - withOnyx({ - reportActions: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, - canEvict: false, - }, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, + +export default withOnyx({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + reportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.reportID}`, + canEvict: false, + }, + transaction: { + key: ({route, reportActions}: Partial) => { + const reportAction = reportActions?.[`${route?.params.reportActionID.toString()}`]; + const transactionID = (reportAction as OriginalMessageIOU)?.originalMessage.IOUTransactionID ? (reportAction as OriginalMessageIOU).originalMessage.IOUTransactionID : 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, - draftTransaction: { - key: ({route, reportActions}) => { - const reportAction = reportActions[`${route.params.reportActionID.toString()}`]; - return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${lodashGet(reportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, + }, + draftTransaction: { + key: ({route, reportActions}: Partial) => { + const reportAction = reportActions?.[`${route?.params.reportActionID.toString()}`]; + const transactionID = (reportAction as OriginalMessageIOU)?.originalMessage.IOUTransactionID ? (reportAction as OriginalMessageIOU).originalMessage.IOUTransactionID : 0; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; }, - }), -)(EditSplitBillPage); + }, +})(EditSplitBillPage); diff --git a/src/pages/ValidateLoginPage/index.tsx b/src/pages/ValidateLoginPage/index.tsx index 5415c359aab0..edc8b61c82b0 100644 --- a/src/pages/ValidateLoginPage/index.tsx +++ b/src/pages/ValidateLoginPage/index.tsx @@ -9,7 +9,7 @@ import type {ValidateLoginPageOnyxNativeProps, ValidateLoginPageProps} from './t function ValidateLoginPage({ route: { - params: {accountID, validateCode}, + params: {accountID, validateCode, exitTo}, }, session, }: ValidateLoginPageProps) { @@ -21,7 +21,7 @@ function ValidateLoginPage({ // because we don't want to block the user with the interstitial page. Navigation.goBack(); } else { - Session.signInWithValidateCodeAndNavigate(Number(accountID), validateCode); + Session.signInWithValidateCodeAndNavigate(Number(accountID), validateCode, '', exitTo); } }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/pages/ValidateLoginPage/index.website.tsx b/src/pages/ValidateLoginPage/index.website.tsx index 6253535e9789..7ce46ee11b14 100644 --- a/src/pages/ValidateLoginPage/index.website.tsx +++ b/src/pages/ValidateLoginPage/index.website.tsx @@ -14,7 +14,7 @@ function ValidateLoginPage({ account, credentials, route: { - params: {accountID, validateCode}, + params: {accountID, validateCode, exitTo}, }, session, }: ValidateLoginPageProps) { @@ -35,6 +35,9 @@ function ValidateLoginPage({ Session.initAutoAuthState(autoAuthState); if (isSignedIn || !login) { + if (exitTo) { + Session.handleExitToNavigation(exitTo); + } return; } @@ -45,6 +48,9 @@ function ValidateLoginPage({ useEffect(() => { if (!!login || !cachedAccountID || !is2FARequired) { + if (exitTo) { + Session.handleExitToNavigation(exitTo); + } return; } @@ -52,7 +58,7 @@ function ValidateLoginPage({ Navigation.isNavigationReady().then(() => { Navigation.goBack(); }); - }, [login, cachedAccountID, is2FARequired]); + }, [login, cachedAccountID, is2FARequired, exitTo]); return ( <> diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index cf4e6a461bc1..c60462e9be5f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -128,6 +128,14 @@ function IOURequestStepConfirmation({ IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, defaultBillable); }, [transactionID, defaultBillable]); + useEffect(() => { + if (!transaction.category) { + return; + } + if (policyCategories[transaction.category] && !policyCategories[transaction.category].enabled) { + IOU.resetMoneyRequestCategory_temporaryForRefactor(transactionID); + } + }, [policyCategories, transaction.category, transactionID]); const defaultCategory = lodashGet( _.find(lodashGet(policy, 'customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE), 'defaultCategory', diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx index 37da06ba32f3..3346b044ceca 100644 --- a/src/pages/settings/AboutPage/AboutPage.tsx +++ b/src/pages/settings/AboutPage/AboutPage.tsx @@ -161,7 +161,7 @@ function AboutPage() { - + ); } diff --git a/src/pages/workspace/WorkspaceProfilePage.js b/src/pages/workspace/WorkspaceProfilePage.js index 3fbba333c75f..2b7402fee3ac 100644 --- a/src/pages/workspace/WorkspaceProfilePage.js +++ b/src/pages/workspace/WorkspaceProfilePage.js @@ -79,6 +79,7 @@ function WorkspaceProfilePage({policy, currencyList, route}) { source={lodashGet(policy, 'avatar')} size={CONST.AVATAR_SIZE.XLARGE} avatarStyle={styles.avatarXLarge} + enablePreview DefaultAvatar={() => ( <% if (htmlWebpackPlugin.options.isProduction) { %> - - + + <% } %> <% } %>