diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index 85fb866b05c4..9887943c77e0 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -69,9 +69,6 @@ jobs:
uses: ./.github/actions/javascript/getGraphiteString
- name: Send graphite data
- env:
- GRAPHITE_SERVER: ${{ vars.GRAPHITE_SERVER }}
- GRAPHITE_PORT: ${{ vars.GRAPHITE_PORT }}
# run only when merged to main
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
- run: echo -e "${{ steps.saveGraphiteString.outputs.GRAPHITE_STRING }}" | nc -q0 "$GRAPHITE_SERVER" "$GRAPHITE_PORT"
+ run: echo -e "${{ steps.saveGraphiteString.outputs.GRAPHITE_STRING }}" | nc -q0 stats.expensify.com 3003
diff --git a/android/app/build.gradle b/android/app/build.gradle
index cb12616ee216..5f5a5a60b1fc 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 1001045001
- versionName "1.4.50-1"
+ versionCode 1001045003
+ versionName "1.4.50-3"
}
flavorDimensions "default"
@@ -181,7 +181,6 @@ android {
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
- implementation("com.facebook.react:flipper-integration")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
diff --git a/assets/images/simple-illustrations/simple-illustration__accounting.svg b/assets/images/simple-illustrations/simple-illustration__accounting.svg
new file mode 100644
index 000000000000..f7634141e966
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__accounting.svg
@@ -0,0 +1,32 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__car.svg b/assets/images/simple-illustrations/simple-illustration__car.svg
new file mode 100644
index 000000000000..2d420be6c3a9
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__car.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__coins.svg b/assets/images/simple-illustrations/simple-illustration__coins.svg
new file mode 100644
index 000000000000..5350886402c6
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__coins.svg
@@ -0,0 +1,26 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__pencil.svg b/assets/images/simple-illustrations/simple-illustration__pencil.svg
new file mode 100644
index 000000000000..8d9f06991612
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__pencil.svg
@@ -0,0 +1,20 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__workflows.svg b/assets/images/simple-illustrations/simple-illustration__workflows.svg
index 47d30d54310f..b684c58126f7 100644
--- a/assets/images/simple-illustrations/simple-illustration__workflows.svg
+++ b/assets/images/simple-illustrations/simple-illustration__workflows.svg
@@ -1 +1,153 @@
-
\ No newline at end of file
+
+
+
diff --git a/babel.config.js b/babel.config.js
index 2a09d086dc5c..7e90fca1c9be 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -82,7 +82,7 @@ const metro = {
};
/*
- * We use Flipper, and react-native-performance to capture/monitor stats
+ * We use and react-native-performance to capture/monitor stats
* By default is disabled in production as it adds small overhead
* When CAPTURE_METRICS is set we're explicitly saying that we want to capture metrics
* To enable the for release builds we add these aliases */
diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js
index 170198987793..2fed8a477aab 100644
--- a/config/webpack/webpack.common.js
+++ b/config/webpack/webpack.common.js
@@ -18,7 +18,6 @@ const includeModules = [
'@react-native-picker',
'react-native-modal',
'react-native-gesture-handler',
- 'react-native-flipper',
'react-native-google-places-autocomplete',
'react-native-qrcode-svg',
'react-native-view-shot',
diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj
index acfc4d933954..e39542ef0303 100644
--- a/ios/NewExpensify.xcodeproj/project.pbxproj
+++ b/ios/NewExpensify.xcodeproj/project.pbxproj
@@ -543,13 +543,10 @@
"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework",
"${BUILT_PRODUCTS_DIR}/Turf/Turf.framework",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Onfido/Onfido.framework/Onfido",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Plaid/LinkKit.framework/LinkKit",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
@@ -557,13 +554,10 @@
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Onfido.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
@@ -613,13 +607,10 @@
"${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework",
"${BUILT_PRODUCTS_DIR}/Turf/Turf.framework",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Onfido/Onfido.framework/Onfido",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Plaid/LinkKit.framework/LinkKit",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
@@ -627,13 +618,10 @@
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Onfido.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 97eb55747062..a82a1d598d95 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.50.1
+ 1.4.50.3
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index af8e99100825..90f736a246fa 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.50.1
+ 1.4.50.3
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index a4c7b52661b6..276cad0c9f9a 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.50
CFBundleVersion
- 1.4.50.1
+ 1.4.50.3
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile b/ios/Podfile
index aa87c3e295f3..83c21797bd0a 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -26,17 +26,6 @@ setup_permissions([
'LocationWhenInUse'
])
-# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
-# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
-#
-# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
-# ```js
-# module.exports = {
-# dependencies: {
-# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
-# ```
-flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled(['DebugProduction', 'DebugDevelopment', 'DebugAdHoc'])
-
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
@@ -86,11 +75,6 @@ target 'NewExpensify' do
use_react_native!(
:path => config[:reactNativePath],
- # Enables Flipper.
- #
- # Note that if you have use_frameworks! enabled, Flipper will not work and
- # you should disable the next line.
- :flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index bb26b37d4015..d0007ec51668 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -30,7 +30,6 @@ PODS:
- boost (1.83.0)
- BVLinearGradient (2.8.1):
- React-Core
- - CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
- EXAV (13.10.4):
- ExpoModulesCore
@@ -127,62 +126,6 @@ PODS:
- FirebaseInstallations (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- - Flipper (0.201.0):
- - Flipper-Folly (~> 2.6)
- - Flipper-Boost-iOSX (1.76.0.1.11)
- - Flipper-DoubleConversion (3.2.0.1)
- - Flipper-Fmt (7.1.7)
- - Flipper-Folly (2.6.10):
- - Flipper-Boost-iOSX
- - Flipper-DoubleConversion
- - Flipper-Fmt (= 7.1.7)
- - Flipper-Glog
- - libevent (~> 2.1.12)
- - OpenSSL-Universal (= 1.1.1100)
- - Flipper-Glog (0.5.0.5)
- - Flipper-PeerTalk (0.0.4)
- - FlipperKit (0.201.0):
- - FlipperKit/Core (= 0.201.0)
- - FlipperKit/Core (0.201.0):
- - Flipper (~> 0.201.0)
- - FlipperKit/CppBridge
- - FlipperKit/FBCxxFollyDynamicConvert
- - FlipperKit/FBDefines
- - FlipperKit/FKPortForwarding
- - SocketRocket (~> 0.6.0)
- - FlipperKit/CppBridge (0.201.0):
- - Flipper (~> 0.201.0)
- - FlipperKit/FBCxxFollyDynamicConvert (0.201.0):
- - Flipper-Folly (~> 2.6)
- - FlipperKit/FBDefines (0.201.0)
- - FlipperKit/FKPortForwarding (0.201.0):
- - CocoaAsyncSocket (~> 7.6)
- - Flipper-PeerTalk (~> 0.0.4)
- - FlipperKit/FlipperKitHighlightOverlay (0.201.0)
- - FlipperKit/FlipperKitLayoutHelpers (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitHighlightOverlay
- - FlipperKit/FlipperKitLayoutTextSearchable
- - FlipperKit/FlipperKitLayoutIOSDescriptors (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitHighlightOverlay
- - FlipperKit/FlipperKitLayoutHelpers
- - FlipperKit/FlipperKitLayoutPlugin (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitHighlightOverlay
- - FlipperKit/FlipperKitLayoutHelpers
- - FlipperKit/FlipperKitLayoutIOSDescriptors
- - FlipperKit/FlipperKitLayoutTextSearchable
- - FlipperKit/FlipperKitLayoutTextSearchable (0.201.0)
- - FlipperKit/FlipperKitNetworkPlugin (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitReactPlugin (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitUserDefaultsPlugin (0.201.0):
- - FlipperKit/Core
- - FlipperKit/SKIOSNetworkPlugin (0.201.0):
- - FlipperKit/Core
- - FlipperKit/FlipperKitNetworkPlugin
- fmt (6.2.1)
- glog (0.3.5)
- GoogleAppMeasurement (8.8.0):
@@ -284,7 +227,6 @@ PODS:
- onfido-react-native-sdk (10.6.0):
- Onfido (~> 29.6.0)
- React
- - OpenSSL-Universal (1.1.1100)
- Plaid (4.7.0)
- PromisesObjC (2.3.1)
- RCT-Folly (2022.05.16.00):
@@ -1486,32 +1428,11 @@ DEPENDENCIES:
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- - Flipper (= 0.201.0)
- - Flipper-Boost-iOSX (= 1.76.0.1.11)
- - Flipper-DoubleConversion (= 3.2.0.1)
- - Flipper-Fmt (= 7.1.7)
- - Flipper-Folly (= 2.6.10)
- - Flipper-Glog (= 0.5.0.5)
- - Flipper-PeerTalk (= 0.0.4)
- - FlipperKit (= 0.201.0)
- - FlipperKit/Core (= 0.201.0)
- - FlipperKit/CppBridge (= 0.201.0)
- - FlipperKit/FBCxxFollyDynamicConvert (= 0.201.0)
- - FlipperKit/FBDefines (= 0.201.0)
- - FlipperKit/FKPortForwarding (= 0.201.0)
- - FlipperKit/FlipperKitHighlightOverlay (= 0.201.0)
- - FlipperKit/FlipperKitLayoutPlugin (= 0.201.0)
- - FlipperKit/FlipperKitLayoutTextSearchable (= 0.201.0)
- - FlipperKit/FlipperKitNetworkPlugin (= 0.201.0)
- - FlipperKit/FlipperKitReactPlugin (= 0.201.0)
- - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.201.0)
- - FlipperKit/SKIOSNetworkPlugin (= 0.201.0)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- libevent (~> 2.1.12)
- lottie-react-native (from `../node_modules/lottie-react-native`)
- "onfido-react-native-sdk (from `../node_modules/@onfido/react-native-sdk`)"
- - OpenSSL-Universal (= 1.1.1100)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
@@ -1520,7 +1441,6 @@ DEPENDENCIES:
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Codegen (from `build/generated/ios`)
- React-Core (from `../node_modules/react-native/`)
- - React-Core/DevSupport (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
@@ -1610,7 +1530,6 @@ SPEC REPOS:
- AirshipFrameworkProxy
- AirshipServiceExtension
- AppAuth
- - CocoaAsyncSocket
- Firebase
- FirebaseABTesting
- FirebaseAnalytics
@@ -1620,14 +1539,6 @@ SPEC REPOS:
- FirebaseInstallations
- FirebasePerformance
- FirebaseRemoteConfig
- - Flipper
- - Flipper-Boost-iOSX
- - Flipper-DoubleConversion
- - Flipper-Fmt
- - Flipper-Folly
- - Flipper-Glog
- - Flipper-PeerTalk
- - FlipperKit
- fmt
- GoogleAppMeasurement
- GoogleDataTransport
@@ -1647,7 +1558,6 @@ SPEC REPOS:
- MapboxMobileEvents
- nanopb
- Onfido
- - OpenSSL-Universal
- Plaid
- PromisesObjC
- SDWebImage
@@ -1869,7 +1779,6 @@ SPEC CHECKSUMS:
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca
- CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
EXAV: 09a4d87fa6b113fbb0ada3aade6799f78271cb44
Expo: 1e3bcf9dd99de57a636127057f6b488f0609681a
@@ -1886,14 +1795,6 @@ SPEC CHECKSUMS:
FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd
FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c
FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b
- Flipper: c7a0093234c4bdd456e363f2f19b2e4b27652d44
- Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
- Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
- Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
- Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
- Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
- Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
- FlipperKit: 37525a5d056ef9b93d1578e04bc3ea1de940094f
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91
@@ -1917,7 +1818,6 @@ SPEC CHECKSUMS:
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
Onfido: c52e797b10cc9e6d29ba91996cb62e501000bfdd
onfido-react-native-sdk: 4e7f0a7a986ed93cb906d2e0b67a6aab9202de0b
- OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
Plaid: 431ef9be5314a1345efb451bc5e6b067bfb3b4c6
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
@@ -2013,8 +1913,8 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a
- Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
+ Yoga: 13c8ef87792450193e117976337b8527b49e8c03
-PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2
+PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530
COCOAPODS: 1.13.0
diff --git a/package-lock.json b/package-lock.json
index ac65cd05115d..f86bdcd2c5cf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.50-1",
+ "version": "1.4.50-3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.50-1",
+ "version": "1.4.50-3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -64,7 +64,7 @@
"lodash": "4.17.21",
"lottie-react-native": "6.4.1",
"mapbox-gl": "^2.15.0",
- "onfido-sdk-ui": "13.6.1",
+ "onfido-sdk-ui": "14.15.0",
"patch-package": "^8.0.0",
"process": "^0.11.10",
"prop-types": "^15.7.2",
@@ -229,7 +229,6 @@
"prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
"react-native-clean-project": "^4.0.0-alpha4.0",
- "react-native-performance-flipper-reporter": "^2.0.0",
"react-test-renderer": "18.2.0",
"reassure": "^0.10.1",
"setimmediate": "^1.0.5",
@@ -7715,16 +7714,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/@mediapipe/face_detection": {
- "version": "0.4.1646425229",
- "resolved": "https://registry.npmjs.org/@mediapipe/face_detection/-/face_detection-0.4.1646425229.tgz",
- "integrity": "sha512-aeCN+fRAojv9ch3NXorP6r5tcGVLR3/gC1HmtqB0WEZBRXrdP6/3W/sGR0dHr1iT6ueiK95G9PVjbzFosf/hrg=="
- },
- "node_modules/@mediapipe/face_mesh": {
- "version": "0.4.1633559619",
- "resolved": "https://registry.npmjs.org/@mediapipe/face_mesh/-/face_mesh-0.4.1633559619.tgz",
- "integrity": "sha512-Vc8cdjxS5+O2gnjWH9KncYpUCVXT0h714KlWAsyqJvJbIgUJBqpppbIx8yWcAzBDxm/5cYSuBI5p5ySIPxzcEg=="
- },
"node_modules/@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -10489,78 +10478,6 @@
"join-component": "^1.1.0"
}
},
- "node_modules/@sentry/browser": {
- "version": "7.11.1",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sentry/core": "7.11.1",
- "@sentry/types": "7.11.1",
- "@sentry/utils": "7.11.1",
- "tslib": "^1.9.3"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@sentry/browser/node_modules/tslib": {
- "version": "1.14.1",
- "license": "0BSD"
- },
- "node_modules/@sentry/core": {
- "version": "7.11.1",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sentry/hub": "7.11.1",
- "@sentry/types": "7.11.1",
- "@sentry/utils": "7.11.1",
- "tslib": "^1.9.3"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@sentry/core/node_modules/tslib": {
- "version": "1.14.1",
- "license": "0BSD"
- },
- "node_modules/@sentry/hub": {
- "version": "7.11.1",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sentry/types": "7.11.1",
- "@sentry/utils": "7.11.1",
- "tslib": "^1.9.3"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@sentry/hub/node_modules/tslib": {
- "version": "1.14.1",
- "license": "0BSD"
- },
- "node_modules/@sentry/types": {
- "version": "7.11.1",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@sentry/utils": {
- "version": "7.11.1",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sentry/types": "7.11.1",
- "tslib": "^1.9.3"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@sentry/utils/node_modules/tslib": {
- "version": "1.14.1",
- "license": "0BSD"
- },
"node_modules/@shopify/flash-list": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.6.3.tgz",
@@ -10639,11 +10556,6 @@
"@sinonjs/commons": "^2.0.0"
}
},
- "node_modules/@socket.io/component-emitter": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
- "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
- },
"node_modules/@storybook/addon-a11y": {
"version": "6.5.10",
"dev": true,
@@ -20007,88 +19919,6 @@
"node": ">=10"
}
},
- "node_modules/@tensorflow-models/face-detection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@tensorflow-models/face-detection/-/face-detection-1.0.2.tgz",
- "integrity": "sha512-anjSxy3MnZdTiVluOEQZeaFWM30IPswFM+SltX6wseXKja/AbrHYqamGNZKUylAs2JAyudq+xqTRPS+nA2ourg==",
- "dependencies": {
- "rimraf": "^3.0.2",
- "tslib": "2.4.0"
- },
- "peerDependencies": {
- "@mediapipe/face_detection": "~0.4.0",
- "@tensorflow/tfjs-backend-webgl": "^4.4.0",
- "@tensorflow/tfjs-converter": "^4.4.0",
- "@tensorflow/tfjs-core": "^4.4.0"
- }
- },
- "node_modules/@tensorflow/tfjs-backend-cpu": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.16.0.tgz",
- "integrity": "sha512-bQFu7FTUgqgss1AwnqSwQ1f02IPrfLLc2lLn5pyyVrS6Ex7zA6Y4YkfktqoJSRE6LlRZv3vxSriUGE1avRe4qQ==",
- "peer": true,
- "dependencies": {
- "@types/seedrandom": "^2.4.28",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- },
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.16.0"
- }
- },
- "node_modules/@tensorflow/tfjs-backend-webgl": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.16.0.tgz",
- "integrity": "sha512-cIGZWuY892iwTRokbDj3qsLi0AlpQn+U7rzB1mddhHrWr9kBXrrnAvIq0h2aiFzRFNePWUcsbgK+HmYG32kosg==",
- "peer": true,
- "dependencies": {
- "@tensorflow/tfjs-backend-cpu": "4.16.0",
- "@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "^2.4.28",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- },
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.16.0"
- }
- },
- "node_modules/@tensorflow/tfjs-converter": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.16.0.tgz",
- "integrity": "sha512-gd8dHl9tqEPQOHZLAUza713nKr42rpvUXrtm7yUhk10THvJT6TXe9Q2AJKmni8J3vfR+ghsCh77F8D4RbShx1Q==",
- "peer": true,
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.16.0"
- }
- },
- "node_modules/@tensorflow/tfjs-core": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.16.0.tgz",
- "integrity": "sha512-MarAtO+Up6wA8pI9QDpQOwwJgb/imYMN++tsoaalyOEE9+B5HS4lQldxDJKXO8Frf4DyXf4FItJktEXaiPfRHw==",
- "peer": true,
- "dependencies": {
- "@types/long": "^4.0.1",
- "@types/offscreencanvas": "~2019.7.0",
- "@types/seedrandom": "^2.4.28",
- "@webgpu/types": "0.1.38",
- "long": "4.0.0",
- "node-fetch": "~2.6.1",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- }
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
- "version": "2019.7.3",
- "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
- "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
- "peer": true
- },
"node_modules/@testing-library/jest-native": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/jest-native/-/jest-native-5.4.1.tgz",
@@ -20578,11 +20408,6 @@
"integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==",
"dev": true
},
- "node_modules/@types/emscripten": {
- "version": "0.0.34",
- "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-0.0.34.tgz",
- "integrity": "sha512-QSb9ojDincskc+uKMI0KXp8e1NALFINCrMlp8VGKGcTSxeEyRTTKyjWw75NYrCZHUsVEEEpr1tYHpbtaC++/sQ=="
- },
"node_modules/@types/eslint": {
"version": "8.4.6",
"license": "MIT",
@@ -20827,11 +20652,6 @@
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==",
"dev": true
},
- "node_modules/@types/long": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
- "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
- },
"node_modules/@types/mapbox-gl": {
"version": "2.7.13",
"resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.13.tgz",
@@ -20904,11 +20724,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/offscreencanvas": {
- "version": "2019.3.0",
- "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
- "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
- },
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -21066,11 +20881,6 @@
"version": "0.16.2",
"license": "MIT"
},
- "node_modules/@types/seedrandom": {
- "version": "2.4.34",
- "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz",
- "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A=="
- },
"node_modules/@types/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz",
@@ -21170,16 +20980,6 @@
"dev": true,
"optional": true
},
- "node_modules/@types/webgl-ext": {
- "version": "0.0.30",
- "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
- "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
- },
- "node_modules/@types/webgl2": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz",
- "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ=="
- },
"node_modules/@types/webpack": {
"version": "4.41.32",
"dev": true,
@@ -22197,12 +21997,6 @@
"@xtuc/long": "4.2.2"
}
},
- "node_modules/@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
- "peer": true
- },
"node_modules/@webpack-cli/configtest": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
@@ -24631,12 +24425,6 @@
"bluebird": "^3.5.5"
}
},
- "node_modules/blueimp-load-image": {
- "version": "2.29.0",
- "resolved": "https://registry.npmjs.org/blueimp-load-image/-/blueimp-load-image-2.29.0.tgz",
- "integrity": "sha512-psm81GlZ0ffKxVT0QN9dvhpzXMv1KxgXSg8ars0XGAcEGsTwFT2IPo59HDXlw4Lo2oImdPzwrwkliZSiLLUpIw==",
- "license": "MIT"
- },
"node_modules/blueimp-md5": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
@@ -28739,10 +28527,6 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
- "node_modules/dompurify": {
- "version": "2.3.10",
- "license": "(MPL-2.0 OR Apache-2.0)"
- },
"node_modules/domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -29261,46 +29045,6 @@
"objectorarray": "^1.0.5"
}
},
- "node_modules/engine.io-client": {
- "version": "6.5.3",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
- "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1",
- "engine.io-parser": "~5.2.1",
- "ws": "~8.11.0",
- "xmlhttprequest-ssl": "~2.0.0"
- }
- },
- "node_modules/engine.io-client/node_modules/ws": {
- "version": "8.11.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
- "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io-parser": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
- "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
@@ -29341,12 +29085,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/enumerate-devices": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/enumerate-devices/-/enumerate-devices-1.1.1.tgz",
- "integrity": "sha512-8zDbrc7ocusTL1ZGmvgy0cTwdyCaM7sGZoYLRmnWJalLQzmftDtce+uDU91gafOTo9MCtgjSIxyMv/F4+Hcchw==",
- "license": "MIT"
- },
"node_modules/env-editor": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz",
@@ -30743,12 +30481,6 @@
"node": ">=6"
}
},
- "node_modules/eventemitter2": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-2.2.2.tgz",
- "integrity": "sha512-AmQ734LWUB9Iyk+2WIU3Z8iRhdL1XQihEE0iF/QC5Xp11zST0Z5tn5jRHa/PgIld2QIPSCys3CREqOQLUhNvkw==",
- "license": "MIT"
- },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -31825,14 +31557,6 @@
"url": "https://opencollective.com/ramda"
}
},
- "node_modules/file-type": {
- "version": "12.4.2",
- "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz",
- "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -33408,19 +33132,6 @@
"node": ">= 8"
}
},
- "node_modules/history": {
- "version": "4.5.1",
- "resolved": "https://registry.npmjs.org/history/-/history-4.5.1.tgz",
- "integrity": "sha512-gfHeJeYeMzFtos61gdA1AloO0hGXPF2Yum+2FRdJvlylYQOz51OnT1zuwg9UYst1BRrONhcAh3Nmsg9iblgl6g==",
- "license": "MIT",
- "dependencies": {
- "invariant": "^2.2.1",
- "loose-envify": "^1.2.0",
- "resolve-pathname": "^2.0.0",
- "value-equal": "^0.2.0",
- "warning": "^3.0.0"
- }
- },
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -38489,13 +38200,6 @@
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz",
"integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA=="
},
- "node_modules/js-cookie": {
- "version": "3.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/js-string-escape": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
@@ -39503,11 +39207,6 @@
"node": ">=6"
}
},
- "node_modules/long": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
- "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
- },
"node_modules/longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@@ -41122,22 +40821,6 @@
"node": ">= 8"
}
},
- "node_modules/mirada": {
- "version": "0.0.15",
- "resolved": "https://registry.npmjs.org/mirada/-/mirada-0.0.15.tgz",
- "integrity": "sha512-mbm4c+wjBVcmUzHRLv/TfOAq+iy03D24KwGxx8H+NSXkD5EOZV9zFWbVxTvZCc9XwR0FIUhryU/kQm12SMSQ3g==",
- "dependencies": {
- "buffer": "^5.4.3",
- "cross-fetch": "^3.0.4",
- "file-type": "^12.3.0",
- "misc-utils-of-mine-generic": "^0.2.31"
- }
- },
- "node_modules/misc-utils-of-mine-generic": {
- "version": "0.2.45",
- "resolved": "https://registry.npmjs.org/misc-utils-of-mine-generic/-/misc-utils-of-mine-generic-0.2.45.tgz",
- "integrity": "sha512-WsG2zYiui2cdEbHF2pXmJfnjHb4zL+cy+PaYcLgIpMju98hwX89VbjlvGIfamCfEodbQ0qjCEvD3ocgkCXfMOQ=="
- },
"node_modules/mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -42149,41 +41832,9 @@
}
},
"node_modules/onfido-sdk-ui": {
- "version": "13.6.1",
- "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-13.6.1.tgz",
- "integrity": "sha512-EcFqTN9uaVINRUttSdt6ySUBlfg25dE9f2yxxXVUmrM9a4M1luv+aICej1zE3vRZPFEuFJ9mqJZQUTYo0YMFyg==",
- "dependencies": {
- "@onfido/active-video-capture": "^0.28.2",
- "@onfido/opencv": "^2.0.0",
- "@sentry/browser": "^7.2.0",
- "blueimp-load-image": "~2.29.0",
- "classnames": "~2.2.5",
- "core-js": "^3.21.1",
- "deepmerge": "^4.2.2",
- "dompurify": "^2.2.6",
- "enumerate-devices": "^1.1.1",
- "eventemitter2": "~2.2.2",
- "history": "~4.5.1",
- "hoist-non-react-statics": "^3.3.2",
- "js-cookie": "^3.0.1",
- "pdfobject": "^2.2.7",
- "preact": "10.11.3",
- "redux": "^4.0.5",
- "socket.io-client": "^4.2.0",
- "supports-webp": "~1.0.3",
- "uuid": "^8.3.2",
- "visibilityjs": "~1.2.4",
- "xstate": "^4.33.6"
- },
- "bin": {
- "migrate_locales": "scripts/migrate_locales.js"
- }
- },
- "node_modules/onfido-sdk-ui/node_modules/classnames": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
- "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==",
- "license": "MIT"
+ "version": "14.15.0",
+ "resolved": "https://registry.npmjs.org/onfido-sdk-ui/-/onfido-sdk-ui-14.15.0.tgz",
+ "integrity": "sha512-4Z+tnH6pQjK4SyazlzJq17NXO8AnhGcwEACbA3PVbAo90LBpGu1WAZ1r6VidlxFr/oPbu6sg/hisYvfXiqOtTg=="
},
"node_modules/open": {
"version": "8.4.2",
@@ -43033,10 +42684,6 @@
"canvas": "^2.11.2"
}
},
- "node_modules/pdfobject": {
- "version": "2.2.8",
- "license": "MIT"
- },
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -43423,15 +43070,6 @@
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
"integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="
},
- "node_modules/preact": {
- "version": "10.11.3",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
- "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/preact"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -44597,16 +44235,6 @@
"react-native-reanimated": ">=2.8.0"
}
},
- "node_modules/react-native-flipper": {
- "version": "0.159.0",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "react-native": ">0.62.0"
- }
- },
"node_modules/react-native-fs": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz",
@@ -44839,17 +44467,6 @@
"react-native": "*"
}
},
- "node_modules/react-native-performance-flipper-reporter": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/react-native-performance-flipper-reporter/-/react-native-performance-flipper-reporter-2.0.0.tgz",
- "integrity": "sha512-ccOgq99eK3OvrNNhpJDC4ydNk/1JGgWZPo2FLrPDLUHXAR4EcE9cUAtb46oGOpvHk5ZOb5aEDofc/CS9OEGcag==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "react-native-flipper": "*",
- "react-native-performance": "*"
- }
- },
"node_modules/react-native-permissions": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.9.3.tgz",
@@ -46951,12 +46568,6 @@
"node": ">=8"
}
},
- "node_modules/resolve-pathname": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
- "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==",
- "license": "MIT"
- },
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
@@ -47325,6 +46936,7 @@
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/select": {
@@ -48223,32 +47835,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/socket.io-client": {
- "version": "4.7.4",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
- "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.2",
- "engine.io-client": "~6.5.2",
- "socket.io-parser": "~4.2.4"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-parser": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
- "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@@ -49309,12 +48895,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/supports-webp": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/supports-webp/-/supports-webp-1.0.7.tgz",
- "integrity": "sha512-ZlqT+sCgZKcykOLrk8DYR4t3Em+nyVSHpiV3q7uzOutLwKIYU23n88KibCLw3FzM4NCQeRorvZ55AV/77lQyOQ==",
- "license": "MIT"
- },
"node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
@@ -51451,12 +51031,6 @@
"builtins": "^1.0.3"
}
},
- "node_modules/value-equal": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.2.1.tgz",
- "integrity": "sha512-yRL36Xb2K/HmFT5Fe3M86S7mu4+a12/3l7uytUh6eNPPjP77ldPBvsAvmnWff39sXn55naRMZN8LZWRO8PWaeQ==",
- "license": "MIT"
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -51524,12 +51098,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/visibilityjs": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/visibilityjs/-/visibilityjs-1.2.8.tgz",
- "integrity": "sha512-Y+aL3OUX88b+/VSmkmC2ApuLbf0grzbNLpCfIDSw3BzTU6PqcPsdgIOaw8b+eZoy+DdQqnVN3y/Evow9vQq9Ig==",
- "license": "MIT"
- },
"node_modules/vlq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
@@ -51591,15 +51159,6 @@
"integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
"license": "MIT"
},
- "node_modules/warning": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
- "integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
@@ -53033,22 +52592,6 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
},
- "node_modules/xmlhttprequest-ssl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
- "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/xstate": {
- "version": "4.37.2",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/xstate"
- }
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 7af92881d627..07464a5eb8b5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.50-1",
+ "version": "1.4.50-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.",
@@ -115,7 +115,7 @@
"lodash": "4.17.21",
"lottie-react-native": "6.4.1",
"mapbox-gl": "^2.15.0",
- "onfido-sdk-ui": "13.6.1",
+ "onfido-sdk-ui": "14.15.0",
"patch-package": "^8.0.0",
"process": "^0.11.10",
"prop-types": "^15.7.2",
@@ -280,7 +280,6 @@
"prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
"react-native-clean-project": "^4.0.0-alpha4.0",
- "react-native-performance-flipper-reporter": "^2.0.0",
"react-test-renderer": "18.2.0",
"reassure": "^0.10.1",
"setimmediate": "^1.0.5",
diff --git a/patches/@react-native-community+cli-platform-ios+12.3.0.patch b/patches/@react-native-community+cli-platform-ios+12.3.0.patch
index cfae504e44fa..e54ab17c43dd 100644
--- a/patches/@react-native-community+cli-platform-ios+12.3.0.patch
+++ b/patches/@react-native-community+cli-platform-ios+12.3.0.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb
-index 82f537c..f5e2cda 100644
+index 82f537c..df441e2 100644
--- a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb
+++ b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb
@@ -12,7 +12,7 @@
@@ -19,7 +19,7 @@ index 82f537c..f5e2cda 100644
if (!config)
json = []
-@@ -36,10 +35,30 @@ def use_native_modules!(config = nil)
+@@ -36,9 +35,24 @@ def use_native_modules!(config = nil)
config = JSON.parse(json.join("\n"))
end
@@ -42,11 +42,5 @@ index 82f537c..f5e2cda 100644
+ end
+
packages = config["dependencies"]
-+
-+ if (ENV["NO_FLIPPER"])
-+ packages = {**packages, "react-native-flipper" => {"platforms" => {"ios" => nil}}}
-+ end
-+
found_pods = []
- packages.each do |package_name, package|
diff --git a/src/CONST.ts b/src/CONST.ts
index ce2029c78713..6c0487a9177f 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -98,6 +98,8 @@ const CONST = {
AVATAR_MAX_WIDTH_PX: 4096,
AVATAR_MAX_HEIGHT_PX: 4096,
+ LOGO_MAX_SCALE: 1.5,
+
BREADCRUMB_TYPE: {
ROOT: 'root',
STRONG: 'strong',
@@ -1169,6 +1171,7 @@ const CONST = {
MISSING_FIELD: 'Missing required additional details fields',
WRONG_ANSWERS: 'Wrong answers',
ONFIDO_FIXABLE_ERROR: 'Onfido returned a fixable error',
+ ONFIDO_USER_CONSENT_DENIED: 'user_consent_denied',
// KBA stands for Knowledge Based Answers (requiring us to show Idology questions)
KBA_NEEDED: 'KBA needed',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 856a6fb89a3e..1c33e2ab5bab 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -557,6 +557,10 @@ const ROUTES = {
route: 'workspace/:policyID/categories/settings',
getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const,
},
+ WORKSPACE_MORE_FEATURES: {
+ route: 'workspace/:policyID/more-features',
+ getRoute: (policyID: string) => `workspace/${policyID}/more-features` as const,
+ },
WORKSPACE_CATEGORY_CREATE: {
route: 'workspace/:policyID/categories/new',
getRoute: (policyID: string) => `workspace/${policyID}/categories/new` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 8546f543b77a..8b653a8e22a1 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -227,6 +227,7 @@ const SCREENS = {
CATEGORY_CREATE: 'Category_Create',
CATEGORY_SETTINGS: 'Category_Settings',
CATEGORIES_SETTINGS: 'Categories_Settings',
+ MORE_FEATURES: 'Workspace_More_Features',
MEMBER_DETAILS: 'Workspace_Member_Details',
MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection',
DISTANCE_RATES: 'Distance_Rates',
diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx
index 7bed44cd8f13..6e1a1e0fd229 100644
--- a/src/components/BaseMiniContextMenuItem.tsx
+++ b/src/components/BaseMiniContextMenuItem.tsx
@@ -32,13 +32,20 @@ type BaseMiniContextMenuItemProps = {
* Whether the button should be in the active state
*/
isDelayButtonStateComplete: boolean;
+ /**
+ * Can be used to control the click event, and for example whether or not to lose focus from the composer when pressing the item
+ */
+ shouldPreventDefaultFocusOnPress?: boolean;
};
/**
* Component that renders a mini context menu item with a
* pressable. Also renders a tooltip when hovering the item.
*/
-function BaseMiniContextMenuItem({tooltipText, onPress, children, isDelayButtonStateComplete = true}: BaseMiniContextMenuItemProps, ref: ForwardedRef) {
+function BaseMiniContextMenuItem(
+ {tooltipText, onPress, children, isDelayButtonStateComplete = true, shouldPreventDefaultFocusOnPress = true}: BaseMiniContextMenuItemProps,
+ ref: ForwardedRef,
+) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
return (
@@ -64,7 +71,9 @@ function BaseMiniContextMenuItem({tooltipText, onPress, children, isDelayButtonS
}
// Prevent text input blur on left click
- event.preventDefault();
+ if (shouldPreventDefaultFocusOnPress) {
+ event.preventDefault();
+ }
}}
accessibilityLabel={tooltipText}
role={CONST.ROLE.BUTTON}
diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx
index 34bc3f7e30c8..e5eb09691eba 100644
--- a/src/components/Breadcrumbs.tsx
+++ b/src/components/Breadcrumbs.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
-import {View} from 'react-native';
+import {PixelRatio, View} from 'react-native';
import LogoComponent from '@assets/images/expensify-wordmark.svg';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -36,7 +36,7 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) {
const theme = useTheme();
const styles = useThemeStyles();
const [primaryBreadcrumb, secondaryBreadcrumb] = breadcrumbs;
-
+ const fontScale = PixelRatio.getFontScale() > CONST.LOGO_MAX_SCALE ? CONST.LOGO_MAX_SCALE : PixelRatio.getFontScale();
return (
{primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT ? (
@@ -47,8 +47,8 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) {
contentFit="contain"
src={LogoComponent}
fill={theme.text}
- width={variables.lhnLogoWidth}
- height={variables.lhnLogoHeight}
+ width={variables.lhnLogoWidth * fontScale}
+ height={variables.lhnLogoHeight * fontScale}
/>
}
shouldShowEnvironmentBadge
diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx
index d6c8fd973983..b80d6a138c9e 100644
--- a/src/components/ContextMenuItem.tsx
+++ b/src/components/ContextMenuItem.tsx
@@ -44,6 +44,8 @@ type ContextMenuItemProps = {
/** Styles to apply to ManuItem wrapper */
wrapperStyle?: StyleProp;
+
+ shouldPreventDefaultFocusOnPress?: boolean;
};
type ContextMenuItemHandle = {
@@ -63,6 +65,7 @@ function ContextMenuItem(
isFocused = false,
shouldLimitWidth = true,
wrapperStyle,
+ shouldPreventDefaultFocusOnPress = true,
}: ContextMenuItemProps,
ref: ForwardedRef,
) {
@@ -94,6 +97,7 @@ function ContextMenuItem(
tooltipText={itemText}
onPress={triggerPressAndUpdateSuccess}
isDelayButtonStateComplete={!isThrottledButtonActive}
+ shouldPreventDefaultFocusOnPress={shouldPreventDefaultFocusOnPress}
>
{({hovered, pressed}) => (
{
- const yearsList = searchText === '' ? years : years.filter((year) => year.text.includes(searchText));
+ const yearsList = searchText === '' ? years : years.filter((year) => year.text?.includes(searchText));
return {
headerMessage: !yearsList.length ? translate('common.noResultsFound') : '',
sections: [{data: yearsList.sort((a, b) => b.value - a.value), indexOffset: 0}],
diff --git a/src/components/DragAndDrop/Provider/types.ts b/src/components/DragAndDrop/Provider/types.ts
index b4394056cac5..57d0fb47c637 100644
--- a/src/components/DragAndDrop/Provider/types.ts
+++ b/src/components/DragAndDrop/Provider/types.ts
@@ -8,7 +8,7 @@ type DragAndDropProviderProps = {
isDisabled?: boolean;
/** Indicate that users are dragging file or not */
- setIsDraggingOver: (value: boolean) => void;
+ setIsDraggingOver?: (value: boolean) => void;
};
type SetOnDropHandlerCallback = (event: DragEvent) => void;
diff --git a/src/components/FlatList/index.android.tsx b/src/components/FlatList/index.android.tsx
index 1246367d29e8..863930203863 100644
--- a/src/components/FlatList/index.android.tsx
+++ b/src/components/FlatList/index.android.tsx
@@ -1,7 +1,7 @@
import {useFocusEffect} from '@react-navigation/native';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useContext} from 'react';
-import type {FlatListProps, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
+import type {FlatListProps} from 'react-native';
import {FlatList} from 'react-native';
import {ActionListContext} from '@pages/home/ReportScreenContext';
@@ -22,9 +22,6 @@ function CustomFlatList(props: FlatListProps, ref: ForwardedRef)
}
}, [scrollPosition?.offset, ref]);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent) => setScrollPosition({offset: event.nativeEvent.contentOffset.y}), []);
-
useFocusEffect(
useCallback(() => {
onScreenFocus();
@@ -35,8 +32,10 @@ function CustomFlatList(props: FlatListProps, ref: ForwardedRef)
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
- onScroll={props.onScroll}
- onMomentumScrollEnd={onMomentumScrollEnd}
+ onScroll={(event) => props.onScroll?.(event)}
+ onMomentumScrollEnd={(event) => {
+ setScrollPosition({offset: event.nativeEvent.contentOffset.y});
+ }}
ref={ref}
/>
);
diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts
index c3ffb500080b..83afbad8636b 100644
--- a/src/components/HeaderWithBackButton/types.ts
+++ b/src/components/HeaderWithBackButton/types.ts
@@ -1,9 +1,9 @@
import type {ReactNode} from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {Action} from '@hooks/useSingleExecution';
import type {StepCounterParams} from '@src/languages/types';
import type {AnchorPosition} from '@src/styles';
-import type {Policy, Report} from '@src/types/onyx';
+import type {PersonalDetails, Policy, Report} from '@src/types/onyx';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type IconAsset from '@src/types/utils/IconAsset';
@@ -101,6 +101,9 @@ type HeaderWithBackButtonProps = Partial & {
/** The report's policy, if we're showing the details for a report and need info about it for AvatarWithDisplay */
policy?: OnyxEntry;
+ /** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */
+ personalDetails?: OnyxCollection;
+
/** Single execution function to prevent concurrent navigation actions */
singleExecution?: (action: Action) => Action;
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 58cefb1877ce..28d1d53ed60c 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -29,13 +29,16 @@ import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg';
import TeleScope from '@assets/images/product-illustrations/telescope.svg';
import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_legged_laptop_woman.svg';
import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg';
+import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg';
import Approval from '@assets/images/simple-illustrations/simple-illustration__approval.svg';
import BankArrow from '@assets/images/simple-illustrations/simple-illustration__bank-arrow.svg';
import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg';
import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg';
import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg';
+import Car from '@assets/images/simple-illustrations/simple-illustration__car.svg';
import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg';
import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg';
+import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg';
import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg';
import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg';
import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
@@ -59,6 +62,7 @@ import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustra
import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg';
import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg';
import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg';
+import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg';
import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg';
import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg';
import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg';
@@ -148,6 +152,10 @@ export {
Workflows,
ThreeLeggedLaptopWoman,
House,
+ Accounting,
+ Car,
+ Coins,
+ Pencil,
Tag,
CarIce,
};
diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
index 0549e19c2eb4..e28400505280 100644
--- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
+++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
@@ -6,11 +6,6 @@ import FlatList from '@components/FlatList';
const WINDOW_SIZE = 15;
const AUTOSCROLL_TO_TOP_THRESHOLD = 128;
-const maintainVisibleContentPosition = {
- minIndexForVisible: 0,
- autoscrollToTopThreshold: AUTOSCROLL_TO_TOP_THRESHOLD,
-};
-
function BaseInvertedFlatList(props: FlatListProps, ref: ForwardedRef) {
return (
(props: FlatListProps, ref: ForwardedRef
);
diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
index 93eac30d5477..f5545f402b14 100644
--- a/src/components/LHNOptionsList/LHNOptionsList.tsx
+++ b/src/components/LHNOptionsList/LHNOptionsList.tsx
@@ -1,8 +1,9 @@
import {FlashList} from '@shopify/flash-list';
import type {ReactElement} from 'react';
-import React, {memo, useCallback, useMemo} from 'react';
+import React, {memo, useCallback} from 'react';
import {StyleSheet, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
+import withCurrentReportID from '@components/withCurrentReportID';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -27,6 +28,7 @@ function LHNOptionsList({
preferredLocale = CONST.LOCALES.DEFAULT,
personalDetails = {},
transactions = {},
+ currentReportID = '',
draftComments = {},
transactionViolations = {},
onFirstItemRendered = () => {},
@@ -84,7 +86,7 @@ function LHNOptionsList({
lastReportActionTransaction={lastReportActionTransaction}
receiptTransactions={transactions}
viewMode={optionMode}
- isFocused={!shouldDisableFocusOptions}
+ isFocused={!shouldDisableFocusOptions && reportID === currentReportID}
onSelectRow={onSelectRow}
preferredLocale={preferredLocale}
comment={itemComment}
@@ -96,6 +98,7 @@ function LHNOptionsList({
);
},
[
+ currentReportID,
draftComments,
onSelectRow,
optionMode,
@@ -113,8 +116,6 @@ function LHNOptionsList({
],
);
- const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]);
-
return (
@@ -135,31 +136,33 @@ function LHNOptionsList({
LHNOptionsList.displayName = 'LHNOptionsList';
-export default withOnyx({
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
- reportActions: {
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- },
- policy: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- preferredLocale: {
- key: ONYXKEYS.NVP_PREFERRED_LOCALE,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
- transactions: {
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- },
- draftComments: {
- key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
- },
- transactionViolations: {
- key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
- },
-})(memo(LHNOptionsList));
+export default withCurrentReportID(
+ withOnyx({
+ reports: {
+ key: ONYXKEYS.COLLECTION.REPORT,
+ },
+ reportActions: {
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ },
+ policy: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
+ preferredLocale: {
+ key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+ },
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ },
+ transactions: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ },
+ draftComments: {
+ key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
+ },
+ transactionViolations: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
+ },
+ })(memo(LHNOptionsList)),
+);
export type {LHNOptionsListProps};
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx
index 9b22b50b64fe..a3394190d0c1 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx
@@ -1,6 +1,5 @@
import {deepEqual} from 'fast-equals';
import React, {useEffect, useMemo, useRef} from 'react';
-import useCurrentReportID from '@hooks/useCurrentReportID';
import * as ReportUtils from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
import * as Report from '@userActions/Report';
@@ -33,8 +32,6 @@ function OptionRowLHNData({
...propsToForward
}: OptionRowLHNDataProps) {
const reportID = propsToForward.reportID;
- const currentReportIDValue = useCurrentReportID();
- const isReportFocused = isFocused && currentReportIDValue?.currentReportID === reportID;
const optionItemRef = useRef();
@@ -88,7 +85,7 @@ function OptionRowLHNData({
);
diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
index 4ca30358f9b1..c122ab018392 100644
--- a/src/components/LHNOptionsList/types.ts
+++ b/src/components/LHNOptionsList/types.ts
@@ -3,6 +3,7 @@ import type {RefObject} from 'react';
import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
+import type {CurrentReportIDContextValue} from '@components/withCurrentReportID';
import type CONST from '@src/CONST';
import type {OptionData} from '@src/libs/ReportUtils';
import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
@@ -63,7 +64,7 @@ type CustomLHNOptionsListProps = {
reportIDsWithErrors: Record;
};
-type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps;
+type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps;
type OptionRowLHNDataProps = {
/** Whether row should be focused */
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 2520520fd467..e0d6c39623ed 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -20,6 +20,7 @@ import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
+import {usePersonalDetails} from './OnyxProvider';
import SettlementButton from './SettlementButton';
type PaymentType = DeepValueOf;
@@ -40,10 +41,11 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & {
report: OnyxTypes.Report;
/** The policy tied to the money request report */
- policy: OnyxTypes.Policy;
+ policy: OnyxEntry;
};
function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport}: MoneyReportHeaderProps) {
+ const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const styles = useThemeStyles();
const {translate} = useLocalize();
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
@@ -79,8 +81,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
// The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
const isWaitingForSubmissionFromCurrentUser = useMemo(
- () => chatReport?.isOwnPolicyExpenseChat && !policy.harvesting?.enabled,
- [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled],
+ () => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled,
+ [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled],
);
const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)];
@@ -100,6 +102,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
shouldShowPinButton={false}
report={moneyRequestReport}
policy={policy}
+ personalDetails={personalDetails}
shouldShowBackButton={isSmallScreenWidth}
onBackButtonPress={() => Navigation.goBack(undefined, false, true)}
// Shows border if no buttons or next steps are showing below the header
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 338796cd856e..b5c84354e466 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -21,6 +21,7 @@ import HeaderWithBackButton from './HeaderWithBackButton';
import HoldBanner from './HoldBanner';
import * as Expensicons from './Icon/Expensicons';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
+import {usePersonalDetails} from './OnyxProvider';
import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
type MoneyRequestHeaderOnyxProps = {
@@ -46,13 +47,14 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & {
report: Report;
/** The policy which the report is tied to */
- policy: Policy;
+ policy: OnyxEntry;
/** The report action the transaction is tied to from the parent report */
- parentReportAction: ReportAction & OriginalMessageIOU;
+ parentReportAction: OnyxEntry;
};
function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) {
+ const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const styles = useThemeStyles();
const {translate} = useLocalize();
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
@@ -69,7 +71,11 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
const deleteTransaction = useCallback(() => {
- IOU.deleteMoneyRequest(parentReportAction?.originalMessage?.IOUTransactionID ?? '', parentReportAction, true);
+ if (parentReportAction) {
+ const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : '';
+ IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true);
+ }
+
setIsDeleteModalVisible(false);
}, [parentReportAction, setIsDeleteModalVisible]);
@@ -83,11 +89,13 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
const canDeleteRequest = isActionOwner && ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) && !isDeletedParentAction;
const changeMoneyRequestStatus = () => {
+ const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : '';
+
if (isOnHold) {
- IOU.unholdRequest(parentReportAction?.originalMessage?.IOUTransactionID ?? '', report?.reportID);
+ IOU.unholdRequest(iouTransactionID, report?.reportID);
} else {
const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams());
- Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type, parentReportAction?.originalMessage?.IOUTransactionID ?? '', report?.reportID, activeRoute));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? '', iouTransactionID, report?.reportID, activeRoute));
}
};
@@ -165,6 +173,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
ownerAccountID: parentReport?.ownerAccountID,
}}
policy={policy}
+ personalDetails={personalDetails}
shouldShowBackButton={isSmallScreenWidth}
onBackButtonPress={() => Navigation.goBack(undefined, false, true)}
/>
diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.js
index ee206b15fc24..57f10f49f396 100644
--- a/src/components/Onfido/BaseOnfidoWeb.js
+++ b/src/components/Onfido/BaseOnfidoWeb.js
@@ -1,5 +1,5 @@
import lodashGet from 'lodash/get';
-import * as OnfidoSDK from 'onfido-sdk-ui';
+import {Onfido as OnfidoSDK} from 'onfido-sdk-ui';
import React, {forwardRef, useEffect} from 'react';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
@@ -15,7 +15,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo
OnfidoSDK.init({
token: sdkToken,
containerId: CONST.ONFIDO.CONTAINER_ID,
- useMemoryHistory: true,
customUI: {
fontFamilyTitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`,
fontFamilySubtitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`,
@@ -86,18 +85,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo
onSuccess(data);
},
onError: (error) => {
- const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR);
const errorType = lodashGet(error, 'type');
+ const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR);
Log.hmmm('Onfido error', {errorType, errorMessage});
+ if (errorType === CONST.WALLET.ERROR.ONFIDO_USER_CONSENT_DENIED) {
+ onUserExit();
+ return;
+ }
onError(errorMessage);
},
- onUserExit: (userExitCode) => {
- Log.hmmm('Onfido user exits the flow', {userExitCode});
- onUserExit(userExitCode);
- },
- onModalRequestClose: () => {
- Log.hmmm('Onfido user closed the modal');
- },
language: {
// We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido
locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale,
diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx
index 4d3a33ae4e67..0232dba99f05 100644
--- a/src/components/PDFThumbnail/index.native.tsx
+++ b/src/components/PDFThumbnail/index.native.tsx
@@ -6,7 +6,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import type PDFThumbnailProps from './types';
-function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword = () => {}}: PDFThumbnailProps) {
+function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) {
const styles = useThemeStyles();
const sizeStyles = [styles.w100, styles.h100];
@@ -25,6 +25,9 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena
if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) {
return;
}
+ if (!onPassword) {
+ return;
+ }
onPassword();
}}
/>
diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx
index e69e4dd5075b..a5b911deb6ff 100644
--- a/src/components/PDFThumbnail/index.tsx
+++ b/src/components/PDFThumbnail/index.tsx
@@ -12,7 +12,7 @@ if (!pdfjs.GlobalWorkerOptions.workerSrc) {
pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'}));
}
-function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword = () => {}}: PDFThumbnailProps) {
+function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) {
const styles = useThemeStyles();
const thumbnail = useMemo(
@@ -25,9 +25,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena
cMapPacked: true,
}}
externalLinkTarget="_blank"
- onPassword={() => {
- onPassword();
- }}
+ onPassword={onPassword}
>
diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx
index 9e169b23391a..05891311ba6d 100644
--- a/src/components/ReportActionItem/MoneyRequestAction.tsx
+++ b/src/components/ReportActionItem/MoneyRequestAction.tsx
@@ -8,9 +8,7 @@ import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import * as IOUUtils from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
-import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
-import * as ReportUtils from '@libs/ReportUtils';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -91,15 +89,7 @@ function MoneyRequestAction({
return;
}
- // If the childReportID is not present, we need to create a new thread
- const childReportID = action?.childReportID;
- if (!childReportID) {
- const thread = ReportUtils.buildTransactionThread(action, requestReportID);
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs ?? []);
- Report.openReport(thread.reportID, userLogins, thread, action.reportActionID);
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(thread.reportID));
- return;
- }
+ const childReportID = action?.childReportID ?? '0';
Report.openReport(childReportID);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID));
};
diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx
index 4605d27b32dc..306846ad7d99 100644
--- a/src/components/ScreenWrapper.tsx
+++ b/src/components/ScreenWrapper.tsx
@@ -14,7 +14,7 @@ import useTackInputFocus from '@hooks/useTackInputFocus';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
-import type {RootStackParamList} from '@libs/Navigation/types';
+import type {CentralPaneNavigatorParamList, RootStackParamList} from '@libs/Navigation/types';
import toggleTestToolsModal from '@userActions/TestTool';
import CONST from '@src/CONST';
import CustomDevMenu from './CustomDevMenu';
@@ -92,7 +92,7 @@ type ScreenWrapperProps = {
*
* This is required because transitionEnd event doesn't trigger in the testing environment.
*/
- navigation?: StackNavigationProp;
+ navigation?: StackNavigationProp | StackNavigationProp;
/** Whether to show offline indicator on wide screens */
shouldShowOfflineIndicatorInWideScreen?: boolean;
diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx
index c032fe2d081b..6eedc322f393 100644
--- a/src/components/SelectionList/BaseListItem.tsx
+++ b/src/components/SelectionList/BaseListItem.tsx
@@ -66,19 +66,19 @@ function BaseListItem({
{...bind}
onPress={() => onSelectRow(item)}
disabled={isDisabled}
- accessibilityLabel={item.text}
+ accessibilityLabel={item.text ?? ''}
role={CONST.ROLE.BUTTON}
hoverDimmingValue={1}
hoverStyle={!item.isSelected && styles.hoveredComponentBG}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
onMouseDown={shouldPreventDefaultFocusOnSelectRow ? (e) => e.preventDefault() : undefined}
- nativeID={keyForList}
+ nativeID={keyForList ?? ''}
style={pressableStyle}
>
{canSelectMultiple && (
(
onDismissError={() => onDismissError?.(item)}
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
rightHandSideComponent={rightHandSideComponent}
- keyForList={item.keyForList}
+ keyForList={item.keyForList ?? ''}
isMultilineSupported={isRowMultilineSupported}
/>
);
@@ -499,7 +499,7 @@ function BaseSelectionList(
getItemLayout={getItemLayout}
onScroll={onScroll}
onScrollBeginDrag={onScrollBeginDrag}
- keyExtractor={(item) => item.keyForList}
+ keyExtractor={(item, index) => item.keyForList ?? `${index}`}
extraData={focusedIndex}
indicatorStyle="white"
keyboardShouldPersistTaps="always"
diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx
index be2302d21b89..f61e0d1c6a3d 100644
--- a/src/components/SelectionList/RadioListItem.tsx
+++ b/src/components/SelectionList/RadioListItem.tsx
@@ -42,7 +42,7 @@ function RadioListItem({
- {(hovered) => (
+ {(hovered?: boolean) => (
<>
{!!item.icons &&
(item.shouldShowSubscript ? (
@@ -81,7 +81,7 @@ function UserListItem({
= {
isFocused?: boolean;
/** Whether this item is disabled */
- isDisabled?: boolean;
+ isDisabled?: boolean | null;
/** Whether this item should show Tooltip */
showTooltip: boolean;
@@ -47,19 +49,19 @@ type CommonListItemProps = {
type ListItem = {
/** Text to display */
- text: string;
+ text?: string;
/** Alternate text to display */
alternateText?: string | null;
/** Key used internally by React */
- keyForList: string;
+ keyForList?: string | null;
/** Whether this option is selected */
isSelected?: boolean;
/** Whether this option is disabled for selection */
- isDisabled?: boolean;
+ isDisabled?: boolean | null;
/** List title is bold by default. Use this props to customize it */
isBold?: boolean;
@@ -90,6 +92,9 @@ type ListItem = {
/** Represents the index of the option within the section it came from */
index?: number;
+ /** ID of the report */
+ reportID?: string;
+
/** Whether this option should show subscript */
shouldShowSubscript?: boolean | null;
@@ -117,7 +122,7 @@ type ListItemProps = CommonListItemProps & {
type BaseListItemProps = CommonListItemProps & {
item: TItem;
shouldPreventDefaultFocusOnSelectRow?: boolean;
- keyForList?: string;
+ keyForList?: string | null;
errors?: Errors | ReceiptErrors | null;
pendingAction?: PendingAction | null;
FooterComponent?: ReactElement;
@@ -158,7 +163,7 @@ type Section = {
type BaseSelectionListProps = Partial & {
/** Sections for the section list */
- sections: Array>>;
+ sections: Array>> | typeof CONST.EMPTY_ARRAY;
/** Default renderer for every item in the list */
ListItem: typeof RadioListItem | typeof UserListItem | typeof TableListItem;
@@ -185,7 +190,7 @@ type BaseSelectionListProps = Partial & {
textInputPlaceholder?: string;
/** Hint for the text input */
- textInputHint?: string;
+ textInputHint?: MaybePhraseKey;
/** Value for the text input */
textInputValue?: string;
@@ -274,6 +279,9 @@ type BaseSelectionListProps = Partial & {
/** Styles for the list header wrapper */
listHeaderWrapperStyle?: StyleProp;
+ /** Whether to auto focus the Search Input */
+ autoFocus?: boolean;
+
/** Whether to wrap long text up to 2 lines */
isRowMultilineSupported?: boolean;
diff --git a/src/components/withViewportOffsetTop.tsx b/src/components/withViewportOffsetTop.tsx
index d3e9b63ad3ee..2b659ac608b5 100644
--- a/src/components/withViewportOffsetTop.tsx
+++ b/src/components/withViewportOffsetTop.tsx
@@ -40,3 +40,5 @@ export default function withViewportOffsetTop {};
-
-export default useFlipper;
diff --git a/src/hooks/useFlipper/types.ts b/src/hooks/useFlipper/types.ts
deleted file mode 100644
index e69272fcb92c..000000000000
--- a/src/hooks/useFlipper/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type {NavigationContainerRefWithCurrent} from '@react-navigation/core';
-import type {RootStackParamList} from '@libs/Navigation/types';
-
-type UseFlipper = (ref: NavigationContainerRefWithCurrent) => void;
-
-export default UseFlipper;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 9c5b388cfff8..96673be70698 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1755,6 +1755,7 @@ export default {
workspaceType: 'Workspace type',
workspaceAvatar: 'Workspace avatar',
mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.',
+ moreFeatures: 'More features',
requested: 'Requested',
distanceRates: 'Distance rates',
selected: ({selectedNumber}) => `${selectedNumber} selected`,
@@ -1779,6 +1780,48 @@ export default {
existingCategoryError: 'A category with this name already exists.',
invalidCategoryName: 'Invalid category name.',
},
+ moreFeatures: {
+ spendSection: {
+ title: 'Spend',
+ subtitle: 'Enable optional functionality that helps you scale your team.',
+ },
+ organizeSection: {
+ title: 'Organize',
+ subtitle: 'Group and analyze spend, record every tax paid.',
+ },
+ integrateSection: {
+ title: 'Integrate',
+ subtitle: 'Connect Expensify to popular financial products.',
+ },
+ distanceRates: {
+ title: 'Distance rates',
+ subtitle: 'Add, update and enforce rates.',
+ },
+ workflows: {
+ title: 'Workflows',
+ subtitle: 'Configure how spend is approved and paid.',
+ },
+ categories: {
+ title: 'Categories',
+ subtitle: 'Track and organize spend.',
+ },
+ tags: {
+ title: 'Tags',
+ subtitle: 'Add additional ways to classify spend.',
+ },
+ taxes: {
+ title: 'Taxes',
+ subtitle: 'Document and reclaim eligible taxes.',
+ },
+ reportFields: {
+ title: 'Report fields',
+ subtitle: 'Set up custom fields for spend.',
+ },
+ connections: {
+ title: 'Connections',
+ subtitle: 'Sync your chart of accounts and more.',
+ },
+ },
tags: {
requiresTag: 'Members must tag all spend',
enableTag: 'Enable tag',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 9e2418d89233..d7dc9a1b404e 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1779,6 +1779,7 @@ export default {
workspaceType: 'Tipo de espacio de trabajo',
workspaceAvatar: 'Espacio de trabajo avatar',
mustBeOnlineToViewMembers: 'Debes estar en lÃnea para poder ver los miembros de este espacio de trabajo.',
+ moreFeatures: 'Más caracterÃsticas',
requested: 'Solicitado',
distanceRates: 'Tasas de distancia',
selected: ({selectedNumber}) => `${selectedNumber} seleccionados`,
@@ -1803,6 +1804,48 @@ export default {
existingCategoryError: 'Ya existe una categorÃa con este nombre.',
invalidCategoryName: 'Lo nombre de la categorÃa es invalido.',
},
+ moreFeatures: {
+ spendSection: {
+ title: 'Gasto',
+ subtitle: 'Habilita otras funcionalidades que ayudan a aumentar tu equipo.',
+ },
+ organizeSection: {
+ title: 'Organizar',
+ subtitle: 'Agrupa y analiza el gasto, registra cada impuesto pagado.',
+ },
+ integrateSection: {
+ title: 'Integrar',
+ subtitle: 'Conecta Expensify a otros productos financieros populares.',
+ },
+ distanceRates: {
+ title: 'Tasas de distancia',
+ subtitle: 'Añade, actualiza y haz cumplir las tasas.',
+ },
+ workflows: {
+ title: 'Flujos de trabajo',
+ subtitle: 'Configura cómo se aprueba y paga los gastos.',
+ },
+ categories: {
+ title: 'CategorÃas',
+ subtitle: 'Monitoriza y organiza los gastos.',
+ },
+ tags: {
+ title: 'Etiquetas',
+ subtitle: 'Añade formas adicionales de clasificar los gastos.',
+ },
+ taxes: {
+ title: 'Impuestos',
+ subtitle: 'Documenta y reclama los impuestos aplicables.',
+ },
+ reportFields: {
+ title: 'Campos de informes',
+ subtitle: 'Configura campos personalizados para los gastos.',
+ },
+ connections: {
+ title: 'Conexión',
+ subtitle: 'Sincroniza tu plan de cuentas y otras opciones.',
+ },
+ },
tags: {
requiresTag: 'Los miembros deben etiquetar todos los gastos',
enableTag: 'Habilitar etiqueta',
diff --git a/src/libs/API/parameters/AddCommentOrAttachementParams.ts b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
index a705c92f7f27..335df4f91fe2 100644
--- a/src/libs/API/parameters/AddCommentOrAttachementParams.ts
+++ b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
@@ -7,7 +7,6 @@ type AddCommentOrAttachementParams = {
reportComment?: string;
file?: FileObject;
timezone?: string;
- shouldAllowActionableMentionWhispers?: boolean;
clientCreatedTime?: string;
isOldDotConciergeChat?: boolean;
};
diff --git a/src/libs/API/parameters/EnablePolicyCategoriesParams.ts b/src/libs/API/parameters/EnablePolicyCategoriesParams.ts
new file mode 100644
index 000000000000..61aa600b8ea0
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyCategoriesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyCategoriesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyCategoriesParams;
diff --git a/src/libs/API/parameters/EnablePolicyConnectionsParams.ts b/src/libs/API/parameters/EnablePolicyConnectionsParams.ts
new file mode 100644
index 000000000000..cd2ac828b359
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyConnectionsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyConnectionsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyConnectionsParams;
diff --git a/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts b/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts
new file mode 100644
index 000000000000..d66f898e6e10
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyDistanceRatesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyDistanceRatesParams;
diff --git a/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts b/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts
new file mode 100644
index 000000000000..7a5670e200c8
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyReportFieldsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyReportFieldsParams;
diff --git a/src/libs/API/parameters/EnablePolicyTagsParams.ts b/src/libs/API/parameters/EnablePolicyTagsParams.ts
new file mode 100644
index 000000000000..8a8e21dd3371
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyTagsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyTagsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyTagsParams;
diff --git a/src/libs/API/parameters/EnablePolicyTaxesParams.ts b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
new file mode 100644
index 000000000000..4a235d5d6a1f
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyTaxesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyTaxesParams;
diff --git a/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts b/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts
new file mode 100644
index 000000000000..1958ec8df581
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyWorkflowsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyWorkflowsParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index b56398f6c4ad..df11c3bb9a7d 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -156,6 +156,13 @@ export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWor
export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams';
export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams';
export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams';
+export type {default as EnablePolicyCategoriesParams} from './EnablePolicyCategoriesParams';
+export type {default as EnablePolicyConnectionsParams} from './EnablePolicyConnectionsParams';
+export type {default as EnablePolicyDistanceRatesParams} from './EnablePolicyDistanceRatesParams';
+export type {default as EnablePolicyTagsParams} from './EnablePolicyTagsParams';
+export type {default as EnablePolicyTaxesParams} from './EnablePolicyTaxesParams';
+export type {default as EnablePolicyWorkflowsParams} from './EnablePolicyWorkflowsParams';
+export type {default as EnablePolicyReportFieldsParams} from './EnablePolicyReportFieldsParams';
export type {default as AcceptJoinRequestParams} from './AcceptJoinRequest';
export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest';
export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index d2aa1c84a9a1..e75e743d5788 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -157,6 +157,13 @@ const WRITE_COMMANDS = {
CANCEL_PAYMENT: 'CancelPayment',
ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount',
SWITCH_TO_OLD_DOT: 'SwitchToOldDot',
+ ENABLE_POLICY_CATEGORIES: 'EnablePolicyCategories',
+ ENABLE_POLICY_CONNECTIONS: 'EnablePolicyConnections',
+ ENABLE_POLICY_DISTANCE_RATES: 'EnablePolicyDistanceRates',
+ ENABLE_POLICY_TAGS: 'EnablePolicyTags',
+ ENABLE_POLICY_TAXES: 'EnablePolicyTaxes',
+ ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows',
+ ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields',
JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink',
ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest',
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
@@ -315,6 +322,13 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams;
[WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams;
[WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS]: Parameters.EnablePolicyConnectionsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES]: Parameters.EnablePolicyDistanceRatesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_TAGS]: Parameters.EnablePolicyTagsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_TAXES]: Parameters.EnablePolicyTaxesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS]: Parameters.EnablePolicyReportFieldsParams;
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 28e3bc8f5a88..5a3af07a3d5a 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -24,6 +24,7 @@ const workspaceSettingsScreens = {
[SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS]: () => require('../../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType,
} satisfies Screens;
diff --git a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx
index fd764cdb5861..5d710afa69e9 100644
--- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx
+++ b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx
@@ -13,7 +13,6 @@ function ReportScreenWrapper({route, navigation}: ReportScreenWrapperProps) {
return (
<>
diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx
index 2ca4c5178a5e..39efd8203c75 100644
--- a/src/libs/Navigation/NavigationRoot.tsx
+++ b/src/libs/Navigation/NavigationRoot.tsx
@@ -3,7 +3,6 @@ import {DefaultTheme, findFocusedRoute, NavigationContainer} from '@react-naviga
import React, {useEffect, useMemo, useRef} from 'react';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useCurrentReportID from '@hooks/useCurrentReportID';
-import useFlipper from '@hooks/useFlipper';
import useTheme from '@hooks/useTheme';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Log from '@libs/Log';
@@ -60,7 +59,6 @@ function parseAndLogRoute(state: NavigationState) {
}
function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) {
- useFlipper(navigationRef);
const firstRenderRef = useRef(true);
const theme = useTheme();
diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
index 6641b2c88f1a..be9c0b55e761 100755
--- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
@@ -14,6 +14,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record = {
SCREENS.WORKSPACE.TRAVEL,
SCREENS.WORKSPACE.MEMBERS,
SCREENS.WORKSPACE.CATEGORIES,
+ SCREENS.WORKSPACE.MORE_FEATURES,
SCREENS.WORKSPACE.DISTANCE_RATES,
],
};
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index fc3ad1668cd4..643860e4d81e 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -69,6 +69,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CATEGORIES]: {
path: ROUTES.WORKSPACE_CATEGORIES.route,
},
+ [SCREENS.WORKSPACE.MORE_FEATURES]: {
+ path: ROUTES.WORKSPACE_MORE_FEATURES.route,
+ },
[SCREENS.WORKSPACE.TAGS]: {
path: ROUTES.WORKSPACE_TAGS.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 8fa7290323a2..b6b19c4560e0 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -91,6 +91,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORIES]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.MORE_FEATURES]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
categoryName: string;
@@ -602,7 +605,7 @@ type AuthScreensParamList = SharedScreensParamList & {
};
};
-type RootStackParamList = PublicScreensParamList & AuthScreensParamList;
+type RootStackParamList = PublicScreensParamList & AuthScreensParamList & SearchNavigatorParamList;
type BottomTabName = keyof BottomTabNavigatorParamList;
diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.ts b/src/libs/Notification/LocalNotification/BrowserNotifications.ts
index 8908b91c4f42..2a0dfc1cd91a 100644
--- a/src/libs/Notification/LocalNotification/BrowserNotifications.ts
+++ b/src/libs/Notification/LocalNotification/BrowserNotifications.ts
@@ -44,7 +44,15 @@ function canUseBrowserNotifications(): Promise {
* @param icon Path to icon
* @param data extra data to attach to the notification
*/
-function push(title: string, body = '', icon: string | ImageSourcePropType = '', data: LocalNotificationData = {}, onClick: LocalNotificationClickHandler = () => {}, silent = false) {
+function push(
+ title: string,
+ body = '',
+ icon: string | ImageSourcePropType = '',
+ data: LocalNotificationData = {},
+ onClick: LocalNotificationClickHandler = () => {},
+ silent = false,
+ tag = '',
+) {
canUseBrowserNotifications().then((canUseNotifications) => {
if (!canUseNotifications) {
return;
@@ -57,6 +65,7 @@ function push(title: string, body = '', icon: string | ImageSourcePropType = '',
icon: String(icon),
data,
silent,
+ tag,
});
notificationCache[notificationID].onclick = () => {
onClick();
@@ -124,9 +133,17 @@ export default {
* Create a notification to indicate that an update is available.
*/
pushUpdateAvailableNotification() {
- push('Update available', 'A new version of this app is available!', '', {}, () => {
- AppUpdate.triggerUpdateAvailable();
- });
+ push(
+ 'Update available',
+ 'A new version of this app is available!',
+ '',
+ {},
+ () => {
+ AppUpdate.triggerUpdateAvailable();
+ },
+ false,
+ 'UpdateAvailable',
+ );
},
/**
diff --git a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts b/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts
index 82410b120df2..8305fa217f79 100644
--- a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts
+++ b/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts
@@ -1,10 +1,14 @@
import type {OnyxEntry} from 'react-native-onyx';
import type {Report} from '@src/types/onyx';
-export default function reportWithoutHasDraftSelector(report: OnyxEntry) {
+type ReportWithoutHasDraft = Omit;
+
+export default function reportWithoutHasDraftSelector(report: OnyxEntry): OnyxEntry {
if (!report) {
- return report;
+ return null;
}
const {hasDraft, ...reportWithoutHasDraft} = report;
return reportWithoutHasDraft;
}
+
+export type {ReportWithoutHasDraft};
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 3dd23752d5db..1aa73e752eae 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -902,7 +902,7 @@ function sortTags(tags: Record | Tag[]) {
* @param options[].name - a name of an option
* @param [isOneLine] - a flag to determine if text should be one line
*/
-function getCategoryOptionTree(options: Record | Category[], isOneLine = false): OptionTree[] {
+function getCategoryOptionTree(options: Record | Category[], isOneLine = false, selectedOptionsName: string[] = []): OptionTree[] {
const optionCollection = new Map();
Object.values(options).forEach((option) => {
if (isOneLine) {
@@ -937,7 +937,7 @@ function getCategoryOptionTree(options: Record | Category[], i
searchText,
tooltipText: optionName,
isDisabled: isChild ? !option.enabled : true,
- isSelected: !!option.isSelected,
+ isSelected: isChild ? !!option.isSelected : selectedOptionsName.includes(searchText),
});
});
});
@@ -1045,7 +1045,7 @@ function getCategoryListSections(
title: Localize.translateLocal('common.all'),
shouldShow: true,
indexOffset,
- data: getCategoryOptionTree(filteredCategories),
+ data: getCategoryOptionTree(filteredCategories, false, selectedOptionNames),
});
return categorySections;
@@ -1723,7 +1723,7 @@ function getOptions(
/**
* Build the options for the Search view
*/
-function getSearchOptions(reports: Record, personalDetails: OnyxEntry, searchValue = '', betas: Beta[] = []): GetOptions {
+function getSearchOptions(reports: OnyxCollection, personalDetails: OnyxEntry, searchValue = '', betas: Beta[] = []): GetOptions {
Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS);
Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS);
const options = getOptions(reports, personalDetails, {
diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx
index 5ac064e75727..8d812014dbf8 100644
--- a/src/libs/Performance.tsx
+++ b/src/libs/Performance.tsx
@@ -122,9 +122,6 @@ if (Metrics.canCapturePerformanceMetrics()) {
* Sets up an observer to capture events recorded in the native layer before the app fully initializes.
*/
Performance.setupPerformanceObserver = () => {
- const performanceReported = require('react-native-performance-flipper-reporter');
- performanceReported.setupDefaultFlipperReporter();
-
// Monitor some native marks that we want to put on the timeline
new perfModule.PerformanceObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => {
list.getEntries().forEach((entry: PerformanceEntry) => {
diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts
index 48c5e5c1409f..3cb15c0f3fc3 100644
--- a/src/libs/Pusher/pusher.ts
+++ b/src/libs/Pusher/pusher.ts
@@ -1,6 +1,5 @@
import isObject from 'lodash/isObject';
import type {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption';
-import {InteractionManager} from 'react-native';
import Onyx from 'react-native-onyx';
import type {LiteralUnion, ValueOf} from 'type-fest';
import Log from '@libs/Log';
@@ -227,50 +226,48 @@ function subscribe(
onResubscribe = () => {},
): Promise {
return new Promise((resolve, reject) => {
- InteractionManager.runAfterInteractions(() => {
- // We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
- if (!socket) {
- throw new Error(`[Pusher] instance not found. Pusher.subscribe()
+ // We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
+ if (!socket) {
+ throw new Error(`[Pusher] instance not found. Pusher.subscribe()
most likely has been called before Pusher.init()`);
- }
-
- Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName});
- let channel = getChannel(channelName);
-
- if (!channel || !channel.subscribed) {
- channel = socket.subscribe(channelName);
- let isBound = false;
- channel.bind('pusher:subscription_succeeded', () => {
- // Check so that we do not bind another event with each reconnect attempt
- if (!isBound) {
- bindEventToChannel(channel, eventName, eventCallback);
- resolve();
- isBound = true;
- return;
- }
-
- // When subscribing for the first time we register a success callback that can be
- // called multiple times when the subscription succeeds again in the future
- // e.g. as a result of Pusher disconnecting and reconnecting. This callback does
- // not fire on the first subscription_succeeded event.
- onResubscribe();
- });
+ }
- channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => {
- const {type, error, status} = data;
- Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', {
- channelName,
- status,
- type,
- error,
- });
- reject(error);
+ Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName});
+ let channel = getChannel(channelName);
+
+ if (!channel || !channel.subscribed) {
+ channel = socket.subscribe(channelName);
+ let isBound = false;
+ channel.bind('pusher:subscription_succeeded', () => {
+ // Check so that we do not bind another event with each reconnect attempt
+ if (!isBound) {
+ bindEventToChannel(channel, eventName, eventCallback);
+ resolve();
+ isBound = true;
+ return;
+ }
+
+ // When subscribing for the first time we register a success callback that can be
+ // called multiple times when the subscription succeeds again in the future
+ // e.g. as a result of Pusher disconnecting and reconnecting. This callback does
+ // not fire on the first subscription_succeeded event.
+ onResubscribe();
+ });
+
+ channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => {
+ const {type, error, status} = data;
+ Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', {
+ channelName,
+ status,
+ type,
+ error,
});
- } else {
- bindEventToChannel(channel, eventName, eventCallback);
- resolve();
- }
- });
+ reject(error);
+ });
+ } else {
+ bindEventToChannel(channel, eventName, eventCallback);
+ resolve();
+ }
});
}
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index b12469941fd9..c158a0c2972c 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -135,7 +135,7 @@ function isModifiedExpenseAction(reportAction: OnyxEntry): boolean
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE;
}
-function isWhisperAction(reportAction: OnyxEntry): boolean {
+function isWhisperAction(reportAction: OnyxEntry | EmptyObject): boolean {
return (reportAction?.whisperedToAccountIDs ?? []).length > 0;
}
@@ -205,7 +205,7 @@ function isSentMoneyReportAction(reportAction: OnyxEntry): boolean {
+function isTransactionThread(parentReportAction: OnyxEntry | EmptyObject): boolean {
return (
parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU &&
(parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE ||
@@ -482,8 +482,8 @@ function getLastVisibleAction(reportID: string, actionsToMerge: OnyxCollection = {}): LastVisibleMessage {
- const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge);
+function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxCollection = {}, reportAction: OnyxEntry | undefined = undefined): LastVisibleMessage {
+ const lastVisibleAction = reportAction ?? getLastVisibleAction(reportID, actionsToMerge);
const message = lastVisibleAction?.message?.[0];
if (message && isReportMessageAttachment(message)) {
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index e3708126322f..6c5b19704af4 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -175,6 +175,7 @@ type OptimisticIOUReportAction = Pick<
| 'pendingAction'
| 'receipt'
| 'whisperedToAccountIDs'
+ | 'childReportID'
>;
type ReportRouteParams = {
@@ -1230,7 +1231,7 @@ function isMoneyRequest(reportOrID: OnyxEntry | string): boolean {
/**
* Checks if a report is an IOU or expense report.
*/
-function isMoneyRequestReport(reportOrID: OnyxEntry | string): boolean {
+function isMoneyRequestReport(reportOrID: OnyxEntry | EmptyObject | string): boolean {
const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null;
return isIOUReport(report) || isExpenseReport(report);
}
@@ -2806,8 +2807,8 @@ function getReportDescriptionText(report: Report): string {
return parser.htmlToText(report.description);
}
-function getPolicyDescriptionText(policy: Policy): string {
- if (!policy.description) {
+function getPolicyDescriptionText(policy: OnyxEntry): string {
+ if (!policy?.description) {
return '';
}
@@ -3160,7 +3161,6 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
* @param [receipt]
* @param [isOwnPolicyExpenseChat] - Whether this is an expense report create from the current user's policy expense chat
*/
-
function buildOptimisticIOUReportAction(
type: ValueOf,
amount: number,
@@ -3925,16 +3925,15 @@ function buildOptimisticTaskReport(
* A helper method to create transaction thread
*
* @param reportAction - the parent IOU report action from which to create the thread
- *
- * @param moneyRequestReportID - the reportID which the report action belong to
+ * @param moneyRequestReport - the report which the report action belongs to
*/
-function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReportID: string): OptimisticChatReport {
+function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReport: Report): OptimisticChatReport {
const participantAccountIDs = [...new Set([currentUserAccountID, Number(reportAction?.actorAccountID)])].filter(Boolean) as number[];
return buildOptimisticChatReport(
participantAccountIDs,
getTransactionReportName(reportAction),
undefined,
- getReport(moneyRequestReportID)?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE,
+ moneyRequestReport.policyID,
CONST.POLICY.OWNER_ACCOUNT_ID_FAKE,
false,
'',
@@ -3942,10 +3941,63 @@ function buildTransactionThread(reportAction: OnyxEntry,
+ amount: number,
+ currency: string,
+ comment: string,
+ payeeEmail: string,
+ participants: Participant[],
+ transactionID: string,
+ paymentType?: PaymentMethodType,
+ isSettlingUp = false,
+ isSendMoneyFlow = false,
+ receipt: Receipt = {},
+ isOwnPolicyExpenseChat = false,
+): [OptimisticCreatedReportAction, OptimisticIOUReportAction, OptimisticChatReport, OptimisticCreatedReportAction] {
+ const iouActionCreationTime = DateUtils.getDBTime();
+
+ // The `CREATED` action must be optimistically generated before the IOU action so that it won't appear after the IOU action in the chat.
+ const createdActionForIOUReport = buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(iouActionCreationTime, 1));
+ const iouAction = buildOptimisticIOUReportAction(
+ type,
+ amount,
+ currency,
+ comment,
+ participants,
+ transactionID,
+ paymentType,
+ iouReport.reportID,
+ isSettlingUp,
+ isSendMoneyFlow,
+ receipt,
+ isOwnPolicyExpenseChat,
+ iouActionCreationTime,
+ );
+
+ // Create optimistic transactionThread and the `CREATED` action for it
+ const transactionThread = buildTransactionThread(iouAction, iouReport);
+ const createdActionForTransactionThread = buildOptimisticCreatedReportAction(payeeEmail);
+
+ // The IOU action and the transactionThread are co-dependent as parent-child, so we need to link them together
+ iouAction.childReportID = transactionThread.reportID;
+
+ return [createdActionForIOUReport, iouAction, transactionThread, createdActionForTransactionThread];
+}
+
function isUnread(report: OnyxEntry): boolean {
if (!report) {
return false;
@@ -4603,7 +4655,7 @@ function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Error
/**
* Return true if the Money Request report is marked for deletion.
*/
-function isMoneyRequestReportPendingDeletion(report: OnyxEntry): boolean {
+function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyObject): boolean {
if (!isMoneyRequestReport(report)) {
return false;
}
@@ -5279,6 +5331,7 @@ export {
buildOptimisticSubmittedReportAction,
buildOptimisticExpenseReport,
buildOptimisticIOUReportAction,
+ buildOptimisticMoneyRequestEntities,
buildOptimisticReportPreview,
buildOptimisticModifiedExpenseReportAction,
buildOptimisticCancelPaymentReportAction,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 71b3fd23a03c..40aa4c7247c6 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -357,7 +357,10 @@ function getOptionData({
} else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) {
result.alternateText = `${lastActorDisplayName}: ${lastMessageText}`;
} else {
- result.alternateText = lastMessageTextFromReport.length > 0 ? lastMessageText : Localize.translate(preferredLocale, 'report.noActivityYet');
+ result.alternateText = lastMessageTextFromReport.length > 0 ? lastMessageText : ReportActionsUtils.getLastVisibleMessage(report.reportID, {}, lastAction)?.lastMessageText;
+ if (!result.alternateText) {
+ result.alternateText = Localize.translate(preferredLocale, 'report.noActivityYet');
+ }
}
} else {
if (!lastMessageText) {
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 4b88bb7a77a8..af5c40836c74 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -914,26 +914,22 @@ function getMoneyRequestInformation(
// 4. The transaction thread, which requires the iouAction, and CREATED action for the transaction thread
// 5. REPORTPREVIEW action for the chatReport
// Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat
- const currentTime = DateUtils.getDBTime();
const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail);
- const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1));
- const iouAction = ReportUtils.buildOptimisticIOUReportAction(
+ const [optimisticCreatedActionForIOUReport, iouAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities(
+ iouReport,
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
amount,
currency,
comment,
+ payeeEmail,
[participant],
optimisticTransaction.transactionID,
undefined,
- iouReport.reportID,
false,
false,
receiptObject,
false,
- currentTime,
);
- const optimisticTransactionThread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID);
- const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail);
let reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID);
if (reportPreviewAction) {
@@ -970,7 +966,7 @@ function getMoneyRequestInformation(
iouReport,
optimisticTransaction,
optimisticCreatedActionForChat,
- optimisticCreatedActionForIOU,
+ optimisticCreatedActionForIOUReport,
iouAction,
optimisticPersonalDetailListAction,
reportPreviewAction,
@@ -994,7 +990,7 @@ function getMoneyRequestInformation(
transaction: optimisticTransaction,
iouAction,
createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : '0',
- createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : '0',
+ createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOUReport.reportActionID : '0',
reportPreviewAction,
transactionThreadReportID: optimisticTransactionThread.reportID,
createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID,
@@ -1889,25 +1885,18 @@ function createSplitsAndOnyxData(
// 1. CREATED action for the chatReport
// 2. CREATED action for the iouReport
// 3. IOU action for the iouReport
- // 4. REPORTPREVIEW action for the chatReport
- // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat
- const currentTime = DateUtils.getDBTime();
+ // 4. Transaction Thread and the CREATED action for it
+ // 5. REPORTPREVIEW action for the chatReport
const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit);
- const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1));
- const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction(
+ const [oneOnOneCreatedActionForIOU, oneOnOneIOUAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities(
+ oneOnOneIOUReport,
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
splitAmount,
currency,
comment,
+ currentUserEmailForIOUSplit,
[participant],
oneOnOneTransaction.transactionID,
- undefined,
- oneOnOneIOUReport.reportID,
- undefined,
- undefined,
- undefined,
- undefined,
- currentTime,
);
// Add optimistic personal details for new participants
@@ -1938,10 +1927,6 @@ function createSplitsAndOnyxData(
// Add tag to optimistic policy recently used tags when a participant is a workspace
const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat ? Policy.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag) : {};
- // Create optimistic transactionThread
- const optimisticTransactionThread = ReportUtils.buildTransactionThread(oneOnOneIOUAction, oneOnOneIOUReport.reportID);
- const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit);
-
// STEP 5: Build Onyx Data
const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest(
oneOnOneChatReport,
@@ -2058,7 +2043,7 @@ function splitBill(
}
/**
- * @param amount - always in smallest currency unit
+ * @param amount - always in the smallest currency unit
*/
function splitBillAndOpenReport(
participants: Participant[],
@@ -2523,16 +2508,16 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA
);
const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit);
- const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit);
- const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction(
+ const [oneOnOneCreatedActionForIOU, oneOnOneIOUAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities(
+ oneOnOneIOUReport,
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
splitAmount,
currency ?? '',
updatedTransaction.comment.comment ?? '',
+ currentUserEmailForIOUSplit,
[participant],
oneOnOneTransaction.transactionID,
undefined,
- oneOnOneIOUReport?.reportID,
);
let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport?.reportID ?? '', oneOnOneIOUReport?.reportID ?? '');
@@ -2542,9 +2527,6 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA
oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport, '', oneOnOneTransaction);
}
- const optimisticTransactionThread = ReportUtils.buildTransactionThread(oneOnOneIOUAction, oneOnOneIOUReport.reportID);
- const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit);
-
const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest(
oneOnOneChatReport,
oneOnOneIOUReport,
@@ -3252,26 +3234,23 @@ function getSendMoneyParams(
value: optimisticTransaction,
};
- // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat
- const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail);
- const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction(
- CONST.IOU.REPORT_ACTION_TYPE.PAY,
- amount,
- currency,
- comment,
- [recipient],
- optimisticTransaction.transactionID,
- paymentMethodType,
- optimisticIOUReport.reportID,
- false,
- true,
- );
+ const [optimisticCreatedActionForIOUReport, optimisticIOUReportAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] =
+ ReportUtils.buildOptimisticMoneyRequestEntities(
+ optimisticIOUReport,
+ CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ amount,
+ currency,
+ comment,
+ recipientEmail,
+ [recipient],
+ optimisticTransaction.transactionID,
+ paymentMethodType,
+ false,
+ true,
+ );
const reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, optimisticIOUReport);
- const optimisticTransactionThread = ReportUtils.buildTransactionThread(optimisticIOUReportAction, optimisticIOUReport.reportID);
- const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail);
-
// Change the method to set for new reports because it doesn't exist yet, is faster,
// and we need the data to be available when we navigate to the chat page
const optimisticChatReportData: OnyxUpdate = isNewChat
@@ -3445,7 +3424,7 @@ function getSendMoneyParams(
if (optimisticChatReportActionsData.value) {
// Add an optimistic created action to the optimistic chat reportActions data
- optimisticChatReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction;
+ optimisticChatReportActionsData.value[optimisticCreatedActionForIOUReport.reportActionID] = optimisticCreatedActionForIOUReport;
}
} else {
failureData.push({
@@ -3481,7 +3460,7 @@ function getSendMoneyParams(
paymentMethodType,
transactionID: optimisticTransaction.transactionID,
newIOUReportDetails,
- createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : '0',
+ createdReportActionID: isNewChat ? optimisticCreatedActionForIOUReport.reportActionID : '0',
reportPreviewReportActionID: reportPreviewAction.reportActionID,
transactionThreadReportID: optimisticTransactionThread.reportID,
createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID,
diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts
index 13e0a42e839f..dc8cefc0c30e 100644
--- a/src/libs/actions/PaymentMethods.ts
+++ b/src/libs/actions/PaymentMethods.ts
@@ -295,7 +295,7 @@ function saveWalletTransferMethodType(filterPaymentMethodType?: FilterMethodPaym
function dismissSuccessfulTransferBalancePage() {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowSuccess: false});
- Navigation.goBack(ROUTES.SETTINGS_WALLET);
+ Navigation.goBack();
}
/**
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index e43a93e26f76..8bfa2a4a11fd 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -15,6 +15,13 @@ import type {
DeleteMembersFromWorkspaceParams,
DeleteWorkspaceAvatarParams,
DeleteWorkspaceParams,
+ EnablePolicyCategoriesParams,
+ EnablePolicyConnectionsParams,
+ EnablePolicyDistanceRatesParams,
+ EnablePolicyReportFieldsParams,
+ EnablePolicyTagsParams,
+ EnablePolicyTaxesParams,
+ EnablePolicyWorkflowsParams,
OpenDraftWorkspaceRequestParams,
OpenPolicyCategoriesPageParams,
OpenPolicyDistanceRatesPageParams,
@@ -35,7 +42,9 @@ import type {
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
+import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import Log from '@libs/Log';
+import Navigation from '@libs/Navigation/Navigation';
import * as NumberUtils from '@libs/NumberUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
@@ -44,6 +53,8 @@ import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import ROUTES from '@src/ROUTES';
import type {
InvitedEmailsToAccountIDs,
PersonalDetailsList,
@@ -2642,6 +2653,346 @@ function clearCategoryErrors(policyID: string, categoryName: string) {
});
}
+function navigateWhenEnableFeature(policyID: string, featureRoute: Route) {
+ const isNarrowLayout = getIsNarrowLayout();
+
+ if (isNarrowLayout) {
+ Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID), CONST.NAVIGATION.TYPE.FORCED_UP);
+
+ return;
+ }
+
+ Navigation.navigate(featureRoute);
+}
+
+function enablePolicyCategories(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areCategoriesEnabled: enabled,
+ pendingFields: {
+ areCategoriesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areCategoriesEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areCategoriesEnabled: !enabled,
+ pendingFields: {
+ areCategoriesEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyCategoriesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID));
+ }
+}
+
+function enablePolicyConnections(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areConnectionsEnabled: enabled,
+ pendingFields: {
+ areConnectionsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areConnectionsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areConnectionsEnabled: !enabled,
+ pendingFields: {
+ areConnectionsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyConnectionsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS, parameters, onyxData);
+}
+
+function enablePolicyDistanceRates(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areDistanceRatesEnabled: enabled,
+ pendingFields: {
+ areDistanceRatesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areDistanceRatesEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areDistanceRatesEnabled: !enabled,
+ pendingFields: {
+ areDistanceRatesEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyDistanceRatesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID));
+ }
+}
+
+function enablePolicyReportFields(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areReportFieldsEnabled: enabled,
+ pendingFields: {
+ areReportFieldsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areReportFieldsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areReportFieldsEnabled: !enabled,
+ pendingFields: {
+ areReportFieldsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyReportFieldsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS, parameters, onyxData);
+}
+
+function enablePolicyTags(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areTagsEnabled: enabled,
+ pendingFields: {
+ areTagsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areTagsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areTagsEnabled: !enabled,
+ pendingFields: {
+ areTagsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyTagsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_TAGS, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_TAGS.getRoute(policyID));
+ }
+}
+
+function enablePolicyTaxes(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ tax: {
+ trackingEnabled: enabled,
+ },
+ pendingFields: {
+ tax: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ tax: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ tax: {
+ trackingEnabled: !enabled,
+ },
+ pendingFields: {
+ tax: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyTaxesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_TAXES, parameters, onyxData);
+}
+
+function enablePolicyWorkflows(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areWorkflowsEnabled: enabled,
+ pendingFields: {
+ areWorkflowsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areWorkflowsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areWorkflowsEnabled: !enabled,
+ pendingFields: {
+ areWorkflowsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyWorkflowsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID));
+ }
+}
+
/**
* Accept user join request to a workspace
*/
@@ -2823,5 +3174,12 @@ export {
declineJoinRequest,
createPolicyCategory,
clearCategoryErrors,
+ enablePolicyCategories,
+ enablePolicyConnections,
+ enablePolicyDistanceRates,
+ enablePolicyReportFields,
+ enablePolicyTags,
+ enablePolicyTaxes,
+ enablePolicyWorkflows,
openPolicyDistanceRatesPage,
};
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index f3cbabcd453c..b677b5369b68 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -447,7 +447,6 @@ function addActions(reportID: string, text = '', file?: FileObject) {
commentReportActionID: file && reportCommentAction ? reportCommentAction.reportActionID : null,
reportComment: reportCommentText,
file,
- shouldAllowActionableMentionWhispers: true,
clientCreatedTime: file ? attachmentAction?.created : reportCommentAction?.created,
};
@@ -2904,17 +2903,6 @@ function clearNewRoomFormError() {
});
}
-function getReportDraftStatus(reportID: string) {
- if (!allReports) {
- return false;
- }
-
- if (!allReports[reportID]) {
- return false;
- }
- return allReports[reportID]?.hasDraft;
-}
-
function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) {
const message = reportAction?.message?.[0];
if (!message) {
@@ -2965,7 +2953,6 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt
}
export {
- getReportDraftStatus,
searchInServer,
addComment,
addAttachment,
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index 07bc7f3ed418..619281ac7ecf 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -253,7 +253,7 @@ function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean, should
* @returns same callback if the action is allowed, otherwise a function that signs out and redirects to sign in
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function checkIfActionIsAllowed any>(callback: TCallback, isAnonymousAction = false): TCallback | (() => void) {
+function checkIfActionIsAllowed any) | void>(callback: TCallback, isAnonymousAction = false): TCallback | (() => void) {
if (isAnonymousUser() && !isAnonymousAction) {
return () => signOutAndRedirectToSignIn();
}
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 27c7f3e36fd4..3e00d8084825 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -100,7 +100,7 @@ function createTaskAndNavigate(
assigneeEmail: string,
assigneeAccountID = 0,
assigneeChatReport: OnyxEntry = null,
- policyID = CONST.POLICY.OWNER_EMAIL_FAKE,
+ policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE,
) {
const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID);
@@ -262,6 +262,7 @@ function createTaskAndNavigate(
API.write(WRITE_COMMANDS.CREATE_TASK, parameters, {optimisticData, successData, failureData});
Navigation.dismissModal(parentReportID);
+ Report.notifyNewAction(parentReportID, currentUserAccountID);
}
/**
diff --git a/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx
index af4b251952de..44f83f55a23f 100644
--- a/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx
+++ b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.tsx
@@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
@@ -48,55 +49,59 @@ function Confirmation({reimbursementAccount, reimbursementAccountDraft, onNext,
};
return (
-
- {translate('bankAccount.letsDoubleCheck')}
- {translate('bankAccount.thisBankAccount')}
- {setupType === CONST.BANK_ACCOUNT.SUBSTEP.MANUAL && (
-
-
+
+ {({safeAreaPaddingBottomStyle}) => (
+
+ {translate('bankAccount.letsDoubleCheck')}
+ {translate('bankAccount.thisBankAccount')}
+ {setupType === CONST.BANK_ACCOUNT.SUBSTEP.MANUAL && (
+
+
-
-
+
+
+ )}
+ {setupType === CONST.BANK_ACCOUNT.SUBSTEP.PLAID && (
+
+ )}
+
+ {error && error.length > 0 && (
+
+ )}
+
+
+
)}
- {setupType === CONST.BANK_ACCOUNT.SUBSTEP.PLAID && (
-
- )}
-
- {error && error.length > 0 && (
-
- )}
-
-
-
+
);
}
diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx
index 4228b1da9d12..3d6f1d5947ef 100644
--- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx
+++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO.tsx
@@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
@@ -39,84 +40,88 @@ function ConfirmationUBO({reimbursementAccount, reimbursementAccountDraft, onNex
const error = reimbursementAccount ? ErrorUtils.getLatestErrorMessage(reimbursementAccount) : '';
return (
-
- {translate('beneficialOwnerInfoStep.letsDoubleCheck')}
- {
- onMove(UBO_STEP_INDEXES.LEGAL_NAME);
- }}
- />
- {
- onMove(UBO_STEP_INDEXES.DATE_OF_BIRTH);
- }}
- />
- {
- onMove(UBO_STEP_INDEXES.SSN);
- }}
- />
- {
- onMove(UBO_STEP_INDEXES.ADDRESS);
- }}
- />
-
-
- {`${translate('beneficialOwnerInfoStep.byAddingThisBankAccount')} `}
-
- {translate('onfidoStep.facialScan')}
-
- {', '}
-
+ {({safeAreaPaddingBottomStyle}) => (
+
- {translate('common.privacy')}
-
- {` ${translate('common.and')} `}
-
- {translate('common.termsOfService')}
-
-
-
- {error && error.length > 0 && (
- {translate('beneficialOwnerInfoStep.letsDoubleCheck')}
+ {
+ onMove(UBO_STEP_INDEXES.LEGAL_NAME);
+ }}
+ />
+ {
+ onMove(UBO_STEP_INDEXES.DATE_OF_BIRTH);
+ }}
/>
- )}
-
-
-
+ {
+ onMove(UBO_STEP_INDEXES.SSN);
+ }}
+ />
+ {
+ onMove(UBO_STEP_INDEXES.ADDRESS);
+ }}
+ />
+
+
+ {`${translate('beneficialOwnerInfoStep.byAddingThisBankAccount')} `}
+
+ {translate('onfidoStep.facialScan')}
+
+ {', '}
+
+ {translate('common.privacy')}
+
+ {` ${translate('common.and')} `}
+
+ {translate('common.termsOfService')}
+
+
+
+ {error && error.length > 0 && (
+
+ )}
+
+
+
+ )}
+
);
}
diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx
index 42bf43d78910..45feabdf102f 100644
--- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx
+++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/CompanyOwnersListUBO.tsx
@@ -6,6 +6,7 @@ import Button from '@components/Button';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
+import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
@@ -103,48 +104,52 @@ function CompanyOwnersListUBO({
});
return (
-
- {translate('beneficialOwnerInfoStep.letsDoubleCheck')}
- {translate('beneficialOwnerInfoStep.regulationRequiresUsToVerifyTheIdentity')}
-
- {`${translate('beneficialOwnerInfoStep.owners')}:`}
- {isUserUBO && (
-
-
-
- {error && error.length > 0 && (
-
- )}
-
-
-
+
+ )}
+
);
}
diff --git a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx
index f05bb70bcd5a..80639c429685 100644
--- a/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx
+++ b/src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.tsx
@@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
@@ -43,87 +44,91 @@ function Confirmation({reimbursementAccount, reimbursementAccountDraft, onNext,
const error = ErrorUtils.getLatestErrorMessage(reimbursementAccount ?? {});
return (
-
- {translate('personalInfoStep.letsDoubleCheck')}
- {
- onMove(PERSONAL_INFO_STEP_INDEXES.LEGAL_NAME);
- }}
- />
- {
- onMove(PERSONAL_INFO_STEP_INDEXES.DATE_OF_BIRTH);
- }}
- />
- {
- onMove(PERSONAL_INFO_STEP_INDEXES.SSN);
- }}
- />
- {
- onMove(PERSONAL_INFO_STEP_INDEXES.ADDRESS);
- }}
- />
-
-
- {`${translate('personalInfoStep.byAddingThisBankAccount')} `}
-
- {translate('onfidoStep.facialScan')}
-
- {', '}
-
+ {({safeAreaPaddingBottomStyle}) => (
+
- {translate('common.privacy')}
-
- {` ${translate('common.and')} `}
-
- {translate('common.termsOfService')}
-
-
-
- {error && error.length > 0 && (
- {translate('personalInfoStep.letsDoubleCheck')}
+ {
+ onMove(PERSONAL_INFO_STEP_INDEXES.LEGAL_NAME);
+ }}
+ />
+ {
+ onMove(PERSONAL_INFO_STEP_INDEXES.DATE_OF_BIRTH);
+ }}
/>
- )}
-
-
-
+ {
+ onMove(PERSONAL_INFO_STEP_INDEXES.SSN);
+ }}
+ />
+ {
+ onMove(PERSONAL_INFO_STEP_INDEXES.ADDRESS);
+ }}
+ />
+
+
+ {`${translate('personalInfoStep.byAddingThisBankAccount')} `}
+
+ {translate('onfidoStep.facialScan')}
+
+ {', '}
+
+ {translate('common.privacy')}
+
+ {` ${translate('common.and')} `}
+
+ {translate('common.termsOfService')}
+
+
+
+ {error && error.length > 0 && (
+
+ )}
+
+
+
+ )}
+
);
}
diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx
index 47ba5174e4b0..82402d62a074 100644
--- a/src/pages/RoomMembersPage.tsx
+++ b/src/pages/RoomMembersPage.tsx
@@ -204,7 +204,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
});
});
- result = result.sort((value1, value2) => localeCompare(value1.text, value2.text));
+ result = result.sort((value1, value2) => localeCompare(value1.text ?? '', value2.text ?? ''));
return result;
};
diff --git a/src/pages/SearchPage/index.js b/src/pages/SearchPage/index.tsx
similarity index 77%
rename from src/pages/SearchPage/index.js
rename to src/pages/SearchPage/index.tsx
index 2a6308d27294..07096ce6c2d5 100644
--- a/src/pages/SearchPage/index.js
+++ b/src/pages/SearchPage/index.tsx
@@ -1,8 +1,8 @@
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {usePersonalDetails} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -12,44 +12,41 @@ import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
+import type {MaybePhraseKey} from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
+import type {RootStackParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import Performance from '@libs/Performance';
import * as ReportUtils from '@libs/ReportUtils';
-import reportPropTypes from '@pages/reportPropTypes';
import * as Report from '@userActions/Report';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import type * as OnyxTypes from '@src/types/onyx';
import SearchPageFooter from './SearchPageFooter';
-const propTypes = {
- /* Onyx Props */
-
+type SearchPageOnyxProps = {
/** Beta features list */
- betas: PropTypes.arrayOf(PropTypes.string),
+ betas: OnyxEntry;
/** All reports shared with the user */
- reports: PropTypes.objectOf(reportPropTypes),
+ reports: OnyxCollection;
/** Whether or not we are searching for reports on the server */
- isSearchingForReports: PropTypes.bool,
-
- /**
- * The navigation prop passed by the navigator.
- *
- * This is required because transitionEnd event doesn't trigger in the automated testing environment.
- */
- navigation: PropTypes.shape({}),
+ isSearchingForReports: OnyxEntry;
};
-const defaultProps = {
- betas: [],
- reports: {},
- isSearchingForReports: false,
- navigation: {},
+type SearchPageProps = SearchPageOnyxProps & StackScreenProps;
+
+type SearchPageSectionItem = {
+ data: ReportUtils.OptionData[];
+ shouldShow: boolean;
+ indexOffset: number;
};
+type SearchPageSectionList = SearchPageSectionItem[];
+
const setPerformanceTimersEnd = () => {
Timing.end(CONST.TIMING.SEARCH_RENDER);
Performance.markEnd(CONST.TIMING.SEARCH_RENDER);
@@ -57,14 +54,14 @@ const setPerformanceTimersEnd = () => {
const SearchPageFooterInstance = ;
-function SearchPage({betas, reports, isSearchingForReports, navigation}) {
+function SearchPage({betas, reports, isSearchingForReports, navigation}: SearchPageProps) {
const [isScreenTransitionEnd, setIsScreenTransitionEnd] = useState(false);
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const themeStyles = useThemeStyles();
const personalDetails = usePersonalDetails();
- const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : '';
+ const offlineMessage: MaybePhraseKey = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : '';
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
@@ -85,24 +82,24 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
} = useMemo(() => {
if (!isScreenTransitionEnd) {
return {
- recentReports: {},
- personalDetails: {},
- userToInvite: {},
+ recentReports: [],
+ personalDetails: [],
+ userToInvite: null,
headerMessage: '',
};
}
- const options = OptionsListUtils.getSearchOptions(reports, personalDetails, debouncedSearchValue.trim(), betas);
+ const options = OptionsListUtils.getSearchOptions(reports, personalDetails, debouncedSearchValue.trim(), betas ?? []);
const header = OptionsListUtils.getHeaderMessage(options.recentReports.length + options.personalDetails.length !== 0, Boolean(options.userToInvite), debouncedSearchValue);
return {...options, headerMessage: header};
}, [debouncedSearchValue, reports, personalDetails, betas, isScreenTransitionEnd]);
- const sections = useMemo(() => {
- const newSections = [];
+ const sections = useMemo((): SearchPageSectionList => {
+ const newSections: SearchPageSectionList = [];
let indexOffset = 0;
- if (recentReports.length > 0) {
+ if (recentReports?.length > 0) {
newSections.push({
- data: _.map(recentReports, (report) => ({...report, isBold: report.isUnread})),
+ data: recentReports.map((report) => ({...report, isBold: report.isUnread})),
shouldShow: true,
indexOffset,
});
@@ -129,7 +126,7 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
return newSections;
}, [localPersonalDetails, recentReports, userToInvite]);
- const selectReport = (option) => {
+ const selectReport = (option: ReportUtils.OptionData) => {
if (!option) {
return;
}
@@ -138,7 +135,7 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
setSearchValue('');
Navigation.dismissModal(option.reportID);
} else {
- Report.navigateToAndOpenReport([option.login]);
+ Report.navigateToAndOpenReport(option.login ? [option.login] : []);
}
};
@@ -163,7 +160,7 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
onBackButtonPress={Navigation.goBack}
/>
-
sections={didScreenTransitionEnd && isOptionsDataReady ? sections : CONST.EMPTY_ARRAY}
ListItem={UserListItem}
textInputValue={searchValue}
@@ -176,7 +173,7 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
onSelectRow={selectReport}
showLoadingPlaceholder={!didScreenTransitionEnd || !isOptionsDataReady}
footerContent={SearchPageFooterInstance}
- isLoadingNewOptions={isSearchingForReports}
+ isLoadingNewOptions={isSearchingForReports ?? undefined}
/>
>
@@ -185,11 +182,9 @@ function SearchPage({betas, reports, isSearchingForReports, navigation}) {
);
}
-SearchPage.propTypes = propTypes;
-SearchPage.defaultProps = defaultProps;
SearchPage.displayName = 'SearchPage';
-export default withOnyx({
+export default withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.tsx
similarity index 74%
rename from src/pages/home/HeaderView.js
rename to src/pages/home/HeaderView.tsx
index 10d2d1414c3a..ad3c40666e76 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.tsx
@@ -1,17 +1,15 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
import React, {memo, useMemo} from 'react';
import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import DisplayNames from '@components/DisplayNames';
+import type {ThreeDotsMenuItem} from '@components/HeaderWithBackButton/types';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import MultipleAvatars from '@components/MultipleAvatars';
import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle';
-import participantPropTypes from '@components/participantPropTypes';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView';
import SubscriptAvatar from '@components/SubscriptAvatar';
@@ -26,11 +24,11 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import {getGroupChatName} from '@libs/GroupChatUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
import Navigation from '@libs/Navigation/Navigation';
+import type {ReportWithoutHasDraft} from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import reportPropTypes from '@pages/reportPropTypes';
import * as Link from '@userActions/Link';
import * as Report from '@userActions/Report';
import * as Session from '@userActions/Session';
@@ -38,118 +36,100 @@ import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
-const propTypes = {
- /** Toggles the navigationMenu open and closed */
- onNavigationMenuButtonClicked: PropTypes.func.isRequired,
+type HeaderViewOnyxProps = {
+ /** URL to the assigned guide's appointment booking calendar */
+ guideCalendarLink: OnyxEntry;
- /** The report currently being looked at */
- report: reportPropTypes,
+ /** Current user session */
+ session: OnyxEntry;
/** Personal details of all the users */
- personalDetails: PropTypes.objectOf(participantPropTypes),
-
- /** Onyx Props */
- parentReport: reportPropTypes,
-
- /** URL to the assigned guide's appointment booking calendar */
- guideCalendarLink: PropTypes.string,
+ personalDetails: OnyxEntry;
- /** Current user session */
- session: PropTypes.shape({
- accountID: PropTypes.number,
- }),
+ /** Parent report */
+ parentReport: OnyxEntry;
/** The current policy of the report */
- policy: PropTypes.shape({
- /** The policy name */
- name: PropTypes.string,
+ policy: OnyxEntry;
+};
- /** The URL for the policy avatar */
- avatar: PropTypes.string,
+type HeaderViewProps = HeaderViewOnyxProps & {
+ /** Toggles the navigationMenu open and closed */
+ onNavigationMenuButtonClicked: () => void;
- /** The id of the policy */
- id: PropTypes.string,
- }),
+ /** The report currently being looked at */
+ report: OnyxTypes.Report;
/** The reportID of the request */
- reportID: PropTypes.string.isRequired,
+ reportID: string;
};
-const defaultProps = {
- personalDetails: {},
- report: null,
- guideCalendarLink: null,
- parentReport: {},
- session: {
- accountID: 0,
- },
- policy: {},
-};
-
-function HeaderView(props) {
+function HeaderView({report, personalDetails, parentReport, policy, session, reportID, guideCalendarLink, onNavigationMenuButtonClicked}: HeaderViewProps) {
const [isDeleteTaskConfirmModalVisible, setIsDeleteTaskConfirmModalVisible] = React.useState(false);
const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
- const isSelfDM = ReportUtils.isSelfDM(props.report);
+ const isSelfDM = ReportUtils.isSelfDM(report);
// Currently, currentUser is not included in participantAccountIDs, so for selfDM, we need to add the currentUser as participants.
- const participants = isSelfDM ? [props.session.accountID] : lodashGet(props.report, 'participantAccountIDs', []);
- const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails);
+ const participants = isSelfDM ? [session?.accountID ?? -1] : report?.participantAccountIDs ?? [];
+ const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails);
const isMultipleParticipant = participants.length > 1;
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant, undefined, isSelfDM);
- const isChatThread = ReportUtils.isChatThread(props.report);
- const isChatRoom = ReportUtils.isChatRoom(props.report);
- const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
- const isTaskReport = ReportUtils.isTaskReport(props.report);
- const reportHeaderData = !isTaskReport && !isChatThread && props.report.parentReportID ? props.parentReport : props.report;
+ const isChatThread = ReportUtils.isChatThread(report);
+ const isChatRoom = ReportUtils.isChatRoom(report);
+ const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
+ const isTaskReport = ReportUtils.isTaskReport(report);
+ const reportHeaderData = !isTaskReport && !isChatThread && report.parentReportID ? parentReport : report;
// Use sorted display names for the title for group chats on native small screen widths
- const title = ReportUtils.isGroupChat(props.report) ? getGroupChatName(props.report) : ReportUtils.getReportName(reportHeaderData);
+ const title = ReportUtils.isGroupChat(report) ? getGroupChatName(report) : ReportUtils.getReportName(reportHeaderData);
const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData);
const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData);
- const isConcierge = ReportUtils.hasSingleParticipant(props.report) && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE);
- const parentReportAction = ReportActionsUtils.getParentReportAction(props.report);
- const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(props.report, parentReportAction);
+ const isConcierge = ReportUtils.hasSingleParticipant(report) && participants.includes(CONST.ACCOUNT_ID.CONCIERGE);
+ const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+ const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction);
const isWhisperAction = ReportActionsUtils.isWhisperAction(parentReportAction);
- const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(props.report);
- const isPolicyMember = useMemo(() => !_.isEmpty(props.policy), [props.policy]);
- const canLeaveRoom = ReportUtils.canLeaveRoom(props.report, isPolicyMember);
- const reportDescription = ReportUtils.getReportDescriptionText(props.report);
- const policyName = ReportUtils.getPolicyName(props.report, true);
- const policyDescription = ReportUtils.getPolicyDescriptionText(props.policy);
- const isPersonalExpenseChat = isPolicyExpenseChat && ReportUtils.isCurrentUserSubmitter(props.report.reportID);
+ const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(report);
+ const isPolicyMember = useMemo(() => !isEmptyObject(policy), [policy]);
+ const canLeaveRoom = ReportUtils.canLeaveRoom(report, isPolicyMember);
+ const reportDescription = ReportUtils.getReportDescriptionText(report);
+ const policyName = ReportUtils.getPolicyName(report, true);
+ const policyDescription = ReportUtils.getPolicyDescriptionText(policy);
+ const isPersonalExpenseChat = isPolicyExpenseChat && ReportUtils.isCurrentUserSubmitter(report.reportID);
const shouldShowSubtitle = () => {
- if (_.isEmpty(subtitle)) {
+ if (!subtitle) {
return false;
}
if (isChatRoom) {
- return _.isEmpty(reportDescription);
+ return !reportDescription;
}
if (isPolicyExpenseChat) {
- return _.isEmpty(policyDescription);
+ return !policyDescription;
}
return true;
};
// We hide the button when we are chatting with an automated Expensify account since it's not possible to contact
// these users via alternative means. It is possible to request a call with Concierge so we leave the option for them.
- const threeDotMenuItems = [];
+ const threeDotMenuItems: ThreeDotsMenuItem[] = [];
if (isTaskReport && !isCanceledTaskReport) {
- const canModifyTask = Task.canModifyTask(props.report, props.session.accountID);
+ const canModifyTask = Task.canModifyTask(report, session?.accountID ?? -1);
// Task is marked as completed
- if (ReportUtils.isCompletedTaskReport(props.report) && canModifyTask) {
+ if (ReportUtils.isCompletedTaskReport(report) && canModifyTask) {
threeDotMenuItems.push({
icon: Expensicons.Checkmark,
text: translate('task.markAsIncomplete'),
- onSelected: Session.checkIfActionIsAllowed(() => Task.reopenTask(props.report)),
+ onSelected: Session.checkIfActionIsAllowed(() => Task.reopenTask(report)),
});
}
// Task is not closed
- if (props.report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && props.report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED && canModifyTask) {
+ if (report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED && canModifyTask) {
threeDotMenuItems.push({
icon: Expensicons.Trashcan,
text: translate('common.delete'),
@@ -159,19 +139,12 @@ function HeaderView(props) {
}
const join = Session.checkIfActionIsAllowed(() =>
- Report.updateNotificationPreference(
- props.reportID,
- props.report.notificationPreference,
- CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
- false,
- props.report.parentReportID,
- props.report.parentReportActionID,
- ),
+ Report.updateNotificationPreference(reportID, report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, report.parentReportID, report.parentReportActionID),
);
const canJoinOrLeave = !isSelfDM && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom);
- const canJoin = canJoinOrLeave && !isWhisperAction && props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom);
+ const canJoin = canJoinOrLeave && !isWhisperAction && report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ const canLeave = canJoinOrLeave && ((isChatThread && !!report.notificationPreference?.length) || isUserCreatedPolicyRoom || canLeaveRoom);
if (canJoin) {
threeDotMenuItems.push({
icon: Expensicons.ChatBubbles,
@@ -179,11 +152,11 @@ function HeaderView(props) {
onSelected: join,
});
} else if (canLeave) {
- const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && lodashGet(props.report, 'visibility', '') === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember;
+ const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember;
threeDotMenuItems.push({
icon: Expensicons.ChatBubbles,
text: translate('common.leave'),
- onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)),
+ onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(reportID, isWorkspaceMemberLeavingWorkspaceRoom)),
});
}
@@ -197,7 +170,7 @@ function HeaderView(props) {
);
const renderAdditionalText = () => {
- if (shouldShowSubtitle() || isPersonalExpenseChat || _.isEmpty(policyName) || !_.isEmpty(parentNavigationSubtitleData) || isSelfDM) {
+ if (shouldShowSubtitle() || isPersonalExpenseChat || !policyName || !isEmptyObject(parentNavigationSubtitleData) || isSelfDM) {
return null;
}
return (
@@ -208,28 +181,28 @@ function HeaderView(props) {
);
};
- threeDotMenuItems.push(HeaderUtils.getPinMenuItem(props.report));
+ threeDotMenuItems.push(HeaderUtils.getPinMenuItem(report));
- if (isConcierge && props.guideCalendarLink) {
+ if (isConcierge && guideCalendarLink) {
threeDotMenuItems.push({
icon: Expensicons.Phone,
text: translate('videoChatButtonAndMenu.tooltip'),
onSelected: Session.checkIfActionIsAllowed(() => {
- Link.openExternalLink(props.guideCalendarLink);
+ Link.openExternalLink(guideCalendarLink);
}),
});
}
const shouldShowThreeDotsButton = !!threeDotMenuItems.length;
- const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(props.report);
- const defaultSubscriptSize = ReportUtils.isExpenseRequest(props.report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT;
- const icons = ReportUtils.getIcons(reportHeaderData, props.personalDetails);
- const brickRoadIndicator = ReportUtils.hasReportNameError(props.report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
+ const shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
+ const defaultSubscriptSize = ReportUtils.isExpenseRequest(report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT;
+ const icons = ReportUtils.getIcons(reportHeaderData, personalDetails);
+ const brickRoadIndicator = ReportUtils.hasReportNameError(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
const shouldShowBorderBottom = !isTaskReport || !isSmallScreenWidth;
- const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(props.report);
+ const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report);
- const isLoading = !props.report || !props.report.reportID || !title;
+ const isLoading = !report.reportID || !title;
return (
{isLoading ? (
-
+
) : (
<>
{isSmallScreenWidth && (
ReportUtils.navigateToDetailsPage(props.report)}
+ onPress={() => ReportUtils.navigateToDetailsPage(report)}
style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]}
disabled={shouldDisableDetailPage}
accessibilityLabel={title}
@@ -293,10 +266,10 @@ function HeaderView(props) {
shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport}
renderAdditionalText={renderAdditionalText}
/>
- {!_.isEmpty(parentNavigationSubtitleData) && (
+ {!isEmptyObject(parentNavigationSubtitleData) && (
)}
@@ -308,14 +281,14 @@ function HeaderView(props) {
{subtitle}
)}
- {isChatRoom && !_.isEmpty(reportDescription) && _.isEmpty(parentNavigationSubtitleData) && (
+ {isChatRoom && !!reportDescription && isEmptyObject(parentNavigationSubtitleData) && (
{
- if (ReportUtils.canEditReportDescription(props.report, props.policy)) {
- Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(props.reportID));
+ if (ReportUtils.canEditReportDescription(report, policy)) {
+ Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(reportID));
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
}}
style={[styles.alignSelfStart, styles.mw100]}
accessibilityLabel={translate('reportDescriptionPage.roomDescription')}
@@ -328,14 +301,14 @@ function HeaderView(props) {
)}
- {isPolicyExpenseChat && !_.isEmpty(policyDescription) && _.isEmpty(parentNavigationSubtitleData) && (
+ {isPolicyExpenseChat && !!policyDescription && isEmptyObject(parentNavigationSubtitleData) && (
{
- if (ReportUtils.canEditPolicyDescription(props.policy)) {
- Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(props.report.policyID));
+ if (ReportUtils.canEditPolicyDescription(policy)) {
+ Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(report.policyID ?? ''));
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID));
}}
style={[styles.alignSelfStart, styles.mw100]}
accessibilityLabel={translate('workspace.editor.descriptionInputLabel')}
@@ -359,7 +332,7 @@ function HeaderView(props) {
)}
- {isTaskReport && !isSmallScreenWidth && ReportUtils.isOpenTaskReport(props.report) && }
+ {isTaskReport && !isSmallScreenWidth && ReportUtils.isOpenTaskReport(report) && }
{canJoin && !isSmallScreenWidth && joinButton}
{shouldShowThreeDotsButton && (
{
setIsDeleteTaskConfirmModalVisible(false);
- Session.checkIfActionIsAllowed(Task.deleteTask(props.report));
+ Session.checkIfActionIsAllowed(Task.deleteTask(report));
}}
onCancel={() => setIsDeleteTaskConfirmModalVisible(false)}
title={translate('task.deleteTask')}
@@ -391,19 +364,18 @@ function HeaderView(props) {
);
}
-HeaderView.propTypes = propTypes;
+
HeaderView.displayName = 'HeaderView';
-HeaderView.defaultProps = defaultProps;
export default memo(
- withOnyx({
+ withOnyx({
guideCalendarLink: {
key: ONYXKEYS.ACCOUNT,
- selector: (account) => (account && account.guideCalendarLink) || null,
+ selector: (account) => account?.guideCalendarLink ?? null,
initialValue: null,
},
parentReport: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report.reportID}`,
+ key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? report?.reportID}`,
selector: reportWithoutHasDraftSelector,
},
session: {
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.tsx
similarity index 56%
rename from src/pages/home/ReportScreen.js
rename to src/pages/home/ReportScreen.tsx
index 2e19a2c6a940..d4221c324ade 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.tsx
@@ -1,10 +1,13 @@
import {useIsFocused} from '@react-navigation/native';
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
+import lodashIsEqual from 'lodash/isEqual';
import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {InteractionManager, View} from 'react-native';
+import {View} from 'react-native';
+import type {FlatList, ViewStyle} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {WithOnyxInstanceState} from 'react-native-onyx/dist/types';
+import type {LayoutChangeEvent} from 'react-native/Libraries/Types/CoreEventTypes';
import Banner from '@components/Banner';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import DragAndDropProvider from '@components/DragAndDrop/Provider';
@@ -14,154 +17,116 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import ScreenWrapper from '@components/ScreenWrapper';
import TaskHeaderActionButton from '@components/TaskHeaderActionButton';
-import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID';
+import withCurrentReportID from '@components/withCurrentReportID';
+import type {CurrentReportIDContextValue} from '@components/withCurrentReportID';
import withViewportOffsetTop from '@components/withViewportOffsetTop';
+import type {ViewportOffsetTopProps} from '@components/withViewportOffsetTop';
import useAppFocusEvent from '@hooks/useAppFocusEvent';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Timing from '@libs/actions/Timing';
-import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import clearReportNotifications from '@libs/Notification/clearReportNotifications';
import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
+import type {ReportWithoutHasDraft} from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
import Performance from '@libs/Performance';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import reportMetadataPropTypes from '@pages/reportMetadataPropTypes';
-import reportPropTypes from '@pages/reportPropTypes';
+import type {CentralPaneNavigatorParamList} from '@navigation/types';
import * as ComposerActions from '@userActions/Composer';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
import HeaderView from './HeaderView';
-import reportActionPropTypes from './report/reportActionPropTypes';
import ReportActionsView from './report/ReportActionsView';
import ReportFooter from './report/ReportFooter';
import {ActionListContext, ReactionListContext} from './ReportScreenContext';
+import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext';
-const propTypes = {
- /** Navigation route context info provided by react navigation */
- route: PropTypes.shape({
- /** Route specific parameters used on this screen */
- params: PropTypes.shape({
- /** The ID of the report this screen should display */
- reportID: PropTypes.string,
-
- /** The reportActionID to scroll to */
- reportActionID: PropTypes.string,
- }).isRequired,
- }).isRequired,
-
+type ReportScreenOnyxProps = {
/** Tells us if the sidebar has rendered */
- isSidebarLoaded: PropTypes.bool,
+ isSidebarLoaded: OnyxEntry;
- /** The report currently being looked at */
- report: reportPropTypes,
+ /** Beta features list */
+ betas: OnyxEntry;
- /** The report metadata loading states */
- reportMetadata: reportMetadataPropTypes,
+ /** The policies which the user has access to */
+ policies: OnyxCollection;
- /** All the report actions for this report */
- reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
+ /** The account manager report ID */
+ accountManagerReportID: OnyxEntry;
- /** The report's parentReportAction */
- parentReportAction: PropTypes.shape(reportActionPropTypes),
+ /** Whether user is leaving the current report */
+ userLeavingStatus: OnyxEntry;
/** Whether the composer is full size */
- isComposerFullSize: PropTypes.bool,
+ isComposerFullSize: OnyxEntry;
- /** Beta features list */
- betas: PropTypes.arrayOf(PropTypes.string),
+ /** All the report actions for this report */
+ reportActions: OnyxTypes.ReportAction[];
- /** The policies which the user has access to */
- policies: PropTypes.objectOf(
- PropTypes.shape({
- /** The policy name */
- name: PropTypes.string,
+ /** The report currently being looked at */
+ report: OnyxEntry;
- /** The type of the policy */
- type: PropTypes.string,
- }),
- ),
+ /** The report metadata loading states */
+ reportMetadata: OnyxEntry;
- /** The account manager report ID */
- accountManagerReportID: PropTypes.string,
+ /** The report's parentReportAction */
+ parentReportAction: OnyxEntry;
+};
+type OnyxHOCProps = {
/** Onyx function that marks the component ready for hydration */
- markReadyForHydration: PropTypes.func,
-
- /** Whether user is leaving the current report */
- userLeavingStatus: PropTypes.bool,
-
- viewportOffsetTop: PropTypes.number.isRequired,
- ...withCurrentReportIDPropTypes,
+ markReadyForHydration?: () => void;
};
-const defaultProps = {
- isSidebarLoaded: false,
- reportActions: {},
- parentReportAction: {},
- report: {},
- reportMetadata: {
- isLoadingInitialReportActions: true,
- isLoadingOlderReportActions: false,
- isLoadingNewerReportActions: false,
- },
- isComposerFullSize: false,
- betas: [],
- policies: {},
- accountManagerReportID: null,
- userLeavingStatus: false,
- markReadyForHydration: null,
- ...withCurrentReportIDDefaultProps,
-};
+type ReportScreenNavigationProps = StackScreenProps;
-/**
- * Get the currently viewed report ID as number
- *
- * @param {Object} route
- * @param {Object} route.params
- * @param {String} route.params.reportID
- * @returns {String}
- */
-function getReportID(route) {
+type ReportScreenProps = OnyxHOCProps & ViewportOffsetTopProps & CurrentReportIDContextValue & ReportScreenOnyxProps & ReportScreenNavigationProps;
+
+/** Get the currently viewed report ID as number */
+function getReportID(route: ReportScreenNavigationProps['route']): string {
// The report ID is used in an onyx key. If it's an empty string, onyx will return
// a collection instead of an individual report.
- // We can't use the default value functionality of `lodash.get()` because it only
- // provides a default value on `undefined`, and will return an empty string.
- // Placing the default value outside of `lodash.get()` is intentional.
- return String(lodashGet(route, 'params.reportID') || 0);
+ return String(route.params?.reportID || 0);
}
function ReportScreen({
- betas,
+ betas = [],
route,
report: reportProp,
- reportMetadata,
- reportActions,
+ reportMetadata = {
+ isLoadingInitialReportActions: true,
+ isLoadingOlderReportActions: false,
+ isLoadingNewerReportActions: false,
+ },
+ reportActions = [],
parentReportAction,
accountManagerReportID,
markReadyForHydration,
- policies,
- isSidebarLoaded,
+ policies = {},
+ isSidebarLoaded = false,
viewportOffsetTop,
- isComposerFullSize,
- errors,
- userLeavingStatus,
- currentReportID,
+ isComposerFullSize = false,
+ userLeavingStatus = false,
+ currentReportID = '',
navigation,
-}) {
+}: ReportScreenProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
+ const isFocused = useIsFocused();
const firstRenderRef = useRef(true);
- const flatListRef = useRef();
- const reactionListRef = useRef();
+ const flatListRef = useRef(null);
+ const reactionListRef = useRef(null);
/**
* Create a lightweight Report so as to keep the re-rendering as light as possible by
* passing in only the required props.
@@ -171,79 +136,79 @@ function ReportScreen({
* put this into onyx selector as it will be the same.
*/
const report = useMemo(
- () => ({
- lastReadTime: reportProp.lastReadTime,
- reportID: reportProp.reportID,
- policyID: reportProp.policyID,
- lastVisibleActionCreated: reportProp.lastVisibleActionCreated,
- statusNum: reportProp.statusNum,
- stateNum: reportProp.stateNum,
- writeCapability: reportProp.writeCapability,
- type: reportProp.type,
- errorFields: reportProp.errorFields,
- isPolicyExpenseChat: reportProp.isPolicyExpenseChat,
- parentReportID: reportProp.parentReportID,
- parentReportActionID: reportProp.parentReportActionID,
- chatType: reportProp.chatType,
- pendingFields: reportProp.pendingFields,
- isDeletedParentAction: reportProp.isDeletedParentAction,
- reportName: reportProp.reportName,
- description: reportProp.description,
- managerID: reportProp.managerID,
- total: reportProp.total,
- nonReimbursableTotal: reportProp.nonReimbursableTotal,
- reportFields: reportProp.reportFields,
- ownerAccountID: reportProp.ownerAccountID,
- currency: reportProp.currency,
- participantAccountIDs: reportProp.participantAccountIDs,
- isWaitingOnBankAccount: reportProp.isWaitingOnBankAccount,
- iouReportID: reportProp.iouReportID,
- isOwnPolicyExpenseChat: reportProp.isOwnPolicyExpenseChat,
- notificationPreference: reportProp.notificationPreference,
- isPinned: reportProp.isPinned,
- chatReportID: reportProp.chatReportID,
- visibility: reportProp.visibility,
- oldPolicyName: reportProp.oldPolicyName,
- policyName: reportProp.policyName,
- isOptimisticReport: reportProp.isOptimisticReport,
- lastMentionedTime: reportProp.lastMentionedTime,
+ (): OnyxTypes.Report => ({
+ lastReadTime: reportProp?.lastReadTime,
+ reportID: reportProp?.reportID ?? '',
+ policyID: reportProp?.policyID,
+ lastVisibleActionCreated: reportProp?.lastVisibleActionCreated,
+ statusNum: reportProp?.statusNum,
+ stateNum: reportProp?.stateNum,
+ writeCapability: reportProp?.writeCapability,
+ type: reportProp?.type,
+ errorFields: reportProp?.errorFields,
+ isPolicyExpenseChat: reportProp?.isPolicyExpenseChat,
+ parentReportID: reportProp?.parentReportID,
+ parentReportActionID: reportProp?.parentReportActionID,
+ chatType: reportProp?.chatType,
+ pendingFields: reportProp?.pendingFields,
+ isDeletedParentAction: reportProp?.isDeletedParentAction,
+ reportName: reportProp?.reportName,
+ description: reportProp?.description,
+ managerID: reportProp?.managerID,
+ total: reportProp?.total,
+ nonReimbursableTotal: reportProp?.nonReimbursableTotal,
+ reportFields: reportProp?.reportFields,
+ ownerAccountID: reportProp?.ownerAccountID,
+ currency: reportProp?.currency,
+ participantAccountIDs: reportProp?.participantAccountIDs,
+ isWaitingOnBankAccount: reportProp?.isWaitingOnBankAccount,
+ iouReportID: reportProp?.iouReportID,
+ isOwnPolicyExpenseChat: reportProp?.isOwnPolicyExpenseChat,
+ notificationPreference: reportProp?.notificationPreference,
+ isPinned: reportProp?.isPinned,
+ chatReportID: reportProp?.chatReportID,
+ visibility: reportProp?.visibility,
+ oldPolicyName: reportProp?.oldPolicyName,
+ policyName: reportProp?.policyName,
+ isOptimisticReport: reportProp?.isOptimisticReport,
+ lastMentionedTime: reportProp?.lastMentionedTime,
}),
[
- reportProp.lastReadTime,
- reportProp.reportID,
- reportProp.policyID,
- reportProp.lastVisibleActionCreated,
- reportProp.statusNum,
- reportProp.stateNum,
- reportProp.writeCapability,
- reportProp.type,
- reportProp.errorFields,
- reportProp.isPolicyExpenseChat,
- reportProp.parentReportID,
- reportProp.parentReportActionID,
- reportProp.chatType,
- reportProp.pendingFields,
- reportProp.isDeletedParentAction,
- reportProp.reportName,
- reportProp.description,
- reportProp.managerID,
- reportProp.total,
- reportProp.nonReimbursableTotal,
- reportProp.reportFields,
- reportProp.ownerAccountID,
- reportProp.currency,
- reportProp.participantAccountIDs,
- reportProp.isWaitingOnBankAccount,
- reportProp.iouReportID,
- reportProp.isOwnPolicyExpenseChat,
- reportProp.notificationPreference,
- reportProp.isPinned,
- reportProp.chatReportID,
- reportProp.visibility,
- reportProp.oldPolicyName,
- reportProp.policyName,
- reportProp.isOptimisticReport,
- reportProp.lastMentionedTime,
+ reportProp?.lastReadTime,
+ reportProp?.reportID,
+ reportProp?.policyID,
+ reportProp?.lastVisibleActionCreated,
+ reportProp?.statusNum,
+ reportProp?.stateNum,
+ reportProp?.writeCapability,
+ reportProp?.type,
+ reportProp?.errorFields,
+ reportProp?.isPolicyExpenseChat,
+ reportProp?.parentReportID,
+ reportProp?.parentReportActionID,
+ reportProp?.chatType,
+ reportProp?.pendingFields,
+ reportProp?.isDeletedParentAction,
+ reportProp?.reportName,
+ reportProp?.description,
+ reportProp?.managerID,
+ reportProp?.total,
+ reportProp?.nonReimbursableTotal,
+ reportProp?.reportFields,
+ reportProp?.ownerAccountID,
+ reportProp?.currency,
+ reportProp?.participantAccountIDs,
+ reportProp?.isWaitingOnBankAccount,
+ reportProp?.iouReportID,
+ reportProp?.isOwnPolicyExpenseChat,
+ reportProp?.notificationPreference,
+ reportProp?.isPinned,
+ reportProp?.chatReportID,
+ reportProp?.visibility,
+ reportProp?.oldPolicyName,
+ reportProp?.policyName,
+ reportProp?.isOptimisticReport,
+ reportProp?.lastMentionedTime,
],
);
@@ -251,7 +216,7 @@ function ReportScreen({
const prevUserLeavingStatus = usePrevious(userLeavingStatus);
const [isBannerVisible, setIsBannerVisible] = useState(true);
const [listHeight, setListHeight] = useState(0);
- const [scrollPosition, setScrollPosition] = useState({});
+ const [scrollPosition, setScrollPosition] = useState({});
const wasReportAccessibleRef = useRef(false);
if (firstRenderRef.current) {
@@ -261,23 +226,23 @@ function ReportScreen({
const reportID = getReportID(route);
const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report);
- const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
- const isEmptyChat = useMemo(() => _.isEmpty(reportActions), [reportActions]);
+ const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
+ const isEmptyChat = useMemo((): boolean => reportActions.length === 0, [reportActions]);
// There are no reportActions at all to display and we are still in the process of loading the next set of actions.
- const isLoadingInitialReportActions = _.isEmpty(reportActions) && reportMetadata.isLoadingInitialReportActions;
- const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS_NUM.CLOSED;
+ const isLoadingInitialReportActions = reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions;
+ const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas);
const isLoading = !reportID || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty();
- const lastReportAction = useMemo(
+ const lastReportAction: OnyxEntry = useMemo(
() =>
reportActions.length
- ? _.find([...reportActions, parentReportAction], (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action))
- : {},
+ ? [...reportActions, parentReportAction].find((action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)) ?? null
+ : null,
[reportActions, parentReportAction],
);
const isSingleTransactionView = ReportUtils.isMoneyRequest(report);
- const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] || {};
+ const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] ?? null;
const isTopMostReportId = currentReportID === getReportID(route);
const didSubscribeToReportLeavingEvents = useRef(false);
@@ -306,7 +271,6 @@ function ReportScreen({
);
@@ -317,25 +281,21 @@ function ReportScreen({
);
}
/**
* When false the ReportActionsView will completely unmount and we will show a loader until it returns true.
- *
- * @returns {Boolean}
*/
- const isReportReadyForDisplay = useMemo(() => {
+ const isReportReadyForDisplay = useMemo((): boolean => {
const reportIDFromPath = getReportID(route);
// This is necessary so that when we are retrieving the next report data from Onyx the ReportActionsView will remount completely
const isTransitioning = report && report.reportID !== reportIDFromPath;
- return reportIDFromPath !== '' && report.reportID && !isTransitioning;
+ return reportIDFromPath !== '' && !!report.reportID && !isTransitioning;
}, [route, report]);
- const isFocused = useIsFocused();
useEffect(() => {
if (!report.reportID || !isFocused) {
return;
@@ -355,7 +315,7 @@ function ReportScreen({
// It possible that we may not have the report object yet in Onyx yet e.g. we navigated to a URL for an accessible report that
// is not stored locally yet. If report.reportID exists, then the report has been stored locally and nothing more needs to be done.
// If it doesn't exist, then we fetch the report from the API.
- if (report.reportID && report.reportID === getReportID(route) && !isLoadingInitialReportActions) {
+ if (report.reportID === getReportID(route) && !isLoadingInitialReportActions) {
return;
}
@@ -367,7 +327,7 @@ function ReportScreen({
}, []);
const chatWithAccountManager = useCallback(() => {
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(accountManagerReportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(accountManagerReportID ?? ''));
}, [accountManagerReportID]);
// Clear notifications for the current report when it's opened and re-focused
@@ -379,6 +339,7 @@ function ReportScreen({
clearReportNotifications(report.reportID);
}, [report.reportID, isTopMostReportId]);
+
useEffect(clearNotifications, [clearNotifications]);
useAppFocusEvent(clearNotifications);
@@ -387,13 +348,8 @@ function ReportScreen({
Performance.markEnd(CONST.TIMING.CHAT_RENDER);
fetchReportIfNeeded();
- const interactionTask = InteractionManager.runAfterInteractions(() => {
- ComposerActions.setShouldShowComposeInput(true);
- });
+ ComposerActions.setShouldShowComposeInput(true);
return () => {
- if (interactionTask) {
- interactionTask.cancel();
- }
if (!didSubscribeToReportLeavingEvents) {
return;
}
@@ -419,14 +375,14 @@ function ReportScreen({
// Navigate to the Concierge chat if the room was removed from another device (e.g. user leaving a room or removed from a room)
if (
// non-optimistic case
- (!prevUserLeavingStatus && userLeavingStatus) ||
+ (!prevUserLeavingStatus && !!userLeavingStatus) ||
// optimistic case
- (prevOnyxReportID &&
+ (!!prevOnyxReportID &&
prevOnyxReportID === routeReportID &&
!onyxReportID &&
prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN &&
(report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) ||
- ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && _.isEmpty(report))
+ ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && isEmptyObject(report))
) {
Navigation.dismissModal();
if (Navigation.getTopmostReportId() === prevOnyxReportID) {
@@ -456,19 +412,7 @@ function ReportScreen({
fetchReportIfNeeded();
ComposerActions.setShouldShowComposeInput(true);
- }, [
- route,
- report,
- errors,
- fetchReportIfNeeded,
- prevReport.reportID,
- prevUserLeavingStatus,
- userLeavingStatus,
- prevReport.statusNum,
- prevReport.parentReportID,
- prevReport.chatType,
- prevReport,
- ]);
+ }, [route, report, fetchReportIfNeeded, prevReport.reportID, prevUserLeavingStatus, userLeavingStatus, prevReport.statusNum, prevReport.parentReportID, prevReport.chatType, prevReport]);
useEffect(() => {
if (!ReportUtils.isValidReportIDFromPath(reportID)) {
@@ -479,24 +423,14 @@ function ReportScreen({
// any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
// Existing reports created will have empty fields for `pendingFields`.
const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat);
- let interactionTask;
if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) {
- interactionTask = InteractionManager.runAfterInteractions(() => {
- Report.subscribeToReportLeavingEvents(reportID);
- didSubscribeToReportLeavingEvents.current = true;
- });
+ Report.subscribeToReportLeavingEvents(reportID);
+ didSubscribeToReportLeavingEvents.current = true;
}
-
- return () => {
- if (!interactionTask) {
- return;
- }
- interactionTask.cancel();
- };
}, [report, didSubscribeToReportLeavingEvents, reportID]);
- const onListLayout = useCallback((e) => {
- setListHeight((prev) => lodashGet(e, 'nativeEvent.layout.height', prev));
+ const onListLayout = useCallback((event: LayoutChangeEvent) => {
+ setListHeight((prev) => event.nativeEvent.layout.height ?? prev);
if (!markReadyForHydration) {
return;
}
@@ -505,23 +439,23 @@ function ReportScreen({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- const reportIDFromParams = lodashGet(route.params, 'reportID');
+ const reportIDFromParams = route.params.reportID;
// eslint-disable-next-line rulesdir/no-negated-variables
const shouldShowNotFoundPage = useMemo(
- () =>
+ (): boolean =>
(!wasReportAccessibleRef.current &&
!firstRenderRef.current &&
!report.reportID &&
!isOptimisticDelete &&
- !reportMetadata.isLoadingInitialReportActions &&
+ !reportMetadata?.isLoadingInitialReportActions &&
!isLoading &&
!userLeavingStatus) ||
shouldHideReport ||
- (reportIDFromParams && !ReportUtils.isValidReportIDFromPath(reportIDFromParams)),
+ (!!reportIDFromParams && !ReportUtils.isValidReportIDFromPath(reportIDFromParams)),
[report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus, reportIDFromParams],
);
- const actionListValue = useMemo(() => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]);
+ const actionListValue = useMemo((): ActionListContextType => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]);
return (
@@ -535,7 +469,6 @@ function ReportScreen({
)}
{/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded.
- If we prevent rendering the report while they are loading then
- we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
+ If we prevent rendering the report while they are loading then
+ we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
{(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && }
{isReportReadyForDisplay ? (
- ) : null}
+ ) : (
+
+ )}
@@ -609,84 +542,82 @@ function ReportScreen({
);
}
-ReportScreen.propTypes = propTypes;
-ReportScreen.defaultProps = defaultProps;
ReportScreen.displayName = 'ReportScreen';
-export default compose(
- withViewportOffsetTop,
- withCurrentReportID,
- withOnyx(
- {
- isSidebarLoaded: {
- key: ONYXKEYS.IS_SIDEBAR_LOADED,
- },
- reportActions: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`,
- canEvict: false,
- selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
- },
- report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`,
- allowStaleData: true,
- selector: reportWithoutHasDraftSelector,
- },
- reportMetadata: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${getReportID(route)}`,
- initialValue: {
- isLoadingInitialReportActions: true,
- isLoadingOlderReportActions: false,
- isLoadingNewerReportActions: false,
+export default withViewportOffsetTop(
+ withCurrentReportID(
+ withOnyx(
+ {
+ isSidebarLoaded: {
+ key: ONYXKEYS.IS_SIDEBAR_LOADED,
},
- },
- isComposerFullSize: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${getReportID(route)}`,
- initialValue: false,
- },
- betas: {
- key: ONYXKEYS.BETAS,
- },
- policies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- allowStaleData: true,
- },
- accountManagerReportID: {
- key: ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID,
- initialValue: null,
- },
- userLeavingStatus: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${getReportID(route)}`,
- initialValue: false,
- },
- parentReportAction: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`,
- selector: (parentReportActions, props) => {
- const parentReportActionID = lodashGet(props, 'report.parentReportActionID');
- if (!parentReportActionID) {
- return {};
- }
- return lodashGet(parentReportActions, parentReportActionID, {});
+ reportActions: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`,
+ canEvict: false,
+ selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
+ },
+ report: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`,
+ allowStaleData: true,
+ selector: reportWithoutHasDraftSelector,
+ },
+ reportMetadata: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${getReportID(route)}`,
+ initialValue: {
+ isLoadingInitialReportActions: true,
+ isLoadingOlderReportActions: false,
+ isLoadingNewerReportActions: false,
+ },
+ },
+ isComposerFullSize: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${getReportID(route)}`,
+ initialValue: false,
+ },
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ policies: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ allowStaleData: true,
+ },
+ accountManagerReportID: {
+ key: ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID,
+ initialValue: null,
+ },
+ userLeavingStatus: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${getReportID(route)}`,
+ initialValue: false,
+ },
+ parentReportAction: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`,
+ selector: (parentReportActions: OnyxEntry, props: WithOnyxInstanceState): OnyxEntry => {
+ const parentReportActionID = props?.report?.parentReportActionID;
+ if (!parentReportActionID) {
+ return null;
+ }
+ return parentReportActions?.[parentReportActionID] ?? null;
+ },
+ canEvict: false,
},
- canEvict: false,
},
- },
- true,
- ),
-)(
- memo(
- ReportScreen,
- (prevProps, nextProps) =>
- prevProps.isSidebarLoaded === nextProps.isSidebarLoaded &&
- _.isEqual(prevProps.reportActions, nextProps.reportActions) &&
- _.isEqual(prevProps.reportMetadata, nextProps.reportMetadata) &&
- prevProps.isComposerFullSize === nextProps.isComposerFullSize &&
- _.isEqual(prevProps.betas, nextProps.betas) &&
- _.isEqual(prevProps.policies, nextProps.policies) &&
- prevProps.accountManagerReportID === nextProps.accountManagerReportID &&
- prevProps.userLeavingStatus === nextProps.userLeavingStatus &&
- prevProps.currentReportID === nextProps.currentReportID &&
- prevProps.viewportOffsetTop === nextProps.viewportOffsetTop &&
- _.isEqual(prevProps.parentReportAction, nextProps.parentReportAction) &&
- _.isEqual(prevProps.report, nextProps.report),
+ true,
+ )(
+ memo(
+ ReportScreen,
+ (prevProps, nextProps) =>
+ prevProps.isSidebarLoaded === nextProps.isSidebarLoaded &&
+ lodashIsEqual(prevProps.reportActions, nextProps.reportActions) &&
+ lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) &&
+ prevProps.isComposerFullSize === nextProps.isComposerFullSize &&
+ lodashIsEqual(prevProps.betas, nextProps.betas) &&
+ lodashIsEqual(prevProps.policies, nextProps.policies) &&
+ prevProps.accountManagerReportID === nextProps.accountManagerReportID &&
+ prevProps.userLeavingStatus === nextProps.userLeavingStatus &&
+ prevProps.currentReportID === nextProps.currentReportID &&
+ prevProps.viewportOffsetTop === nextProps.viewportOffsetTop &&
+ lodashIsEqual(prevProps.parentReportAction, nextProps.parentReportAction) &&
+ lodashIsEqual(prevProps.report, nextProps.report),
+ ),
+ ),
),
);
diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts
index 6f177098c2c4..e67bf73f7452 100644
--- a/src/pages/home/ReportScreenContext.ts
+++ b/src/pages/home/ReportScreenContext.ts
@@ -15,9 +15,11 @@ type ReactionListRef = {
type FlatListRefType = RefObject> | null;
+type ScrollPosition = {offset?: number};
+
type ActionListContextType = {
flatListRef: FlatListRefType;
- scrollPosition: {offset: number} | null;
+ scrollPosition: ScrollPosition | null;
setScrollPosition: (position: {offset: number}) => void;
};
type ReactionListContextType = RefObject | null;
@@ -26,4 +28,4 @@ const ActionListContext = createContext({flatListRef: nul
const ReactionListContext = createContext(null);
export {ActionListContext, ReactionListContext};
-export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType, ReactionListAnchor, ReactionListEvent};
+export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType, ReactionListAnchor, ReactionListEvent, ScrollPosition};
diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx
index 974a8824f5ff..4a5948545345 100755
--- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx
+++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx
@@ -265,6 +265,7 @@ function BaseReportActionContextMenu({
description={contextAction.getDescription?.(selection) ?? ''}
isAnonymousAction={contextAction.isAnonymousAction}
isFocused={focusedIndex === index}
+ shouldPreventDefaultFocusOnPress={contextAction.shouldPreventDefaultFocusOnPress}
/>
);
})}
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
index ffdbcab577b7..c5ab9bbff1f5 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
@@ -18,7 +18,6 @@ import * as Localize from '@libs/Localize';
import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage';
import Navigation from '@libs/Navigation/Navigation';
import Permissions from '@libs/Permissions';
-import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
@@ -101,6 +100,7 @@ type ContextMenuActionWithIcon = {
type ContextMenuAction = (ContextMenuActionWithContent | ContextMenuActionWithIcon) & {
isAnonymousAction: boolean;
shouldShow: ShouldShow;
+ shouldPreventDefaultFocusOnPress?: boolean;
};
// A list of all the context actions in this menu.
@@ -219,13 +219,6 @@ const ContextMenuActions: ContextMenuAction[] = [
if (ReportActionsUtils.isMoneyRequestAction(reportAction)) {
hideContextMenu(false);
const childReportID = reportAction?.childReportID ?? '0';
- if (!childReportID) {
- const thread = ReportUtils.buildTransactionThread(reportAction, reportID);
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs ?? []);
- Report.openReport(thread.reportID, userLogins, thread, reportAction?.reportActionID);
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(thread.reportID));
- return;
- }
Report.openReport(childReportID);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID));
return;
@@ -501,6 +494,7 @@ const ContextMenuActions: ContextMenuAction[] = [
openContextMenu();
},
getDescription: () => {},
+ shouldPreventDefaultFocusOnPress: false,
},
];
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/floatingMessageCounterContainerPropTypes.js b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/floatingMessageCounterContainerPropTypes.js
deleted file mode 100644
index af0f22208457..000000000000
--- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/floatingMessageCounterContainerPropTypes.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import PropTypes from 'prop-types';
-
-const propTypes = {
- /** Styles to be assigned to Container */
- containerStyles: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /** Rendered child component */
- children: PropTypes.element.isRequired,
-};
-
-export default propTypes;
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx
similarity index 60%
rename from src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js
rename to src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx
index 700a2fb399e4..64391909b197 100644
--- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js
+++ b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx
@@ -1,18 +1,18 @@
import React from 'react';
import {Animated, View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
-import floatingMessageCounterContainerPropTypes from './floatingMessageCounterContainerPropTypes';
+import type FloatingMessageCounterContainerProps from './types';
-function FloatingMessageCounterContainer(props) {
+function FloatingMessageCounterContainer({containerStyles, children}: FloatingMessageCounterContainerProps) {
const styles = useThemeStyles();
+
return (
-
- {props.children}
+
+ {children}
);
}
-FloatingMessageCounterContainer.propTypes = floatingMessageCounterContainerPropTypes;
FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer';
export default FloatingMessageCounterContainer;
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js
deleted file mode 100644
index 19123e65cbf2..000000000000
--- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import {Animated} from 'react-native';
-import useThemeStyles from '@hooks/useThemeStyles';
-import floatingMessageCounterContainerPropTypes from './floatingMessageCounterContainerPropTypes';
-
-function FloatingMessageCounterContainer(props) {
- const styles = useThemeStyles();
- return (
-
- {props.children}
-
- );
-}
-
-FloatingMessageCounterContainer.propTypes = floatingMessageCounterContainerPropTypes;
-FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer';
-
-export default FloatingMessageCounterContainer;
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx
new file mode 100644
index 000000000000..8757d66160c4
--- /dev/null
+++ b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import {Animated} from 'react-native';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type FloatingMessageCounterContainerProps from './types';
+
+function FloatingMessageCounterContainer({accessibilityHint, containerStyles, children}: FloatingMessageCounterContainerProps) {
+ const styles = useThemeStyles();
+
+ return (
+
+ {children}
+
+ );
+}
+
+FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer';
+
+export default FloatingMessageCounterContainer;
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts
new file mode 100644
index 000000000000..cfe791eed79c
--- /dev/null
+++ b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts
@@ -0,0 +1,12 @@
+import type {StyleProp, ViewStyle} from 'react-native';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+type FloatingMessageCounterContainerProps = ChildrenProps & {
+ /** Styles to be assigned to Container */
+ containerStyles?: StyleProp;
+
+ /** Specifies the accessibility hint for the component */
+ accessibilityHint?: string;
+};
+
+export default FloatingMessageCounterContainerProps;
diff --git a/src/pages/home/report/FloatingMessageCounter/index.js b/src/pages/home/report/FloatingMessageCounter/index.tsx
similarity index 83%
rename from src/pages/home/report/FloatingMessageCounter/index.js
rename to src/pages/home/report/FloatingMessageCounter/index.tsx
index 07138104bf74..d3048848936d 100644
--- a/src/pages/home/report/FloatingMessageCounter/index.js
+++ b/src/pages/home/report/FloatingMessageCounter/index.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo} from 'react';
import {Animated, View} from 'react-native';
import Button from '@components/Button';
@@ -12,23 +11,18 @@ import useNativeDriver from '@libs/useNativeDriver';
import CONST from '@src/CONST';
import FloatingMessageCounterContainer from './FloatingMessageCounterContainer';
-const propTypes = {
+type FloatingMessageCounterProps = {
/** Whether the New Messages indicator is active */
- isActive: PropTypes.bool,
+ isActive?: boolean;
/** Callback to be called when user clicks the New Messages indicator */
- onClick: PropTypes.func,
-};
-
-const defaultProps = {
- isActive: false,
- onClick: () => {},
+ onClick?: () => void;
};
const MARKER_INACTIVE_TRANSLATE_Y = -40;
const MARKER_ACTIVE_TRANSLATE_Y = 10;
-function FloatingMessageCounter(props) {
+function FloatingMessageCounter({isActive = false, onClick = () => {}}: FloatingMessageCounterProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -37,7 +31,6 @@ function FloatingMessageCounter(props) {
const show = useCallback(() => {
Animated.spring(translateY, {
toValue: MARKER_ACTIVE_TRANSLATE_Y,
- duration: 80,
useNativeDriver,
}).start();
}, [translateY]);
@@ -45,30 +38,29 @@ function FloatingMessageCounter(props) {
const hide = useCallback(() => {
Animated.spring(translateY, {
toValue: MARKER_INACTIVE_TRANSLATE_Y,
- duration: 80,
useNativeDriver,
}).start();
}, [translateY]);
useEffect(() => {
- if (props.isActive) {
+ if (isActive) {
show();
} else {
hide();
}
- }, [props.isActive, show, hide]);
+ }, [isActive, show, hide]);
return (
@@ -583,8 +558,8 @@ function ReportActionsList({
);
}
-ReportActionsList.propTypes = propTypes;
-ReportActionsList.defaultProps = defaultProps;
ReportActionsList.displayName = 'ReportActionsList';
-export default compose(withWindowDimensions, withCurrentUserPersonalDetails)(memo(ReportActionsList));
+export default withCurrentUserPersonalDetails(memo(ReportActionsList));
+
+export type {LoadNewerChats};
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.tsx
similarity index 55%
rename from src/pages/home/report/ReportActionsListItemRenderer.js
rename to src/pages/home/report/ReportActionsListItemRenderer.tsx
index bc8e6a94359f..47902ead9ed5 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.js
+++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx
@@ -1,47 +1,39 @@
-import PropTypes from 'prop-types';
import React, {memo, useMemo} from 'react';
-import _ from 'underscore';
+import type {OnyxEntry} from 'react-native-onyx';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import reportPropTypes from '@pages/reportPropTypes';
import CONST from '@src/CONST';
+import type {Report, ReportAction} from '@src/types/onyx';
import ReportActionItem from './ReportActionItem';
import ReportActionItemParentAction from './ReportActionItemParentAction';
-import reportActionPropTypes from './reportActionPropTypes';
-const propTypes = {
+type ReportActionsListItemRendererProps = {
/** All the data of the action item */
- reportAction: PropTypes.shape(reportActionPropTypes).isRequired,
+ reportAction: ReportAction;
/** The report's parentReportAction */
- parentReportAction: PropTypes.shape(reportActionPropTypes),
+ parentReportAction: OnyxEntry;
/** Position index of the report action in the overall report FlatList view */
- index: PropTypes.number.isRequired,
+ index: number;
/** Report for this action */
- report: reportPropTypes.isRequired,
+ report: Report;
/** Should the comment have the appearance of being grouped with the previous comment? */
- displayAsGroup: PropTypes.bool.isRequired,
+ displayAsGroup: boolean;
/** The ID of the most recent IOU report action connected with the shown report */
- mostRecentIOUReportActionID: PropTypes.string,
+ mostRecentIOUReportActionID?: string | null;
/** If the thread divider line should be hidden */
- shouldHideThreadDividerLine: PropTypes.bool.isRequired,
+ shouldHideThreadDividerLine: boolean;
/** Should we display the new marker on top of the comment? */
- shouldDisplayNewMarker: PropTypes.bool.isRequired,
+ shouldDisplayNewMarker: boolean;
/** Linked report action ID */
- linkedReportActionID: PropTypes.string,
-};
-
-const defaultProps = {
- mostRecentIOUReportActionID: '',
- linkedReportActionID: '',
- parentReportAction: {},
+ linkedReportActionID?: string;
};
function ReportActionsListItemRenderer({
@@ -50,11 +42,11 @@ function ReportActionsListItemRenderer({
index,
report,
displayAsGroup,
- mostRecentIOUReportActionID,
+ mostRecentIOUReportActionID = '',
shouldHideThreadDividerLine,
shouldDisplayNewMarker,
- linkedReportActionID,
-}) {
+ linkedReportActionID = '',
+}: ReportActionsListItemRendererProps) {
const shouldDisplayParentAction =
reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isChatThread(report) && !ReportActionsUtils.isTransactionThread(parentReportAction);
@@ -62,36 +54,37 @@ function ReportActionsListItemRenderer({
* Create a lightweight ReportAction so as to keep the re-rendering as light as possible by
* passing in only the required props.
*/
- const action = useMemo(
- () => ({
- reportActionID: reportAction.reportActionID,
- message: reportAction.message,
- pendingAction: reportAction.pendingAction,
- actionName: reportAction.actionName,
- errors: reportAction.errors,
- originalMessage: reportAction.originalMessage,
- childCommenterCount: reportAction.childCommenterCount,
- linkMetadata: reportAction.linkMetadata,
- childReportID: reportAction.childReportID,
- childLastVisibleActionCreated: reportAction.childLastVisibleActionCreated,
- whisperedToAccountIDs: reportAction.whisperedToAccountIDs,
- error: reportAction.error,
- created: reportAction.created,
- actorAccountID: reportAction.actorAccountID,
- childVisibleActionCount: reportAction.childVisibleActionCount,
- childOldestFourAccountIDs: reportAction.childOldestFourAccountIDs,
- childType: reportAction.childType,
- person: reportAction.person,
- isOptimisticAction: reportAction.isOptimisticAction,
- delegateAccountID: reportAction.delegateAccountID,
- previousMessage: reportAction.previousMessage,
- attachmentInfo: reportAction.attachmentInfo,
- childStateNum: reportAction.childStateNum,
- childStatusNum: reportAction.childStatusNum,
- childReportName: reportAction.childReportName,
- childManagerAccountID: reportAction.childManagerAccountID,
- childMoneyRequestCount: reportAction.childMoneyRequestCount,
- }),
+ const action: ReportAction = useMemo(
+ () =>
+ ({
+ reportActionID: reportAction.reportActionID,
+ message: reportAction.message,
+ pendingAction: reportAction.pendingAction,
+ actionName: reportAction.actionName,
+ errors: reportAction.errors,
+ originalMessage: reportAction.originalMessage,
+ childCommenterCount: reportAction.childCommenterCount,
+ linkMetadata: reportAction.linkMetadata,
+ childReportID: reportAction.childReportID,
+ childLastVisibleActionCreated: reportAction.childLastVisibleActionCreated,
+ whisperedToAccountIDs: reportAction.whisperedToAccountIDs,
+ error: reportAction.error,
+ created: reportAction.created,
+ actorAccountID: reportAction.actorAccountID,
+ childVisibleActionCount: reportAction.childVisibleActionCount,
+ childOldestFourAccountIDs: reportAction.childOldestFourAccountIDs,
+ childType: reportAction.childType,
+ person: reportAction.person,
+ isOptimisticAction: reportAction.isOptimisticAction,
+ delegateAccountID: reportAction.delegateAccountID,
+ previousMessage: reportAction.previousMessage,
+ attachmentInfo: reportAction.attachmentInfo,
+ childStateNum: reportAction.childStateNum,
+ childStatusNum: reportAction.childStatusNum,
+ childReportName: reportAction.childReportName,
+ childManagerAccountID: reportAction.childManagerAccountID,
+ childMoneyRequestCount: reportAction.childMoneyRequestCount,
+ } as ReportAction),
[
reportAction.actionName,
reportAction.childCommenterCount,
@@ -139,9 +132,8 @@ function ReportActionsListItemRenderer({
shouldDisplayNewMarker={shouldDisplayNewMarker}
shouldShowSubscriptAvatar={
(ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isExpenseReport(report)) &&
- _.contains(
- [CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW, CONST.REPORT.ACTIONS.TYPE.SUBMITTED, CONST.REPORT.ACTIONS.TYPE.APPROVED],
- reportAction.actionName,
+ [CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW, CONST.REPORT.ACTIONS.TYPE.SUBMITTED, CONST.REPORT.ACTIONS.TYPE.APPROVED].some(
+ (type) => type === reportAction.actionName,
)
}
isMostRecentIOUReportAction={reportAction.reportActionID === mostRecentIOUReportActionID}
@@ -150,8 +142,6 @@ function ReportActionsListItemRenderer({
);
}
-ReportActionsListItemRenderer.propTypes = propTypes;
-ReportActionsListItemRenderer.defaultProps = defaultProps;
ReportActionsListItemRenderer.displayName = 'ReportActionsListItemRenderer';
export default memo(ReportActionsListItemRenderer);
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.tsx
similarity index 56%
rename from src/pages/home/report/ReportActionsView.js
rename to src/pages/home/report/ReportActionsView.tsx
index e1c98a22bec8..ab3bda2fa8ca 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -1,116 +1,88 @@
import {useIsFocused} from '@react-navigation/native';
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import lodashIsEqual from 'lodash/isEqual';
+import lodashThrottle from 'lodash/throttle';
import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react';
-import {InteractionManager} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import networkPropTypes from '@components/networkPropTypes';
-import {withNetwork} from '@components/OnyxProvider';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
+import type {OnyxEntry} from 'react-native-onyx';
import useCopySelectionHelper from '@hooks/useCopySelectionHelper';
import useInitialValue from '@hooks/useInitialValue';
+import useNetwork from '@hooks/useNetwork';
import usePrevious from '@hooks/usePrevious';
-import compose from '@libs/compose';
+import useWindowDimensions from '@hooks/useWindowDimensions';
import getIsReportFullyVisible from '@libs/getIsReportFullyVisible';
import Performance from '@libs/Performance';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import {isUserCreatedPolicyRoom} from '@libs/ReportUtils';
import {didUserLogInDuringSession} from '@libs/SessionUtils';
import {ReactionListContext} from '@pages/home/ReportScreenContext';
-import reportPropTypes from '@pages/reportPropTypes';
import * as Report from '@userActions/Report';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
import PopoverReactionList from './ReactionList/PopoverReactionList';
-import reportActionPropTypes from './reportActionPropTypes';
import ReportActionsList from './ReportActionsList';
+import type {LoadNewerChats} from './ReportActionsList';
-const propTypes = {
+type ReportActionsViewOnyxProps = {
+ /** Session info for the currently logged in user. */
+ session: OnyxEntry;
+};
+
+type ReportActionsViewProps = ReportActionsViewOnyxProps & {
/** The report currently being looked at */
- report: reportPropTypes.isRequired,
+ report: OnyxTypes.Report;
/** Array of report actions for this report */
- reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)),
+ reportActions?: OnyxTypes.ReportAction[];
/** The report's parentReportAction */
- parentReportAction: PropTypes.shape(reportActionPropTypes),
+ parentReportAction: OnyxEntry;
/** The report metadata loading states */
- isLoadingInitialReportActions: PropTypes.bool,
+ isLoadingInitialReportActions?: boolean;
/** The report actions are loading more data */
- isLoadingOlderReportActions: PropTypes.bool,
+ isLoadingOlderReportActions?: boolean;
/** The report actions are loading newer data */
- isLoadingNewerReportActions: PropTypes.bool,
-
- /** Whether the composer is full size */
- /* eslint-disable-next-line react/no-unused-prop-types */
- isComposerFullSize: PropTypes.bool.isRequired,
-
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
- /** The policy object for the current route */
- policy: PropTypes.shape({
- /** The name of the policy */
- name: PropTypes.string,
-
- /** The URL for the policy avatar */
- avatar: PropTypes.string,
- }),
-
- /** Session info for the currently logged in user. */
- session: PropTypes.shape({
- /** Currently logged in user authToken */
- authToken: PropTypes.string,
- }),
-
- ...windowDimensionsPropTypes,
- ...withLocalizePropTypes,
-};
-
-const defaultProps = {
- reportActions: [],
- policy: null,
- isLoadingInitialReportActions: false,
- isLoadingOlderReportActions: false,
- isLoadingNewerReportActions: false,
- session: {
- authTokenType: '',
- },
- parentReportAction: {},
+ isLoadingNewerReportActions?: boolean;
};
-function ReportActionsView(props) {
+function ReportActionsView({
+ report,
+ session,
+ parentReportAction,
+ reportActions = [],
+ isLoadingInitialReportActions = false,
+ isLoadingOlderReportActions = false,
+ isLoadingNewerReportActions = false,
+}: ReportActionsViewProps) {
useCopySelectionHelper();
const reactionListRef = useContext(ReactionListContext);
const didLayout = useRef(false);
const didSubscribeToReportTypingEvents = useRef(false);
const isFirstRender = useRef(true);
- const hasCachedActions = useInitialValue(() => _.size(props.reportActions) > 0);
- const mostRecentIOUReportActionID = useMemo(() => ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions), [props.reportActions]);
- const prevNetworkRef = useRef(props.network);
- const prevAuthTokenType = usePrevious(props.session.authTokenType);
+ const hasCachedActions = useInitialValue(() => reportActions.length > 0);
+ const mostRecentIOUReportActionID = useMemo(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActions), [reportActions]);
+ const network = useNetwork();
+ const {isSmallScreenWidth} = useWindowDimensions();
+ const prevNetworkRef = useRef(network);
+ const prevAuthTokenType = usePrevious(session?.authTokenType);
- const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth);
+ const prevIsSmallScreenWidthRef = useRef(isSmallScreenWidth);
const isFocused = useIsFocused();
- const reportID = props.report.reportID;
- const hasNewestReportAction = lodashGet(props.reportActions[0], 'isNewestReportAction');
+ const reportID = report.reportID;
+ const hasNewestReportAction = reportActions[0]?.isNewestReportAction;
- /**
- * @returns {Boolean}
- */
- const isReportFullyVisible = useMemo(() => getIsReportFullyVisible(isFocused), [isFocused]);
+ const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]);
const openReportIfNecessary = () => {
- const createChatError = _.get(props.report, ['errorFields', 'createChat']);
+ const createChatError = report.errorFields?.createChat;
// If the report is optimistic (AKA not yet created) we don't need to call openReport again
- if (props.report.isOptimisticReport || !_.isEmpty(createChatError)) {
+ if (!!report.isOptimisticReport || !isEmptyObject(createChatError)) {
return;
}
@@ -118,15 +90,7 @@ function ReportActionsView(props) {
};
useEffect(() => {
- const interactionTask = InteractionManager.runAfterInteractions(() => {
- openReportIfNecessary();
- });
- return () => {
- if (!interactionTask) {
- return;
- }
- interactionTask.cancel();
- };
+ openReportIfNecessary();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -135,7 +99,7 @@ function ReportActionsView(props) {
// When returning from offline to online state we want to trigger a request to OpenReport which
// will fetch the reportActions data and mark the report as read. If the report is not fully visible
// then we call ReconnectToReport which only loads the reportActions data without marking the report as read.
- const wasNetworkChangeDetected = lodashGet(prevNetwork, 'isOffline') && !lodashGet(props.network, 'isOffline');
+ const wasNetworkChangeDetected = prevNetwork.isOffline && !network.isOffline;
if (wasNetworkChangeDetected) {
if (isReportFullyVisible) {
openReportIfNecessary();
@@ -144,13 +108,13 @@ function ReportActionsView(props) {
}
}
// update ref with current network state
- prevNetworkRef.current = props.network;
+ prevNetworkRef.current = network;
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.network, isReportFullyVisible]);
+ }, [network, isReportFullyVisible]);
useEffect(() => {
- const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS && !props.session.authTokenType;
- if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) {
+ const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS && !session?.authTokenType;
+ if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(report)) {
if (isReportFullyVisible) {
openReportIfNecessary();
} else {
@@ -158,45 +122,35 @@ function ReportActionsView(props) {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.session, props.report, isReportFullyVisible]);
+ }, [session, report, isReportFullyVisible]);
useEffect(() => {
const prevIsSmallScreenWidth = prevIsSmallScreenWidthRef.current;
// If the view is expanded from mobile to desktop layout
// we update the new marker position, mark the report as read, and fetch new report actions
- const didScreenSizeIncrease = prevIsSmallScreenWidth && !props.isSmallScreenWidth;
+ const didScreenSizeIncrease = prevIsSmallScreenWidth && !isSmallScreenWidth;
const didReportBecomeVisible = isReportFullyVisible && didScreenSizeIncrease;
if (didReportBecomeVisible) {
openReportIfNecessary();
}
// update ref with current state
- prevIsSmallScreenWidthRef.current = props.isSmallScreenWidth;
+ prevIsSmallScreenWidthRef.current = isSmallScreenWidth;
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.isSmallScreenWidth, props.reportActions, isReportFullyVisible]);
+ }, [isSmallScreenWidth, reportActions, isReportFullyVisible]);
useEffect(() => {
// Ensures subscription event succeeds when the report/workspace room is created optimistically.
// Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming
// any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
// Existing reports created will have empty fields for `pendingFields`.
- const didCreateReportSuccessfully = !props.report.pendingFields || (!props.report.pendingFields.addWorkspaceRoom && !props.report.pendingFields.createChat);
- let interactionTask;
+ const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat);
if (!didSubscribeToReportTypingEvents.current && didCreateReportSuccessfully) {
- interactionTask = InteractionManager.runAfterInteractions(() => {
- Report.subscribeToReportTypingEvents(reportID);
- didSubscribeToReportTypingEvents.current = true;
- });
+ Report.subscribeToReportTypingEvents(reportID);
+ didSubscribeToReportTypingEvents.current = true;
}
+ }, [report.pendingFields, didSubscribeToReportTypingEvents, reportID]);
- return () => {
- if (!interactionTask) {
- return;
- }
- interactionTask.cancel();
- };
- }, [props.report.pendingFields, didSubscribeToReportTypingEvents, reportID]);
-
- const oldestReportAction = useMemo(() => _.last(props.reportActions), [props.reportActions]);
+ const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]);
/**
* Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently
@@ -204,7 +158,7 @@ function ReportActionsView(props) {
*/
const loadOlderChats = useCallback(() => {
// Only fetch more if we are neither already fetching (so that we don't initiate duplicate requests) nor offline.
- if (props.network.isOffline || props.isLoadingOlderReportActions) {
+ if (!!network.isOffline || isLoadingOlderReportActions) {
return;
}
@@ -214,16 +168,16 @@ function ReportActionsView(props) {
}
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments
Report.getOlderActions(reportID);
- }, [props.isLoadingOlderReportActions, props.network.isOffline, oldestReportAction, reportID]);
+ }, [isLoadingOlderReportActions, network.isOffline, oldestReportAction, reportID]);
/**
* Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently
* displaying.
*/
- const loadNewerChats = useMemo(
+ const loadNewerChats: LoadNewerChats = useMemo(
() =>
- _.throttle(({distanceFromStart}) => {
- if (props.isLoadingNewerReportActions || props.isLoadingInitialReportActions || hasNewestReportAction) {
+ lodashThrottle(({distanceFromStart}) => {
+ if (isLoadingNewerReportActions || isLoadingInitialReportActions || hasNewestReportAction) {
return;
}
@@ -244,7 +198,7 @@ function ReportActionsView(props) {
Report.getNewerActions(reportID);
}, 500),
- [props.isLoadingNewerReportActions, props.isLoadingInitialReportActions, reportID, hasNewestReportAction],
+ [isLoadingNewerReportActions, isLoadingInitialReportActions, reportID, hasNewestReportAction],
);
/**
@@ -268,48 +222,42 @@ function ReportActionsView(props) {
}, [hasCachedActions]);
// Comments have not loaded at all yet do nothing
- if (!_.size(props.reportActions)) {
+ if (!reportActions.length) {
return null;
}
return (
<>
>
);
}
-ReportActionsView.propTypes = propTypes;
-ReportActionsView.defaultProps = defaultProps;
ReportActionsView.displayName = 'ReportActionsView';
+ReportActionsView.initMeasured = false;
-function arePropsEqual(oldProps, newProps) {
- if (!_.isEqual(oldProps.reportActions, newProps.reportActions)) {
- return false;
- }
-
- if (!_.isEqual(oldProps.parentReportAction, newProps.parentReportAction)) {
+function arePropsEqual(oldProps: ReportActionsViewProps, newProps: ReportActionsViewProps): boolean {
+ if (!lodashIsEqual(oldProps.reportActions, newProps.reportActions)) {
return false;
}
- if (lodashGet(oldProps.network, 'isOffline') !== lodashGet(newProps.network, 'isOffline')) {
+ if (!lodashIsEqual(oldProps.parentReportAction, newProps.parentReportAction)) {
return false;
}
- if (lodashGet(oldProps.session, 'authTokenType') !== lodashGet(newProps.session, 'authTokenType')) {
+ if (oldProps.session?.authTokenType !== newProps.session?.authTokenType) {
return false;
}
@@ -325,35 +273,15 @@ function arePropsEqual(oldProps, newProps) {
return false;
}
- if (newProps.isSmallScreenWidth !== oldProps.isSmallScreenWidth) {
- return false;
- }
-
- if (newProps.isComposerFullSize !== oldProps.isComposerFullSize) {
- return false;
- }
-
- if (lodashGet(newProps, 'policy.avatar') !== lodashGet(oldProps, 'policy.avatar')) {
- return false;
- }
-
- if (lodashGet(newProps, 'policy.name') !== lodashGet(oldProps, 'policy.name')) {
- return false;
- }
-
- return _.isEqual(oldProps.report, newProps.report);
+ return lodashIsEqual(oldProps.report, newProps.report);
}
const MemoizedReportActionsView = React.memo(ReportActionsView, arePropsEqual);
-export default compose(
- Performance.withRenderTrace({id: ' rendering'}),
- withWindowDimensions,
- withLocalize,
- withNetwork(),
- withOnyx({
+export default Performance.withRenderTrace({id: ' rendering'})(
+ withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
- }),
-)(MemoizedReportActionsView);
+ })(MemoizedReportActionsView),
+);
diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.tsx
similarity index 51%
rename from src/pages/home/report/ReportFooter.js
rename to src/pages/home/report/ReportFooter.tsx
index ec242116269e..c8f8433bbc46 100644
--- a/src/pages/home/report/ReportFooter.js
+++ b/src/pages/home/report/ReportFooter.tsx
@@ -1,84 +1,83 @@
-import PropTypes from 'prop-types';
+import lodashIsEqual from 'lodash/isEqual';
import React, {memo, useCallback} from 'react';
import {Keyboard, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import _, {isEqual} from 'underscore';
+import type {OnyxEntry} from 'react-native-onyx';
import AnonymousReportFooter from '@components/AnonymousReportFooter';
import ArchivedReportFooter from '@components/ArchivedReportFooter';
import OfflineIndicator from '@components/OfflineIndicator';
import {usePersonalDetails} from '@components/OnyxProvider';
import SwipeableView from '@components/SwipeableView';
-import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
+import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ReportUtils from '@libs/ReportUtils';
-import reportPropTypes from '@pages/reportPropTypes';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
import ReportActionCompose from './ReportActionCompose/ReportActionCompose';
-import reportActionPropTypes from './reportActionPropTypes';
-const propTypes = {
+type ReportFooterOnyxProps = {
+ /** Whether to show the compose input */
+ shouldShowComposeInput: OnyxEntry;
+
+ /** Session info for the currently logged in user. */
+ session: OnyxEntry;
+};
+
+type ReportFooterProps = ReportFooterOnyxProps & {
/** Report object for the current report */
- report: reportPropTypes,
+ report?: OnyxTypes.Report;
- lastReportAction: PropTypes.shape(reportActionPropTypes),
+ /** The last report action */
+ lastReportAction?: OnyxEntry;
- isEmptyChat: PropTypes.bool,
+ /** Whether the chat is empty */
+ isEmptyChat?: boolean;
/** The pending action when we are adding a chat */
- pendingAction: PropTypes.string,
+ pendingAction?: PendingAction;
/** Height of the list which the composer is part of */
- listHeight: PropTypes.number,
-
- /** Whetjer the report is ready for display */
- isReportReadyForDisplay: PropTypes.bool,
+ listHeight?: number;
- /** Whether to show the compose input */
- shouldShowComposeInput: PropTypes.bool,
+ /** Whether the report is ready for display */
+ isReportReadyForDisplay?: boolean;
- /** Session info for the currently logged in user. */
- session: PropTypes.shape({
- /** Currently logged in user auth token type */
- authTokenType: PropTypes.string,
- }),
-
- ...windowDimensionsPropTypes,
-};
-
-const defaultProps = {
- report: {reportID: '0'},
- pendingAction: null,
- listHeight: 0,
- isReportReadyForDisplay: true,
- lastReportAction: null,
- isEmptyChat: true,
- shouldShowComposeInput: false,
- session: {},
+ /** Whether the composer is in full size */
+ isComposerFullSize?: boolean;
};
-function ReportFooter(props) {
+function ReportFooter({
+ lastReportAction,
+ pendingAction,
+ session,
+ report = {reportID: '0'},
+ shouldShowComposeInput = false,
+ isEmptyChat = true,
+ isReportReadyForDisplay = true,
+ listHeight = 0,
+ isComposerFullSize = false,
+}: ReportFooterProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();
+ const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const chatFooterStyles = {...styles.chatFooter, minHeight: !isOffline ? CONST.CHAT_FOOTER_MIN_HEIGHT : 0};
- const isArchivedRoom = ReportUtils.isArchivedRoom(props.report);
- const isAnonymousUser = props.session.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS;
+ const isArchivedRoom = ReportUtils.isArchivedRoom(report);
+ const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS;
- const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint;
- const hideComposer = !ReportUtils.canUserPerformWriteAction(props.report);
+ const isSmallSizeLayout = windowWidth - (isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint;
+ const hideComposer = !ReportUtils.canUserPerformWriteAction(report);
const allPersonalDetails = usePersonalDetails();
- /**
- * @param {String} text
- */
const handleCreateTask = useCallback(
- (text) => {
+ (text: string): boolean => {
/**
* Matching task rule by group
* Group 1: Start task rule with []
@@ -96,57 +95,56 @@ function ReportFooter(props) {
return false;
}
const email = match[1] ? match[1].trim() : undefined;
- let assignee = {};
+ let assignee: OnyxTypes.PersonalDetails | EmptyObject = {};
if (email) {
- assignee = _.find(_.values(allPersonalDetails), (p) => p.login === email) || {};
+ assignee = Object.values(allPersonalDetails).find((value) => value?.login === email) ?? {};
}
- Task.createTaskAndNavigate(props.report.reportID, title, '', assignee.login, assignee.accountID, assignee.assigneeChatReport, props.report.policyID);
+ Task.createTaskAndNavigate(report.reportID, title, '', assignee?.login ?? '', assignee.accountID, assignee.assigneeChatReport, report.policyID);
return true;
},
- [allPersonalDetails, props.report.policyID, props.report.reportID],
+ [allPersonalDetails, report.policyID, report.reportID],
);
const onSubmitComment = useCallback(
- (text) => {
+ (text: string) => {
const isTaskCreated = handleCreateTask(text);
if (isTaskCreated) {
return;
}
- Report.addComment(props.report.reportID, text);
+ Report.addComment(report.reportID, text);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
- [props.report.reportID, handleCreateTask],
+ [report.reportID, handleCreateTask],
);
return (
<>
{hideComposer && (
-
+
{isAnonymousUser && !isArchivedRoom && (
)}
- {isArchivedRoom && }
- {!props.isSmallScreenWidth && (
- {hideComposer && }
- )}
+ {isArchivedRoom && }
+ {!isSmallScreenWidth && {hideComposer && }}
)}
- {!hideComposer && (props.shouldShowComposeInput || !props.isSmallScreenWidth) && (
-
+ {!hideComposer && (!!shouldShowComposeInput || !isSmallScreenWidth) && (
+
@@ -156,33 +154,27 @@ function ReportFooter(props) {
}
ReportFooter.displayName = 'ReportFooter';
-ReportFooter.propTypes = propTypes;
-ReportFooter.defaultProps = defaultProps;
-export default compose(
- withWindowDimensions,
- withOnyx({
- shouldShowComposeInput: {
- key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT,
- initialValue: false,
- },
- session: {
- key: ONYXKEYS.SESSION,
- },
- }),
-)(
+
+export default withOnyx({
+ shouldShowComposeInput: {
+ key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT,
+ initialValue: false,
+ },
+ session: {
+ key: ONYXKEYS.SESSION,
+ },
+})(
memo(
ReportFooter,
(prevProps, nextProps) =>
- isEqual(prevProps.report, nextProps.report) &&
+ lodashIsEqual(prevProps.report, nextProps.report) &&
prevProps.pendingAction === nextProps.pendingAction &&
prevProps.listHeight === nextProps.listHeight &&
prevProps.isComposerFullSize === nextProps.isComposerFullSize &&
prevProps.isEmptyChat === nextProps.isEmptyChat &&
prevProps.lastReportAction === nextProps.lastReportAction &&
prevProps.shouldShowComposeInput === nextProps.shouldShowComposeInput &&
- prevProps.windowWidth === nextProps.windowWidth &&
- prevProps.isSmallScreenWidth === nextProps.isSmallScreenWidth &&
prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay &&
- isEqual(prevProps.session, nextProps.session),
+ lodashIsEqual(prevProps.session, nextProps.session),
),
);
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index dc5d5d4d07dd..0a97f00c5002 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -1,7 +1,7 @@
/* eslint-disable rulesdir/onyx-props-must-have-default */
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
-import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react';
+import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import {InteractionManager, StyleSheet, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
@@ -173,5 +173,5 @@ export default withOnyx({
activePolicy: {
key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`,
},
-})(memo(SidebarLinks));
+})(SidebarLinks);
export {basePropTypes};
diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js
index e8a1b5a39cfb..c443a91a7313 100644
--- a/src/pages/settings/Profile/DisplayNamePage.js
+++ b/src/pages/settings/Profile/DisplayNamePage.js
@@ -59,6 +59,8 @@ function DisplayNamePage(props) {
// First we validate the first name field
if (!ValidationUtils.isValidDisplayName(values.firstName)) {
ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter');
+ } else if (values.firstName.length > CONST.TITLE_CHARACTER_LIMIT) {
+ ErrorUtils.addErrorMessage(errors, 'firstName', ['common.error.characterLimitExceedCounter', {length: values.firstName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]);
}
if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_NAMES)) {
ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord');
@@ -67,6 +69,8 @@ function DisplayNamePage(props) {
// Then we validate the last name field
if (!ValidationUtils.isValidDisplayName(values.lastName)) {
ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.hasInvalidCharacter');
+ } else if (values.lastName.length > CONST.TITLE_CHARACTER_LIMIT) {
+ ErrorUtils.addErrorMessage(errors, 'lastName', ['common.error.characterLimitExceedCounter', {length: values.lastName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]);
}
if (ValidationUtils.doesContainReservedWord(values.lastName, CONST.DISPLAY_NAME.RESERVED_NAMES)) {
ErrorUtils.addErrorMessage(errors, 'lastName', 'personalDetails.error.containsReservedWord');
@@ -107,7 +111,6 @@ function DisplayNamePage(props) {
aria-label={props.translate('common.firstName')}
role={CONST.ROLE.PRESENTATION}
defaultValue={lodashGet(currentUserDetails, 'firstName', '')}
- maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
spellCheck={false}
/>
@@ -120,7 +123,6 @@ function DisplayNamePage(props) {
aria-label={props.translate('common.lastName')}
role={CONST.ROLE.PRESENTATION}
defaultValue={lodashGet(currentUserDetails, 'lastName', '')}
- maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
spellCheck={false}
/>
diff --git a/src/pages/settings/Wallet/TransferBalancePage.tsx b/src/pages/settings/Wallet/TransferBalancePage.tsx
index 85b7bef0550c..b50e5520a999 100644
--- a/src/pages/settings/Wallet/TransferBalancePage.tsx
+++ b/src/pages/settings/Wallet/TransferBalancePage.tsx
@@ -156,12 +156,10 @@ function TransferBalancePage({bankAccountList, fundList, userWallet, walletTrans
titleKey="notFound.pageNotFound"
subtitleKey="transferAmountPage.notHereSubTitle"
linkKey="transferAmountPage.goToWallet"
- onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_WALLET)}
>
Navigation.goBack(ROUTES.SETTINGS_WALLET)}
/>
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 745f95c30c80..240a148110f7 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -143,32 +143,50 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
},
];
- const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [
- {
+ const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [];
+
+ if (policy?.areDistanceRatesEnabled) {
+ protectedCollectPolicyMenuItems.push({
+ translationKey: 'workspace.common.distanceRates',
+ icon: Expensicons.Car,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.DISTANCE_RATES,
+ });
+ }
+
+ if (policy?.areWorkflowsEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.workflows',
icon: Expensicons.Workflows,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.WORKFLOWS,
- },
- {
+ });
+ }
+
+ if (policy?.areCategoriesEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.categories',
icon: Expensicons.Folder,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.CATEGORIES,
- },
- {
+ });
+ }
+
+ if (policy?.areTagsEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.tags',
icon: Expensicons.Tag,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAGS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.TAGS,
- },
- {
- translationKey: 'workspace.common.distanceRates',
- icon: Expensicons.Car,
- action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID)))),
- routeName: SCREENS.WORKSPACE.DISTANCE_RATES,
- },
- ];
+ });
+ }
+
+ protectedCollectPolicyMenuItems.push({
+ translationKey: 'workspace.common.moreFeatures',
+ icon: Expensicons.Gear,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.MORE_FEATURES,
+ });
const menuItems: WorkspaceMenuItem[] = [
{
diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx
index 33975128af81..a574f64cf774 100644
--- a/src/pages/workspace/WorkspaceMembersPage.tsx
+++ b/src/pages/workspace/WorkspaceMembersPage.tsx
@@ -384,7 +384,7 @@ function WorkspaceMembersPage({
});
});
- result = result.sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase()));
+ result = result.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase()));
return result;
}, [
diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
new file mode 100644
index 000000000000..45a950e0fafb
--- /dev/null
+++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
@@ -0,0 +1,168 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Section from '@components/Section';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types';
+import * as Policy from '@userActions/Policy';
+import type {TranslationPaths} from '@src/languages/types';
+import type SCREENS from '@src/SCREENS';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+import type IconAsset from '@src/types/utils/IconAsset';
+import AdminPolicyAccessOrNotFoundWrapper from './AdminPolicyAccessOrNotFoundWrapper';
+import PaidPolicyAccessOrNotFoundWrapper from './PaidPolicyAccessOrNotFoundWrapper';
+import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
+import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
+import ToggleSettingOptionRow from './workflows/ToggleSettingsOptionRow';
+
+type WorkspaceMoreFeaturesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps;
+
+type Item = {
+ icon: IconAsset;
+ titleTranslationKey: TranslationPaths;
+ subtitleTranslationKey: TranslationPaths;
+ isActive: boolean;
+ action: (isEnabled: boolean) => void;
+ pendingAction: PendingAction | undefined;
+};
+
+type SectionObject = {
+ titleTranslationKey: TranslationPaths;
+ subtitleTranslationKey: TranslationPaths;
+ items: Item[];
+};
+
+function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPageProps) {
+ const styles = useThemeStyles();
+ const {isSmallScreenWidth} = useWindowDimensions();
+ const {translate} = useLocalize();
+
+ const spendItems: Item[] = [
+ {
+ icon: Illustrations.Car,
+ titleTranslationKey: 'workspace.moreFeatures.distanceRates.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.distanceRates.subtitle',
+ isActive: policy?.areDistanceRatesEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areDistanceRatesEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyDistanceRates(policy?.id ?? '', isEnabled);
+ },
+ },
+ {
+ icon: Illustrations.Workflows,
+ titleTranslationKey: 'workspace.moreFeatures.workflows.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.workflows.subtitle',
+ isActive: policy?.areWorkflowsEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areWorkflowsEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyWorkflows(policy?.id ?? '', isEnabled);
+ },
+ },
+ ];
+
+ const organizeItems: Item[] = [
+ {
+ icon: Illustrations.FolderOpen,
+ titleTranslationKey: 'workspace.moreFeatures.categories.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.categories.subtitle',
+ isActive: policy?.areCategoriesEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areCategoriesEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyCategories(policy?.id ?? '', isEnabled);
+ },
+ },
+ {
+ icon: Illustrations.Tag,
+ titleTranslationKey: 'workspace.moreFeatures.tags.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.tags.subtitle',
+ isActive: policy?.areTagsEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areTagsEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyTags(policy?.id ?? '', isEnabled);
+ },
+ },
+ ];
+
+ const sections: SectionObject[] = [
+ {
+ titleTranslationKey: 'workspace.moreFeatures.spendSection.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.spendSection.subtitle',
+ items: spendItems,
+ },
+ {
+ titleTranslationKey: 'workspace.moreFeatures.organizeSection.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.organizeSection.subtitle',
+ items: organizeItems,
+ },
+ ];
+
+ const renderItem = useCallback(
+ (item: Item) => (
+
+
+
+ ),
+ [styles, translate],
+ );
+
+ const renderSection = useCallback(
+ (section: SectionObject) => (
+
+
+ {section.items.map(renderItem)}
+
+
+ ),
+ [isSmallScreenWidth, styles, renderItem, translate],
+ );
+
+ return (
+
+
+
+
+
+ {sections.map(renderSection)}
+
+
+
+ );
+}
+
+WorkspaceMoreFeaturesPage.displayName = 'WorkspaceMoreFeaturesPage';
+
+export default withPolicyAndFullscreenLoading(WorkspaceMoreFeaturesPage);
diff --git a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
index 62f32992601a..c5c4465937b9 100644
--- a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
+++ b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
@@ -1,16 +1,16 @@
-import React, {useState} from 'react';
+import React from 'react';
import {View} from 'react-native';
-import type {SvgProps} from 'react-native-svg';
import Icon from '@components/Icon';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Switch from '@components/Switch';
import Text from '@components/Text';
import useThemeStyles from '@hooks/useThemeStyles';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+import type IconAsset from '@src/types/utils/IconAsset';
type ToggleSettingOptionRowProps = {
/** Icon to be shown for the option */
- icon: React.FC;
+ icon: IconAsset;
/** Title of the option */
title: string;
/** Subtitle of the option */
@@ -27,12 +27,7 @@ type ToggleSettingOptionRowProps = {
const ICON_SIZE = 48;
function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems, isActive, pendingAction}: ToggleSettingOptionRowProps) {
- const [isEnabled, setIsEnabled] = useState(isActive);
const styles = useThemeStyles();
- const toggleSwitch = () => {
- setIsEnabled(!isEnabled);
- onToggle(!isEnabled);
- };
return (
@@ -45,7 +40,6 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
width={ICON_SIZE}
additionalStyles={{
...styles.mr3,
- ...styles.pb4,
}}
/>
@@ -53,6 +47,7 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
style={{
...styles.textMicroBold,
...styles.textNormal,
+ ...styles.lh20,
}}
>
{title}
@@ -71,11 +66,11 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
- {isEnabled && subMenuItems}
+ {isActive && subMenuItems}
);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 303e531ea52d..0f129d898758 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -1512,25 +1512,23 @@ const styles = (theme: ThemeColors) =>
},
breadcrumsContainer: {
- height: 24,
+ minHeight: 24,
},
breadcrumb: {
color: theme.textSupporting,
- fontSize: variables.fontSizeh1,
- lineHeight: variables.lineHeightSizeh1,
+ fontSize: variables.breadcrumbsFontSize,
...headlineFont,
},
breadcrumbStrong: {
color: theme.text,
- fontSize: variables.fontSizeXLarge,
+ fontSize: variables.breadcrumbsFontSize,
},
breadcrumbSeparator: {
color: theme.icon,
- fontSize: variables.fontSizeXLarge,
- lineHeight: variables.lineHeightSizeh1,
+ fontSize: variables.breadcrumbsFontSize,
...headlineFont,
},
diff --git a/src/styles/utils/FontUtils/index.ts b/src/styles/utils/FontUtils/index.ts
index b93655fdf63d..c460a20d855d 100644
--- a/src/styles/utils/FontUtils/index.ts
+++ b/src/styles/utils/FontUtils/index.ts
@@ -13,7 +13,7 @@ const FontUtils = {
bold: fontWeightBold,
normal: '400',
},
-};
+} as const;
type FontUtilsType = typeof FontUtils;
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 63611eccb199..d63fb5e9f339 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -47,6 +47,7 @@ export default {
avatarSizeSmallSubscript: 12,
defaultAvatarPreviewSize: 360,
fabBottom: 25,
+ breadcrumbsFontSize: getValueUsingPixelRatio(19, 32),
fontSizeOnlyEmojis: 30,
fontSizeOnlyEmojisHeight: 35,
fontSizeSmall: getValueUsingPixelRatio(11, 17),
diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts
index 42482f9104dc..a6a3426af10a 100644
--- a/src/types/onyx/PersonalDetails.ts
+++ b/src/types/onyx/PersonalDetails.ts
@@ -1,6 +1,7 @@
import type {AvatarSource} from '@libs/UserUtils';
import type TIMEZONES from '@src/TIMEZONES';
import type * as OnyxCommon from './OnyxCommon';
+import type Report from './Report';
type SelectedTimezone = (typeof TIMEZONES)[number];
@@ -79,6 +80,9 @@ type PersonalDetails = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Status of the current user from their personal details */
status?: Status;
+
+ /** Chat report with assignee of task */
+ assigneeChatReport?: Report;
}>;
type PersonalDetailsList = Record;
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index bb5bf50ec6cf..f6c34fe742a4 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -198,6 +198,9 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Server side errors keyed by microtime */
errors?: OnyxCommon.Errors | OnyxCommon.ErrorFields;
+ /** Error associated with the report action */
+ error?: string;
+
/** Whether the report action is attachment */
isAttachment?: boolean;
diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js
index ec32be4d241c..64c8edb134b1 100644
--- a/tests/actions/IOUTest.js
+++ b/tests/actions/IOUTest.js
@@ -1509,7 +1509,7 @@ describe('actions/IOU', () => {
}),
)
.then(() => {
- thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID);
+ thread = ReportUtils.buildTransactionThread(iouAction, iouReport);
Onyx.set(`report_${thread.reportID}`, thread);
return waitForBatchedUpdates();
})
@@ -1643,7 +1643,7 @@ describe('actions/IOU', () => {
}),
)
.then(() => {
- thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID);
+ thread = ReportUtils.buildTransactionThread(iouAction, iouReport);
Onyx.set(`report_${thread.reportID}`, thread);
return waitForBatchedUpdates();
})
@@ -2214,7 +2214,7 @@ describe('actions/IOU', () => {
jest.advanceTimersByTime(10);
// Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
@@ -2295,7 +2295,7 @@ describe('actions/IOU', () => {
jest.advanceTimersByTime(10);
// Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
@@ -2374,7 +2374,7 @@ describe('actions/IOU', () => {
await waitForBatchedUpdates();
// Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
@@ -2460,7 +2460,7 @@ describe('actions/IOU', () => {
// Given a thread report
jest.advanceTimersByTime(10);
- thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
@@ -2686,7 +2686,7 @@ describe('actions/IOU', () => {
// Given a thread report
jest.advanceTimersByTime(10);
- thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);