diff --git a/.eslintrc.js b/.eslintrc.js
index f852c970f85c..281f8269804e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,12 +1,13 @@
const restrictedImportPaths = [
{
name: 'react-native',
- importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable'],
+ importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', 'Text'],
message: [
'',
"For 'useWindowDimensions', please use 'src/hooks/useWindowDimensions' instead.",
"For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.",
"For 'StatusBar', please use 'src/libs/StatusBar' instead.",
+ "For 'Text', please use '@components/Text' instead.",
].join('\n'),
},
{
diff --git a/README.md b/README.md
index 3b9010695760..f6629af8604d 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ In order to have more consistent builds, we use a strict `node` and `npm` versio
## Configuring HTTPS
The webpack development server now uses https. If you're using a mac, you can simply run `npm run setup-https`.
-If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `new.expesify.com.dev` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`.
+If you're using another operating system, you will need to ensure `mkcert` is installed, and then follow the instructions in the repository to generate certificates valid for `dev.new.expensify.com` and `localhost`. The certificate should be named `certificate.pem` and the key should be named `key.pem`. They should be placed in `config/webpack`.
## Running the web app 🕸
* To run the **development web app**: `npm run web`
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4088f69cf008..47f19acfe6ae 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001042501
- versionName "1.4.25-1"
+ versionCode 1001042601
+ versionName "1.4.26-1"
}
flavorDimensions "default"
diff --git a/android/app/src/main/res/drawable/ic_launcher_monochrome.png b/android/app/src/main/res/drawable/ic_launcher_monochrome.png
index b1a286b6f8dd..0af99b087923 100644
Binary files a/android/app/src/main/res/drawable/ic_launcher_monochrome.png and b/android/app/src/main/res/drawable/ic_launcher_monochrome.png differ
diff --git a/assets/images/expensify-logo--adhoc.svg b/assets/images/expensify-logo--adhoc.svg
index 273002deca9b..52b381dc4b78 100644
--- a/assets/images/expensify-logo--adhoc.svg
+++ b/assets/images/expensify-logo--adhoc.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/expensify-logo--dev.svg b/assets/images/expensify-logo--dev.svg
index e8e3fb5033d9..2c9ae142e283 100644
--- a/assets/images/expensify-logo--dev.svg
+++ b/assets/images/expensify-logo--dev.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/expensify-logo--staging.svg b/assets/images/expensify-logo--staging.svg
index 78dcc1581f99..a1e7482c133b 100644
--- a/assets/images/expensify-logo--staging.svg
+++ b/assets/images/expensify-logo--staging.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/images/home-background--mobile-new.svg b/assets/images/home-background--mobile-new.svg
index 0da937cae059..d81f2a18cc78 100644
--- a/assets/images/home-background--mobile-new.svg
+++ b/assets/images/home-background--mobile-new.svg
@@ -1,8835 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/assets/images/new-expensify.svg b/assets/images/new-expensify.svg
index 89102ecbc5e4..7bfef1fd38b4 100644
--- a/assets/images/new-expensify.svg
+++ b/assets/images/new-expensify.svg
@@ -1 +1 @@
-
+
\ 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 813c136f3c2c..bb67f5840fad 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.25
+ 1.4.26
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.25.1
+ 1.4.26.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index dfa278adacc5..8d5fb7867c37 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.25
+ 1.4.26
CFBundleSignature
????
CFBundleVersion
- 1.4.25.1
+ 1.4.26.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 73420efed711..85f148305fde 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -3,9 +3,9 @@
CFBundleShortVersionString
- 1.4.25
+ 1.4.26
CFBundleVersion
- 1.4.25.1
+ 1.4.26.1
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 379194a70fd9..f433c4f1e1e2 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1180,7 +1180,7 @@ PODS:
- 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
@@ -1911,7 +1911,7 @@ SPEC CHECKSUMS:
react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5
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
diff --git a/package-lock.json b/package-lock.json
index b530468d7725..ab98b21fca69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.25-1",
+ "version": "1.4.26-1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.25-1",
+ "version": "1.4.26-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -94,9 +94,9 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.126",
+ "react-native-onyx": "1.0.118",
"react-native-pager-view": "6.2.2",
- "react-native-pdf": "^6.7.4",
+ "react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
"react-native-permissions": "^3.9.3",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
@@ -47034,17 +47034,17 @@
}
},
"node_modules/react-native-onyx": {
- "version": "1.0.126",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.126.tgz",
- "integrity": "sha512-tUJI1mQaWXLfyBFYQQWM6mm9GiCqIXGvjbqJkH1fLY3OqbGW6DyH4CxC+qJrqfi4bKZgZHp5xlBHhkPV4pKK2A==",
+ "version": "1.0.118",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
+ "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
"underscore": "^1.13.6"
},
"engines": {
- "node": "20.9.0",
- "npm": "10.1.0"
+ "node": ">=16.15.1 <=20.9.0",
+ "npm": ">=8.11.0 <=10.1.0"
},
"peerDependencies": {
"idb-keyval": "^6.2.1",
@@ -47079,9 +47079,9 @@
}
},
"node_modules/react-native-pdf": {
- "version": "6.7.4",
- "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
- "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
+ "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
"dependencies": {
"crypto-js": "4.2.0",
"deprecated-react-native-prop-types": "^2.3.0"
@@ -89702,9 +89702,9 @@
}
},
"react-native-onyx": {
- "version": "1.0.126",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.126.tgz",
- "integrity": "sha512-tUJI1mQaWXLfyBFYQQWM6mm9GiCqIXGvjbqJkH1fLY3OqbGW6DyH4CxC+qJrqfi4bKZgZHp5xlBHhkPV4pKK2A==",
+ "version": "1.0.118",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
+ "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
"requires": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -89718,9 +89718,9 @@
"requires": {}
},
"react-native-pdf": {
- "version": "6.7.4",
- "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
- "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
+ "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
"requires": {
"crypto-js": "4.2.0",
"deprecated-react-native-prop-types": "^2.3.0"
diff --git a/package.json b/package.json
index a5823e18e357..7d792cae8cc0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.25-1",
+ "version": "1.4.26-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -142,9 +142,9 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.126",
+ "react-native-onyx": "1.0.118",
"react-native-pager-view": "6.2.2",
- "react-native-pdf": "^6.7.4",
+ "react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
"react-native-permissions": "^3.9.3",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
diff --git a/patches/react-native-blob-util+0.17.3.patch b/patches/react-native-blob-util+0.17.3.patch
new file mode 100644
index 000000000000..2ade175a7b30
--- /dev/null
+++ b/patches/react-native-blob-util+0.17.3.patch
@@ -0,0 +1,17 @@
+diff --git a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java
+index 4b41402..4f07fc6 100644
+--- a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java
++++ b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java
+@@ -279,7 +279,11 @@ public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnabl
+ DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
+ downloadManagerId = dm.enqueue(req);
+ androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
+- appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
++ if(Build.VERSION.SDK_INT >= 34 ){
++ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);
++ }else{
++ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
++ }
+ future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
diff --git a/src/CONST.ts b/src/CONST.ts
index f0f7ab736b78..d6f3d3cdcef6 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -639,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,
@@ -1449,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: {
diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.js
index 06541565f567..fc88aaa03fe5 100644
--- a/src/components/AddressSearch/CurrentLocationButton.js
+++ b/src/components/AddressSearch/CurrentLocationButton.js
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
-import {Text} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
+import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js
index 357f5af8cb58..08760dc5a771 100644
--- a/src/components/AddressSearch/index.js
+++ b/src/components/AddressSearch/index.js
@@ -1,13 +1,14 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {ActivityIndicator, Keyboard, LogBox, ScrollView, Text, View} from 'react-native';
+import {ActivityIndicator, Keyboard, LogBox, ScrollView, View} from 'react-native';
import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete';
import _ from 'underscore';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import LocationErrorMessage from '@components/LocationErrorMessage';
import networkPropTypes from '@components/networkPropTypes';
import {withNetwork} from '@components/OnyxProvider';
+import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx
index bb3792f59d9f..99a0ee3bf683 100644
--- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx
+++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx
@@ -1,5 +1,6 @@
import Str from 'expensify-common/lib/str';
import React, {useEffect, useRef} from 'react';
+// eslint-disable-next-line no-restricted-imports
import type {Text as RNText} from 'react-native';
import {StyleSheet} from 'react-native';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx
index ad79e316baf3..04e8a5f8d55b 100644
--- a/src/components/AnonymousReportFooter.tsx
+++ b/src/components/AnonymousReportFooter.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import {Text, View} from 'react-native';
+import {View} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx/lib/types';
import useLocalize from '@hooks/useLocalize';
@@ -9,6 +9,7 @@ import type {PersonalDetails, Report} from '@src/types/onyx';
import AvatarWithDisplayName from './AvatarWithDisplayName';
import Button from './Button';
import ExpensifyWordmark from './ExpensifyWordmark';
+import Text from './Text';
type AnonymousReportFooterProps = {
/** The report currently being looked at */
diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js
index 92cbe3a4da04..f69fe7eb5ecb 100644
--- a/src/components/AvatarCropModal/ImageCropView.js
+++ b/src/components/AvatarCropModal/ImageCropView.js
@@ -6,7 +6,6 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import useStyleUtils from '@hooks/useStyleUtils';
-import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import ControlSelection from '@libs/ControlSelection';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';
@@ -51,7 +50,6 @@ const defaultProps = {
};
function ImageCropView(props) {
- const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize);
@@ -90,7 +88,8 @@ function ImageCropView(props) {
diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx
index 5993e60861f5..807029addf5e 100644
--- a/src/components/BlockingViews/FullPageNotFoundView.tsx
+++ b/src/components/BlockingViews/FullPageNotFoundView.tsx
@@ -33,10 +33,10 @@ type FullPageNotFoundViewProps = {
linkKey?: TranslationPaths;
/** Method to trigger when pressing the back button of the header */
- onBackButtonPress: () => void;
+ onBackButtonPress?: () => void;
/** Function to call when pressing the navigation link */
- onLinkPress: () => void;
+ onLinkPress?: () => void;
};
// eslint-disable-next-line rulesdir/no-negated-variables
diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.tsx
similarity index 56%
rename from src/components/ButtonWithDropdownMenu.js
rename to src/components/ButtonWithDropdownMenu.tsx
index 4d3ec8796a31..466c68229a32 100644
--- a/src/components/ButtonWithDropdownMenu.js
+++ b/src/components/ButtonWithDropdownMenu.tsx
@@ -1,89 +1,89 @@
-import PropTypes from 'prop-types';
+import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
+import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
-import _ from 'underscore';
+import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
+import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
-import sourcePropTypes from './Image/sourcePropTypes';
+import type {AnchorAlignment} from './Popover/types';
import PopoverMenu from './PopoverMenu';
-const propTypes = {
+type DropdownOption = {
+ value: string;
+ text: string;
+ icon: IconAsset;
+ iconWidth?: number;
+ iconHeight?: number;
+ iconDescription?: string;
+};
+
+type ButtonWithDropdownMenuProps = {
/** Text to display for the menu header */
- menuHeaderText: PropTypes.string,
+ menuHeaderText?: string;
/** Callback to execute when the main button is pressed */
- onPress: PropTypes.func.isRequired,
+ onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void;
/** Call the onPress function on main button when Enter key is pressed */
- pressOnEnter: PropTypes.bool,
+ pressOnEnter?: boolean;
/** Whether we should show a loading state for the main button */
- isLoading: PropTypes.bool,
+ isLoading?: boolean;
/** The size of button size */
- buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)),
+ buttonSize: ValueOf;
/** Should the confirmation button be disabled? */
- isDisabled: PropTypes.bool,
+ isDisabled?: boolean;
/** Additional styles to add to the component */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+ style?: StyleProp;
/** Menu options to display */
/** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */
- options: PropTypes.arrayOf(
- PropTypes.shape({
- value: PropTypes.string.isRequired,
- text: PropTypes.string.isRequired,
- icon: sourcePropTypes,
- iconWidth: PropTypes.number,
- iconHeight: PropTypes.number,
- iconDescription: PropTypes.string,
- }),
- ).isRequired,
+ options: DropdownOption[];
/** The anchor alignment of the popover menu */
- anchorAlignment: PropTypes.shape({
- horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
- vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
- }),
+ anchorAlignment?: AnchorAlignment;
/* ref for the button */
- buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ buttonRef: RefObject;
};
-const defaultProps = {
- isLoading: false,
- isDisabled: false,
- pressOnEnter: false,
- menuHeaderText: '',
- style: [],
- buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
- anchorAlignment: {
+function ButtonWithDropdownMenu({
+ isLoading = false,
+ isDisabled = false,
+ pressOnEnter = false,
+ menuHeaderText = '',
+ style,
+ buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
+ anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
},
- buttonRef: () => {},
-};
-
-function ButtonWithDropdownMenu(props) {
+ buttonRef,
+ onPress,
+ options,
+}: ButtonWithDropdownMenuProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [selectedItemIndex, setSelectedItemIndex] = useState(0);
const [isMenuVisible, setIsMenuVisible] = useState(false);
- const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null);
+ const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null);
const {windowWidth, windowHeight} = useWindowDimensions();
- const caretButton = useRef(null);
- const selectedItem = props.options[selectedItemIndex] || _.first(props.options);
- const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(props.buttonSize);
- const isButtonSizeLarge = props.buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
+ const caretButton = useRef(null);
+ const selectedItem = options[selectedItemIndex] || options[0];
+ const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize);
+ const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
useEffect(() => {
if (!caretButton.current) {
@@ -92,29 +92,31 @@ function ButtonWithDropdownMenu(props) {
if (!isMenuVisible) {
return;
}
- caretButton.current.measureInWindow((x, y, w, h) => {
- setPopoverAnchorPosition({
- horizontal: x + w,
- vertical:
- props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP
- ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding
- : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding
+ if ('measureInWindow' in caretButton.current) {
+ caretButton.current.measureInWindow((x, y, w, h) => {
+ setPopoverAnchorPosition({
+ horizontal: x + w,
+ vertical:
+ anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP
+ ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding
+ : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding
+ });
});
- });
- }, [windowWidth, windowHeight, isMenuVisible, props.anchorAlignment.vertical]);
+ }
+ }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]);
return (
- {props.options.length > 1 ? (
-
+ {options.length > 1 ? (
+
{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/Popover/types.ts b/src/components/Popover/types.ts
index 3d1f95822e6a..87a09895d50f 100644
--- a/src/components/Popover/types.ts
+++ b/src/components/Popover/types.ts
@@ -1,8 +1,11 @@
+import type {RefObject} from 'react';
+import type {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import type {PopoverAnchorPosition} from '@components/Modal/types';
import type BaseModalProps from '@components/Modal/types';
import type {WindowDimensionsProps} from '@components/withWindowDimensions/types';
import type CONST from '@src/CONST';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
type AnchorAlignment = {
/** The horizontal anchor alignment of the popover */
@@ -17,34 +20,32 @@ type PopoverDimensions = {
height: number;
};
-type PopoverProps = BaseModalProps & {
- /** The anchor position of the popover */
- anchorPosition?: PopoverAnchorPosition;
+type PopoverProps = BaseModalProps &
+ ChildrenProps & {
+ /** The anchor position of the popover */
+ anchorPosition?: PopoverAnchorPosition;
- /** The anchor alignment of the popover */
- anchorAlignment: AnchorAlignment;
+ /** The anchor alignment of the popover */
+ anchorAlignment?: AnchorAlignment;
- /** The anchor ref of the popover */
- anchorRef: React.RefObject;
+ /** The anchor ref of the popover */
+ anchorRef: RefObject;
- /** Whether disable the animations */
- disableAnimation: boolean;
+ /** Whether disable the animations */
+ disableAnimation?: boolean;
- /** Whether we don't want to show overlay */
- withoutOverlay: boolean;
+ /** Whether we don't want to show overlay */
+ withoutOverlay: boolean;
- /** The dimensions of the popover */
- popoverDimensions?: PopoverDimensions;
+ /** The dimensions of the popover */
+ popoverDimensions?: PopoverDimensions;
- /** The ref of the popover */
- withoutOverlayRef?: React.RefObject;
+ /** The ref of the popover */
+ withoutOverlayRef?: RefObject;
- /** Whether we want to show the popover on the right side of the screen */
- fromSidebarMediumScreen?: boolean;
-
- /** The popover children */
- children: React.ReactNode;
-};
+ /** Whether we want to show the popover on the right side of the screen */
+ fromSidebarMediumScreen?: boolean;
+ };
type PopoverWithWindowDimensionsProps = PopoverProps & WindowDimensionsProps;
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/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx
index b50b04289813..b1a6ebb0c5c0 100644
--- a/src/components/PopoverProvider/index.tsx
+++ b/src/components/PopoverProvider/index.tsx
@@ -1,18 +1,27 @@
-import React from 'react';
+import type {RefObject} from 'react';
+import React, {createContext, useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import type {View} from 'react-native';
import type {AnchorRef, PopoverContextProps, PopoverContextValue} from './types';
-const PopoverContext = React.createContext({
+const PopoverContext = createContext({
onOpen: () => {},
popover: {},
close: () => {},
isOpen: false,
});
+function elementContains(ref: RefObject | undefined, target: EventTarget | null) {
+ if (ref?.current && 'contains' in ref?.current && ref?.current?.contains(target as Node)) {
+ return true;
+ }
+ return false;
+}
+
function PopoverContextProvider(props: PopoverContextProps) {
- const [isOpen, setIsOpen] = React.useState(false);
- const activePopoverRef = React.useRef(null);
+ const [isOpen, setIsOpen] = useState(false);
+ const activePopoverRef = useRef(null);
- const closePopover = React.useCallback((anchorRef?: React.RefObject) => {
+ const closePopover = useCallback((anchorRef?: RefObject) => {
if (!activePopoverRef.current || (anchorRef && anchorRef !== activePopoverRef.current.anchorRef)) {
return;
}
@@ -25,10 +34,9 @@ function PopoverContextProvider(props: PopoverContextProps) {
setIsOpen(false);
}, []);
- React.useEffect(() => {
+ useEffect(() => {
const listener = (e: Event) => {
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- if (activePopoverRef.current?.ref?.current?.contains(e.target as Node) || activePopoverRef.current?.anchorRef?.current?.contains(e.target as Node)) {
+ if (elementContains(activePopoverRef.current?.ref, e.target) || elementContains(activePopoverRef.current?.anchorRef, e.target)) {
return;
}
const ref = activePopoverRef.current?.anchorRef;
@@ -40,9 +48,9 @@ function PopoverContextProvider(props: PopoverContextProps) {
};
}, [closePopover]);
- React.useEffect(() => {
+ useEffect(() => {
const listener = (e: Event) => {
- if (activePopoverRef.current?.ref?.current?.contains(e.target as Node)) {
+ if (elementContains(activePopoverRef.current?.ref, e.target)) {
return;
}
closePopover();
@@ -53,7 +61,7 @@ function PopoverContextProvider(props: PopoverContextProps) {
};
}, [closePopover]);
- React.useEffect(() => {
+ useEffect(() => {
const listener = (e: KeyboardEvent) => {
if (e.key !== 'Escape') {
return;
@@ -66,7 +74,7 @@ function PopoverContextProvider(props: PopoverContextProps) {
};
}, [closePopover]);
- React.useEffect(() => {
+ useEffect(() => {
const listener = () => {
if (document.hasFocus()) {
return;
@@ -79,9 +87,9 @@ function PopoverContextProvider(props: PopoverContextProps) {
};
}, [closePopover]);
- React.useEffect(() => {
+ useEffect(() => {
const listener = (e: Event) => {
- if (activePopoverRef.current?.ref?.current?.contains(e.target as Node)) {
+ if (elementContains(activePopoverRef.current?.ref, e.target)) {
return;
}
@@ -93,7 +101,7 @@ function PopoverContextProvider(props: PopoverContextProps) {
};
}, [closePopover]);
- const onOpen = React.useCallback(
+ const onOpen = useCallback(
(popoverParams: AnchorRef) => {
if (activePopoverRef.current && activePopoverRef.current.ref !== popoverParams?.ref) {
closePopover(activePopoverRef.current.anchorRef);
@@ -107,7 +115,7 @@ function PopoverContextProvider(props: PopoverContextProps) {
[closePopover],
);
- const contextValue = React.useMemo(
+ const contextValue = useMemo(
() => ({
onOpen,
close: closePopover,
diff --git a/src/components/PopoverProvider/types.ts b/src/components/PopoverProvider/types.ts
index ffd0087cd5ff..49705d7ea7a8 100644
--- a/src/components/PopoverProvider/types.ts
+++ b/src/components/PopoverProvider/types.ts
@@ -1,18 +1,21 @@
+import type {ReactNode, RefObject} from 'react';
+import type {View} from 'react-native';
+
type PopoverContextProps = {
- children: React.ReactNode;
+ children: ReactNode;
};
type PopoverContextValue = {
onOpen?: (popoverParams: AnchorRef) => void;
popover?: AnchorRef | Record | null;
- close: (anchorRef?: React.RefObject) => void;
+ close: (anchorRef?: RefObject) => void;
isOpen: boolean;
};
type AnchorRef = {
- ref: React.RefObject;
- close: (anchorRef?: React.RefObject) => void;
- anchorRef: React.RefObject;
+ ref: RefObject;
+ close: (anchorRef?: RefObject) => void;
+ anchorRef: RefObject;
onOpenCallback?: () => void;
onCloseCallback?: () => void;
};
diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx
index 6aed275bd2dc..58d022ef9d65 100644
--- a/src/components/PopoverWithoutOverlay/index.tsx
+++ b/src/components/PopoverWithoutOverlay/index.tsx
@@ -8,6 +8,7 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Modal from '@userActions/Modal';
+import viewRef from '@src/types/utils/viewRef';
import type PopoverWithoutOverlayProps from './types';
function PopoverWithoutOverlay(
@@ -52,7 +53,7 @@ function PopoverWithoutOverlay(
close: onClose,
anchorRef,
});
- removeOnClose = Modal.setCloseModal(() => onClose(anchorRef));
+ removeOnClose = Modal.setCloseModal(onClose);
} else {
onModalHide();
close(anchorRef);
@@ -119,7 +120,7 @@ function PopoverWithoutOverlay(
return (
;
+ anchorRef: RefObject;
/** A react-native-animatable animation timing for the modal display animation */
animationInTiming?: number;
@@ -22,7 +23,7 @@ type PopoverWithoutOverlayProps = ChildrenProps &
disableAnimation?: boolean;
/** The ref of the popover */
- withoutOverlayRef: React.RefObject;
+ withoutOverlayRef: RefObject;
};
export default PopoverWithoutOverlayProps;
diff --git a/src/components/ProcessMoneyRequestHoldMenu.tsx b/src/components/ProcessMoneyRequestHoldMenu.tsx
index 1b711633ed3b..5f32240aca9b 100644
--- a/src/components/ProcessMoneyRequestHoldMenu.tsx
+++ b/src/components/ProcessMoneyRequestHoldMenu.tsx
@@ -1,3 +1,4 @@
+import type {RefObject} from 'react';
import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
@@ -27,7 +28,7 @@ type ProcessMoneyRequestHoldMenuProps = {
anchorAlignment: AnchorAlignment;
/** The anchor ref of the popover menu */
- anchorRef: React.RefObject;
+ anchorRef: RefObject;
};
function ProcessMoneyRequestHoldMenu({isVisible, onClose, onConfirm, anchorPosition, anchorAlignment, anchorRef}: ProcessMoneyRequestHoldMenuProps) {
diff --git a/src/components/QRShare/QRShareWithDownload/index.native.tsx b/src/components/QRShare/QRShareWithDownload/index.native.tsx
index d1d9f13147f1..7d192c84c454 100644
--- a/src/components/QRShare/QRShareWithDownload/index.native.tsx
+++ b/src/components/QRShare/QRShareWithDownload/index.native.tsx
@@ -3,6 +3,7 @@ import React, {forwardRef, useImperativeHandle, useRef} from 'react';
import ViewShot from 'react-native-view-shot';
import getQrCodeFileName from '@components/QRShare/getQrCodeDownloadFileName';
import type {QRShareProps} from '@components/QRShare/types';
+import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import fileDownload from '@libs/fileDownload';
import QRShare from '..';
@@ -10,14 +11,16 @@ import type QRShareWithDownloadHandle from './types';
function QRShareWithDownload(props: QRShareProps, ref: ForwardedRef) {
const {isOffline} = useNetwork();
+ const {translate} = useLocalize();
+
const qrCodeScreenshotRef = useRef(null);
useImperativeHandle(
ref,
() => ({
- download: () => qrCodeScreenshotRef.current?.capture?.().then((uri) => fileDownload(uri, getQrCodeFileName(props.title))),
+ download: () => qrCodeScreenshotRef.current?.capture?.().then((uri) => fileDownload(uri, getQrCodeFileName(props.title), translate('fileDownload.success.qrMessage'))),
}),
- [props.title],
+ [props.title, translate],
);
return (
diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx
index 9f38da6bdb3d..1b489166e949 100644
--- a/src/components/Reactions/MiniQuickEmojiReactions.tsx
+++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx
@@ -1,6 +1,5 @@
import React, {useRef} from 'react';
import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {Emoji} from '@assets/emojis/types';
import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem';
@@ -16,16 +15,7 @@ import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {ReportActionReactions} from '@src/types/onyx';
-import type {BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types';
-
-type MiniQuickEmojiReactionsOnyxProps = {
- /** All the emoji reactions for the report action. */
- emojiReactions: OnyxEntry;
-
- /** The user's preferred skin tone. */
- preferredSkinTone: OnyxEntry;
-};
+import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types';
type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
/**
@@ -112,11 +102,14 @@ function MiniQuickEmojiReactions({
MiniQuickEmojiReactions.displayName = 'MiniQuickEmojiReactions';
-export default withOnyx({
+export default withOnyx({
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
},
emojiReactions: {
key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
},
+ preferredLocale: {
+ key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+ },
})(MiniQuickEmojiReactions);
diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts
index d782d5ae35c7..9c17a87c56c0 100644
--- a/src/components/Reactions/QuickEmojiReactions/types.ts
+++ b/src/components/Reactions/QuickEmojiReactions/types.ts
@@ -11,18 +11,7 @@ type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrig
type CloseContextMenuCallback = () => void;
-type BaseQuickEmojiReactionsOnyxProps = {
- /** All the emoji reactions for the report action. */
- emojiReactions: OnyxEntry;
-
- /** The user's preferred locale. */
- preferredLocale: OnyxEntry;
-
- /** The user's preferred skin tone. */
- preferredSkinTone: OnyxEntry;
-};
-
-type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & {
+type BaseReactionsProps = {
/** Callback to fire when an emoji is selected. */
onEmojiSelected: (emoji: Emoji, emojiReactions: OnyxEntry) => void;
@@ -45,7 +34,20 @@ type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & {
reportActionID: string;
};
-type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
+type BaseQuickEmojiReactionsOnyxProps = {
+ /** All the emoji reactions for the report action. */
+ emojiReactions: OnyxEntry;
+
+ /** The user's preferred locale. */
+ preferredLocale: OnyxEntry;
+
+ /** The user's preferred skin tone. */
+ preferredSkinTone: OnyxEntry;
+};
+
+type BaseQuickEmojiReactionsProps = BaseReactionsProps & BaseQuickEmojiReactionsOnyxProps;
+
+type QuickEmojiReactionsProps = BaseReactionsProps & {
/**
* Function that can be called to close the context menu
* in which this component is rendered.
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={[]}
+ />
+
+ );
+ })}
{translate('iou.pendingConversionMessage')}
)}
- {(shouldShowDescription || shouldShowMerchant) && {merchantOrDescription}}
+ {shouldShowDescription && }
+ {shouldShowMerchant && {merchantOrDescription}}
{props.isBillSplit && !_.isEmpty(participantAccountIDs) && requestAmount > 0 && (
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index 7c7998c24c95..86affbcac114 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -313,8 +313,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
shouldShowRightIcon={canEditMerchant}
titleStyle={styles.flex1}
onPress={() => 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..8ef837ed986d 100644
--- a/src/components/ReportActionItem/TaskPreview.tsx
+++ b/src/components/ReportActionItem/TaskPreview.tsx
@@ -1,5 +1,7 @@
import Str from 'expensify-common/lib/str';
import React from 'react';
+// eslint-disable-next-line no-restricted-imports
+import type {Text as RNText} from 'react-native';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
@@ -63,7 +65,7 @@ type TaskPreviewProps = WithCurrentUserPersonalDetailsProps &
chatReportID: string;
/** Popover context menu anchor, used for showing context menu */
- contextMenuAnchor: Element;
+ contextMenuAnchor: RNText | null;
/** Callback for updating context menu active state, used for showing context menu */
checkIfContextMenuActive: () => void;
@@ -84,12 +86,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 ?? '';
@@ -111,7 +114,7 @@ function TaskPreview({
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
- onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action ?? {}, checkIfContextMenuActive)}
+ onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
style={[styles.flexRow, styles.justifyContentBetween]}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('task.task')}
diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js
index cfd39ab0ebb8..6a067ea0fe3d 100644
--- a/src/components/SelectionList/BaseListItem.js
+++ b/src/components/SelectionList/BaseListItem.js
@@ -105,11 +105,11 @@ function BaseListItem({
textStyles={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
- isUserItem || item.isSelected || item.alternateText ? styles.sidebarLinkTextBold : null,
+ styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
- alternateTextStyles={[styles.optionAlternateText, styles.textLabelSupporting, isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText, styles.pre]}
+ alternateTextStyles={[styles.textLabelSupporting, styles.lh16, styles.pre]}
isDisabled={isDisabled}
onSelectRow={onSelectRow}
showTooltip={showTooltip}
diff --git a/src/components/ShowContextMenuContext.js b/src/components/ShowContextMenuContext.js
deleted file mode 100644
index 04ccd5002b60..000000000000
--- a/src/components/ShowContextMenuContext.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import * as DeviceCapabilities from '@libs/DeviceCapabilities';
-import * as ReportUtils from '@libs/ReportUtils';
-import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import CONST from '@src/CONST';
-
-const ShowContextMenuContext = React.createContext({
- anchor: null,
- report: null,
- action: undefined,
- checkIfContextMenuActive: () => {},
-});
-
-ShowContextMenuContext.displayName = 'ShowContextMenuContext';
-
-/**
- * Show the report action context menu.
- *
- * @param {Object} event - Press event object
- * @param {Element} anchor - Context menu anchor
- * @param {String} reportID - Active Report ID
- * @param {Object} action - ReportAction for ContextMenu
- * @param {Function} checkIfContextMenuActive Callback to update context menu active state
- * @param {Boolean} [isArchivedRoom=false] - Is the report an archived room
- */
-function showContextMenuForReport(event, anchor, reportID, action, checkIfContextMenuActive, isArchivedRoom = false) {
- if (!DeviceCapabilities.canUseTouchScreen()) {
- return;
- }
- ReportActionContextMenu.showContextMenu(
- CONST.CONTEXT_MENU_TYPES.REPORT_ACTION,
- event,
- '',
- anchor,
- reportID,
- action.reportActionID,
- ReportUtils.getOriginalReportID(reportID, action),
- undefined,
- checkIfContextMenuActive,
- checkIfContextMenuActive,
- isArchivedRoom,
- );
-}
-
-export {ShowContextMenuContext, showContextMenuForReport};
diff --git a/src/components/ShowContextMenuContext.ts b/src/components/ShowContextMenuContext.ts
new file mode 100644
index 000000000000..17557051bef9
--- /dev/null
+++ b/src/components/ShowContextMenuContext.ts
@@ -0,0 +1,64 @@
+import {createContext} from 'react';
+// eslint-disable-next-line no-restricted-imports
+import type {GestureResponderEvent, Text as RNText} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
+import * as DeviceCapabilities from '@libs/DeviceCapabilities';
+import * as ReportUtils from '@libs/ReportUtils';
+import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
+import CONST from '@src/CONST';
+import type {Report, ReportAction} from '@src/types/onyx';
+
+type ShowContextMenuContextProps = {
+ anchor: RNText | null;
+ report: OnyxEntry;
+ action: OnyxEntry;
+ checkIfContextMenuActive: () => void;
+};
+
+const ShowContextMenuContext = createContext({
+ anchor: null,
+ report: null,
+ action: null,
+ checkIfContextMenuActive: () => {},
+});
+
+ShowContextMenuContext.displayName = 'ShowContextMenuContext';
+
+/**
+ * Show the report action context menu.
+ *
+ * @param event - Press event object
+ * @param anchor - Context menu anchor
+ * @param reportID - Active Report ID
+ * @param action - ReportAction for ContextMenu
+ * @param checkIfContextMenuActive Callback to update context menu active state
+ * @param isArchivedRoom - Is the report an archived room
+ */
+function showContextMenuForReport(
+ event: GestureResponderEvent | MouseEvent,
+ anchor: RNText | null,
+ reportID: string,
+ action: OnyxEntry,
+ checkIfContextMenuActive: () => void,
+ isArchivedRoom = false,
+) {
+ if (!DeviceCapabilities.canUseTouchScreen()) {
+ return;
+ }
+
+ ReportActionContextMenu.showContextMenu(
+ CONST.CONTEXT_MENU_TYPES.REPORT_ACTION,
+ event,
+ '',
+ anchor,
+ reportID,
+ action?.reportActionID,
+ ReportUtils.getOriginalReportID(reportID, action),
+ undefined,
+ checkIfContextMenuActive,
+ checkIfContextMenuActive,
+ isArchivedRoom,
+ );
+}
+
+export {ShowContextMenuContext, showContextMenuForReport};
diff --git a/src/components/ShowMoreButton/index.js b/src/components/ShowMoreButton/index.js
index 34b55fa5dcf1..28c33d185cff 100644
--- a/src/components/ShowMoreButton/index.js
+++ b/src/components/ShowMoreButton/index.js
@@ -1,9 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
-import {Text, View} from 'react-native';
+import {View} from 'react-native';
import _ from 'underscore';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
+import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
diff --git a/src/components/SingleChoiceQuestion.tsx b/src/components/SingleChoiceQuestion.tsx
index 4c2ba9c34a9f..c8bf783032ad 100644
--- a/src/components/SingleChoiceQuestion.tsx
+++ b/src/components/SingleChoiceQuestion.tsx
@@ -1,5 +1,6 @@
import type {ForwardedRef} from 'react';
import React, {forwardRef} from 'react';
+// eslint-disable-next-line no-restricted-imports
import type {Text as RNText} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type {MaybePhraseKey} from '@libs/Localize';
diff --git a/src/components/Text.tsx b/src/components/Text.tsx
index f436b9f4495a..b94530a423f7 100644
--- a/src/components/Text.tsx
+++ b/src/components/Text.tsx
@@ -1,5 +1,6 @@
import type {ForwardedRef} from 'react';
import React from 'react';
+// eslint-disable-next-line no-restricted-imports
import {Text as RNText, StyleSheet} from 'react-native';
import type {TextProps as RNTextProps, TextStyle} from 'react-native';
import useTheme from '@hooks/useTheme';
diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx
index d19d835d68bb..99b3e98588ac 100644
--- a/src/components/TextInput/BaseTextInput/index.native.tsx
+++ b/src/components/TextInput/BaseTextInput/index.native.tsx
@@ -263,7 +263,7 @@ function BaseTextInput(
return (
<>
diff --git a/src/components/TextInput/TextInputLabel/index.tsx b/src/components/TextInput/TextInputLabel/index.tsx
index 507d40f475a7..8f6d3efdcd8d 100644
--- a/src/components/TextInput/TextInputLabel/index.tsx
+++ b/src/components/TextInput/TextInputLabel/index.tsx
@@ -1,4 +1,5 @@
import React, {useEffect, useRef} from 'react';
+// eslint-disable-next-line no-restricted-imports
import type {Text} from 'react-native';
import {Animated} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx
index d3c515115d56..c8cd39b05fcc 100644
--- a/src/components/TextLink.tsx
+++ b/src/components/TextLink.tsx
@@ -1,5 +1,6 @@
import type {ForwardedRef, KeyboardEventHandler, MouseEventHandler} from 'react';
import React, {forwardRef} from 'react';
+// eslint-disable-next-line no-restricted-imports
import type {GestureResponderEvent, Text as RNText, StyleProp, TextStyle} from 'react-native';
import useEnvironment from '@hooks/useEnvironment';
import useThemeStyles from '@hooks/useThemeStyles';
diff --git a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
index 0df9993f8c69..21e19ac7c2e8 100644
--- a/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
+++ b/src/components/UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx
@@ -1,8 +1,9 @@
import Str from 'expensify-common/lib/str';
import React, {useCallback} from 'react';
-import {Text, View} from 'react-native';
+import {View} from 'react-native';
import Avatar from '@components/Avatar';
import {usePersonalDetails} from '@components/OnyxProvider';
+import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
import type UserDetailsTooltipProps from '@components/UserDetailsTooltip/types';
import useLocalize from '@hooks/useLocalize';
diff --git a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.tsx
similarity index 71%
rename from src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js
rename to src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.tsx
index 54e7309ee48b..9f615cef525d 100755
--- a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js
+++ b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.tsx
@@ -1,7 +1,5 @@
-import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {Dimensions, View} from 'react-native';
-import _ from 'underscore';
import GoogleMeetIcon from '@assets/images/google-meet.svg';
import ZoomIcon from '@assets/images/zoom-icon.svg';
import Icon from '@components/Icon';
@@ -10,37 +8,34 @@ import MenuItem from '@components/MenuItem';
import Popover from '@components/Popover';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
+import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
+import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Link from '@userActions/Link';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
-import {defaultProps, propTypes as videoChatButtonAndMenuPropTypes} from './videoChatButtonAndMenuPropTypes';
+import type VideoChatButtonAndMenuProps from './types';
-const propTypes = {
+type BaseVideoChatButtonAndMenuProps = VideoChatButtonAndMenuProps & {
/** Link to open when user wants to create a new google meet meeting */
- googleMeetURL: PropTypes.string.isRequired,
-
- ...videoChatButtonAndMenuPropTypes,
- ...withLocalizePropTypes,
- ...windowDimensionsPropTypes,
+ googleMeetURL: string;
};
-function BaseVideoChatButtonAndMenu(props) {
+function BaseVideoChatButtonAndMenu({googleMeetURL, isConcierge = false, guideCalendarLink}: BaseVideoChatButtonAndMenuProps) {
const theme = useTheme();
const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const {isSmallScreenWidth} = useWindowDimensions();
const [isVideoChatMenuActive, setIsVideoChatMenuActive] = useState(false);
const [videoChatIconPosition, setVideoChatIconPosition] = useState({x: 0, y: 0});
- const videoChatIconWrapperRef = useRef(null);
- const videoChatButtonRef = useRef(null);
+ const videoChatIconWrapperRef = useRef(null);
+ const videoChatButtonRef = useRef(null);
const menuItemData = [
{
icon: ZoomIcon,
- text: props.translate('videoChatButtonAndMenu.zoom'),
+ text: translate('videoChatButtonAndMenu.zoom'),
onPress: () => {
setIsVideoChatMenuActive(false);
Link.openExternalLink(CONST.NEW_ZOOM_MEETING_URL);
@@ -48,10 +43,10 @@ function BaseVideoChatButtonAndMenu(props) {
},
{
icon: GoogleMeetIcon,
- text: props.translate('videoChatButtonAndMenu.googleMeet'),
+ text: translate('videoChatButtonAndMenu.googleMeet'),
onPress: () => {
setIsVideoChatMenuActive(false);
- Link.openExternalLink(props.googleMeetURL);
+ Link.openExternalLink(googleMeetURL);
},
},
];
@@ -87,22 +82,22 @@ function BaseVideoChatButtonAndMenu(props) {
ref={videoChatIconWrapperRef}
onLayout={measureVideoChatIconPosition}
>
-
+
{
// Drop focus to avoid blue focus ring.
- videoChatButtonRef.current.blur();
+ videoChatButtonRef.current?.blur();
// If this is the Concierge chat, we'll open the modal for requesting a setup call instead
- if (props.isConcierge && props.guideCalendarLink) {
- Link.openExternalLink(props.guideCalendarLink);
+ if (isConcierge && guideCalendarLink) {
+ Link.openExternalLink(guideCalendarLink);
return;
}
setIsVideoChatMenuActive((previousVal) => !previousVal);
})}
style={styles.touchableButtonImage}
- accessibilityLabel={props.translate('videoChatButtonAndMenu.tooltip')}
+ accessibilityLabel={translate('videoChatButtonAndMenu.tooltip')}
role={CONST.ROLE.BUTTON}
>
-
- {_.map(menuItemData, ({icon, text, onPress}) => (
+
+ {menuItemData.map(({icon, text, onPress}) => (