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;