diff --git a/android/app/build.gradle b/android/app/build.gradle
index d9eb60471773..c6a9c3147118 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -90,8 +90,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001037005
- versionName "1.3.70-5"
+ versionCode 1001037007
+ versionName "1.3.70-7"
}
flavorDimensions "default"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 440e48e0c83d..03dcc7770df0 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.3.70.5
+ 1.3.70.7
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 9d7fc804acd9..941d232244e1 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.3.70.5
+ 1.3.70.7
diff --git a/package-lock.json b/package-lock.json
index 3e08356a76ef..382dcf45f55e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.3.70-5",
+ "version": "1.3.70-7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.3.70-5",
+ "version": "1.3.70-7",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -52,7 +52,6 @@
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086",
"fbjs": "^3.0.2",
- "focus-trap-react": "^10.2.1",
"htmlparser2": "^7.2.0",
"idb-keyval": "^6.2.1",
"jest-when": "^3.5.2",
@@ -90,10 +89,10 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.76",
+ "react-native-onyx": "1.0.77",
"react-native-pager-view": "^6.2.0",
"react-native-pdf": "^6.7.1",
- "react-native-performance": "^4.0.0",
+ "react-native-performance": "^5.1.0",
"react-native-permissions": "^3.0.1",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
"react-native-plaid-link-sdk": "^10.0.0",
@@ -103,7 +102,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.21.0",
- "react-native-svg": "^13.9.0",
+ "react-native-svg": "^13.13.0",
"react-native-tab-view": "^3.5.2",
"react-native-url-polyfill": "^2.0.0",
"react-native-view-shot": "^3.6.0",
@@ -28295,28 +28294,6 @@
"readable-stream": "^2.3.6"
}
},
- "node_modules/focus-trap": {
- "version": "7.5.2",
- "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz",
- "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==",
- "dependencies": {
- "tabbable": "^6.2.0"
- }
- },
- "node_modules/focus-trap-react": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz",
- "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==",
- "dependencies": {
- "focus-trap": "^7.5.2",
- "tabbable": "^6.2.0"
- },
- "peerDependencies": {
- "prop-types": "^15.8.1",
- "react": ">=16.3.0",
- "react-dom": ">=16.3.0"
- }
- },
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
@@ -40567,9 +40544,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "1.0.76",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.76.tgz",
- "integrity": "sha512-mcMlYQCo1B/kom+4hu7CQKKLwvPFjQAJsVIzV2s9aa8XKNlcnYiJbfuM6RSJ1fFmSIeud4Y66rhv4/oWUkSl5A==",
+ "version": "1.0.77",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.77.tgz",
+ "integrity": "sha512-HmeS1Pz/BkKNbYuhWULC9I0VRBDt8yadG0ZFIW6wuZ+VajhjD960qh7Il1+XzEBI6Vb4d7BZkPcad87ad1IEOQ==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -40583,7 +40560,7 @@
"idb-keyval": "^6.2.1",
"react": ">=18.1.0",
"react-native-device-info": "^10.3.0",
- "react-native-performance": "^4.0.0",
+ "react-native-performance": "^5.1.0",
"react-native-quick-sqlite": "^8.0.0-beta.2"
},
"peerDependenciesMeta": {
@@ -40625,8 +40602,9 @@
}
},
"node_modules/react-native-performance": {
- "version": "4.0.0",
- "license": "MIT",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-performance/-/react-native-performance-5.1.0.tgz",
+ "integrity": "sha512-rq/YBf0/GptSOM/Lj64/1yRq8uN2YE0psFB16wFbYBbTcIEp/0rrgN2HyS5lhvfBOFgKoDRWQ53jHSCb+QJ5eA==",
"peerDependencies": {
"react-native": "*"
}
@@ -40783,8 +40761,9 @@
}
},
"node_modules/react-native-svg": {
- "version": "13.9.0",
- "license": "MIT",
+ "version": "13.13.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz",
+ "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==",
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
@@ -44692,11 +44671,6 @@
"dev": true,
"license": "BSD-3-Clause"
},
- "node_modules/tabbable": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
- "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
- },
"node_modules/table": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
@@ -67708,23 +67682,6 @@
"readable-stream": "^2.3.6"
}
},
- "focus-trap": {
- "version": "7.5.2",
- "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz",
- "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==",
- "requires": {
- "tabbable": "^6.2.0"
- }
- },
- "focus-trap-react": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz",
- "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==",
- "requires": {
- "focus-trap": "^7.5.2",
- "tabbable": "^6.2.0"
- }
- },
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
@@ -76116,9 +76073,9 @@
}
},
"react-native-onyx": {
- "version": "1.0.76",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.76.tgz",
- "integrity": "sha512-mcMlYQCo1B/kom+4hu7CQKKLwvPFjQAJsVIzV2s9aa8XKNlcnYiJbfuM6RSJ1fFmSIeud4Y66rhv4/oWUkSl5A==",
+ "version": "1.0.77",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.77.tgz",
+ "integrity": "sha512-HmeS1Pz/BkKNbYuhWULC9I0VRBDt8yadG0ZFIW6wuZ+VajhjD960qh7Il1+XzEBI6Vb4d7BZkPcad87ad1IEOQ==",
"requires": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -76141,7 +76098,9 @@
}
},
"react-native-performance": {
- "version": "4.0.0",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-performance/-/react-native-performance-5.1.0.tgz",
+ "integrity": "sha512-rq/YBf0/GptSOM/Lj64/1yRq8uN2YE0psFB16wFbYBbTcIEp/0rrgN2HyS5lhvfBOFgKoDRWQ53jHSCb+QJ5eA==",
"requires": {}
},
"react-native-performance-flipper-reporter": {
@@ -76238,7 +76197,9 @@
}
},
"react-native-svg": {
- "version": "13.9.0",
+ "version": "13.13.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz",
+ "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==",
"requires": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
@@ -78848,11 +78809,6 @@
"version": "2.0.15",
"dev": true
},
- "tabbable": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
- "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
- },
"table": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
diff --git a/package.json b/package.json
index 221c3fa7cbf2..0073dedb741c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.3.70-5",
+ "version": "1.3.70-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.",
@@ -94,7 +94,6 @@
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086",
"fbjs": "^3.0.2",
- "focus-trap-react": "^10.2.1",
"htmlparser2": "^7.2.0",
"idb-keyval": "^6.2.1",
"jest-when": "^3.5.2",
@@ -132,10 +131,10 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.76",
+ "react-native-onyx": "1.0.77",
"react-native-pager-view": "^6.2.0",
"react-native-pdf": "^6.7.1",
- "react-native-performance": "^4.0.0",
+ "react-native-performance": "^5.1.0",
"react-native-permissions": "^3.0.1",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
"react-native-plaid-link-sdk": "^10.0.0",
@@ -145,7 +144,7 @@
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.21.0",
- "react-native-svg": "^13.9.0",
+ "react-native-svg": "^13.13.0",
"react-native-tab-view": "^3.5.2",
"react-native-url-polyfill": "^2.0.0",
"react-native-view-shot": "^3.6.0",
diff --git a/src/CONST.ts b/src/CONST.ts
index 93576e0ccf9d..762186439cec 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1170,6 +1170,7 @@ const CONST = {
SMALL_NORMAL: 'small-normal',
},
EXPENSIFY_CARD: {
+ BANK: 'Expensify Card',
FRAUD_TYPES: {
DOMAIN: 'domain',
INDIVIDUAL: 'individal',
diff --git a/src/Expensify.js b/src/Expensify.js
index 1086bd32cff9..fba65e42c06c 100644
--- a/src/Expensify.js
+++ b/src/Expensify.js
@@ -30,7 +30,6 @@ import KeyboardShortcutsModal from './components/KeyboardShortcutsModal';
import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
-import DownloadAppModal from './components/DownloadAppModal';
import DeeplinkWrapper from './components/DeeplinkWrapper';
// This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection
@@ -193,7 +192,6 @@ function Expensify(props) {
{shouldInit && (
<>
-
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 80afc4d5ffee..6eb795befe3c 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -87,9 +87,6 @@ const ONYXKEYS = {
SESSION: 'session',
BETAS: 'betas',
- /** Denotes if the Download App Banner has been dismissed */
- SHOW_DOWNLOAD_APP_BANNER: 'showDownloadAppBanner',
-
/** NVP keys
* Contains the user's payPalMe data */
PAYPAL: 'paypal',
@@ -307,7 +304,6 @@ type OnyxValues = {
[ONYXKEYS.ACTIVE_CLIENTS]: string[];
[ONYXKEYS.DEVICE_ID]: string;
[ONYXKEYS.IS_SIDEBAR_LOADED]: boolean;
- [ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER]: boolean;
[ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[];
[ONYXKEYS.QUEUED_ONYX_UPDATES]: OnyxTypes.QueuedOnyxUpdates;
[ONYXKEYS.CURRENT_DATE]: string;
diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js
index d5da25c89576..8a623a44709f 100644
--- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js
+++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js
@@ -59,6 +59,7 @@ function extractAttachmentsFromReport(report, reportActions) {
isAuthTokenRequired: true,
file: {name: transaction.filename},
isReceipt: true,
+ transactionID,
});
return;
}
diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js
index 5c731a0ccfee..574cb496d02f 100644
--- a/src/components/Attachments/AttachmentCarousel/index.js
+++ b/src/components/Attachments/AttachmentCarousel/index.js
@@ -19,6 +19,7 @@ import BlockingView from '../../BlockingViews/BlockingView';
import * as Illustrations from '../../Icon/Illustrations';
import variables from '../../../styles/variables';
import * as DeviceCapabilities from '../../../libs/DeviceCapabilities';
+import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
const viewabilityConfig = {
// To facilitate paging through the attachments, we want to consider an item "viewable" when it is
@@ -38,13 +39,25 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl
const [activeSource, setActiveSource] = useState(source);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();
+ const compareImage = useCallback(
+ (attachment) => {
+ if (attachment.isReceipt) {
+ const action = ReportActionsUtils.getParentReportAction(report);
+ const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']);
+ return attachment.transactionID === transactionID;
+ }
+ return attachment.source === source;
+ },
+ [source, report],
+ );
+
useEffect(() => {
const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions);
- const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source);
+ const initialPage = _.findIndex(attachmentsFromReport, compareImage);
// Dismiss the modal when deleting an attachment during its display in preview.
- if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) {
+ if (initialPage === -1 && _.find(attachments, compareImage)) {
Navigation.dismissModal();
} else {
setPage(initialPage);
@@ -57,7 +70,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl
if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [report, reportActions, source]);
+ }, [reportActions, compareImage]);
/**
* Updates the page state when the user navigates between attachments
diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js
index 95cda7c2f5c9..a7a2f35a2ccc 100644
--- a/src/components/Attachments/AttachmentCarousel/index.native.js
+++ b/src/components/Attachments/AttachmentCarousel/index.native.js
@@ -16,6 +16,7 @@ import * as Illustrations from '../../Icon/Illustrations';
import variables from '../../../styles/variables';
import compose from '../../../libs/compose';
import withLocalize from '../../withLocalize';
+import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, setDownloadButtonVisibility, translate}) {
const pagerRef = useRef(null);
@@ -27,13 +28,25 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose,
const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();
+ const compareImage = useCallback(
+ (attachment) => {
+ if (attachment.isReceipt) {
+ const action = ReportActionsUtils.getParentReportAction(report);
+ const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']);
+ return attachment.transactionID === transactionID;
+ }
+ return attachment.source === source;
+ },
+ [source, report],
+ );
+
useEffect(() => {
const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions);
- const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source);
+ const initialPage = _.findIndex(attachmentsFromReport, compareImage);
// Dismiss the modal when deleting an attachment during its display in preview.
- if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) {
+ if (initialPage === -1 && _.find(attachments, compareImage)) {
Navigation.dismissModal();
} else {
setPage(initialPage);
@@ -46,7 +59,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose,
if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [report, reportActions, source]);
+ }, [reportActions, compareImage]);
/**
* Updates the page state when the user navigates between attachments
diff --git a/src/components/Button/index.js b/src/components/Button/index.js
index bfde528a4750..c16860344837 100644
--- a/src/components/Button/index.js
+++ b/src/components/Button/index.js
@@ -218,6 +218,7 @@ class Button extends Component {
this.props.icon && styles.textAlignLeft,
...this.props.textStyles,
]}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{this.props.text}
diff --git a/src/components/CollapsibleSection/index.js b/src/components/CollapsibleSection/index.js
index e9c3a90a7b30..7009d1905e1d 100644
--- a/src/components/CollapsibleSection/index.js
+++ b/src/components/CollapsibleSection/index.js
@@ -51,6 +51,7 @@ class CollapsibleSection extends React.Component {
{this.props.title}
diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js
index 9a72d4e7d584..ab3e23d6b1c1 100644
--- a/src/components/ConfirmContent.js
+++ b/src/components/ConfirmContent.js
@@ -100,8 +100,8 @@ function ConfirmContent(props) {
diff --git a/src/components/DownloadAppModal.js b/src/components/DownloadAppModal.js
deleted file mode 100644
index c96c6b3d28c0..000000000000
--- a/src/components/DownloadAppModal.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, {useState} from 'react';
-import PropTypes from 'prop-types';
-import {withOnyx} from 'react-native-onyx';
-import ONYXKEYS from '../ONYXKEYS';
-import styles from '../styles/styles';
-import CONST from '../CONST';
-import AppIcon from '../../assets/images/expensify-app-icon.svg';
-import useLocalize from '../hooks/useLocalize';
-import * as Link from '../libs/actions/Link';
-import * as Browser from '../libs/Browser';
-import getOperatingSystem from '../libs/getOperatingSystem';
-import setShowDownloadAppModal from '../libs/actions/DownloadAppModal';
-import ConfirmModal from './ConfirmModal';
-
-const propTypes = {
- /** ONYX PROP to hide banner for a user that has dismissed it */
- // eslint-disable-next-line react/forbid-prop-types
- showDownloadAppBanner: PropTypes.bool,
-
- /** Whether the user is logged in */
- isAuthenticated: PropTypes.bool.isRequired,
-};
-
-const defaultProps = {
- showDownloadAppBanner: true,
-};
-
-function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) {
- const [shouldShowBanner, setShouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner);
-
- const {translate} = useLocalize();
-
- const handleCloseBanner = () => {
- setShowDownloadAppModal(false);
- setShouldShowBanner(false);
- };
-
- let link = '';
-
- if (getOperatingSystem() === CONST.OS.IOS) {
- link = CONST.APP_DOWNLOAD_LINKS.IOS;
- } else if (getOperatingSystem() === CONST.OS.ANDROID) {
- link = CONST.APP_DOWNLOAD_LINKS.ANDROID;
- }
-
- const handleOpenAppStore = () => {
- setShowDownloadAppModal(false);
- setShouldShowBanner(false);
- Link.openExternalLink(link, true);
- };
-
- return (
-
- );
-}
-
-DownloadAppModal.displayName = 'DownloadAppModal';
-DownloadAppModal.propTypes = propTypes;
-DownloadAppModal.defaultProps = defaultProps;
-
-export default withOnyx({
- showDownloadAppBanner: {
- key: ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER,
- },
-})(DownloadAppModal);
diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js
deleted file mode 100644
index 2dcab7b9d998..000000000000
--- a/src/components/FocusTrapView/index.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * The FocusTrap is only used on web and desktop
- */
-import React, {useEffect, useRef} from 'react';
-import FocusTrap from 'focus-trap-react';
-import {View} from 'react-native';
-import {PropTypes} from 'prop-types';
-import {useIsFocused} from '@react-navigation/native';
-
-const propTypes = {
- /** Children to wrap with FocusTrap */
- children: PropTypes.node.isRequired,
-
- /** Whether to enable the FocusTrap */
- enabled: PropTypes.bool,
-
- /**
- * Whether to disable auto focus
- * It is used when the component inside the FocusTrap have their own auto focus logic
- */
- shouldEnableAutoFocus: PropTypes.bool,
-};
-
-const defaultProps = {
- enabled: true,
- shouldEnableAutoFocus: false,
-};
-
-function FocusTrapView({enabled, shouldEnableAutoFocus, ...props}) {
- const isFocused = useIsFocused();
-
- /**
- * Focus trap always needs a focusable element.
- * In case that we don't have any focusable elements in the modal,
- * the FocusTrap will use fallback View element using this ref.
- */
- const ref = useRef(null);
-
- /**
- * We have to set the 'tabindex' attribute to 0 to make the View focusable.
- * Currently, it is not possible to set this through props.
- * After the upgrade of 'react-native-web' to version 0.19 we can use 'tabIndex={0}' prop instead.
- */
- useEffect(() => {
- if (!ref.current) {
- return;
- }
- ref.current.setAttribute('tabindex', '0');
- }, []);
-
- return enabled ? (
- shouldEnableAutoFocus && ref.current,
- fallbackFocus: () => ref.current,
- clickOutsideDeactivates: true,
- }}
- >
-
-
- ) : (
- props.children
- );
-}
-
-FocusTrapView.displayName = 'FocusTrapView';
-FocusTrapView.propTypes = propTypes;
-FocusTrapView.defaultProps = defaultProps;
-
-export default FocusTrapView;
diff --git a/src/components/FocusTrapView/index.native.js b/src/components/FocusTrapView/index.native.js
deleted file mode 100644
index 5720601f5a2b..000000000000
--- a/src/components/FocusTrapView/index.native.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * The FocusTrap is only used on web and desktop
- */
-
-function FocusTrapView({children}) {
- return children;
-}
-
-FocusTrapView.displayName = 'FocusTrapView';
-
-export default FocusTrapView;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
index d91510c3ec6a..262a4d1f178e 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import React from 'react';
+import CONST from '../../../CONST';
import htmlRendererPropTypes from './htmlRendererPropTypes';
import withLocalize, {withLocalizePropTypes} from '../../withLocalize';
import Text from '../../Text';
@@ -28,6 +29,7 @@ function EditedRenderer(props) {
{' '}
diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js
index b21a275a6597..454aacc8a03b 100644
--- a/src/components/MagicCodeInput.js
+++ b/src/components/MagicCodeInput.js
@@ -258,7 +258,13 @@ function MagicCodeInput(props) {
{/* Hide the input above the text. Cannot set opacity to 0 as it would break pasting on iOS Safari. */}
(inputRefs.current[index] = ref)}
+ ref={(ref) => {
+ inputRefs.current[index] = ref;
+ // Setting attribute type to "search" to prevent Password Manager from appearing in Mobile Chrome
+ if (ref && ref.setAttribute) {
+ ref.setAttribute('type', 'search');
+ }
+ }}
autoFocus={index === 0 && props.autoFocus}
inputMode="numeric"
textContentType="oneTimeCode"
diff --git a/src/components/MapView/responder/index.android.ts b/src/components/MapView/responder/index.android.ts
new file mode 100644
index 000000000000..a0fce71d8ef5
--- /dev/null
+++ b/src/components/MapView/responder/index.android.ts
@@ -0,0 +1,8 @@
+import {PanResponder} from 'react-native';
+
+const responder = PanResponder.create({
+ onStartShouldSetPanResponder: () => true,
+ onPanResponderTerminationRequest: () => false,
+});
+
+export default responder;
diff --git a/src/components/MapView/responder/index.ts b/src/components/MapView/responder/index.ts
index a0fce71d8ef5..422d6c1b4963 100644
--- a/src/components/MapView/responder/index.ts
+++ b/src/components/MapView/responder/index.ts
@@ -1,7 +1,7 @@
import {PanResponder} from 'react-native';
const responder = PanResponder.create({
- onStartShouldSetPanResponder: () => true,
+ onMoveShouldSetPanResponder: () => true,
onPanResponderTerminationRequest: () => false,
});
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index 4f654df2a7b2..88d3df3b7001 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -228,6 +228,7 @@ const MenuItem = React.forwardRef((props, ref) => {
{convertToLTR(props.title)}
diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js
index 4c6ba1307fb7..916646b5619a 100644
--- a/src/components/MultipleAvatars.js
+++ b/src/components/MultipleAvatars.js
@@ -221,6 +221,7 @@ function MultipleAvatars(props) {
{`+${avatars.length - props.maxAvatarsInRow}`}
@@ -278,6 +279,7 @@ function MultipleAvatars(props) {
{`+${props.icons.length - 1}`}
diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js
index fe0c36d32e41..1e1ef3c3fad3 100644
--- a/src/components/NewDatePicker/CalendarPicker/index.js
+++ b/src/components/NewDatePicker/CalendarPicker/index.js
@@ -130,7 +130,10 @@ class CalendarPicker extends React.PureComponent {
return (
-
+
this.setState({isYearPickerVisible: true})}
style={[styles.alignItemsCenter, styles.flexRow, styles.flex1, styles.justifyContentStart]}
@@ -186,6 +189,7 @@ class CalendarPicker extends React.PureComponent {
{dayOfWeek[0]}
@@ -212,6 +216,7 @@ class CalendarPicker extends React.PureComponent {
accessibilityLabel={day ? day.toString() : undefined}
focusable={Boolean(day)}
accessible={Boolean(day)}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{({hovered, pressed}) => (
{
+ const listener = () => {
+ if (!isVisible) {
+ return;
+ }
+
+ onClose();
+ };
+ window.addEventListener('popstate', listener);
+ return () => {
+ window.removeEventListener('popstate', listener);
+ };
+ }, [onClose, isVisible]);
+
+ if (!fullscreen && !isSmallScreenWidth) {
return createPortal(
,
document.body,
);
}
- if (props.withoutOverlay && !props.isSmallScreenWidth) {
+ if (withoutOverlay && !isSmallScreenWidth) {
// eslint-disable-next-line react/jsx-props-no-spreading
return createPortal(, document.body);
}
@@ -36,12 +54,12 @@ function Popover(props) {
);
}
diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js
index 65fca85bc324..774ac3ac5092 100644
--- a/src/components/Pressable/GenericPressable/index.js
+++ b/src/components/Pressable/GenericPressable/index.js
@@ -15,7 +15,7 @@ const WebGenericPressable = forwardRef((props, ref) => (
aria-labelledby={props.accessibilityLabelledBy}
aria-valuenow={props.accessibilityValue}
nativeID={props.nativeID}
- dataSet={{tag: 'pressable', ...(props.noDragArea && {dragArea: false})}}
+ dataSet={{tag: 'pressable', ...(props.noDragArea && {dragArea: false}), ...props.dataSet}}
/>
));
diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js
index 3f9039489b7c..d84a3f282e97 100644
--- a/src/components/PressableWithSecondaryInteraction/index.js
+++ b/src/components/PressableWithSecondaryInteraction/index.js
@@ -47,7 +47,7 @@ function PressableWithSecondaryInteraction({
if (forwardedRef) {
if (_.isFunction(forwardedRef)) {
- forwardedRef(pressableRef);
+ forwardedRef(pressableRef.current);
} else if (_.isObject(forwardedRef)) {
// eslint-disable-next-line no-param-reassign
forwardedRef.current = pressableRef.current;
diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.js
index 922be96084d8..0315c63aabf1 100644
--- a/src/components/Reactions/AddReactionBubble.js
+++ b/src/components/Reactions/AddReactionBubble.js
@@ -94,6 +94,7 @@ function AddReactionBubble(props) {
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
// disable dimming
pressDimmingValue={1}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{({hovered, pressed}) => (
<>
diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js
index bb37735d6920..818bc8f33309 100644
--- a/src/components/Reactions/EmojiReactionBubble.js
+++ b/src/components/Reactions/EmojiReactionBubble.js
@@ -60,6 +60,7 @@ function EmojiReactionBubble(props) {
styles.emojiReactionBubble,
StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, props.hasUserReacted, props.isContextMenu),
props.shouldBlockReactions && styles.cursorDisabled,
+ styles.userSelectNone,
]}
onPress={() => {
if (props.shouldBlockReactions) {
@@ -83,9 +84,10 @@ function EmojiReactionBubble(props) {
}}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={props.emojiCodes.join('')}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
- {props.emojiCodes.join('')}
- {props.count > 0 && {props.count}}
+ {props.emojiCodes.join('')}
+ {props.count > 0 && {props.count}}
);
}
diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.js
index 82f83cb1e961..a22a2967cefe 100644
--- a/src/components/Reactions/MiniQuickEmojiReactions.js
+++ b/src/components/Reactions/MiniQuickEmojiReactions.js
@@ -80,7 +80,12 @@ function MiniQuickEmojiReactions(props) {
tooltipText={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, props.preferredLocale)}:`}
onPress={Session.checkIfActionIsAllowed(() => props.onEmojiSelected(emoji, props.emojiReactions))}
>
- {EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)}
+
+ {EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)}
+
))}
{requestMerchant}
)}
-
+
{!isCurrentUserManager && props.shouldShowPendingConversionMessage && (
- {props.translate('iou.pendingConversionMessage')}
+ {props.translate('iou.pendingConversionMessage')}
)}
- {shouldShowDescription && {description}}
+ {shouldShowDescription && {description}}
{props.isBillSplit && !_.isEmpty(participantAccountIDs) && (
-
+
{props.translate('iou.amountEach', {
amount: CurrencyUtils.convertToDisplayString(
IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency),
@@ -342,6 +342,7 @@ function MoneyRequestPreview(props) {
onLongPress={showContextMenu}
accessibilityLabel={props.isBillSplit ? props.translate('iou.split') : props.translate('iou.cash')}
accessibilityHint={CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)}
+ style={styles.moneyRequestPreviewBox}
>
{childContainer}
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index a9264812b99d..902e21b9ce25 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -105,6 +105,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans
style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]}
/>
+
{hasReceipt && (
Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))}
+ wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
+ numberOfLinesTitle={0}
/>
diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js
index 1f60dddef6ec..5f8151b385a2 100644
--- a/src/components/ReportActionItem/ReportPreview.js
+++ b/src/components/ReportActionItem/ReportPreview.js
@@ -177,7 +177,7 @@ function ReportPreview(props) {
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)}
- style={[styles.flexRow, styles.justifyContentBetween]}
+ style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]}
accessibilityRole="button"
accessibilityLabel={props.translate('iou.viewDetails')}
>
diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js
index f0f8b8a4b09b..f760e5d5aeb4 100644
--- a/src/components/ScreenWrapper/index.js
+++ b/src/components/ScreenWrapper/index.js
@@ -3,7 +3,6 @@ import React from 'react';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import {PickerAvoidingView} from 'react-native-picker-select';
-import FocusTrapView from '../FocusTrapView';
import KeyboardAvoidingView from '../KeyboardAvoidingView';
import CONST from '../../CONST';
import styles from '../../styles/styles';
@@ -125,26 +124,20 @@ class ScreenWrapper extends React.Component {
style={styles.flex1}
enabled={this.props.shouldEnablePickerAvoiding}
>
-
-
- {this.props.environment === CONST.ENVIRONMENT.DEV && }
- {this.props.environment === CONST.ENVIRONMENT.DEV && }
- {
- // If props.children is a function, call it to provide the insets to the children.
- _.isFunction(this.props.children)
- ? this.props.children({
- insets,
- safeAreaPaddingBottomStyle,
- didScreenTransitionEnd: this.state.didScreenTransitionEnd,
- })
- : this.props.children
- }
- {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && }
-
+
+ {this.props.environment === CONST.ENVIRONMENT.DEV && }
+ {this.props.environment === CONST.ENVIRONMENT.DEV && }
+ {
+ // If props.children is a function, call it to provide the insets to the children.
+ _.isFunction(this.props.children)
+ ? this.props.children({
+ insets,
+ safeAreaPaddingBottomStyle,
+ didScreenTransitionEnd: this.state.didScreenTransitionEnd,
+ })
+ : this.props.children
+ }
+ {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && }
diff --git a/src/components/ScreenWrapper/propTypes.js b/src/components/ScreenWrapper/propTypes.js
index c3538b3c026d..83033d9e97b7 100644
--- a/src/components/ScreenWrapper/propTypes.js
+++ b/src/components/ScreenWrapper/propTypes.js
@@ -48,12 +48,6 @@ const propTypes = {
/** Styles for the offline indicator */
offlineIndicatorStyle: stylePropTypes,
-
- /** Whether to disable the focus trap */
- shouldDisableFocusTrap: PropTypes.bool,
-
- /** Whether to disable auto focus of the focus trap */
- shouldEnableAutoFocus: PropTypes.bool,
};
const defaultProps = {
@@ -69,8 +63,6 @@ const defaultProps = {
shouldShowOfflineIndicator: true,
offlineIndicatorStyle: [],
headerGapStyles: [],
- shouldDisableFocusTrap: false,
- shouldEnableAutoFocus: false,
};
export {propTypes, defaultProps};
diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js
index 2a1bca24ab74..e57f00e1849c 100644
--- a/src/components/SelectionList/BaseSelectionList.js
+++ b/src/components/SelectionList/BaseSelectionList.js
@@ -348,6 +348,7 @@ function BaseSelectionList({
accessibilityRole="button"
accessibilityState={{checked: flattenedSections.allSelected}}
disabled={flattenedSections.allOptions.length === flattenedSections.disabledOptionsIndexes.length}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js
index a3c25f09af3b..014e0cf879a5 100644
--- a/src/components/SelectionList/UserListItem.js
+++ b/src/components/SelectionList/UserListItem.js
@@ -63,6 +63,7 @@ function UserListItem({item, isFocused = false, showTooltip, onSelectRow, onDism
hoverDimmingValue={1}
hoverStyle={styles.hoveredComponentBG}
focusStyle={styles.hoveredComponentBG}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{props.prefixCharacter}
diff --git a/src/components/withNavigationFallback.js b/src/components/withNavigationFallback.js
index bc4ea5dd3fad..e82946c9e049 100644
--- a/src/components/withNavigationFallback.js
+++ b/src/components/withNavigationFallback.js
@@ -1,39 +1,30 @@
-import React, {Component} from 'react';
+import React, {forwardRef, useContext, useMemo} from 'react';
import {NavigationContext} from '@react-navigation/core';
import getComponentDisplayName from '../libs/getComponentDisplayName';
import refPropTypes from './refPropTypes';
export default function (WrappedComponent) {
- class WithNavigationFallback extends Component {
- render() {
- if (!this.context) {
- return (
- true,
- addListener: () => () => {},
- removeListener: () => () => {},
- }}
- >
-
-
- );
- }
+ function WithNavigationFallback(props) {
+ const context = useContext(NavigationContext);
- return (
+ const navigationContextValue = useMemo(() => ({isFocused: () => true, addListener: () => () => {}, removeListener: () => () => {}}), []);
+
+ return context ? (
+
+ ) : (
+
- );
- }
+
+ );
}
- WithNavigationFallback.contextType = NavigationContext;
WithNavigationFallback.displayName = `WithNavigationFocusWithFallback(${getComponentDisplayName(WrappedComponent)})`;
WithNavigationFallback.propTypes = {
forwardedRef: refPropTypes,
@@ -41,7 +32,8 @@ export default function (WrappedComponent) {
WithNavigationFallback.defaultProps = {
forwardedRef: undefined,
};
- return React.forwardRef((props, ref) => (
+
+ return forwardRef((props, ref) => (
variables.mobileResponsiveWidthBreakpoint && windowWidth <= variables.tabletResponsiveWidthBreakpoint;
const isLargeScreenWidth = windowWidth > variables.tabletResponsiveWidthBreakpoint;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 9c44b4356a31..c31e39319a98 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -344,11 +344,6 @@ export default {
`It's always great to see a new face around here! Please enter the magic code sent to ${login}. It should arrive within a minute or two.`,
welcomeEnterMagicCode: ({login}: WelcomeEnterMagicCodeParams) => `Please enter the magic code sent to ${login}. It should arrive within a minute or two.`,
},
- DownloadAppModal: {
- downloadTheApp: 'Download the app',
- keepTheConversationGoing: 'Keep the conversation going in New Expensify, download the app for an enhanced experience.',
- noThanks: 'No thanks',
- },
login: {
hero: {
header: 'Split bills, request payments, and chat with friends.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index a50b6821fc34..d83104ff85e0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -335,11 +335,6 @@ export default {
`¡Siempre es genial ver una cara nueva por aquí! Por favor ingresa el código mágico enviado a ${login}. Debería llegar en un par de minutos.`,
welcomeEnterMagicCode: ({login}: WelcomeEnterMagicCodeParams) => `Por favor, introduce el código mágico enviado a ${login}. Debería llegar en un par de minutos.`,
},
- DownloadAppModal: {
- downloadTheApp: 'Descarga la aplicación',
- keepTheConversationGoing: 'Mantén la conversación en New Expensify, descarga la aplicación para una experiencia mejorada.',
- noThanks: 'No, gracias',
- },
login: {
hero: {
header: 'Divida las facturas, solicite pagos y chatee con sus amigos.',
diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts
index bbb938a666ac..beb0ea800091 100644
--- a/src/libs/CardUtils.ts
+++ b/src/libs/CardUtils.ts
@@ -1,3 +1,6 @@
+import {Card} from '../types/onyx';
+import CONST from '../CONST';
+
/**
* @returns string with a month in MM format
*/
@@ -15,4 +18,11 @@ function getYearFromExpirationDateString(expirationDateString: string) {
return cardYear.length === 2 ? `20${cardYear}` : cardYear;
}
-export {getMonthFromExpirationDateString, getYearFromExpirationDateString};
+function getCompanyCards(cardList: {string: Card}) {
+ if (!cardList) {
+ return [];
+ }
+ return Object.values(cardList).filter((card) => card.bank !== CONST.EXPENSIFY_CARD.BANK);
+}
+
+export {getMonthFromExpirationDateString, getYearFromExpirationDateString, getCompanyCards};
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index f24959c4bac2..9396ea921b61 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -1535,6 +1535,11 @@ function getModifiedExpenseMessage(reportAction) {
if (hasModifiedMerchant) {
return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.merchant, reportActionOriginalMessage.oldMerchant, Localize.translateLocal('common.merchant'), true);
}
+
+ const hasModifiedCategory = _.has(reportActionOriginalMessage, 'oldCategory') && _.has(reportActionOriginalMessage, 'category');
+ if (hasModifiedCategory) {
+ return getProperSchemaForModifiedExpenseMessage(reportActionOriginalMessage.category, reportActionOriginalMessage.oldCategory, Localize.translateLocal('common.category'), true);
+ }
}
/**
@@ -1575,6 +1580,11 @@ function getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, i
originalMessage.currency = lodashGet(transactionChanges, 'currency', originalMessage.oldCurrency);
}
+ if (_.has(transactionChanges, 'category')) {
+ originalMessage.oldCategory = TransactionUtils.getCategory(oldTransaction);
+ originalMessage.category = transactionChanges.category;
+ }
+
return originalMessage;
}
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index fffe43b88ee9..a9c3f8775cba 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -438,6 +438,15 @@ function beginDeepLinkRedirect(shouldAuthenticateWithCurrentAccount = true) {
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects('OpenOldDotLink', {shouldRetry: false}, {}).then((response) => {
+ if (!response) {
+ Log.alert(
+ 'Trying to redirect via deep link, but the response is empty. User likely not authenticated.',
+ {response, shouldAuthenticateWithCurrentAccount, currentUserAccountID},
+ true,
+ );
+ return;
+ }
+
Browser.openRouteInDesktopApp(response.shortLivedAuthToken, currentUserEmail);
});
}
diff --git a/src/libs/actions/Device/getDeviceInfo/getBaseInfo.js b/src/libs/actions/Device/getDeviceInfo/getBaseInfo.js
index a02178cbc0a3..bb66f3fe7a9b 100644
--- a/src/libs/actions/Device/getDeviceInfo/getBaseInfo.js
+++ b/src/libs/actions/Device/getDeviceInfo/getBaseInfo.js
@@ -1,8 +1,8 @@
-import {version} from '../../../../../package.json';
+import packageConfig from '../../../../../package.json';
export default function getBaseInfo() {
return {
- app_version: version,
+ app_version: packageConfig.version,
timestamp: new Date().toISOString().slice(0, 19),
};
}
diff --git a/src/libs/actions/DownloadAppModal.js b/src/libs/actions/DownloadAppModal.js
deleted file mode 100644
index 5dc2d3fdca22..000000000000
--- a/src/libs/actions/DownloadAppModal.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import Onyx from 'react-native-onyx';
-import ONYXKEYS from '../../ONYXKEYS';
-
-/**
- * @param {Boolean} shouldShowBanner
- */
-function setShowDownloadAppModal(shouldShowBanner) {
- Onyx.set(ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER, shouldShowBanner);
-}
-
-export default setShowDownloadAppModal;
diff --git a/src/libs/actions/OnyxUpdateManager.js b/src/libs/actions/OnyxUpdateManager.js
index f0051b85f302..e0f3f8fd4622 100644
--- a/src/libs/actions/OnyxUpdateManager.js
+++ b/src/libs/actions/OnyxUpdateManager.js
@@ -1,9 +1,11 @@
import Onyx from 'react-native-onyx';
+import _ from 'underscore';
import ONYXKEYS from '../../ONYXKEYS';
import Log from '../Log';
import * as SequentialQueue from '../Network/SequentialQueue';
import * as App from './App';
import * as OnyxUpdates from './OnyxUpdates';
+import CONST from '../../CONST';
// This file is in charge of looking at the updateIDs coming from the server and comparing them to the last updateID that the client has.
// If the client is behind the server, then we need to
@@ -35,6 +37,19 @@ export default () => {
return;
}
+ // Since we used the same key that used to store another object, let's confirm that the current object is
+ // following the new format before we proceed. If it isn't, then let's clear the object in Onyx.
+ if (
+ !_.isObject(val) ||
+ !_.has(val, 'type') ||
+ (!(val.type === CONST.ONYX_UPDATE_TYPES.HTTPS && _.has(val, 'request') && _.has(val, 'response')) && !(val.type === CONST.ONYX_UPDATE_TYPES.PUSHER && _.has(val, 'updates')))
+ ) {
+ console.debug('[OnyxUpdateManager] Invalid format found for updates, cleaning and unpausing the queue');
+ Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null);
+ SequentialQueue.unpause();
+ return;
+ }
+
const updateParams = val;
const lastUpdateIDFromServer = val.lastUpdateID;
const previousUpdateIDFromServer = val.previousUpdateID;
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 5cf0e51279a9..2a34c839a94e 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1760,10 +1760,6 @@ function openReportFromDeepLink(url, isAuthenticated) {
// Navigate to the report after sign-in/sign-up.
InteractionManager.runAfterInteractions(() => {
Session.waitForUserSignIn().then(() => {
- if (reportID) {
- Navigation.navigate(ROUTES.getReportRoute(reportID), CONST.NAVIGATION.TYPE.UP);
- return;
- }
if (route === ROUTES.CONCIERGE) {
navigateToConciergeChat();
return;
diff --git a/src/pages/EditRequestDescriptionPage.js b/src/pages/EditRequestDescriptionPage.js
index 1db81c88daae..3916daecd33c 100644
--- a/src/pages/EditRequestDescriptionPage.js
+++ b/src/pages/EditRequestDescriptionPage.js
@@ -10,6 +10,7 @@ import styles from '../styles/styles';
import Navigation from '../libs/Navigation/Navigation';
import CONST from '../CONST';
import useLocalize from '../hooks/useLocalize';
+import * as Browser from '../libs/Browser';
const propTypes = {
/** Transaction default description value */
@@ -49,6 +50,10 @@ function EditRequestDescriptionPage({defaultDescription, onSubmit}) {
accessibilityLabel={translate('moneyRequestConfirmationList.whatsItFor')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT}
ref={(e) => (descriptionInputRef.current = e)}
+ autoGrowHeight
+ containerStyles={[styles.autoGrowHeightMultilineInput]}
+ textAlignVertical="top"
+ submitOnEnter={!Browser.isMobile()}
/>
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index b306164a8ba0..19f2b1fdc0c6 100755
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -144,7 +144,7 @@ function ProfilePage(props) {
const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0;
return (
-
+
Navigation.goBack(navigateBackTo)}
diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js
index 67d4ebb57876..2ee29380ff80 100755
--- a/src/pages/SearchPage.js
+++ b/src/pages/SearchPage.js
@@ -1,5 +1,5 @@
import _ from 'underscore';
-import React, {useCallback, useEffect, useState, useMemo} from 'react';
+import React, {Component} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
@@ -9,15 +9,17 @@ import * as ReportUtils from '../libs/ReportUtils';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
import Navigation from '../libs/Navigation/Navigation';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../components/withWindowDimensions';
import * as Report from '../libs/actions/Report';
import HeaderWithBackButton from '../components/HeaderWithBackButton';
import ScreenWrapper from '../components/ScreenWrapper';
import Timing from '../libs/actions/Timing';
import CONST from '../CONST';
+import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
+import compose from '../libs/compose';
import personalDetailsPropType from './personalDetailsPropType';
import reportPropTypes from './reportPropTypes';
import Performance from '../libs/Performance';
-import useLocalize from '../hooks/useLocalize';
const propTypes = {
/* Onyx Props */
@@ -30,6 +32,11 @@ const propTypes = {
/** All reports shared with the user */
reports: PropTypes.objectOf(reportPropTypes),
+
+ /** Window Dimensions Props */
+ ...windowDimensionsPropTypes,
+
+ ...withLocalizePropTypes,
};
const defaultProps = {
@@ -38,158 +45,172 @@ const defaultProps = {
reports: {},
};
-function SearchPage({betas, personalDetails, reports}) {
- // Data for initialization (runs only on the first render)
- const {
- recentReports: initialRecentReports,
- personalDetails: initialPersonalDetails,
- userToInvite: initialUserToInvite,
- // Ignoring the rule because in this case we need the data only initially
- // eslint-disable-next-line react-hooks/exhaustive-deps
- } = useMemo(() => OptionsListUtils.getSearchOptions(reports, personalDetails, '', betas), []);
-
- const [searchValue, setSearchValue] = useState('');
- const [searchOptions, setSearchOptions] = useState({
- recentReports: initialRecentReports,
- personalDetails: initialPersonalDetails,
- userToInvite: initialUserToInvite,
- });
-
- const {translate} = useLocalize();
-
- const updateOptions = useCallback(() => {
- const {
- recentReports: localRecentReports,
- personalDetails: localPersonalDetails,
- userToInvite: localUserToInvite,
- } = OptionsListUtils.getSearchOptions(reports, personalDetails, searchValue.trim(), betas);
-
- setSearchOptions({
- recentReports: localRecentReports,
- personalDetails: localPersonalDetails,
- userToInvite: localUserToInvite,
- });
- }, [reports, personalDetails, searchValue, betas]);
-
- const debouncedUpdateOptions = useMemo(() => _.debounce(updateOptions, 75), [updateOptions]);
+class SearchPage extends Component {
+ constructor(props) {
+ super(props);
- useEffect(() => {
Timing.start(CONST.TIMING.SEARCH_RENDER);
Performance.markStart(CONST.TIMING.SEARCH_RENDER);
- }, []);
- useEffect(() => {
- debouncedUpdateOptions();
- }, [searchValue, debouncedUpdateOptions]);
+ this.searchRendered = this.searchRendered.bind(this);
+ this.selectReport = this.selectReport.bind(this);
+ this.onChangeText = this.onChangeText.bind(this);
+ this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75);
+
+ const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas);
+
+ this.state = {
+ searchValue: '',
+ recentReports,
+ personalDetails,
+ userToInvite,
+ };
+ }
+
+ componentDidUpdate(prevProps) {
+ if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) {
+ return;
+ }
+ this.updateOptions();
+ }
+
+ onChangeText(searchValue = '') {
+ this.setState({searchValue}, this.debouncedUpdateOptions);
+ }
/**
* Returns the sections needed for the OptionsSelector
*
* @returns {Array}
*/
- const getSections = () => {
+ getSections() {
const sections = [];
let indexOffset = 0;
- if (searchOptions.recentReports.length > 0) {
+ if (this.state.recentReports.length > 0) {
sections.push({
- data: searchOptions.recentReports,
+ data: this.state.recentReports,
shouldShow: true,
indexOffset,
});
- indexOffset += searchOptions.recentReports.length;
+ indexOffset += this.state.recentReports.length;
}
- if (searchOptions.personalDetails.length > 0) {
+ if (this.state.personalDetails.length > 0) {
sections.push({
- data: searchOptions.personalDetails,
+ data: this.state.personalDetails,
shouldShow: true,
indexOffset,
});
- indexOffset += searchOptions.recentReports.length;
+ indexOffset += this.state.recentReports.length;
}
- if (searchOptions.userToInvite) {
+ if (this.state.userToInvite) {
sections.push({
- data: [searchOptions.userToInvite],
+ data: [this.state.userToInvite],
shouldShow: true,
indexOffset,
});
}
return sections;
- };
+ }
- const searchRendered = () => {
+ searchRendered() {
Timing.end(CONST.TIMING.SEARCH_RENDER);
Performance.markEnd(CONST.TIMING.SEARCH_RENDER);
- };
-
- const onChangeText = (value = '') => {
- setSearchValue(value);
- };
+ }
+
+ updateOptions() {
+ const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(
+ this.props.reports,
+ this.props.personalDetails,
+ this.state.searchValue.trim(),
+ this.props.betas,
+ );
+ this.setState({
+ userToInvite,
+ recentReports,
+ personalDetails,
+ });
+ }
/**
* Reset the search value and redirect to the selected report
*
* @param {Object} option
*/
- const selectReport = (option) => {
+ selectReport(option) {
if (!option) {
return;
}
+
if (option.reportID) {
- setSearchValue('');
- Navigation.dismissModal(option.reportID);
+ this.setState(
+ {
+ searchValue: '',
+ },
+ () => {
+ Navigation.dismissModal(option.reportID);
+ },
+ );
} else {
Report.navigateToAndOpenReport([option.login]);
}
- };
-
- const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails);
- const headerMessage = OptionsListUtils.getHeaderMessage(
- searchOptions.recentReports.length + searchOptions.personalDetails.length !== 0,
- Boolean(searchOptions.userToInvite),
- searchValue,
- );
- return (
-
- {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
- <>
-
-
-
-
- >
- )}
-
- );
+ }
+
+ render() {
+ const sections = this.getSections();
+ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails);
+ const headerMessage = OptionsListUtils.getHeaderMessage(
+ this.state.recentReports.length + this.state.personalDetails.length !== 0,
+ Boolean(this.state.userToInvite),
+ this.state.searchValue,
+ );
+
+ return (
+
+ {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
+ <>
+
+
+
+
+ >
+ )}
+
+ );
+ }
}
SearchPage.propTypes = propTypes;
SearchPage.defaultProps = defaultProps;
-SearchPage.displayName = 'SearchPage';
-export default withOnyx({
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
- betas: {
- key: ONYXKEYS.BETAS,
- },
-})(SearchPage);
+
+export default compose(
+ withLocalize,
+ withWindowDimensions,
+ withOnyx({
+ reports: {
+ key: ONYXKEYS.COLLECTION.REPORT,
+ },
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ },
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
+)(SearchPage);
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 004087c22308..a4145843ab87 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -37,6 +37,7 @@ import ReportScreenContext from './ReportScreenContext';
import TaskHeaderActionButton from '../../components/TaskHeaderActionButton';
import DragAndDropProvider from '../../components/DragAndDrop/Provider';
import usePrevious from '../../hooks/usePrevious';
+import CONST from '../../CONST';
import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDefaultProps} from '../../components/withCurrentReportID';
const propTypes = {
@@ -107,6 +108,15 @@ const defaultProps = {
...withCurrentReportIDDefaultProps,
};
+/**
+ *
+ * Function to check weather the report available in props is default
+ *
+ * @param {Object} report
+ * @returns {Boolean}
+ */
+const checkDefaultReport = (report) => report === defaultProps.report;
+
/**
* Get the currently viewed report ID as number
*
@@ -151,6 +161,8 @@ 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) && report.isLoadingReportActions;
+ const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS.CLOSED;
+
const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas);
const isLoading = !reportID || !isSidebarLoaded || _.isEmpty(personalDetails) || firstRenderRef.current;
@@ -163,6 +175,8 @@ function ReportScreen({
const isTopMostReportId = currentReportID === getReportID(route);
+ const isDefaultReport = checkDefaultReport(report);
+
let headerView = (
(!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading) || shouldHideReport,
+ [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete],
+ );
+
return (
{translate('newMessages')}
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js
index 3b5b181d2fcb..04757b0ff276 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js
@@ -305,7 +305,7 @@ function ComposerWithSuggestions({
const onSelectionChange = useCallback(
(e) => {
- if (suggestionsRef.current.onSelectionChange(e)) {
+ if (textInputRef.current && textInputRef.current.isFocused() && suggestionsRef.current.onSelectionChange(e)) {
return;
}
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index d768fcacd5b7..e9fd30f5b057 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -151,6 +151,7 @@ function ReportActionItemFragment(props) {
{' '}
diff --git a/src/pages/home/report/ReportActionItemThread.js b/src/pages/home/report/ReportActionItemThread.js
index ebd965e80daf..9c688911759e 100644
--- a/src/pages/home/report/ReportActionItemThread.js
+++ b/src/pages/home/report/ReportActionItemThread.js
@@ -62,6 +62,7 @@ function ReportActionItemThread(props) {
{`${numberOfRepliesText} ${replyText}`}
@@ -69,6 +70,7 @@ function ReportActionItemThread(props) {
selectable={false}
numberOfLines={1}
style={[styles.ml2, styles.textMicroSupporting, styles.flex1]}
+ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{timeStamp}
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index 16cc3f2da458..3d54306b6248 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -34,7 +34,6 @@ function BaseSidebarScreen(props) {
includeSafeAreaPaddingBottom={false}
shouldEnableKeyboardAvoidingView={false}
style={[styles.sidebar, Browser.isMobile() ? styles.userSelectNone : {}]}
- shouldDisableFocusTrap
>
{({insets}) => (
<>
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
index 418b7c89aa91..26cd4b180109 100644
--- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
@@ -5,7 +5,6 @@ import lodashGet from 'lodash/get';
import {View} from 'react-native';
import styles from '../../../../styles/styles';
import * as Expensicons from '../../../../components/Icon/Expensicons';
-import * as Browser from '../../../../libs/Browser';
import Navigation from '../../../../libs/Navigation/Navigation';
import ROUTES from '../../../../ROUTES';
import NAVIGATORS from '../../../../NAVIGATORS';
@@ -61,9 +60,6 @@ const propTypes = {
/** Indicated whether the report data is loading */
isLoading: PropTypes.bool,
- /** For first time users, whether the download app banner should show */
- shouldShowDownloadAppBanner: PropTypes.bool,
-
/** Forwarded ref to FloatingActionButtonAndPopover */
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
};
@@ -74,7 +70,6 @@ const defaultProps = {
betas: [],
isLoading: false,
innerRef: null,
- shouldShowDownloadAppBanner: true,
};
/**
@@ -157,12 +152,9 @@ function FloatingActionButtonAndPopover(props) {
if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) {
return;
}
- // Avoid rendering the create menu for first-time users until they have dismissed the download app banner (mWeb only).
- if (props.shouldShowDownloadAppBanner && Browser.isMobile()) {
- return;
- }
Welcome.show({routes, showCreateMenu});
- }, [props.shouldShowDownloadAppBanner, props.navigation, showCreateMenu, props.demoInfo]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
useEffect(() => {
if (!didScreenBecomeInactive()) {
@@ -274,9 +266,6 @@ export default compose(
isLoading: {
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
},
- shouldShowDownloadAppBanner: {
- key: ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER,
- },
}),
)(
forwardRef((props, ref) => (
diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js
index 6712a8c7cd81..7159e1d7252f 100644
--- a/src/pages/iou/steps/NewRequestAmountPage.js
+++ b/src/pages/iou/steps/NewRequestAmountPage.js
@@ -99,6 +99,12 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) {
// Because we use Onyx to store IOU info, when we try to make two different money requests from different tabs,
// it can result in an IOU sent with improper values. In such cases we want to reset the flow and redirect the user to the first step of the IOU.
useEffect(() => {
+ const moneyRequestID = `${iouType}${reportID}`;
+ const shouldReset = iou.id !== moneyRequestID;
+ if (shouldReset) {
+ IOU.resetMoneyRequestInfo(moneyRequestID);
+ }
+
if (isEditing) {
// ID in Onyx could change by initiating a new request in a separate browser tab or completing a request
if (prevMoneyRequestID.current !== iou.id) {
@@ -109,11 +115,6 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) {
Navigation.goBack(ROUTES.getMoneyRequestRoute(iouType, reportID), true);
return;
}
- const moneyRequestID = `${iouType}${reportID}`;
- const shouldReset = iou.id !== moneyRequestID;
- if (shouldReset) {
- IOU.resetMoneyRequestInfo(moneyRequestID);
- }
if (!isDistanceRequestTab && (_.isEmpty(iou.participantAccountIDs) || iou.amount === 0 || shouldReset)) {
Navigation.goBack(ROUTES.getMoneyRequestRoute(iouType, reportID), true);
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 5110bed598f1..7bb9a91c130e 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useCallback, useEffect, useState, useMemo} from 'react';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import {View} from 'react-native';
@@ -75,6 +75,8 @@ function WorkspaceMembersPage(props) {
const [errors, setErrors] = useState({});
const [searchValue, setSearchValue] = useState('');
const prevIsOffline = usePrevious(props.network.isOffline);
+ const accountIDs = useMemo(() => _.keys(props.policyMembers), [props.policyMembers]);
+ const prevAccountIDs = usePrevious(accountIDs);
/**
* Get members for the current workspace
@@ -109,6 +111,9 @@ function WorkspaceMembersPage(props) {
}, [props.preferredLocale, validateSelection]);
useEffect(() => {
+ if (removeMembersConfirmModalVisible && !_.isEqual(accountIDs, prevAccountIDs)) {
+ setRemoveMembersConfirmModalVisible(false);
+ }
setSelectedEmployees((prevSelected) =>
_.intersection(
prevSelected,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 23966d1a1a14..38b28ee7eaf0 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -29,6 +29,8 @@ import textUnderline from './utilities/textUnderline';
// touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target
const touchCalloutNone = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {};
+// to prevent vertical text offset in Safari for badges, new lineHeight values have been added
+const lineHeightBadge = Browser.isSafari() ? {lineHeight: variables.lineHeightXSmall} : {lineHeight: variables.lineHeightNormal};
const picker = (theme) => ({
backgroundColor: theme.transparent,
@@ -758,7 +760,7 @@ const styles = (theme) => ({
badgeText: {
color: theme.text,
fontSize: variables.fontSizeSmall,
- lineHeight: variables.lineHeightNormal,
+ ...lineHeightBadge,
...whiteSpace.noWrap,
},
@@ -2723,6 +2725,10 @@ const styles = (theme) => ({
padding: 16,
},
+ amountSplitPadding: {
+ paddingTop: 2,
+ },
+
moneyRequestPreviewBoxLoading: {
// When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading.
// Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container.
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 17d056fbe6f4..d91c881e1ffd 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -25,7 +25,7 @@ export default {
componentBorderRadiusLarge: 16,
componentBorderRadiusCard: 12,
componentBorderRadiusRounded: 24,
- downloadAppModalAppIconSize: 48,
+ appModalAppIconSize: 48,
buttonBorderRadius: 100,
avatarSizeLargeBordered: 88,
avatarSizeLarge: 80,
@@ -74,7 +74,7 @@ export default {
emojiLineHeight: 28,
iouAmountTextSize: 40,
extraSmallMobileResponsiveWidthBreakpoint: 320,
- extraSmallMobileResponsiveHeightBreakpoint: 550,
+ extraSmallMobileResponsiveHeightBreakpoint: 667,
mobileResponsiveWidthBreakpoint: 800,
modalFullscreenBackdropOpacity: 0.5,
tabletResponsiveWidthBreakpoint: 1024,
@@ -88,6 +88,7 @@ export default {
optionRowHeightCompact: 52,
optionsListSectionHeaderHeight: getValueUsingPixelRatio(32, 38),
overlayOpacity: 0.6,
+ lineHeightXSmall: getValueUsingPixelRatio(11, 17),
lineHeightSmall: getValueUsingPixelRatio(14, 16),
lineHeightNormal: getValueUsingPixelRatio(16, 21),
lineHeightLarge: getValueUsingPixelRatio(18, 24),
diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts
index 1efa5906360e..e89c966d49da 100644
--- a/src/types/onyx/Card.ts
+++ b/src/types/onyx/Card.ts
@@ -1,5 +1,6 @@
import {ValueOf} from 'type-fest';
import CONST from '../../CONST';
+import * as OnyxCommon from './OnyxCommon';
type State = 3 /* OPEN */ | 4 /* NOT_ACTIVATED */ | 5 /* STATE_DEACTIVATED */ | 6 /* CLOSED */ | 7 /* STATE_SUSPENDED */;
@@ -10,10 +11,12 @@ type Card = {
availableSpend: number;
domainName: string;
maskedPan: string;
+ cardName: string;
isVirtual: boolean;
fraud: ValueOf;
cardholderFirstName: string;
cardholderLastName: string;
+ errors?: OnyxCommon.Errors;
};
export default Card;