diff --git a/README.md b/README.md
index 3b9010695760..f6629af8604d 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ In order to have more consistent builds, we use a strict `node` and `npm` versio
## Configuring HTTPS
The webpack development server now uses https. If you're using a mac, you can simply run `npm run setup-https`.
-If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `new.expesify.com.dev` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`.
+If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `dev.new.expensify.com` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`.
## Running the web app 🕸
* To run the **development web app**: `npm run web`
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 162147aeff0c..3d10b31e72f5 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001042407
- versionName "1.4.24-7"
+ versionCode 1001042507
+ versionName "1.4.25-7"
}
flavorDimensions "default"
diff --git a/android/app/src/main/res/drawable/ic_launcher_monochrome.png b/android/app/src/main/res/drawable/ic_launcher_monochrome.png
index b1a286b6f8dd..0af99b087923 100644
Binary files a/android/app/src/main/res/drawable/ic_launcher_monochrome.png and b/android/app/src/main/res/drawable/ic_launcher_monochrome.png differ
diff --git a/assets/images/expensify-logo--adhoc.svg b/assets/images/expensify-logo--adhoc.svg
index 273002deca9b..52b381dc4b78 100644
--- a/assets/images/expensify-logo--adhoc.svg
+++ b/assets/images/expensify-logo--adhoc.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/expensify-logo--dev.svg b/assets/images/expensify-logo--dev.svg
index e8e3fb5033d9..2c9ae142e283 100644
--- a/assets/images/expensify-logo--dev.svg
+++ b/assets/images/expensify-logo--dev.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/expensify-logo--staging.svg b/assets/images/expensify-logo--staging.svg
index 78dcc1581f99..a1e7482c133b 100644
--- a/assets/images/expensify-logo--staging.svg
+++ b/assets/images/expensify-logo--staging.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/home-background--mobile-new.svg b/assets/images/home-background--mobile-new.svg
index 0da937cae059..d81f2a18cc78 100644
--- a/assets/images/home-background--mobile-new.svg
+++ b/assets/images/home-background--mobile-new.svg
@@ -1,8835 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/new-expensify.svg b/assets/images/new-expensify.svg
index 38276ecd9385..7bfef1fd38b4 100644
--- a/assets/images/new-expensify.svg
+++ b/assets/images/new-expensify.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/images/product-illustrations/payment-hands.svg b/assets/images/product-illustrations/payment-hands.svg
index bf76b528ee76..2dbebd24994b 100644
--- a/assets/images/product-illustrations/payment-hands.svg
+++ b/assets/images/product-illustrations/payment-hands.svg
@@ -1 +1,140 @@
-
\ No newline at end of file
+
diff --git a/assets/images/product-illustrations/telescope.svg b/assets/images/product-illustrations/telescope.svg
index 95617c801789..1830dff0fe3c 100644
--- a/assets/images/product-illustrations/telescope.svg
+++ b/assets/images/product-illustrations/telescope.svg
@@ -1,79 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/signIn/google-logo.svg b/assets/images/signIn/google-logo.svg
index 4fbdc804a0a2..169ea34b23ee 100644
--- a/assets/images/signIn/google-logo.svg
+++ b/assets/images/signIn/google-logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__bigrocket.svg b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg
index 1afd5f66b6ea..64d6dc2200f0 100644
--- a/assets/images/simple-illustrations/simple-illustration__bigrocket.svg
+++ b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg
@@ -1,100 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg
index 829d3ee2e3fe..ab9d3ae4db70 100644
--- a/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg
+++ b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg
@@ -1,22 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__handcard.svg b/assets/images/simple-illustrations/simple-illustration__handcard.svg
index 7419b33d425c..a49e0ee5b77f 100644
--- a/assets/images/simple-illustrations/simple-illustration__handcard.svg
+++ b/assets/images/simple-illustrations/simple-illustration__handcard.svg
@@ -1,41 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
index 471b978bb97e..5b5e12a99a9b 100644
--- a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
+++ b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
@@ -1,98 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__hourglass.svg b/assets/images/simple-illustrations/simple-illustration__hourglass.svg
index 539e1e45b795..683e74a657e8 100644
--- a/assets/images/simple-illustrations/simple-illustration__hourglass.svg
+++ b/assets/images/simple-illustrations/simple-illustration__hourglass.svg
@@ -1,56 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__mailbox.svg b/assets/images/simple-illustrations/simple-illustration__mailbox.svg
index 81b1f508fb52..7af7c71e24f3 100644
--- a/assets/images/simple-illustrations/simple-illustration__mailbox.svg
+++ b/assets/images/simple-illustrations/simple-illustration__mailbox.svg
@@ -1,71 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__smallrocket.svg b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg
index 0f8f166c849f..388bb968a762 100644
--- a/assets/images/simple-illustrations/simple-illustration__smallrocket.svg
+++ b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg
@@ -1,45 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__trashcan.svg b/assets/images/simple-illustrations/simple-illustration__trashcan.svg
index 4e66efa0a67e..66cc9ee27550 100644
--- a/assets/images/simple-illustrations/simple-illustration__trashcan.svg
+++ b/assets/images/simple-illustrations/simple-illustration__trashcan.svg
@@ -1,52 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/thumbs-up.svg b/assets/images/thumbs-up.svg
index ef81c88fc854..3e2a4a5125b6 100644
--- a/assets/images/thumbs-up.svg
+++ b/assets/images/thumbs-up.svg
@@ -1,8 +1 @@
-
-
+
\ No newline at end of file
diff --git a/docs/assets/images/send-money.svg b/docs/assets/images/send-money.svg
index e858f0d5c327..7abce818f09e 100644
--- a/docs/assets/images/send-money.svg
+++ b/docs/assets/images/send-money.svg
@@ -1,25 +1 @@
-
+
\ No newline at end of file
diff --git a/docs/assets/images/subscription-annual.svg b/docs/assets/images/subscription-annual.svg
index a4b99a43b16e..f74ce086b2c7 100644
--- a/docs/assets/images/subscription-annual.svg
+++ b/docs/assets/images/subscription-annual.svg
@@ -1,23 +1 @@
-
+
\ No newline at end of file
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 7081805db569..c76c947aafd9 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.24
+ 1.4.25
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.24.7
+ 1.4.25.7
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 20d4ea1a4820..1dde1a528b3c 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.24
+ 1.4.25
CFBundleSignature
????
CFBundleVersion
- 1.4.24.7
+ 1.4.25.7
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index f941edc1100e..b840fa5cd80a 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -3,9 +3,9 @@
CFBundleShortVersionString
- 1.4.24
+ 1.4.25
CFBundleVersion
- 1.4.24.7
+ 1.4.25.7
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index acc8720dafce..f433c4f1e1e2 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1176,11 +1176,11 @@ PODS:
- React-Core
- react-native-key-command (1.0.6):
- React-Core
- - react-native-netinfo (11.1.0):
+ - react-native-netinfo (11.2.1):
- React-Core
- react-native-pager-view (6.2.2):
- React-Core
- - react-native-pdf (6.7.4):
+ - react-native-pdf (6.7.3):
- React-Core
- react-native-performance (5.1.0):
- React-Core
@@ -1909,9 +1909,9 @@ SPEC CHECKSUMS:
react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56
react-native-image-picker: c33d4e79f0a14a2b66e5065e14946ae63749660b
react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5
- react-native-netinfo: 3aa5637c18834966e0c932de8ae1ae56fea20a97
+ react-native-netinfo: 8a7fd3f7130ef4ad2fb4276d5c9f8d3f28d2df3d
react-native-pager-view: 02a5c4962530f7efc10dd51ee9cdabeff5e6c631
- react-native-pdf: 79aa75e39a80c1d45ffe58aa500f3cf08f267a2e
+ react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e
react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886
react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1
react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4
@@ -1967,7 +1967,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7
- Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
+ Yoga: 13c8ef87792450193e117976337b8527b49e8c03
PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2
diff --git a/package-lock.json b/package-lock.json
index ac012bea728f..148bf7157119 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.24-7",
+ "version": "1.4.25-7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.24-7",
+ "version": "1.4.25-7",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -27,7 +27,7 @@
"@react-native-camera-roll/camera-roll": "5.4.0",
"@react-native-clipboard/clipboard": "^1.12.1",
"@react-native-community/geolocation": "^3.0.6",
- "@react-native-community/netinfo": "11.1.0",
+ "@react-native-community/netinfo": "11.2.1",
"@react-native-firebase/analytics": "^12.3.0",
"@react-native-firebase/app": "^12.3.0",
"@react-native-firebase/crashlytics": "^12.3.0",
@@ -96,7 +96,7 @@
"react-native-modal": "^13.0.0",
"react-native-onyx": "1.0.126",
"react-native-pager-view": "6.2.2",
- "react-native-pdf": "^6.7.4",
+ "react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
"react-native-permissions": "^3.9.3",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
@@ -9608,9 +9608,9 @@
}
},
"node_modules/@react-native-community/netinfo": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.1.0.tgz",
- "integrity": "sha512-pIbCuqgrY7SkngAcjUs9fMzNh1h4soQMVw1IeGp1HN5//wox3fUVOuvyIubTscUbdLFKiltJAiuQek7Nhx1bqA==",
+ "version": "11.2.1",
+ "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.2.1.tgz",
+ "integrity": "sha512-n9kgmH7vLaU7Cdo8vGfJGGwhrlgppaOSq5zKj9I7H4k5iRM3aNtwURw83mgrc22Ip7nSye2afZV2xDiIyvHttQ==",
"peerDependencies": {
"react-native": ">=0.59"
}
@@ -47079,9 +47079,9 @@
}
},
"node_modules/react-native-pdf": {
- "version": "6.7.4",
- "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
- "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
+ "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
"dependencies": {
"crypto-js": "4.2.0",
"deprecated-react-native-prop-types": "^2.3.0"
@@ -62630,9 +62630,9 @@
"requires": {}
},
"@react-native-community/netinfo": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.1.0.tgz",
- "integrity": "sha512-pIbCuqgrY7SkngAcjUs9fMzNh1h4soQMVw1IeGp1HN5//wox3fUVOuvyIubTscUbdLFKiltJAiuQek7Nhx1bqA==",
+ "version": "11.2.1",
+ "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.2.1.tgz",
+ "integrity": "sha512-n9kgmH7vLaU7Cdo8vGfJGGwhrlgppaOSq5zKj9I7H4k5iRM3aNtwURw83mgrc22Ip7nSye2afZV2xDiIyvHttQ==",
"requires": {}
},
"@react-native-firebase/analytics": {
@@ -89718,9 +89718,9 @@
"requires": {}
},
"react-native-pdf": {
- "version": "6.7.4",
- "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
- "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
+ "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
"requires": {
"crypto-js": "4.2.0",
"deprecated-react-native-prop-types": "^2.3.0"
diff --git a/package.json b/package.json
index 4a28617f649d..a842ffd2fdb4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.24-7",
+ "version": "1.4.25-7",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -75,7 +75,7 @@
"@react-native-camera-roll/camera-roll": "5.4.0",
"@react-native-clipboard/clipboard": "^1.12.1",
"@react-native-community/geolocation": "^3.0.6",
- "@react-native-community/netinfo": "11.1.0",
+ "@react-native-community/netinfo": "11.2.1",
"@react-native-firebase/analytics": "^12.3.0",
"@react-native-firebase/app": "^12.3.0",
"@react-native-firebase/crashlytics": "^12.3.0",
@@ -144,7 +144,7 @@
"react-native-modal": "^13.0.0",
"react-native-onyx": "1.0.126",
"react-native-pager-view": "6.2.2",
- "react-native-pdf": "^6.7.4",
+ "react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
"react-native-permissions": "^3.9.3",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
diff --git a/src/CONST.ts b/src/CONST.ts
index 8b5c0f5a88ca..d6f3d3cdcef6 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -479,7 +479,9 @@ const CONST = {
ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/',
// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
- EXPENSIFY_INBOX_URL: 'https://www.expensify.com/inbox',
+ OLDDOT_URLS: {
+ INBOX: 'inbox',
+ },
SIGN_IN_FORM_WIDTH: 300,
@@ -601,9 +603,11 @@ const CONST = {
},
THREAD_DISABLED: ['CREATED'],
},
+ CANCEL_PAYMENT_REASONS: {
+ ADMIN: 'CANCEL_REASON_ADMIN',
+ },
ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
INVITE: 'invited',
- NOTHING: 'nothing',
},
ARCHIVE_REASON: {
DEFAULT: 'default',
@@ -635,18 +639,13 @@ const CONST = {
ANNOUNCE: '#announce',
ADMINS: '#admins',
},
- STATE: {
- OPEN: 'OPEN',
- SUBMITTED: 'SUBMITTED',
- PROCESSING: 'PROCESSING',
- },
STATE_NUM: {
OPEN: 0,
- PROCESSING: 1,
- SUBMITTED: 2,
+ SUBMITTED: 1,
+ APPROVED: 2,
BILLING: 3,
},
- STATUS: {
+ STATUS_NUM: {
OPEN: 0,
SUBMITTED: 1,
CLOSED: 2,
@@ -1445,6 +1444,8 @@ const CONST = {
INVISIBLE_CHARACTERS_GROUPS: /[\p{C}\p{Z}]/gu,
OTHER_INVISIBLE_CHARACTERS: /[\u3164]/g,
+
+ REPORT_FIELD_TITLE: /{report:([a-zA-Z]+)}/g,
},
PRONOUNS: {
@@ -2730,7 +2731,7 @@ const CONST = {
EXPECTED_OUTPUT: 'FCFA 123,457',
},
- PATHS_TO_TREAT_AS_EXTERNAL: ['NewExpensify.dmg'],
+ PATHS_TO_TREAT_AS_EXTERNAL: ['NewExpensify.dmg', 'docs/index.html'],
// Test tool menu parameters
TEST_TOOL: {
diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js
index 92cbe3a4da04..f69fe7eb5ecb 100644
--- a/src/components/AvatarCropModal/ImageCropView.js
+++ b/src/components/AvatarCropModal/ImageCropView.js
@@ -6,7 +6,6 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import useStyleUtils from '@hooks/useStyleUtils';
-import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import ControlSelection from '@libs/ControlSelection';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';
@@ -51,7 +50,6 @@ const defaultProps = {
};
function ImageCropView(props) {
- const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize);
@@ -90,7 +88,8 @@ function ImageCropView(props) {
diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx
index 5993e60861f5..807029addf5e 100644
--- a/src/components/BlockingViews/FullPageNotFoundView.tsx
+++ b/src/components/BlockingViews/FullPageNotFoundView.tsx
@@ -33,10 +33,10 @@ type FullPageNotFoundViewProps = {
linkKey?: TranslationPaths;
/** Method to trigger when pressing the back button of the header */
- onBackButtonPress: () => void;
+ onBackButtonPress?: () => void;
/** Function to call when pressing the navigation link */
- onLinkPress: () => void;
+ onLinkPress?: () => void;
};
// eslint-disable-next-line rulesdir/no-negated-variables
diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.tsx
similarity index 56%
rename from src/components/ButtonWithDropdownMenu.js
rename to src/components/ButtonWithDropdownMenu.tsx
index 4d3ec8796a31..466c68229a32 100644
--- a/src/components/ButtonWithDropdownMenu.js
+++ b/src/components/ButtonWithDropdownMenu.tsx
@@ -1,89 +1,89 @@
-import PropTypes from 'prop-types';
+import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
+import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
-import _ from 'underscore';
+import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
+import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
-import sourcePropTypes from './Image/sourcePropTypes';
+import type {AnchorAlignment} from './Popover/types';
import PopoverMenu from './PopoverMenu';
-const propTypes = {
+type DropdownOption = {
+ value: string;
+ text: string;
+ icon: IconAsset;
+ iconWidth?: number;
+ iconHeight?: number;
+ iconDescription?: string;
+};
+
+type ButtonWithDropdownMenuProps = {
/** Text to display for the menu header */
- menuHeaderText: PropTypes.string,
+ menuHeaderText?: string;
/** Callback to execute when the main button is pressed */
- onPress: PropTypes.func.isRequired,
+ onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void;
/** Call the onPress function on main button when Enter key is pressed */
- pressOnEnter: PropTypes.bool,
+ pressOnEnter?: boolean;
/** Whether we should show a loading state for the main button */
- isLoading: PropTypes.bool,
+ isLoading?: boolean;
/** The size of button size */
- buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)),
+ buttonSize: ValueOf;
/** Should the confirmation button be disabled? */
- isDisabled: PropTypes.bool,
+ isDisabled?: boolean;
/** Additional styles to add to the component */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+ style?: StyleProp;
/** Menu options to display */
/** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */
- options: PropTypes.arrayOf(
- PropTypes.shape({
- value: PropTypes.string.isRequired,
- text: PropTypes.string.isRequired,
- icon: sourcePropTypes,
- iconWidth: PropTypes.number,
- iconHeight: PropTypes.number,
- iconDescription: PropTypes.string,
- }),
- ).isRequired,
+ options: DropdownOption[];
/** The anchor alignment of the popover menu */
- anchorAlignment: PropTypes.shape({
- horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
- vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
- }),
+ anchorAlignment?: AnchorAlignment;
/* ref for the button */
- buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ buttonRef: RefObject;
};
-const defaultProps = {
- isLoading: false,
- isDisabled: false,
- pressOnEnter: false,
- menuHeaderText: '',
- style: [],
- buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
- anchorAlignment: {
+function ButtonWithDropdownMenu({
+ isLoading = false,
+ isDisabled = false,
+ pressOnEnter = false,
+ menuHeaderText = '',
+ style,
+ buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
+ anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
},
- buttonRef: () => {},
-};
-
-function ButtonWithDropdownMenu(props) {
+ buttonRef,
+ onPress,
+ options,
+}: ButtonWithDropdownMenuProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [selectedItemIndex, setSelectedItemIndex] = useState(0);
const [isMenuVisible, setIsMenuVisible] = useState(false);
- const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null);
+ const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null);
const {windowWidth, windowHeight} = useWindowDimensions();
- const caretButton = useRef(null);
- const selectedItem = props.options[selectedItemIndex] || _.first(props.options);
- const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(props.buttonSize);
- const isButtonSizeLarge = props.buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
+ const caretButton = useRef(null);
+ const selectedItem = options[selectedItemIndex] || options[0];
+ const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize);
+ const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
useEffect(() => {
if (!caretButton.current) {
@@ -92,29 +92,31 @@ function ButtonWithDropdownMenu(props) {
if (!isMenuVisible) {
return;
}
- caretButton.current.measureInWindow((x, y, w, h) => {
- setPopoverAnchorPosition({
- horizontal: x + w,
- vertical:
- props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP
- ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding
- : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding
+ if ('measureInWindow' in caretButton.current) {
+ caretButton.current.measureInWindow((x, y, w, h) => {
+ setPopoverAnchorPosition({
+ horizontal: x + w,
+ vertical:
+ anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP
+ ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding
+ : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding
+ });
});
- });
- }, [windowWidth, windowHeight, isMenuVisible, props.anchorAlignment.vertical]);
+ }
+ }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]);
return (
- {props.options.length > 1 ? (
-
+ {options.length > 1 ? (
+
)}
+ setIsConfirmModalVisible(false)}
+ prompt={translate('iou.cancelPaymentConfirmation')}
+ confirmText={translate('iou.cancelPayment')}
+ cancelText={translate('common.dismiss')}
+ danger
+ />
);
}
diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js
index 197829bb1ea9..412aeedcf965 100755
--- a/src/components/OptionsSelector/BaseOptionsSelector.js
+++ b/src/components/OptionsSelector/BaseOptionsSelector.js
@@ -7,12 +7,9 @@ import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager';
import Button from '@components/Button';
import FixedFooter from '@components/FixedFooter';
import FormHelpMessage from '@components/FormHelpMessage';
-import Icon from '@components/Icon';
-import {Info} from '@components/Icon/Expensicons';
import OptionsList from '@components/OptionsList';
-import {PressableWithoutFeedback} from '@components/Pressable';
+import ReferralProgramCTA from '@components/ReferralProgramCTA';
import ShowMoreButton from '@components/ShowMoreButton';
-import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withNavigationFocus from '@components/withNavigationFocus';
@@ -21,10 +18,8 @@ import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeSt
import compose from '@libs/compose';
import getPlatform from '@libs/getPlatform';
import KeyboardShortcut from '@libs/KeyboardShortcut';
-import Navigation from '@libs/Navigation/Navigation';
import setSelection from '@libs/setSelection';
import CONST from '@src/CONST';
-import ROUTES from '@src/ROUTES';
import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes';
const propTypes = {
@@ -667,39 +662,7 @@ class BaseOptionsSelector extends Component {
{this.props.shouldShowReferralCTA && (
- {
- Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(this.props.referralContentType));
- }}
- style={[
- this.props.themeStyles.p5,
- this.props.themeStyles.w100,
- this.props.themeStyles.br2,
- this.props.themeStyles.highlightBG,
- this.props.themeStyles.flexRow,
- this.props.themeStyles.justifyContentBetween,
- this.props.themeStyles.alignItemsCenter,
- {gap: 10},
- ]}
- accessibilityLabel="referral"
- role={CONST.ACCESSIBILITY_ROLE.BUTTON}
- >
-
- {this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText1`)}
-
- {this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText2`)}
-
-
-
-
+
)}
diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx
index 2d6f74f7cd46..17b1a119671a 100644
--- a/src/components/PopoverMenu.tsx
+++ b/src/components/PopoverMenu.tsx
@@ -3,13 +3,13 @@ import type {RefObject} from 'react';
import React, {useRef} from 'react';
import {View} from 'react-native';
import type {ModalProps} from 'react-native-modal';
-import type {SvgProps} from 'react-native-svg';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import CONST from '@src/CONST';
import type {AnchorPosition} from '@src/styles';
+import type IconAsset from '@src/types/utils/IconAsset';
import MenuItem from './MenuItem';
import type {AnchorAlignment} from './Popover/types';
import PopoverWithMeasuredContent from './PopoverWithMeasuredContent';
@@ -17,7 +17,7 @@ import Text from './Text';
type PopoverMenuItem = {
/** An icon element displayed on the left side */
- icon: React.FC;
+ icon: IconAsset;
/** Text label */
text: string;
@@ -46,7 +46,7 @@ type PopoverMenuItem = {
type PopoverModalProps = Pick;
-type PopoverMenuProps = PopoverModalProps & {
+type PopoverMenuProps = Partial & {
/** Callback method fired when the user requests to close the modal */
onClose: () => void;
diff --git a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx
similarity index 68%
rename from src/pages/iou/MoneyRequestReferralProgramCTA.tsx
rename to src/components/ReferralProgramCTA.tsx
index 31394e1bd0e1..473d5cdbed08 100644
--- a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx
+++ b/src/components/ReferralProgramCTA.tsx
@@ -1,20 +1,24 @@
import React from 'react';
-import Icon from '@components/Icon';
-import {Info} from '@components/Icon/Expensicons';
-import {PressableWithoutFeedback} from '@components/Pressable';
-import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import Navigation from '@src/libs/Navigation/Navigation';
import ROUTES from '@src/ROUTES';
+import Icon from './Icon';
+import {Info} from './Icon/Expensicons';
+import {PressableWithoutFeedback} from './Pressable';
+import Text from './Text';
-type MoneyRequestReferralProgramCTAProps = {
- referralContentType: typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
+type ReferralProgramCTAProps = {
+ referralContentType:
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND;
};
-function MoneyRequestReferralProgramCTA({referralContentType}: MoneyRequestReferralProgramCTAProps) {
+function ReferralProgramCTA({referralContentType}: ReferralProgramCTAProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
@@ -41,9 +45,10 @@ function MoneyRequestReferralProgramCTA({referralContentType}: MoneyRequestRefer
src={Info}
height={20}
width={20}
+ fill={theme.icon}
/>
);
}
-export default MoneyRequestReferralProgramCTA;
+export default ReferralProgramCTA;
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 16ea27b17f42..4fcca3e518a5 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -1,11 +1,14 @@
-import React from 'react';
+import React, {useMemo} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import {View} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
import SpacerView from '@components/SpacerView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -14,22 +17,26 @@ import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground';
import variables from '@styles/variables';
-import type {Report} from '@src/types/onyx';
+import type {PolicyReportField, Report} from '@src/types/onyx';
type MoneyReportViewProps = {
/** The report currently being looked at */
report: Report;
+ /** Policy report fields */
+ policyReportFields: PolicyReportField[];
+
/** Whether we should display the horizontal rule below the component */
shouldShowHorizontalRule: boolean;
};
-function MoneyReportView({report, shouldShowHorizontalRule}: MoneyReportViewProps) {
+function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: MoneyReportViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
+ const {canUseReportFields} = usePermissions();
const isSettled = ReportUtils.isSettled(report.reportID);
const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report);
@@ -46,10 +53,41 @@ function MoneyReportView({report, shouldShowHorizontalRule}: MoneyReportViewProp
StyleUtils.getColorStyle(theme.textSupporting),
];
+ const sortedPolicyReportFields = useMemo(
+ () => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight),
+ [policyReportFields],
+ );
+
return (
+ {canUseReportFields &&
+ sortedPolicyReportFields.map((reportField) => {
+ const title = ReportUtils.getReportFieldTitle(report, reportField);
+ return (
+
+ {}}
+ shouldShowRightIcon
+ disabled={false}
+ wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
+ shouldGreyOutWhenDisabled={false}
+ numberOfLinesTitle={0}
+ interactive
+ shouldStackHorizontally={false}
+ onSecondaryInteraction={() => {}}
+ hoverAndPressStyle={false}
+ titleWithTooltips={[]}
+ />
+
+ );
+ })}
Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))}
- brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
- error={hasErrors && isEmptyMerchant ? translate('common.error.enterMerchant') : ''}
+ brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant && isPolicyExpenseChat) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
+ error={hasErrors && isPolicyExpenseChat && isEmptyMerchant ? translate('common.error.enterMerchant') : ''}
/>
{canUseViolations && }
diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js
index abc7e3954200..622cd75a568b 100644
--- a/src/components/ReportActionItem/ReportPreview.js
+++ b/src/components/ReportActionItem/ReportPreview.js
@@ -1,6 +1,6 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
-import React, {useEffect, useMemo, useState} from 'react';
+import React, {useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
@@ -13,6 +13,7 @@ import refPropTypes from '@components/refPropTypes';
import SettlementButton from '@components/SettlementButton';
import {showContextMenuForReport} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
+import transactionPropTypes from '@components/transactionPropTypes';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
@@ -22,7 +23,6 @@ import ControlSelection from '@libs/ControlSelection';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
-import onyxSubscribe from '@libs/onyxSubscribe';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportActionUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
@@ -105,6 +105,9 @@ const propTypes = {
/** Whether a message is a whisper */
isWhisper: PropTypes.bool,
+ /** All the transactions, used to update ReportPreview label and status */
+ transactions: PropTypes.objectOf(transactionPropTypes),
+
...withLocalizePropTypes,
};
@@ -121,6 +124,7 @@ const defaultProps = {
policy: {
isHarvestingEnabled: false,
},
+ transactions: {},
};
function ReportPreview(props) {
@@ -128,10 +132,17 @@ function ReportPreview(props) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [hasMissingSmartscanFields, sethasMissingSmartscanFields] = useState(false);
- const [areAllRequestsBeingSmartScanned, setAreAllRequestsBeingSmartScanned] = useState(false);
- const [hasOnlyDistanceRequests, setHasOnlyDistanceRequests] = useState(false);
- const [hasNonReimbursableTransactions, setHasNonReimbursableTransactions] = useState(false);
+ const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyDistanceRequests, hasNonReimbursableTransactions} = useMemo(
+ () => ({
+ hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(props.iouReportID),
+ areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action),
+ hasOnlyDistanceRequests: ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID),
+ hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(props.iouReportID),
+ }),
+ // When transactions get updated these status may have changed, so that is a case where we also want to run this.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [props.transactions, props.iouReportID, props.action],
+ );
const managerID = props.iouReport.managerID || 0;
const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID');
@@ -162,7 +173,7 @@ function ReportPreview(props) {
const previewSubtitle =
formattedMerchant ||
props.translate('iou.requestCount', {
- count: numberOfRequests,
+ count: numberOfRequests - numberOfScanningReceipts,
scanningReceipts: numberOfScanningReceipts,
});
@@ -218,28 +229,6 @@ function ReportPreview(props) {
const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport);
- useEffect(() => {
- const unsubscribeOnyxTransaction = onyxSubscribe({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- if (_.isEmpty(allTransactions)) {
- return;
- }
-
- sethasMissingSmartscanFields(ReportUtils.hasMissingSmartscanFields(props.iouReportID));
- setAreAllRequestsBeingSmartScanned(ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action));
- setHasOnlyDistanceRequests(ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID));
- setHasNonReimbursableTransactions(ReportUtils.hasNonReimbursableTransactions(props.iouReportID));
- },
- });
-
- return () => {
- unsubscribeOnyxTransaction();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(props.chatReport);
const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(props.policy, 'role') === CONST.POLICY.ROLE.ADMIN;
const isPayer = isPaidGroupPolicy
@@ -370,5 +359,8 @@ export default compose(
session: {
key: ONYXKEYS.SESSION,
},
+ transactions: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ },
}),
)(ReportPreview);
diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx
index fbc58a381318..a509d8d922e1 100644
--- a/src/components/ReportActionItem/TaskPreview.tsx
+++ b/src/components/ReportActionItem/TaskPreview.tsx
@@ -84,12 +84,13 @@ function TaskPreview({
const StyleUtils = useStyleUtils();
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const {translate} = useLocalize();
+
// The reportAction might not contain details regarding the taskReport
// Only the direct parent reportAction will contain details about the taskReport
// Other linked reportActions will only contain the taskReportID and we will grab the details from there
const isTaskCompleted = !isEmptyObject(taskReport)
- ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && taskReport.statusNum === CONST.REPORT.STATUS.APPROVED
- : action?.childStateNum === CONST.REPORT.STATE_NUM.SUBMITTED && action?.childStatusNum === CONST.REPORT.STATUS.APPROVED;
+ ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED
+ : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED;
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? ''));
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport ?? {}) ?? action?.childManagerAccountID ?? '';
const assigneeLogin = personalDetails[taskAssigneeAccountID]?.login ?? '';
diff --git a/src/hooks/useDragAndDrop.ts b/src/hooks/useDragAndDrop.ts
index 8c9054dc0bf1..7644d7bba5f0 100644
--- a/src/hooks/useDragAndDrop.ts
+++ b/src/hooks/useDragAndDrop.ts
@@ -1,6 +1,6 @@
import {useIsFocused} from '@react-navigation/native';
import type React from 'react';
-import {useCallback, useContext, useEffect, useState} from 'react';
+import {useCallback, useContext, useEffect, useRef, useState} from 'react';
import type {View} from 'react-native';
import {PopoverContext} from '@components/PopoverProvider';
@@ -31,6 +31,8 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
const [isDraggingOver, setIsDraggingOver] = useState(false);
const {close: closePopover} = useContext(PopoverContext);
+ const enterTarget = useRef(null);
+
useEffect(() => {
if (isFocused && !isDisabled) {
return;
@@ -76,6 +78,7 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
break;
case DRAG_ENTER_EVENT:
handleDragEvent(event);
+ enterTarget.current = event.target;
if (isDraggingOver) {
return;
}
@@ -86,7 +89,7 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
return;
}
// This is necessary because dragging over children will cause dragleave to execute on the parent.
- if ((event.currentTarget as HTMLElement | null)?.contains(event.relatedTarget as HTMLElement | null)) {
+ if (enterTarget.current !== event.target) {
return;
}
diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts
index 3ca2482ec387..dd782a9dbba5 100644
--- a/src/hooks/useResponsiveLayout.ts
+++ b/src/hooks/useResponsiveLayout.ts
@@ -3,7 +3,7 @@ import {useRoute} from '@react-navigation/native';
import useWindowDimensions from './useWindowDimensions';
type RouteParams = ParamListBase & {
- params: {layout?: string};
+ params: {isInRHP?: boolean};
};
type ResponsiveLayoutResult = {
shouldUseNarrowLayout: boolean;
@@ -16,11 +16,10 @@ export default function useResponsiveLayout(): ResponsiveLayoutResult {
try {
// eslint-disable-next-line react-hooks/rules-of-hooks
const {params} = useRoute>();
- const isNarrowLayout = params?.layout === 'narrow' ?? false;
- const shouldUseNarrowLayout = isSmallScreenWidth || isNarrowLayout;
-
- return {shouldUseNarrowLayout};
+ return {shouldUseNarrowLayout: isSmallScreenWidth || (params?.isInRHP ?? false)};
} catch (error) {
- return {shouldUseNarrowLayout: isSmallScreenWidth};
+ return {
+ shouldUseNarrowLayout: isSmallScreenWidth,
+ };
}
}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 90f98d9aec85..b6fa37560536 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1,7 +1,9 @@
import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST';
+import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';
import type {
AddressLineParams,
+ AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
@@ -111,6 +113,7 @@ type AllCountries = Record;
export default {
common: {
cancel: 'Cancel',
+ dismiss: 'Dismiss',
yes: 'Yes',
no: 'No',
ok: 'OK',
@@ -573,6 +576,8 @@ export default {
requestMoney: 'Request money',
sendMoney: 'Send money',
pay: 'Pay',
+ cancelPayment: 'Cancel payment',
+ cancelPaymentConfirmation: 'Are you sure that you want to cancel this payment?',
viewDetails: 'View details',
pending: 'Pending',
canceled: 'Canceled',
@@ -584,7 +589,8 @@ export default {
receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.",
receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.',
transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.',
- requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`,
+ requestCount: ({count, scanningReceipts = 0}: RequestCountParams) =>
+ `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`,
deleteRequest: 'Delete request',
deleteConfirmation: 'Are you sure that you want to delete this request?',
settledExpensify: 'Paid',
@@ -609,6 +615,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `approved ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`,
+ adminCanceledRequest: ({amount}: AdminCanceledRequestParams) => `The ${amount} payment has been cancelled by the admin.`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) =>
`Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 331a6a7b92b1..271f0787bfde 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1,6 +1,8 @@
+import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';
import type {
AddressLineParams,
+ AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
@@ -101,6 +103,7 @@ import type {
export default {
common: {
cancel: 'Cancelar',
+ dismiss: 'Descartar',
yes: 'SÃ',
no: 'No',
ok: 'OK',
@@ -566,6 +569,8 @@ export default {
requestMoney: 'Pedir dinero',
sendMoney: 'Enviar dinero',
pay: 'Pagar',
+ cancelPayment: 'Cancelar el pago',
+ cancelPaymentConfirmation: '¿Estás seguro de que quieres cancelar este pago?',
viewDetails: 'Ver detalles',
pending: 'Pendiente',
canceled: 'Canceló',
@@ -577,7 +582,8 @@ export default {
receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.',
receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.',
transactionPendingText: 'La transacción tarda unos dÃas en contabilizarse desde la fecha en que se utilizó la tarjeta.',
- requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`,
+ requestCount: ({count, scanningReceipts = 0}: RequestCountParams) =>
+ `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`,
deleteRequest: 'Eliminar pedido',
deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?',
settledExpensify: 'Pagado',
@@ -602,6 +608,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `aprobó ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`,
+ adminCanceledRequest: ({amount}: AdminCanceledRequestParams) => `El pago de ${amount} ha sido cancelado por el administrador.`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) =>
`Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 dÃas.`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
diff --git a/src/languages/types.ts b/src/languages/types.ts
index 3185b7a8f6f1..35a5110abf79 100644
--- a/src/languages/types.ts
+++ b/src/languages/types.ts
@@ -135,6 +135,8 @@ type WaitingOnBankAccountParams = {submitterDisplayName: string};
type CanceledRequestParams = {amount: string; submitterDisplayName: string};
+type AdminCanceledRequestParams = {amount: string};
+
type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string};
type PaidElsewhereWithAmountParams = {payer?: string; amount: string};
@@ -288,6 +290,7 @@ type TranslationFlatObject = {
};
export type {
+ AdminCanceledRequestParams,
ApprovedAmountParams,
AddressLineParams,
AlreadySignedInParams,
diff --git a/src/libs/DoInteractionTask/index.desktop.ts b/src/libs/DoInteractionTask/index.desktop.ts
new file mode 100644
index 000000000000..73b3cb19ec32
--- /dev/null
+++ b/src/libs/DoInteractionTask/index.desktop.ts
@@ -0,0 +1,10 @@
+import {InteractionManager} from 'react-native';
+
+// For desktop, we should call the callback after all interactions to prevent freezing. See more detail in https://github.com/Expensify/App/issues/28916
+function doInteractionTask(callback: () => void) {
+ return InteractionManager.runAfterInteractions(() => {
+ callback();
+ });
+}
+
+export default doInteractionTask;
diff --git a/src/libs/DoInteractionTask/index.ts b/src/libs/DoInteractionTask/index.ts
new file mode 100644
index 000000000000..dffbb0562b98
--- /dev/null
+++ b/src/libs/DoInteractionTask/index.ts
@@ -0,0 +1,6 @@
+function doInteractionTask(callback: () => void) {
+ callback();
+ return null;
+}
+
+export default doInteractionTask;
diff --git a/src/libs/E2E/apiMocks/openApp.ts b/src/libs/E2E/apiMocks/openApp.ts
index ec714d693666..d6dd4a8f8003 100644
--- a/src/libs/E2E/apiMocks/openApp.ts
+++ b/src/libs/E2E/apiMocks/openApp.ts
@@ -2043,10 +2043,10 @@ const openApp = (): Response => ({
managerID: 16,
currency: 'USD',
chatReportID: '98817646',
- state: 'SUBMITTED',
cachedTotal: '($1,473.11)',
total: 147311,
stateNum: 1,
+ statusNum: 1,
},
report_4249286573496381: {
reportID: '4249286573496381',
@@ -2054,10 +2054,10 @@ const openApp = (): Response => ({
managerID: 21,
currency: 'USD',
chatReportID: '4867098979334014',
- state: 'SUBMITTED',
cachedTotal: '($212.78)',
total: 21278,
stateNum: 1,
+ statusNum: 1,
},
},
},
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 9d4be56ba08f..b0f33af0ce2e 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -62,7 +62,6 @@ function createModalStackNavigator(screens:
key={name}
name={name}
getComponent={(screens as Required)[name as Screen]}
- initialParams={{layout: 'narrow'} as TStackParams[string]}
/>
))}
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 8563db7db172..8d227fa6f697 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -3,6 +3,7 @@ import type {CommonActions, NavigationContainerRefWithCurrent, NavigationHelpers
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type NAVIGATORS from '@src/NAVIGATORS';
+import type {Route as Routes} from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
type NavigationRef = NavigationContainerRefWithCurrent;
@@ -378,10 +379,11 @@ type RightModalNavigatorParamList = {
type PublicScreensParamList = {
[SCREENS.HOME]: undefined;
[SCREENS.TRANSITION_BETWEEN_APPS]: {
- shouldForceLogin: string;
- email: string;
- shortLivedAuthToken: string;
- exitTo: string;
+ email?: string;
+ error?: string;
+ shortLivedAuthToken?: string;
+ shortLivedToken?: string;
+ exitTo?: Routes;
};
[SCREENS.VALIDATE_LOGIN]: {
accountID: string;
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 988398009dd8..37b7a9424fee 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -420,7 +420,7 @@ function getLastMessageTextForReport(report) {
} else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isReimbursementDeQueuedAction(lastReportAction)) {
- lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(report);
+ lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) {
lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction);
} else if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) {
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 2e2426cdf8da..561934273ae8 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -14,9 +14,9 @@ import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportMetadata, Session, Transaction} from '@src/types/onyx';
+import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyReportField, Report, ReportAction, ReportMetadata, Session, Transaction} from '@src/types/onyx';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
-import type {IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage';
+import type {IOUMessage, OriginalMessageActionName, OriginalMessageCreated, ReimbursementDeQueuedMessage} from '@src/types/onyx/OriginalMessage';
import type {Status} from '@src/types/onyx/PersonalDetails';
import type {NotificationPreference} from '@src/types/onyx/Report';
import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
@@ -112,7 +112,6 @@ type OptimisticExpenseReport = Pick<
| 'ownerAccountID'
| 'currency'
| 'reportName'
- | 'state'
| 'stateNum'
| 'statusNum'
| 'total'
@@ -180,6 +179,11 @@ type OptimisticSubmittedReportAction = Pick<
'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
>;
+type OptimisticCancelPaymentReportAction = Pick<
+ ReportAction,
+ 'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
+>;
+
type OptimisticEditedTaskReportAction = Pick<
ReportAction,
'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person'
@@ -306,13 +310,12 @@ type OptimisticIOUReport = Pick<
| 'participantAccountIDs'
| 'visibleChatMemberAccountIDs'
| 'reportID'
- | 'state'
| 'stateNum'
+ | 'statusNum'
| 'total'
| 'reportName'
| 'notificationPreference'
| 'parentReportID'
- | 'statusNum'
| 'lastVisibleActionCreated'
>;
type DisplayNameWithTooltips = Array>;
@@ -583,14 +586,16 @@ function isCanceledTaskReport(report: OnyxEntry | EmptyObject = {}, pare
* @param parentReportAction - The parent report action of the report (Used to check if the task has been canceled)
*/
function isOpenTaskReport(report: OnyxEntry, parentReportAction: OnyxEntry | EmptyObject = {}): boolean {
- return isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN;
+ return (
+ isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN
+ );
}
/**
* Checks if a report is a completed task report.
*/
function isCompletedTaskReport(report: OnyxEntry): boolean {
- return isTaskReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED;
+ return isTaskReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED;
}
/**
@@ -605,14 +610,14 @@ function isReportManager(report: OnyxEntry): boolean {
*/
function isReportApproved(reportOrID: OnyxEntry | string | EmptyObject): boolean {
const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID;
- return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED;
+ return report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED;
}
/**
* Checks if the supplied report is an expense report in Open state and status.
*/
function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean {
- return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN;
+ return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN;
}
/**
@@ -643,11 +648,11 @@ function isSettled(reportID: string | undefined): boolean {
// In case the payment is scheduled and we are waiting for the payee to set up their wallet,
// consider the report as paid as well.
- if (report.isWaitingOnBankAccount && report.statusNum === CONST.REPORT.STATUS.APPROVED) {
+ if (report.isWaitingOnBankAccount && report.statusNum === CONST.REPORT.STATUS_NUM.APPROVED) {
return true;
}
- return report?.statusNum === CONST.REPORT.STATUS.REIMBURSED;
+ return report?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;
}
/**
@@ -830,7 +835,7 @@ function isConciergeChatReport(report: OnyxEntry): boolean {
* Returns true if report is still being processed
*/
function isProcessingReport(report: OnyxEntry | EmptyObject): boolean {
- return report?.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && report?.statusNum === CONST.REPORT.STATUS.SUBMITTED;
+ return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED;
}
/**
@@ -936,7 +941,7 @@ function findLastAccessedReport(
* Whether the provided report is an archived room
*/
function isArchivedRoom(report: OnyxEntry | EmptyObject): boolean {
- return report?.statusNum === CONST.REPORT.STATUS.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED;
+ return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED;
}
/**
@@ -1095,13 +1100,25 @@ function getReportNotificationPreference(report: OnyxEntry): string | nu
}
/**
- * Returns whether or not the author of the action is this user
- *
+ * Checks if the current user is the action's author
*/
-function isActionCreator(reportAction: OnyxEntry): boolean {
+function isActionCreator(reportAction: OnyxEntry | Partial): boolean {
return reportAction?.actorAccountID === currentUserAccountID;
}
+/**
+ * Returns the notification preference of the action's child report if it exists.
+ * Otherwise, calculates it based on the action's authorship.
+ */
+function getChildReportNotificationPreference(reportAction: OnyxEntry | Partial): NotificationPreference {
+ const childReportNotificationPreference = reportAction?.childReportNotificationPreference ?? '';
+ if (childReportNotificationPreference) {
+ return childReportNotificationPreference;
+ }
+
+ return isActionCreator(reportAction) ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+}
+
/**
* Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report, or if the user is a
* policy admin
@@ -1592,9 +1609,13 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry): string {
+function getReimbursementDeQueuedActionMessage(reportAction: OnyxEntry, report: OnyxEntry): string {
+ const amount = CurrencyUtils.convertToDisplayString(Math.abs(report?.total ?? 0), report?.currency);
+ const originalMessage = reportAction?.originalMessage as ReimbursementDeQueuedMessage | undefined;
+ if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN) {
+ return Localize.translateLocal('iou.adminCanceledRequest', {amount});
+ }
const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, true) ?? '';
- const amount = CurrencyUtils.convertToDisplayString(report?.total ?? 0, report?.currency);
return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount});
}
@@ -2520,7 +2541,7 @@ function buildOptimisticTaskCommentReportAction(taskReportID: string, taskTitle:
reportAction.reportAction.childType = CONST.REPORT.TYPE.TASK;
reportAction.reportAction.childReportName = taskTitle;
reportAction.reportAction.childManagerAccountID = taskAssigneeAccountID;
- reportAction.reportAction.childStatusNum = CONST.REPORT.STATUS.OPEN;
+ reportAction.reportAction.childStatusNum = CONST.REPORT.STATUS_NUM.OPEN;
reportAction.reportAction.childStateNum = CONST.REPORT.STATE_NUM.OPEN;
return reportAction;
@@ -2555,9 +2576,8 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number
participantAccountIDs: participantsAccountIDs,
visibleChatMemberAccountIDs: participantsAccountIDs,
reportID: generateReportID(),
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: isSendingMoney ? CONST.REPORT.STATUS.REIMBURSED : CONST.REPORT.STATE_NUM.PROCESSING,
+ stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED,
total,
// We don't translate reportName because the server response is always in English
@@ -2588,9 +2608,8 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa
const isFree = policy?.type === CONST.POLICY.TYPE.FREE;
// Define the state and status of the report based on whether the policy is free or paid
- const state = isFree ? CONST.REPORT.STATE.SUBMITTED : CONST.REPORT.STATE.OPEN;
- const stateNum = isFree ? CONST.REPORT.STATE_NUM.PROCESSING : CONST.REPORT.STATE_NUM.OPEN;
- const statusNum = isFree ? CONST.REPORT.STATUS.SUBMITTED : CONST.REPORT.STATUS.OPEN;
+ const stateNum = isFree ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN;
+ const statusNum = isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN;
return {
reportID: generateReportID(),
@@ -2602,7 +2621,6 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa
// We don't translate reportName because the server response is always in English
reportName: `${policyName} owes ${formattedTotal}`,
- state,
stateNum,
statusNum,
total: storedTotal,
@@ -2877,6 +2895,40 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string,
};
}
+/**
+ * Builds an optimistic REIMBURSEMENTDEQUEUED report action with a randomly generated reportActionID.
+ *
+ */
+function buildOptimisticCancelPaymentReportAction(expenseReportID: string): OptimisticCancelPaymentReportAction {
+ return {
+ actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED,
+ actorAccountID: currentUserAccountID,
+ message: [
+ {
+ cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
+ expenseReportID,
+ type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
+ text: '',
+ },
+ ],
+ originalMessage: {
+ cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
+ expenseReportID,
+ },
+ person: [
+ {
+ style: 'strong',
+ text: currentUserPersonalDetails?.displayName ?? currentUserEmail,
+ type: 'TEXT',
+ },
+ ],
+ reportActionID: NumberUtils.rand64(),
+ shouldShow: true,
+ created: DateUtils.getDBTime(),
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ };
+}
+
/**
* Builds an optimistic report preview action with a randomly generated reportActionID.
*
@@ -3308,7 +3360,7 @@ function buildOptimisticTaskReport(
parentReportID,
policyID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
lastVisibleActionCreated: DateUtils.getDBTime(),
};
@@ -3545,7 +3597,7 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID:
if (!report?.participantAccountIDs) {
return false;
}
- const sortedParticipanctsAccountIDs = report.parentReportActionIDs?.sort();
+ const sortedParticipanctsAccountIDs = report.participantAccountIDs?.sort();
// Only return the room if it has all the participants and is not a policy room
return report.policyID === policyID && lodashIsEqual(newParticipantList, sortedParticipanctsAccountIDs);
}) ?? null
@@ -4199,17 +4251,22 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry)
const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency) ?? '';
const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport) : getDisplayNameForParticipant(iouReport?.managerID, true);
- switch (originalMessage.paymentType) {
- case CONST.IOU.PAYMENT_TYPE.ELSEWHERE:
- translationKey = 'iou.paidElsewhereWithAmount';
- break;
- case CONST.IOU.PAYMENT_TYPE.EXPENSIFY:
- case CONST.IOU.PAYMENT_TYPE.VBBA:
- translationKey = 'iou.paidWithExpensifyWithAmount';
- break;
- default:
- translationKey = 'iou.payerPaidAmount';
- break;
+ // If the payment was cancelled, show the "Owes" message
+ if (!isSettled(IOUReportID)) {
+ translationKey = 'iou.payerOwesAmount';
+ } else {
+ switch (originalMessage.paymentType) {
+ case CONST.IOU.PAYMENT_TYPE.ELSEWHERE:
+ translationKey = 'iou.paidElsewhereWithAmount';
+ break;
+ case CONST.IOU.PAYMENT_TYPE.EXPENSIFY:
+ case CONST.IOU.PAYMENT_TYPE.VBBA:
+ translationKey = 'iou.paidWithExpensifyWithAmount';
+ break;
+ default:
+ translationKey = 'iou.payerPaidAmount';
+ break;
+ }
}
return Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName ?? ''});
}
@@ -4325,6 +4382,25 @@ function navigateToPrivateNotes(report: Report, session: Session) {
Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
}
+/**
+ * Given a report field and a report, get the title of the field.
+ * This is specially useful when we have a report field of type formula.
+ */
+function getReportFieldTitle(report: OnyxEntry, reportField: PolicyReportField): string {
+ const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue;
+
+ if (reportField.type !== 'formula') {
+ return value;
+ }
+
+ return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => {
+ if (report && property in report) {
+ return report[property as keyof Report]?.toString() ?? match;
+ }
+ return match;
+ });
+}
+
/**
* Checks if thread replies should be displayed
*/
@@ -4436,6 +4512,7 @@ export {
buildOptimisticIOUReportAction,
buildOptimisticReportPreview,
buildOptimisticModifiedExpenseReportAction,
+ buildOptimisticCancelPaymentReportAction,
updateReportPreview,
buildOptimisticTaskReportAction,
buildOptimisticAddCommentReportAction,
@@ -4532,8 +4609,10 @@ export {
canEditWriteCapability,
hasSmartscanError,
shouldAutoFocusOnKeyPress,
+ getReportFieldTitle,
shouldDisplayThreadReplies,
shouldDisableThread,
+ getChildReportNotificationPreference,
};
export type {ExpenseOriginalMessage, OptionData, OptimisticChatReport, OptimisticCreatedReportAction};
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index 9aa7c52b1ea0..49b6a3b445e1 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -945,6 +945,7 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`];
const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport);
+ const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport);
const transactionDetails = ReportUtils.getTransactionDetails(updatedTransaction);
@@ -1030,6 +1031,30 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
},
});
+ if (isScanning && (_.has(transactionChanges, 'amount') || _.has(transactionChanges, 'currency'))) {
+ optimisticData.push(
+ ...[
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+ value: {
+ [transactionThread.parentReportActionID]: {
+ whisperedToAccountIDs: [],
+ },
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.parentReportID}`,
+ value: {
+ [iouReport.parentReportActionID]: {
+ whisperedToAccountIDs: [],
+ },
+ },
+ },
+ ],
+ );
+ }
// Update recently used categories if the category is changed
if (_.has(transactionChanges, 'category')) {
const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, transactionChanges.category);
@@ -1158,6 +1183,21 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) {
API.write('UpdateMoneyRequestTag', params, onyxData);
}
+/**
+ * Updates the description of a money request
+ *
+ * @param {String} transactionID
+ * @param {Number} transactionThreadReportID
+ * @param {String} comment
+ */
+function updateMoneyRequestDescription(transactionID, transactionThreadReportID, comment) {
+ const transactionChanges = {
+ comment,
+ };
+ const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
+ API.write('UpdateMoneyRequestDescription', params, onyxData);
+}
+
/**
* Edits an existing distance request
*
@@ -3016,7 +3056,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
lastMessageText: optimisticIOUReportAction.message[0].text,
lastMessageHtml: optimisticIOUReportAction.message[0].html,
hasOutstandingChildRequest: false,
- statusNum: CONST.REPORT.STATUS.REIMBURSED,
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
},
},
{
@@ -3164,8 +3204,8 @@ function approveMoneyRequest(expenseReport) {
...expenseReport,
lastMessageText: optimisticApprovedReportAction.message[0].text,
lastMessageHtml: optimisticApprovedReportAction.message[0].html,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.APPROVED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
};
const optimisticData = [optimisticIOUReportData, optimisticReportActionsData];
@@ -3238,9 +3278,8 @@ function submitReport(expenseReport) {
...expenseReport,
lastMessageText: lodashGet(optimisticSubmittedReportAction, 'message.0.text', ''),
lastMessageHtml: lodashGet(optimisticSubmittedReportAction, 'message.0.html', ''),
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
},
},
...(parentReport.reportID
@@ -3286,7 +3325,7 @@ function submitReport(expenseReport) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`,
value: {
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
},
},
@@ -3328,6 +3367,108 @@ function submitReport(expenseReport) {
);
}
+/**
+ * @param {Object} expenseReport
+ * @param {Object} chatReport
+ */
+function cancelPayment(expenseReport, chatReport) {
+ const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(expenseReport.reportID);
+ const policy = ReportUtils.getPolicy(chatReport.policyID);
+ const isFree = policy && policy.type === CONST.POLICY.TYPE.FREE;
+ const optimisticData = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
+ value: {
+ [optimisticReportAction.reportActionID]: {
+ ...optimisticReportAction,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`,
+ value: {
+ ...expenseReport,
+ lastMessageText: lodashGet(optimisticReportAction, 'message.0.text', ''),
+ lastMessageHtml: lodashGet(optimisticReportAction, 'message.0.html', ''),
+ stateNum: isFree ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN,
+ statusNum: isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN,
+ },
+ },
+ ...(chatReport.reportID
+ ? [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ value: {
+ ...chatReport,
+ hasOutstandingIOU: true,
+ hasOutstandingChildRequest: true,
+ iouReportID: expenseReport.reportID,
+ },
+ },
+ ]
+ : []),
+ ];
+
+ const successData = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
+ value: {
+ [optimisticReportAction.reportActionID]: {
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+
+ const failureData = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
+ value: {
+ [expenseReport.reportActionID]: {
+ errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'),
+ },
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`,
+ value: {
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
+ },
+ },
+ ...(chatReport.reportID
+ ? [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
+ value: {
+ hasOutstandingIOU: false,
+ hasOutstandingChildRequest: false,
+ iouReportID: 0,
+ },
+ },
+ ]
+ : []),
+ ];
+
+ API.write(
+ 'CancelPayment',
+ {
+ iouReportID: expenseReport.reportID,
+ chatReportID: chatReport.reportID,
+ managerAccountID: expenseReport.managerID,
+ reportActionID: optimisticReportAction.reportActionID,
+ },
+ {optimisticData, successData, failureData},
+ );
+}
+
/**
* @param {String} paymentType
* @param {Object} chatReport
@@ -3653,8 +3794,10 @@ export {
updateMoneyRequestMerchant,
updateMoneyRequestTag,
updateMoneyRequestAmountAndCurrency,
+ updateMoneyRequestDescription,
replaceReceipt,
detachReceipt,
getIOUReportID,
editMoneyRequest,
+ cancelPayment,
};
diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts
index 2fb863467e32..186c9beed970 100644
--- a/src/libs/actions/Link.ts
+++ b/src/libs/actions/Link.ts
@@ -65,7 +65,7 @@ function openOldDotLink(url: string) {
function getInternalNewExpensifyPath(href: string) {
const attrPath = Url.getPathFromURL(href);
return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) &&
- !CONST.PATHS_TO_TREAT_AS_EXTERNAL.find((path) => path === attrPath)
+ !CONST.PATHS_TO_TREAT_AS_EXTERNAL.find((path) => attrPath.startsWith(path))
? attrPath
: '';
}
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index a21b795fa89a..25c8cf5ade80 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -173,8 +173,8 @@ function deleteWorkspace(policyID, reports, policyName) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
hasDraft: false,
oldPolicyName: allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`].name,
},
@@ -368,8 +368,8 @@ function removeMembers(accountIDs, policyID) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`,
value: {
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
oldPolicyName: policy.name,
hasDraft: false,
},
@@ -475,7 +475,7 @@ function createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, hasOutsta
key: `${ONYXKEYS.COLLECTION.REPORT}${oldChat.reportID}`,
value: {
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
},
});
return;
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 2c4cb3dcaaa7..6449ea416c31 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -741,7 +741,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P
'',
undefined,
undefined,
- CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+ ReportUtils.getChildReportNotificationPreference(parentReportAction),
parentReportAction.reportActionID,
parentReportID,
);
@@ -2095,8 +2095,8 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
}
: {
reportID: null,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
},
},
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index aadf2f5fe0f8..9e848bddaaf2 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -246,8 +246,8 @@ function completeTask(taskReport) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
value: {
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.APPROVED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
},
@@ -275,7 +275,7 @@ function completeTask(taskReport) {
key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
value: {
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
},
},
{
@@ -314,7 +314,7 @@ function reopenTask(taskReport) {
key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
value: {
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
lastVisibleActionCreated: reopenedTaskReportAction.created,
lastMessageText: message,
lastActorAccountID: reopenedTaskReportAction.actorAccountID,
@@ -344,8 +344,8 @@ function reopenTask(taskReport) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
value: {
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.APPROVED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
},
{
diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts
index 5046449c7389..3e3cba49480d 100644
--- a/src/libs/actions/Welcome.ts
+++ b/src/libs/actions/Welcome.ts
@@ -132,7 +132,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false}
const workspaceChatReport = Object.values(allReports ?? {}).find((report) => {
if (report) {
- return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED;
+ return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED;
}
return false;
});
diff --git a/src/pages/ConciergePage.js b/src/pages/ConciergePage.tsx
similarity index 64%
rename from src/pages/ConciergePage.js
rename to src/pages/ConciergePage.tsx
index 841ce524b2cb..514dd0921dfc 100644
--- a/src/pages/ConciergePage.js
+++ b/src/pages/ConciergePage.tsx
@@ -1,36 +1,32 @@
import {useFocusEffect} from '@react-navigation/native';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Navigation from '@libs/Navigation/Navigation';
+import type {AuthScreensParamList} from '@libs/Navigation/types';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Session} from '@src/types/onyx';
-const propTypes = {
+type ConciergePageOnyxProps = {
/** Session info for the currently logged in user. */
- session: PropTypes.shape({
- /** Currently logged in user authToken */
- authToken: PropTypes.string,
- }),
+ session: OnyxEntry;
};
-const defaultProps = {
- session: {
- authToken: null,
- },
-};
+type ConciergePageProps = ConciergePageOnyxProps & StackScreenProps;
/*
* This is a "utility page", that does this:
* - If the user is authenticated, find their concierge chat and re-route to it
* - Else re-route to the login page
*/
-function ConciergePage(props) {
+function ConciergePage({session}: ConciergePageProps) {
useFocusEffect(() => {
- if (_.has(props.session, 'authToken')) {
+ if (session && 'authToken' in session) {
// Pop the concierge loading page before opening the concierge report.
Navigation.isNavigationReady().then(() => {
Navigation.goBack(ROUTES.HOME);
@@ -44,11 +40,9 @@ function ConciergePage(props) {
return ;
}
-ConciergePage.propTypes = propTypes;
-ConciergePage.defaultProps = defaultProps;
ConciergePage.displayName = 'ConciergePage';
-export default withOnyx({
+export default withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index fe43d96001a0..606d3da1ddb9 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -176,18 +176,22 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep
[transactionTag, transaction.transactionID, report.reportID],
);
+ const saveComment = useCallback(
+ ({comment: newComment}) => {
+ // Only update comment if it has changed
+ if (newComment.trim() !== transactionDescription) {
+ IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim());
+ }
+ Navigation.dismissModal();
+ },
+ [transactionDescription, transaction.transactionID, report.reportID],
+ );
+
if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) {
return (
{
- // In case the comment hasn't been changed, do not make the API request.
- if (transactionChanges.comment.trim() === transactionDescription) {
- Navigation.dismissModal();
- return;
- }
- editMoneyRequest({comment: transactionChanges.comment.trim()});
- }}
+ onSubmit={saveComment}
/>
);
}
diff --git a/src/pages/ErrorPage/ErrorBodyText/index.js b/src/pages/ErrorPage/ErrorBodyText/index.tsx
similarity index 54%
rename from src/pages/ErrorPage/ErrorBodyText/index.js
rename to src/pages/ErrorPage/ErrorBodyText/index.tsx
index 47b765f8f5e8..e675e0447361 100644
--- a/src/pages/ErrorPage/ErrorBodyText/index.js
+++ b/src/pages/ErrorPage/ErrorBodyText/index.tsx
@@ -1,29 +1,27 @@
import React from 'react';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
-const propTypes = {
- ...withLocalizePropTypes,
-};
-
-function ErrorBodyText(props) {
+function ErrorBodyText() {
const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
return (
- {`${props.translate('genericErrorPage.body.helpTextMobile')} `}
+ {`${translate('genericErrorPage.body.helpTextMobile')} `}
- {props.translate('genericErrorPage.body.helpTextWeb')}
+ {translate('genericErrorPage.body.helpTextWeb')}
);
}
ErrorBodyText.displayName = 'ErrorBodyText';
-ErrorBodyText.propTypes = propTypes;
-export default withLocalize(ErrorBodyText);
+
+export default ErrorBodyText;
diff --git a/src/pages/ErrorPage/ErrorBodyText/index.website.js b/src/pages/ErrorPage/ErrorBodyText/index.website.tsx
similarity index 100%
rename from src/pages/ErrorPage/ErrorBodyText/index.website.js
rename to src/pages/ErrorPage/ErrorBodyText/index.website.tsx
diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.tsx
similarity index 91%
rename from src/pages/ErrorPage/GenericErrorPage.js
rename to src/pages/ErrorPage/GenericErrorPage.tsx
index 56fb5b970084..f4f1d91418c7 100644
--- a/src/pages/ErrorPage/GenericErrorPage.js
+++ b/src/pages/ErrorPage/GenericErrorPage.tsx
@@ -9,7 +9,7 @@ import ImageSVG from '@components/ImageSVG';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -18,20 +18,18 @@ import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import ErrorBodyText from './ErrorBodyText';
-const propTypes = {
- ...withLocalizePropTypes,
-};
-
-function GenericErrorPage({translate}) {
+function GenericErrorPage() {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
+ const {translate} = useLocalize();
+
const {resetBoundary} = useErrorBoundary();
return (
{({paddingBottom}) => (
-
+
@@ -78,7 +76,7 @@ function GenericErrorPage({translate}) {
-
+
void;
};
// eslint-disable-next-line rulesdir/no-negated-variables
-function NotFoundPage(props) {
+function NotFoundPage({onBackButtonPress}: NotFoundPageProps) {
return (
);
}
NotFoundPage.displayName = 'NotFoundPage';
-NotFoundPage.propTypes = propTypes;
-NotFoundPage.defaultProps = defaultProps;
export default NotFoundPage;
diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.js b/src/pages/LogInWithShortLivedAuthTokenPage.tsx
similarity index 65%
rename from src/pages/LogInWithShortLivedAuthTokenPage.js
rename to src/pages/LogInWithShortLivedAuthTokenPage.tsx
index 1fe9b67eef16..c5f8a9c20d5b 100644
--- a/src/pages/LogInWithShortLivedAuthTokenPage.js
+++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx
@@ -1,7 +1,7 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect} from 'react';
import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Icon from '@components/Icon';
@@ -13,62 +13,40 @@ import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import type {PublicScreensParamList} from '@libs/Navigation/types';
import * as Session from '@userActions/Session';
import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import type {Account} from '@src/types/onyx';
-const propTypes = {
- /** The parameters needed to authenticate with a short-lived token are in the URL */
- route: PropTypes.shape({
- /** Each parameter passed via the URL */
- params: PropTypes.shape({
- /** Short-lived authToken to sign in a user */
- shortLivedAuthToken: PropTypes.string,
-
- /** Short-lived authToken to sign in as a user, if they are coming from the old mobile app */
- shortLivedToken: PropTypes.string,
-
- /** The email of the transitioning user */
- email: PropTypes.string,
- }),
- }).isRequired,
-
+type LogInWithShortLivedAuthTokenPageOnyxProps = {
/** The details about the account that the user is signing in with */
- account: PropTypes.shape({
- /** Whether a sign is loading */
- isLoading: PropTypes.bool,
- }),
+ account: OnyxEntry;
};
-const defaultProps = {
- account: {
- isLoading: false,
- },
-};
+type LogInWithShortLivedAuthTokenPageProps = LogInWithShortLivedAuthTokenPageOnyxProps & StackScreenProps;
-function LogInWithShortLivedAuthTokenPage(props) {
+function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedAuthTokenPageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const {email = '', shortLivedAuthToken = '', shortLivedToken = '', exitTo, error} = route?.params ?? {};
useEffect(() => {
- const email = lodashGet(props, 'route.params.email', '');
-
// We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated.
- const shortLivedAuthToken = lodashGet(props, 'route.params.shortLivedAuthToken', '') || lodashGet(props, 'route.params.shortLivedToken', '');
+ const token = shortLivedAuthToken || shortLivedToken;
// Try to authenticate using the shortLivedToken if we're not already trying to load the accounts
- if (shortLivedAuthToken && !props.account.isLoading) {
- Session.signInWithShortLivedAuthToken(email, shortLivedAuthToken);
+ if (token && !account?.isLoading) {
+ Session.signInWithShortLivedAuthToken(email, token);
return;
}
// If an error is returned as part of the route, ensure we set it in the onyxData for the account
- const error = lodashGet(props, 'route.params.error', '');
if (error) {
Session.setAccountError(error);
}
- const exitTo = lodashGet(props, 'route.params.exitTo', '');
if (exitTo) {
Navigation.isNavigationReady().then(() => {
Navigation.navigate(exitTo);
@@ -76,9 +54,9 @@ function LogInWithShortLivedAuthTokenPage(props) {
}
// The only dependencies of the effect are based on props.route
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.route]);
+ }, [route]);
- if (props.account.isLoading) {
+ if (account?.isLoading) {
return ;
}
@@ -94,7 +72,7 @@ function LogInWithShortLivedAuthTokenPage(props) {
{translate('deeplinkWrapper.launching')}
-
+
{translate('deeplinkWrapper.expired')}{' '}
{
@@ -119,10 +97,8 @@ function LogInWithShortLivedAuthTokenPage(props) {
);
}
-LogInWithShortLivedAuthTokenPage.propTypes = propTypes;
-LogInWithShortLivedAuthTokenPage.defaultProps = defaultProps;
LogInWithShortLivedAuthTokenPage.displayName = 'LogInWithShortLivedAuthTokenPage';
-export default withOnyx({
+export default withOnyx({
account: {key: ONYXKEYS.ACCOUNT},
})(LogInWithShortLivedAuthTokenPage);
diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js
index 3a58727eddb7..b90ce6bbc247 100755
--- a/src/pages/NewChatPage.js
+++ b/src/pages/NewChatPage.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
-import {InteractionManager, View} from 'react-native';
+import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
@@ -15,6 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import compose from '@libs/compose';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
+import doInteractionTask from '@libs/DoInteractionTask';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
@@ -209,11 +210,16 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
}, [reports, personalDetails, searchTerm]);
useEffect(() => {
- const interactionTask = InteractionManager.runAfterInteractions(() => {
+ const interactionTask = doInteractionTask(() => {
setDidScreenTransitionEnd(true);
});
- return interactionTask.cancel;
+ return () => {
+ if (!interactionTask) {
+ return;
+ }
+ interactionTask.cancel();
+ };
}, []);
useEffect(() => {
diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js
index 209b8f5fadc3..c10500c428da 100644
--- a/src/pages/ReferralDetailsPage.js
+++ b/src/pages/ReferralDetailsPage.js
@@ -64,15 +64,15 @@ function ReferralDetailsPage({route, account}) {
headerContent={
}
headerContainerStyles={[styles.staticHeaderImage, styles.justifyContentEnd]}
backgroundColor={theme.PAGE_THEMES[SCREENS.RIGHT_MODAL.REFERRAL].backgroundColor}
>
- {contentHeader}
- {contentBody}
+ {contentHeader}
+ {contentBody}
{shouldShowClipboard && (
- {
- Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND));
- }}
- style={[
- themeStyles.p5,
- themeStyles.w100,
- themeStyles.br2,
- themeStyles.highlightBG,
- themeStyles.flexRow,
- themeStyles.justifyContentBetween,
- themeStyles.alignItemsCenter,
- {gap: 10},
- ]}
- accessibilityLabel="referral"
- role={CONST.ACCESSIBILITY_ROLE.BUTTON}
- >
-
- {translate(`referralProgram.${CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND}.buttonText1`)}
-
- {translate(`referralProgram.${CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND}.buttonText2`)}
-
-
-
-
+
);
}
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index edf6b65b2f4a..9b2765718250 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -109,9 +109,7 @@ function HeaderView(props) {
const isAutomatedExpensifyAccount = ReportUtils.hasSingleParticipant(props.report) && ReportUtils.hasAutomatedExpensifyAccountIDs(participants);
const parentReportAction = ReportActionsUtils.getParentReportAction(props.report);
const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(props.report, parentReportAction);
- const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(props.report.reportID);
const isWhisperAction = ReportActionsUtils.isWhisperAction(parentReportAction);
- const isEmptyChat = !props.report.lastMessageText && !props.report.lastMessageTranslationKey && !lastVisibleMessage.lastMessageText && !lastVisibleMessage.lastMessageTranslationKey;
const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(props.report);
const isPolicyMember = useMemo(() => !_.isEmpty(props.policy), [props.policy]);
const canLeaveRoom = ReportUtils.canLeaveRoom(props.report, isPolicyMember);
@@ -133,7 +131,7 @@ function HeaderView(props) {
}
// Task is not closed
- if (props.report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum !== CONST.REPORT.STATUS.CLOSED && canModifyTask) {
+ if (props.report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && props.report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED && canModifyTask) {
threeDotMenuItems.push({
icon: Expensicons.Trashcan,
text: translate('common.delete'),
@@ -153,7 +151,7 @@ function HeaderView(props) {
),
);
- const canJoinOrLeave = (isChatThread && !isEmptyChat) || isUserCreatedPolicyRoom || canLeaveRoom;
+ const canJoinOrLeave = 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);
if (canJoin) {
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 64e48ecd5509..b35d9240f3f7 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -187,7 +187,7 @@ function ReportScreen({
// 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.CLOSED;
+ const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS_NUM.CLOSED;
const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas);
const isLoading = !reportID || !isSidebarLoaded || _.isEmpty(personalDetails);
const isSingleTransactionView = ReportUtils.isMoneyRequest(report);
@@ -383,8 +383,8 @@ function ReportScreen({
(prevOnyxReportID &&
prevOnyxReportID === routeReportID &&
!onyxReportID &&
- prevReport.statusNum === CONST.REPORT.STATUS.OPEN &&
- (report.statusNum === CONST.REPORT.STATUS.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) ||
+ 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))
) {
Navigation.dismissModal();
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index f22eda58ce7f..aa815b0b32dc 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -156,14 +156,10 @@ export default [
successTextTranslateKey: '',
successIcon: null,
shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => {
- let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
- if (!childReportNotificationPreference) {
- const isActionCreator = ReportUtils.isActionCreator(reportAction);
- childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- }
+ const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction);
const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(reportAction, reportID);
- const subscribed = childReportNotificationPreference !== 'hidden';
+ const subscribed = childReportNotificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID);
const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW;
const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction);
@@ -171,11 +167,7 @@ export default [
return !subscribed && !isWhisperAction && (isCommentAction || isReportPreviewAction || isIOUAction) && (!isDeletedAction || shouldDisplayThreadReplies);
},
onPress: (closePopover, {reportAction, reportID}) => {
- let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
- if (!childReportNotificationPreference) {
- const isActionCreator = ReportUtils.isActionCreator(reportAction);
- childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- }
+ const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
if (closePopover) {
hideContextMenu(false, () => {
ReportActionComposeFocusManager.focus();
@@ -196,14 +188,10 @@ export default [
successTextTranslateKey: '',
successIcon: null,
shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => {
- let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
- if (!childReportNotificationPreference) {
- const isActionCreator = ReportUtils.isActionCreator(reportAction);
- childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- }
+ const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction);
const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(reportAction, reportID);
- const subscribed = childReportNotificationPreference !== 'hidden';
+ const subscribed = childReportNotificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
if (type !== CONST.CONTEXT_MENU_TYPES.REPORT_ACTION) {
return false;
}
@@ -213,11 +201,7 @@ export default [
return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction) && (!isDeletedAction || shouldDisplayThreadReplies);
},
onPress: (closePopover, {reportAction, reportID}) => {
- let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
- if (!childReportNotificationPreference) {
- const isActionCreator = ReportUtils.isActionCreator(reportAction);
- childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- }
+ const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction);
if (closePopover) {
hideContextMenu(false, () => {
ReportActionComposeFocusManager.focus();
@@ -285,6 +269,11 @@ export default [
} else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) {
const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportAction);
Clipboard.setString(modifyExpenseMessage);
+ } else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) {
+ const {expenseReportID} = reportAction.originalMessage;
+ const expenseReport = ReportUtils.getReport(expenseReportID);
+ const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReport);
+ Clipboard.setString(displayMessage);
} else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) {
const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction);
Clipboard.setString(displayMessage);
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
index 6c1d71625dc9..413807b1f992 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
@@ -127,8 +127,8 @@ function ComposerWithSuggestions({
const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES;
const isEmptyChat = useMemo(() => _.size(reportActions) === 1, [reportActions]);
- const parentAction = ReportActionsUtils.getParentReportAction(report);
- const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentAction))) && shouldShowComposeInput;
+ const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]);
+ const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && shouldShowComposeInput;
const valueRef = useRef(value);
valueRef.current = value;
@@ -344,9 +344,6 @@ function ComposerWithSuggestions({
const valueLength = valueRef.current.length;
if (e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey && textInputRef.current.selectionStart === 0 && valueLength === 0 && !ReportUtils.chatIncludesChronos(report)) {
e.preventDefault();
-
- const parentReportActionID = lodashGet(report, 'parentReportActionID', '');
- const parentReportAction = lodashGet(parentReportActions, [parentReportActionID], {});
const lastReportAction = _.find(
[...reportActions, parentReportAction],
(action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action),
@@ -356,7 +353,7 @@ function ComposerWithSuggestions({
}
}
},
- [isKeyboardShown, isSmallScreenWidth, parentReportActions, report, reportActions, reportID, handleSendMessage, suggestionsRef, valueRef],
+ [isKeyboardShown, isSmallScreenWidth, parentReportAction, report, reportActions, reportID, handleSendMessage, suggestionsRef, valueRef],
);
const onChangeText = useCallback(
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index e490c4601d10..9573d4a4ff1a 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -38,7 +38,6 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import ControlSelection from '@libs/ControlSelection';
-import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import focusTextInputAfterAnimation from '@libs/focusTextInputAfterAnimation';
import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage';
@@ -440,10 +439,7 @@ function ReportActionItem(props) {
);
} else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) {
- const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(lodashGet(personalDetails, props.report.ownerAccountID));
- const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency);
-
- children = ;
+ children = ;
} else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) {
children = ;
} else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) {
@@ -649,6 +645,7 @@ function ReportActionItem(props) {
@@ -797,6 +794,10 @@ export default compose(
},
initialValue: {},
},
+ policyReportFields: {
+ key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined),
+ initialValue: [],
+ },
emojiReactions: {
key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`,
initialValue: {},
@@ -838,6 +839,8 @@ export default compose(
prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine &&
lodashGet(prevProps.report, 'total', 0) === lodashGet(nextProps.report, 'total', 0) &&
lodashGet(prevProps.report, 'nonReimbursableTotal', 0) === lodashGet(nextProps.report, 'nonReimbursableTotal', 0) &&
- prevProps.linkedReportActionID === nextProps.linkedReportActionID,
+ prevProps.linkedReportActionID === nextProps.linkedReportActionID &&
+ _.isEqual(prevProps.policyReportFields, nextProps.policyReportFields) &&
+ _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields),
),
);
diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx
index 3a71ee8356b3..025b0cbb8b0a 100644
--- a/src/pages/home/report/ReportActionItemMessage.tsx
+++ b/src/pages/home/report/ReportActionItemMessage.tsx
@@ -57,7 +57,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid
const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null;
const iouReportID = originalMessage?.IOUReportID;
if (iouReportID) {
- iouMessage = ReportUtils.getReportPreviewMessage(ReportUtils.getReport(iouReportID), action);
+ iouMessage = ReportUtils.getIOUReportActionDisplayMessage(action);
}
}
diff --git a/src/pages/home/report/ReportAttachments.js b/src/pages/home/report/ReportAttachments.tsx
similarity index 55%
rename from src/pages/home/report/ReportAttachments.js
rename to src/pages/home/report/ReportAttachments.tsx
index 8ecbb036a756..42ca51cabe0b 100644
--- a/src/pages/home/report/ReportAttachments.js
+++ b/src/pages/home/report/ReportAttachments.tsx
@@ -1,43 +1,47 @@
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback} from 'react';
-import _ from 'underscore';
import AttachmentModal from '@components/AttachmentModal';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import Navigation from '@libs/Navigation/Navigation';
+import type {AuthScreensParamList} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
-const propTypes = {
- /** Navigation route context info provided by react navigation */
- route: PropTypes.shape({
- /** Route specific parameters used on this screen */
- params: PropTypes.shape({
- /** The report ID which the attachment is associated with */
- reportID: PropTypes.string.isRequired,
- /** The uri encoded source of the attachment */
- source: PropTypes.string.isRequired,
- }).isRequired,
- }).isRequired,
+type File = {
+ name: string;
};
-function ReportAttachments(props) {
- const reportID = _.get(props, ['route', 'params', 'reportID']);
+type Attachment = {
+ file: File;
+ hasBeenFlagged: boolean;
+ isAuthTokenRequired: boolean;
+ isReceipt: boolean;
+ reportActionID: string;
+ source: string;
+};
+
+type ReportAttachmentsProps = StackScreenProps;
+
+function ReportAttachments({route}: ReportAttachmentsProps) {
+ const reportID = route.params.reportID;
const report = ReportUtils.getReport(reportID);
// In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource
- const decodedSource = decodeURI(_.get(props, ['route', 'params', 'source']));
+ const decodedSource = decodeURI(route.params.source);
const source = Number(decodedSource) || decodedSource;
const onCarouselAttachmentChange = useCallback(
- (attachment) => {
- const route = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source);
- Navigation.navigate(route);
+ (attachment: Attachment) => {
+ const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source);
+ Navigation.navigate(routeToNavigate);
},
[reportID],
);
return (
(
-
+
{shouldShowSplitBillErrorMessage && (
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 9edede770233..9567b17ecdf5 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -8,6 +8,7 @@ import Button from '@components/Button';
import FormHelpMessage from '@components/FormHelpMessage';
import {usePersonalDetails} from '@components/OnyxProvider';
import {PressableWithFeedback} from '@components/Pressable';
+import ReferralProgramCTA from '@components/ReferralProgramCTA';
import SelectCircle from '@components/SelectCircle';
import SelectionList from '@components/SelectionList';
import useLocalize from '@hooks/useLocalize';
@@ -16,7 +17,6 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@libs/actions/Report';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
-import MoneyRequestReferralProgramCTA from '@pages/iou/MoneyRequestReferralProgramCTA';
import reportPropTypes from '@pages/reportPropTypes';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -285,7 +285,7 @@ function MoneyRequestParticipantsSelector({
() => (
-
+
{shouldShowSplitBillErrorMessage && (
diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js
index 329bf66f7275..3a056ee7c0a3 100644
--- a/src/pages/reportPropTypes.js
+++ b/src/pages/reportPropTypes.js
@@ -63,11 +63,14 @@ export default PropTypes.shape({
stateNum: PropTypes.oneOf(_.values(CONST.REPORT.STATE_NUM)),
/** The status of the current report */
- statusNum: PropTypes.oneOf(_.values(CONST.REPORT.STATUS)),
+ statusNum: PropTypes.oneOf(_.values(CONST.REPORT.STATUS_NUM)),
/** Which user role is capable of posting messages on the report */
writeCapability: PropTypes.oneOf(_.values(CONST.REPORT.WRITE_CAPABILITIES)),
/** Field-specific pending states for offline UI status */
pendingFields: PropTypes.objectOf(PropTypes.string),
+
+ /** Custom fields attached to the report */
+ reportFields: PropTypes.objectOf(PropTypes.string),
});
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index d2b91ed6b76b..6e310b9a62bd 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -267,11 +267,11 @@ function InitialSettingsPage(props) {
translationKey: 'initialSettingsPage.goToExpensifyClassic',
icon: Expensicons.NewExpensify,
action: () => {
- Link.openExternalLink(CONST.EXPENSIFY_INBOX_URL);
+ Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX);
},
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
- link: CONST.EXPENSIFY_INBOX_URL,
+ link: Link.buildOldDotURL(CONST.OLDDOT_URLS.INBOX),
},
{
translationKey: 'initialSettingsPage.signOut',
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js
index 3c44f806fdb8..856c0613cec7 100644
--- a/src/pages/settings/Wallet/ExpensifyCardPage.js
+++ b/src/pages/settings/Wallet/ExpensifyCardPage.js
@@ -208,7 +208,7 @@ function ExpensifyCardPage({
medium
style={[styles.mh5, styles.mb5]}
text={translate('cardPage.reviewTransaction')}
- onPress={() => Link.openOldDotLink('inbox')}
+ onPress={() => Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX)}
/>
>
) : null}
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js
index bf547bc4bd10..8382014a01e5 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.js
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js
@@ -556,6 +556,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod
}}
onItemSelected={(method) => addPaymentMethodTypePressed(method)}
anchorRef={addPaymentMethodAnchorRef}
+ shouldShowPersonalBankAccountOption
/>
>
);
diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js
index a4fc61910be2..1a526a9cdd9b 100644
--- a/src/pages/tasks/TaskAssigneeSelectorModal.js
+++ b/src/pages/tasks/TaskAssigneeSelectorModal.js
@@ -184,29 +184,29 @@ function TaskAssigneeSelectorModal(props) {
return sectionsList;
}, [filteredCurrentUserOption, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, props]);
- const selectReport = (option) => {
- if (!option) {
- return;
- }
-
- // Check to see if we're creating a new task
- // If there's no route params, we're creating a new task
- if (!props.route.params && option.accountID) {
- Task.setAssigneeValue(option.login, option.accountID, props.task.shareDestination, OptionsListUtils.isCurrentUser(option));
- return Navigation.goBack(ROUTES.NEW_TASK);
- }
-
- // Check to see if we're editing a task and if so, update the assignee
- if (report) {
- if (option.accountID !== report.managerID) {
- const assigneeChatReport = Task.setAssigneeValue(option.login, option.accountID, props.route.params.reportID, OptionsListUtils.isCurrentUser(option));
+ const selectReport = useCallback(
+ (option) => {
+ if (!option) {
+ return;
+ }
- // Pass through the selected assignee
- Task.editTaskAssignee(report, props.session.accountID, option.login, option.accountID, assigneeChatReport);
+ // Check to see if we're editing a task and if so, update the assignee
+ if (report) {
+ if (option.accountID !== report.managerID) {
+ const assigneeChatReport = Task.setAssigneeValue(option.login, option.accountID, report.reportID, OptionsListUtils.isCurrentUser(option));
+
+ // Pass through the selected assignee
+ Task.editTaskAssignee(report, props.session.accountID, option.login, option.accountID, assigneeChatReport);
+ }
+ Navigation.dismissModal(report.reportID);
+ // If there's no report, we're creating a new task
+ } else if (option.accountID) {
+ Task.setAssigneeValue(option.login, option.accountID, props.task.shareDestination, OptionsListUtils.isCurrentUser(option));
+ Navigation.goBack(ROUTES.NEW_TASK);
}
- return Navigation.dismissModal(report.reportID);
- }
- };
+ },
+ [props.session.accountID, props.task.shareDestination, report],
+ );
const isOpen = ReportUtils.isOpenTaskReport(report);
const canModifyTask = Task.canModifyTask(report, props.currentUserPersonalDetails.accountID, lodashGet(props.rootParentReportPolicy, 'role', ''));
diff --git a/src/pages/wallet/WalletStatementPage.js b/src/pages/wallet/WalletStatementPage.js
index e79a2add5213..e97bb33519a0 100644
--- a/src/pages/wallet/WalletStatementPage.js
+++ b/src/pages/wallet/WalletStatementPage.js
@@ -11,6 +11,7 @@ import {withNetwork} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import WalletStatementModal from '@components/WalletStatementModal';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useThemePreference from '@hooks/useThemePreference';
import compose from '@libs/compose';
import DateUtils from '@libs/DateUtils';
import fileDownload from '@libs/fileDownload';
@@ -53,6 +54,7 @@ const defaultProps = {
};
function WalletStatementPage(props) {
+ const themePreference = useThemePreference();
const yearMonth = lodashGet(props.route.params, 'yearMonth', null);
useEffect(() => {
@@ -89,7 +91,7 @@ function WalletStatementPage(props) {
const month = yearMonth.substring(4) || getMonth(new Date());
const monthName = format(new Date(year, month - 1), CONST.DATE.MONTH_FORMAT);
const title = props.translate('statementPage.title', year, monthName);
- const url = `${CONFIG.EXPENSIFY.EXPENSIFY_URL}statement.php?period=${yearMonth}`;
+ const url = `${CONFIG.EXPENSIFY.EXPENSIFY_URL}statement.php?period=${yearMonth}${themePreference === CONST.THEME.DARK ? '&isDarkMode=true' : ''}`;
return (
{
setSearchTerm(SearchInputManager.searchInput);
- }, []);
+ return () => {
+ Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {});
+ };
+ }, [props.route.params.policyID]);
useEffect(() => {
Policy.clearErrors(props.route.params.policyID);
@@ -105,6 +110,12 @@ function WorkspaceInvitePage(props) {
_.each(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail)));
const newSelectedOptions = [];
+ _.each(_.keys(props.invitedEmailsToAccountIDsDraft), (login) => {
+ if (!_.has(detailsMap, login)) {
+ return;
+ }
+ newSelectedOptions.push({...detailsMap[login], isSelected: true});
+ });
_.each(selectedOptions, (option) => {
newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option);
});
@@ -323,5 +334,8 @@ export default compose(
isLoadingReportData: {
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
},
+ invitedEmailsToAccountIDsDraft: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`,
+ },
}),
)(WorkspaceInvitePage);
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index f10696ced00f..09be2d9e04dd 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -37,6 +37,7 @@ type IOUMessage = {
/** The ID of the iou transaction */
IOUTransactionID?: string;
IOUReportID?: string;
+ expenseReportID?: string;
amount: number;
comment?: string;
currency: string;
@@ -44,10 +45,15 @@ type IOUMessage = {
participantAccountIDs?: number[];
type: ValueOf;
paymentType?: DeepValueOf;
+ cancellationReason?: string;
/** Only exists when we are sending money */
IOUDetails?: IOUDetails;
};
+type ReimbursementDeQueuedMessage = {
+ cancellationReason: string;
+};
+
type OriginalMessageIOU = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.IOU;
originalMessage: IOUMessage;
@@ -274,6 +280,7 @@ export type {
Reaction,
ActionName,
IOUMessage,
+ ReimbursementDeQueuedMessage,
Closed,
OriginalMessageActionName,
ChangeLog,
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index f6af87038d00..7cc3c508d926 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -83,14 +83,11 @@ type Report = {
/** ID of the chat report */
chatReportID?: string;
- /** The state of the report */
- state?: ValueOf;
-
/** The state that the report is currently in */
stateNum?: ValueOf;
/** The status of the current report */
- statusNum?: ValueOf;
+ statusNum?: ValueOf;
/** Which user role is capable of posting messages on the report */
writeCapability?: WriteCapability;
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index 509e3a286ea4..d5c0e19f9373 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -54,6 +54,12 @@ type Message = {
/** ID of a task report */
taskReportID?: string;
+ /** Reason of payment cancellation */
+ cancellationReason?: string;
+
+ /** ID of an expense report */
+ expenseReportID?: string;
+
/** resolution for actionable mention whisper */
resolution?: ValueOf | null;
};
@@ -152,7 +158,7 @@ type ReportActionBase = {
childManagerAccountID?: number;
/** The status of the child report */
- childStatusNum?: ValueOf;
+ childStatusNum?: ValueOf;
/** Report action child status name */
childStateNum?: ValueOf;
diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js
index 3ba19199c30a..5b304346f38f 100644
--- a/tests/actions/IOUTest.js
+++ b/tests/actions/IOUTest.js
@@ -1237,9 +1237,8 @@ describe('actions/IOU', () => {
expect(chatReport.pendingFields).toBeFalsy();
expect(iouReport.pendingFields).toBeFalsy();
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS.SUBMITTED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED);
- // expect(iouReport.state).toBe(CONST.REPORT.STATE.SUBMITTED);
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.SUBMITTED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
resolve();
},
@@ -1306,9 +1305,8 @@ describe('actions/IOU', () => {
expect(chatReport.iouReportID).toBeFalsy();
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS.REIMBURSED);
- // expect(iouReport.state).toBe(CONST.REPORT.STATE.MANUALREIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED);
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
resolve();
},
@@ -1356,9 +1354,8 @@ describe('actions/IOU', () => {
expect(chatReport.iouReportID).toBeFalsy();
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS.REIMBURSED);
- // expect(iouReport.state).toBe(CONST.REPORT.STATE.MANUALREIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED);
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
resolve();
},
@@ -1770,9 +1767,8 @@ describe('actions/IOU', () => {
expect.objectContaining({
lastMessageHtml: `paid $${amount / 100}.00 with Expensify`,
lastMessageText: `paid $${amount / 100}.00 with Expensify`,
- state: CONST.REPORT.STATE.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.REIMBURSED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
}),
);
expect(updatedChatReport).toEqual(
diff --git a/tests/perf-test/OptionsSelector.perf-test.js b/tests/perf-test/OptionsSelector.perf-test.js
index 3959ed87460a..b30169b8b53f 100644
--- a/tests/perf-test/OptionsSelector.perf-test.js
+++ b/tests/perf-test/OptionsSelector.perf-test.js
@@ -82,7 +82,7 @@ test('[OptionsSelector] should render 1 section', () => {
measurePerformance(, {runs});
});
-test('[OptionsSelector] should render mutliple sections', () => {
+test('[OptionsSelector] should render multiple sections', () => {
const sections = generateSections(mutlipleSectionsConfig);
measurePerformance(, {runs});
});
diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts
index ea3b48bacf47..2a96a5959942 100644
--- a/tests/perf-test/ReportActionsUtils.perf-test.ts
+++ b/tests/perf-test/ReportActionsUtils.perf-test.ts
@@ -9,18 +9,6 @@ import createCollection from '../utils/collections/createCollection';
import createRandomReportAction from '../utils/collections/reportActions';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
-beforeAll(() =>
- Onyx.init({
- keys: ONYXKEYS,
- safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
- }),
-);
-
-// Clear out Onyx after each test so that each test starts with a clean slate
-afterEach(() => {
- Onyx.clear();
-});
-
const getMockedReportActionsMap = (reportsLength = 10, actionsPerReportLength = 100) => {
const mockReportActions = Array.from({length: actionsPerReportLength}, (v, i) => {
const reportActionKey = i + 1;
@@ -49,120 +37,113 @@ const reportId = '1';
const runs = CONST.PERFORMANCE_TESTS.RUNS;
-/**
- * This function will be executed 20 times and the average time will be used on the comparison.
- * It will fail based on the CI configuration around Reassure:
- * @see /.github/workflows/reassurePerformanceTests.yml
- *
- * Max deviation on the duration is set to 20% at the time of writing.
- *
- * More on the measureFunction API:
- * @see https://callstack.github.io/reassure/docs/api#measurefunction-function
- */
-test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => {
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+describe('ReportActionsUtils', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
+ });
+
+ Onyx.multiSet({
+ ...mockedReportActionsMap,
+ });
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs});
-});
+ afterAll(() => {
+ Onyx.clear();
+ });
-test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => {
- const parentReportActionId = '1';
- const fakeParentAction = reportActions[parentReportActionId];
- const actionsToMerge = {
- [parentReportActionId]: {
- pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
- previousMessage: fakeParentAction.message,
- message: [
- {
- translationKey: '',
- type: 'COMMENT',
- html: '',
- text: '',
- isEdited: true,
- isDeletedParentAction: true,
- },
- ],
- errors: null,
- linkMetaData: [],
- },
- } as unknown as ReportActions;
-
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ /**
+ * This function will be executed 20 times and the average time will be used on the comparison.
+ * It will fail based on the CI configuration around Reassure:
+ * @see /.github/workflows/reassurePerformanceTests.yml
+ *
+ * Max deviation on the duration is set to 20% at the time of writing.
+ *
+ * More on the measureFunction API:
+ * @see https://callstack.github.io/reassure/docs/api#measurefunction-function
+ */
+ test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => {
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs});
-});
-test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => {
- const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions);
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => {
+ const parentReportActionId = '1';
+ const fakeParentAction = reportActions[parentReportActionId];
+ const actionsToMerge = {
+ [parentReportActionId]: {
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ previousMessage: fakeParentAction.message,
+ message: [
+ {
+ translationKey: '',
+ type: 'COMMENT',
+ html: '',
+ text: '',
+ isEdited: true,
+ isDeletedParentAction: true,
+ },
+ ],
+ errors: null,
+ linkMetaData: [],
+ },
+ } as unknown as ReportActions;
+
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs});
-});
-test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => {
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => {
+ const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions);
+
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs});
-});
-test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => {
- const parentReportActionId = '1';
- const fakeParentAction = reportActions[parentReportActionId];
- const actionsToMerge = {
- [parentReportActionId]: {
- pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
- previousMessage: fakeParentAction.message,
- message: [
- {
- translationKey: '',
- type: 'COMMENT',
- html: '',
- text: '',
- isEdited: true,
- isDeletedParentAction: true,
- },
- ],
- errors: null,
- linkMetaData: [],
- },
- } as unknown as ReportActions;
-
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => {
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs});
-});
-test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => {
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => {
+ const parentReportActionId = '1';
+ const fakeParentAction = reportActions[parentReportActionId];
+ const actionsToMerge = {
+ [parentReportActionId]: {
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ previousMessage: fakeParentAction.message,
+ message: [
+ {
+ translationKey: '',
+ type: 'COMMENT',
+ html: '',
+ text: '',
+ isEdited: true,
+ isDeletedParentAction: true,
+ },
+ ],
+ errors: null,
+ linkMetaData: [],
+ },
+ } as unknown as ReportActions;
+
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs});
-});
-test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => {
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => {
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs});
+ });
+
+ test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => {
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs});
-});
-test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => {
- await Onyx.multiSet({
- ...mockedReportActionsMap,
+ test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => {
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs});
});
diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts
index 62b911fb0417..30f874c669ab 100644
--- a/tests/perf-test/ReportUtils.perf-test.ts
+++ b/tests/perf-test/ReportUtils.perf-test.ts
@@ -13,19 +13,6 @@ import createRandomTransaction from '../utils/collections/transaction';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
const runs = CONST.PERFORMANCE_TESTS.RUNS;
-
-beforeAll(() =>
- Onyx.init({
- keys: ONYXKEYS,
- safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
- }),
-);
-
-// Clear out Onyx after each test so that each test starts with a clean state
-afterEach(() => {
- Onyx.clear();
-});
-
const getMockedReports = (length = 500) =>
createCollection(
(item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`,
@@ -50,195 +37,169 @@ const mockedReportsMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COL
const mockedPoliciesMap = getMockedPolicies(5000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>;
const participantAccountIDs = Array.from({length: 1000}, (v, i) => i + 1);
-test('[ReportUtils] findLastAccessedReport on 2k reports and policies', async () => {
- const ignoreDomainRooms = true;
- const isFirstTimeNewExpensifyUser = true;
- const reports = getMockedReports(2000);
- const policies = getMockedPolicies(2000);
- const openOnAdminRoom = true;
-
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs});
-});
+describe('ReportUtils', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
+ });
+
+ Onyx.multiSet({
+ ...mockedPoliciesMap,
+ ...mockedReportsMap,
+ });
+ });
-test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => {
- const reportID = '1';
+ afterAll(() => {
+ Onyx.clear();
+ });
- const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction;
+ test('[ReportUtils] findLastAccessedReport on 2k reports and policies', async () => {
+ const ignoreDomainRooms = true;
+ const isFirstTimeNewExpensifyUser = true;
+ const reports = getMockedReports(2000);
+ const policies = getMockedPolicies(2000);
+ const openOnAdminRoom = true;
- await Onyx.multiSet({
- ...mockedPoliciesMap,
- ...mockedReportsMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs});
-});
+ test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => {
+ const reportID = '1';
+ const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction;
-test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => {
- const report = {...createRandomReport(1), participantAccountIDs};
- const currentLoginAccountID = 1;
-
- await Onyx.multiSet({
- ...mockedReportsMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs});
-});
-
-test('[ReportUtils] getIconsForParticipants on 1k participants', async () => {
- const participants = Array.from({length: 1000}, (v, i) => i + 1);
+ test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => {
+ const report = {...createRandomReport(1), participantAccountIDs};
+ const currentLoginAccountID = 1;
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs});
-});
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs});
+ });
-test('[ReportUtils] getIcons on 1k participants', async () => {
- const report = {...createRandomReport(1), parentReportID: '1', parentReportActionID: '1', type: CONST.REPORT.TYPE.CHAT};
- const policy = createRandomPolicy(1);
- const defaultIcon = null;
- const defaultName = '';
- const defaultIconId = -1;
+ test('[ReportUtils] getIconsForParticipants on 1k participants', async () => {
+ const participants = Array.from({length: 1000}, (v, i) => i + 1);
- await Onyx.multiSet({
- [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs});
-});
-
-test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => {
- const isMultipleParticipantReport = true;
- const shouldFallbackToHidden = true;
+ test('[ReportUtils] getIcons on 1k participants', async () => {
+ const report = {...createRandomReport(1), parentReportID: '1', parentReportActionID: '1', type: CONST.REPORT.TYPE.CHAT};
+ const policy = createRandomPolicy(1);
+ const defaultIcon = null;
+ const defaultName = '';
+ const defaultIconId = -1;
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs});
-});
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs});
+ });
-test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => {
- const reportAction = createRandomReportAction(1);
- const report = createRandomReport(1);
- const policy = createRandomPolicy(1);
- const shouldConsiderReceiptBeingScanned = true;
- const isPreviewMessageForParentChatReport = true;
+ test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => {
+ const isMultipleParticipantReport = true;
+ const shouldFallbackToHidden = true;
- await Onyx.multiSet({
- ...mockedPoliciesMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs});
-});
+ test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => {
+ const reportAction = createRandomReportAction(1);
+ const report = createRandomReport(1);
+ const policy = createRandomPolicy(1);
+ const shouldConsiderReceiptBeingScanned = true;
+ const isPreviewMessageForParentChatReport = true;
-test('[ReportUtils] getReportName on 1k participants', async () => {
- const report = {...createRandomReport(1), chatType: undefined, participantAccountIDs};
- const policy = createRandomPolicy(1);
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs});
+ });
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getReportName(report, policy), {runs});
-});
+ test('[ReportUtils] getReportName on 1k participants', async () => {
+ const report = {...createRandomReport(1), chatType: undefined, participantAccountIDs};
+ const policy = createRandomPolicy(1);
-test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => {
- const report = {...createRandomReport(1), participantAccountIDs};
- const accountID = 1;
- await Onyx.multiSet({
- ...mockedReportsMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getReportName(report, policy), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs});
-});
-
-test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => {
- const report = {...createRandomReport(1), participantAccountIDs, type: CONST.REPORT.TYPE.CHAT};
- const currentReportId = '2';
- const isInGSDMode = true;
- const betas = [CONST.BETAS.DEFAULT_ROOMS];
- const policies = getMockedPolicies();
+ test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => {
+ const report = {...createRandomReport(1), participantAccountIDs};
+ const accountID = 1;
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs});
-});
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs});
+ });
-test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => {
- const report = createRandomReport(1);
- const policy = createRandomPolicy(1);
+ test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => {
+ const report = {...createRandomReport(1), participantAccountIDs, type: CONST.REPORT.TYPE.CHAT};
+ const currentReportId = '2';
+ const isInGSDMode = true;
+ const betas = [CONST.BETAS.DEFAULT_ROOMS];
+ const policies = getMockedPolicies();
- await Onyx.multiSet({
- ...mockedPoliciesMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs});
-});
-
-test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => {
- const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true};
- const policy = createRandomPolicy(1);
- const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1);
+ test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => {
+ const report = createRandomReport(1);
+ const policy = createRandomPolicy(1);
- await Onyx.multiSet({
- ...mockedPoliciesMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs});
-});
-
-test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => {
- const report = createRandomReport(1);
+ test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => {
+ const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true};
+ const policy = createRandomPolicy(1);
+ const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1);
- await Onyx.multiSet({
- ...mockedPoliciesMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs});
-});
-test('[ReportUtils] getWorkspaceChat on 5k policies', async () => {
- const policyID = '1';
- const accountsID = Array.from({length: 20}, (v, i) => i + 1);
+ test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => {
+ const report = createRandomReport(1);
- await Onyx.multiSet({
- ...mockedReportsMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs});
-});
-
-test('[ReportUtils] getTransactionDetails on 5k reports', async () => {
- const transaction = createRandomTransaction(1);
+ test('[ReportUtils] getWorkspaceChat on 5k policies', async () => {
+ const policyID = '1';
+ const accountsID = Array.from({length: 20}, (v, i) => i + 1);
- await Onyx.multiSet({
- ...mockedReportsMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs});
});
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs});
-});
+ test('[ReportUtils] getTransactionDetails on 5k reports', async () => {
+ const transaction = createRandomTransaction(1);
+
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs});
+ });
-test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => {
- const reportAction = {
- ...createRandomReportAction(1),
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- originalMessage: {
- IOUReportID: '1',
- IOUTransactionID: '1',
- amount: 100,
- participantAccountID: 1,
- currency: CONST.CURRENCY.USD,
- type: CONST.IOU.REPORT_ACTION_TYPE.PAY,
- paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
- },
- };
-
- await Onyx.multiSet({
- ...mockedPoliciesMap,
- });
-
- await waitForBatchedUpdates();
- await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs});
+ test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => {
+ const reportAction = {
+ ...createRandomReportAction(1),
+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
+ originalMessage: {
+ IOUReportID: '1',
+ IOUTransactionID: '1',
+ amount: 100,
+ participantAccountID: 1,
+ currency: CONST.CURRENCY.USD,
+ type: CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ };
+
+ await waitForBatchedUpdates();
+ await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs});
+ });
});
diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js
index 1f529b08e6b3..1bc674045c23 100644
--- a/tests/perf-test/SidebarLinks.perf-test.js
+++ b/tests/perf-test/SidebarLinks.perf-test.js
@@ -15,25 +15,6 @@ jest.mock('../../src/components/Icon/Expensicons');
jest.mock('@react-navigation/native');
-beforeAll(() =>
- Onyx.init({
- keys: ONYXKEYS,
- safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
- registerStorageEventListener: () => {},
- }),
-);
-
-// Initialize the network key for OfflineWithFeedback
-beforeEach(() => {
- wrapOnyxWithWaitForBatchedUpdates(Onyx);
- return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
-});
-
-// Clear out Onyx after each test so that each test starts with a clean slate
-afterEach(() => {
- Onyx.clear();
-});
-
const getMockedReportsMap = (length = 100) => {
const mockReports = Array.from({length}, (__, i) => {
const reportID = i + 1;
@@ -51,71 +32,81 @@ const mockedResponseMap = getMockedReportsMap(500);
const runs = CONST.PERFORMANCE_TESTS.RUNS;
-test('[SidebarLinks] should render Sidebar with 500 reports stored', () => {
- const scenario = async () => {
- // Query for the sidebar
- await screen.findByTestId('lhn-options-list');
- /**
- * Query for display names of participants [1, 2].
- * This will ensure that the sidebar renders a list of items.
- */
- await screen.findAllByText('One, Two');
- };
-
- return waitForBatchedUpdates()
- .then(() =>
- Onyx.multiSet({
- [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
- [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
- [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
- [ONYXKEYS.IS_LOADING_REPORT_DATA]: false,
- ...mockedResponseMap,
- }),
- )
- .then(() => measurePerformance(, {scenario, runs}));
-});
+describe('SidebarLinks', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
+ registerStorageEventListener: () => {},
+ });
+
+ Onyx.multiSet({
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
+ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails,
+ [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
+ [ONYXKEYS.IS_LOADING_REPORT_DATA]: false,
+ ...mockedResponseMap,
+ });
+ });
-test('[SidebarLinks] should scroll and click some of the items', () => {
- const scenario = async () => {
- const eventData = {
- nativeEvent: {
- contentOffset: {
- y: variables.optionRowHeight * 5,
- },
- contentSize: {
- // Dimensions of the scrollable content
- height: variables.optionRowHeight * 10,
- width: 100,
- },
- layoutMeasurement: {
- // Dimensions of the device
- height: variables.optionRowHeight * 5,
- width: 100,
+ // Initialize the network key for OfflineWithFeedback
+ beforeEach(() => {
+ wrapOnyxWithWaitForBatchedUpdates(Onyx);
+ return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
+ });
+
+ afterAll(() => {
+ Onyx.clear();
+ });
+
+ test('[SidebarLinks] should render Sidebar with 500 reports stored', async () => {
+ const scenario = async () => {
+ // Query for the sidebar
+ await screen.findByTestId('lhn-options-list');
+ /**
+ * Query for display names of participants [1, 2].
+ * This will ensure that the sidebar renders a list of items.
+ */
+ await screen.findAllByText('One, Two');
+ };
+
+ await waitForBatchedUpdates();
+ await measurePerformance(, {scenario, runs});
+ });
+
+ test('[SidebarLinks] should scroll and click some of the items', async () => {
+ const scenario = async () => {
+ const eventData = {
+ nativeEvent: {
+ contentOffset: {
+ y: variables.optionRowHeight * 5,
+ },
+ contentSize: {
+ // Dimensions of the scrollable content
+ height: variables.optionRowHeight * 10,
+ width: 100,
+ },
+ layoutMeasurement: {
+ // Dimensions of the device
+ height: variables.optionRowHeight * 5,
+ width: 100,
+ },
},
- },
+ };
+
+ const lhnOptionsList = await screen.findByTestId('lhn-options-list');
+
+ fireEvent.scroll(lhnOptionsList, eventData);
+ // find elements that are currently visible in the viewport
+ const button1 = await screen.findByTestId('7');
+ const button2 = await screen.findByTestId('8');
+ fireEvent.press(button1);
+ fireEvent.press(button2);
};
- const lhnOptionsList = await screen.findByTestId('lhn-options-list');
-
- fireEvent.scroll(lhnOptionsList, eventData);
- // find elements that are currently visible in the viewport
- const button1 = await screen.findByTestId('7');
- const button2 = await screen.findByTestId('8');
- fireEvent.press(button1);
- fireEvent.press(button2);
- };
-
- return waitForBatchedUpdates()
- .then(() =>
- Onyx.multiSet({
- [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
- [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
- [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
- [ONYXKEYS.IS_LOADING_REPORT_DATA]: false,
- ...mockedResponseMap,
- }),
- )
- .then(() => measurePerformance(, {scenario, runs}));
+ await waitForBatchedUpdates();
+
+ await measurePerformance(, {scenario, runs});
+ });
});
diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts
index 6722cbf493a5..6ca81796d3ac 100644
--- a/tests/perf-test/SidebarUtils.perf-test.ts
+++ b/tests/perf-test/SidebarUtils.perf-test.ts
@@ -15,18 +15,6 @@ import createRandomReportAction from '../utils/collections/reportActions';
import createRandomReport from '../utils/collections/reports';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
-beforeAll(() =>
- Onyx.init({
- keys: ONYXKEYS,
- safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
- }),
-);
-
-// Clear out Onyx after each test so that each test starts with a clean slate
-afterEach(() => {
- Onyx.clear();
-});
-
const getMockedReports = (length = 500) =>
createCollection(
(item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`,
@@ -47,52 +35,61 @@ const personalDetails = createCollection(
const mockedResponseMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>;
const runs = CONST.PERFORMANCE_TESTS.RUNS;
-test('[SidebarUtils] getOptionData on 5k reports', async () => {
- const report = createRandomReport(1);
- const preferredLocale = 'en';
- const policy = createRandomPolicy(1);
- const parentReportAction = createRandomReportAction(1);
+describe('SidebarUtils', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
+ });
- Onyx.multiSet({
- ...mockedResponseMap,
+ Onyx.multiSet({
+ ...mockedResponseMap,
+ });
});
- await waitForBatchedUpdates();
- await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs});
-});
+ afterAll(() => {
+ Onyx.clear();
+ });
-test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => {
- const currentReportId = '1';
- const allReports = getMockedReports();
- const betas = [CONST.BETAS.DEFAULT_ROOMS];
+ test('[SidebarUtils] getOptionData on 5k reports', async () => {
+ const report = createRandomReport(1);
+ const preferredLocale = 'en';
+ const policy = createRandomPolicy(1);
+ const parentReportAction = createRandomReportAction(1);
- const policies = createCollection(
- (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`,
- (index) => createRandomPolicy(index),
- );
+ await waitForBatchedUpdates();
+ await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs});
+ });
+
+ test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => {
+ const currentReportId = '1';
+ const allReports = getMockedReports();
+ const betas = [CONST.BETAS.DEFAULT_ROOMS];
- const allReportActions = Object.fromEntries(
- Object.keys(reportActions).map((key) => [
- key,
- [
- {
- errors: reportActions[key].errors ?? [],
- message: [
- {
- moderationDecision: {
- decision: reportActions[key].message?.[0]?.moderationDecision?.decision,
+ const policies = createCollection(
+ (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`,
+ (index) => createRandomPolicy(index),
+ );
+
+ const allReportActions = Object.fromEntries(
+ Object.keys(reportActions).map((key) => [
+ key,
+ [
+ {
+ errors: reportActions[key].errors ?? [],
+ message: [
+ {
+ moderationDecision: {
+ decision: reportActions[key].message?.[0]?.moderationDecision?.decision,
+ },
},
- },
- ],
- },
- ],
- ]),
- ) as unknown as OnyxCollection;
+ ],
+ },
+ ],
+ ]),
+ ) as unknown as OnyxCollection;
- Onyx.multiSet({
- ...mockedResponseMap,
+ await waitForBatchedUpdates();
+ await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions), {runs});
});
-
- await waitForBatchedUpdates();
- await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions), {runs});
});
diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js
index d700aa4724f1..c9e8053e3146 100644
--- a/tests/unit/ReportUtilsTest.js
+++ b/tests/unit/ReportUtilsTest.js
@@ -149,8 +149,8 @@ describe('ReportUtils', () => {
test('Archived', () => {
const archivedAdminsRoom = {
...baseAdminsRoom,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
expect(ReportUtils.getReportName(archivedAdminsRoom)).toBe('#admins (archived)');
@@ -172,8 +172,8 @@ describe('ReportUtils', () => {
test('Archived', () => {
const archivedPolicyRoom = {
...baseUserCreatedRoom,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
expect(ReportUtils.getReportName(archivedPolicyRoom)).toBe('#VikingsChat (archived)');
@@ -213,8 +213,8 @@ describe('ReportUtils', () => {
ownerAccountID: 1,
policyID: policy.policyID,
oldPolicyName: policy.name,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
test('as member', () => {
@@ -307,7 +307,7 @@ describe('ReportUtils', () => {
managerID: currentUserAccountID,
isUnreadWithMention: false,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
};
expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(true);
});
@@ -368,7 +368,7 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.IOU,
- statusNum: CONST.REPORT.STATUS.REIMBURSED,
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
};
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]);
expect(moneyRequestOptions.length).toBe(0);
@@ -378,8 +378,8 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.EXPENSE,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- statusNum: CONST.REPORT.STATUS.APPROVED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
};
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]);
expect(moneyRequestOptions.length).toBe(0);
@@ -389,7 +389,7 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.EXPENSE,
- statusNum: CONST.REPORT.STATUS.REIMBURSED,
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
};
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]);
expect(moneyRequestOptions.length).toBe(0);
@@ -419,8 +419,8 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.EXPENSE,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
parentReportID: '101',
};
const paidPolicy = {
@@ -508,7 +508,7 @@ describe('ReportUtils', () => {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.EXPENSE,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
parentReportID: '103',
};
const paidPolicy = {
@@ -523,9 +523,8 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.IOU,
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]);
expect(moneyRequestOptions.length).toBe(1);
@@ -536,9 +535,8 @@ describe('ReportUtils', () => {
const report = {
...LHNTestUtils.getFakeReport(),
type: CONST.REPORT.TYPE.IOU,
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]);
expect(moneyRequestOptions.length).toBe(1);
diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js
index 088e5a1af4d0..83ccf3e92752 100644
--- a/tests/unit/SidebarFilterTest.js
+++ b/tests/unit/SidebarFilterTest.js
@@ -457,20 +457,20 @@ describe('Sidebar', () => {
// Given an archived chat report, an archived default policy room, and an archived user created policy room
const archivedReport = {
...LHNTestUtils.getFakeReport([1, 2]),
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
const archivedPolicyRoomReport = {
...LHNTestUtils.getFakeReport([1, 2]),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
const archivedUserCreatedPolicyRoomReport = {
...LHNTestUtils.getFakeReport([1, 2]),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
LHNTestUtils.getDefaultRenderedSidebarLinks();
@@ -681,8 +681,8 @@ describe('Sidebar', () => {
const report = {
...LHNTestUtils.getFakeReport(),
lastVisibleActionCreated: '2022-11-22 03:48:27.267',
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
// Given the user is in all betas
@@ -732,8 +732,8 @@ describe('Sidebar', () => {
// Given an archived report that has all comments read
const report = {
...LHNTestUtils.getFakeReport(),
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
// Given the user is in all betas
@@ -781,8 +781,8 @@ describe('Sidebar', () => {
const report = {
...LHNTestUtils.getFakeReport(),
isPinned: false,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
// Given the user is in all betas
@@ -826,8 +826,8 @@ describe('Sidebar', () => {
// Given an archived report that is not the active report
const report = {
...LHNTestUtils.getFakeReport(),
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
// Given the user is in all betas
diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js
index 4d49cb3ad516..ed50c32559a7 100644
--- a/tests/unit/SidebarOrderTest.js
+++ b/tests/unit/SidebarOrderTest.js
@@ -255,7 +255,7 @@ describe('Sidebar', () => {
reportName: taskReportName,
managerID: 2,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
};
// Each report has at least one ADDCOMMENT action so should be rendered in the LNH
@@ -313,8 +313,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
report3.iouReportID = iouReport.reportID;
@@ -374,9 +374,8 @@ describe('Sidebar', () => {
policyName: 'Workspace',
total: -10000,
currency: 'USD',
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
chatReportID: report3.reportID,
parentReportID: report3.reportID,
};
@@ -575,9 +574,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- state: CONST.REPORT.STATE.SUBMITTED,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
report3.iouReportID = iouReport.reportID;
const currentReportId = report2.reportID;
@@ -740,8 +738,8 @@ describe('Sidebar', () => {
const report1 = {
...LHNTestUtils.getFakeReport([1, 2]),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
const report2 = LHNTestUtils.getFakeReport([3, 4]);
const report3 = LHNTestUtils.getFakeReport([5, 6]);
@@ -837,8 +835,8 @@ describe('Sidebar', () => {
const report1 = {
...LHNTestUtils.getFakeReport([1, 2], 3, true),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true);
const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true);
@@ -914,8 +912,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const iouReport2 = {
...LHNTestUtils.getFakeReport([9, 10]),
@@ -926,8 +924,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const iouReport3 = {
...LHNTestUtils.getFakeReport([11, 12]),
@@ -938,8 +936,8 @@ describe('Sidebar', () => {
total: 100000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const iouReport4 = {
...LHNTestUtils.getFakeReport([11, 12]),
@@ -950,8 +948,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const iouReport5 = {
...LHNTestUtils.getFakeReport([11, 12]),
@@ -962,8 +960,8 @@ describe('Sidebar', () => {
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
- stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
- statusNum: CONST.REPORT.STATUS.SUBMITTED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
report1.iouReportID = iouReport1.reportID;
diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js
index 56009ee382d5..1101c5732707 100644
--- a/tests/unit/SidebarTest.js
+++ b/tests/unit/SidebarTest.js
@@ -51,8 +51,8 @@ describe('Sidebar', () => {
const report = {
...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
// Given the user is in all betas
@@ -86,8 +86,8 @@ describe('Sidebar', () => {
...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true),
policyName: 'Vikings Policy',
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
- statusNum: CONST.REPORT.STATUS.CLOSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
};
const action = {
...LHNTestUtils.getFakeReportAction('email1@test.com', 3, true),
diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js
index 535fb018dbc3..900a7852c92d 100644
--- a/tests/utils/LHNTestUtils.js
+++ b/tests/utils/LHNTestUtils.js
@@ -209,8 +209,8 @@ function getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorksp
...getFakeReport([1, 2], 0, isUnread),
type: CONST.REPORT.TYPE.CHAT,
chatType: isUserCreatedPolicyRoom ? CONST.REPORT.CHAT_TYPE.POLICY_ROOM : CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
- statusNum: isArchived ? CONST.REPORT.STATUS.CLOSED : 0,
- stateNum: isArchived ? CONST.REPORT.STATE_NUM.SUBMITTED : 0,
+ statusNum: isArchived ? CONST.REPORT.STATUS_NUM.CLOSED : 0,
+ stateNum: isArchived ? CONST.REPORT.STATE_NUM.APPROVED : 0,
errorFields: hasAddWorkspaceError ? {addWorkspaceRoom: 'blah'} : null,
isPinned,
hasDraft,
diff --git a/web/index.html b/web/index.html
index 967873fe586c..7c02614d17b2 100644
--- a/web/index.html
+++ b/web/index.html
@@ -96,6 +96,11 @@
caret-color: #ffffff;
}
+ /* Customize Plaid iframe */
+ [id^="plaid-link-iframe"] {
+ color-scheme: dark !important;
+ }
+
/* Prevent autofill from overlapping with the input label in Chrome */
div:has(input:-webkit-autofill, input[chrome-autofilled]) > label {
transform: translateY(var(--active-label-translate-y)) scale(var(--active-label-scale)) !important;