From c8692c0887191667c1335814eb05f2a77c90ee2a Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 27 Oct 2023 00:44:15 +0700 Subject: [PATCH 001/674] fix: app uses ultra-wide camera by default --- src/pages/iou/ReceiptSelector/index.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index ca9fe90575e7..9181d0bd2701 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -1,5 +1,5 @@ import {View, Text, PixelRatio, ActivityIndicator, PanResponder} from 'react-native'; -import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import lodashGet from 'lodash/get'; import _ from 'underscore'; import PropTypes from 'prop-types'; @@ -84,6 +84,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); const [isTorchAvailable, setIsTorchAvailable] = useState(true); const cameraRef = useRef(null); + const normalCameraDeviceIdRef = useRef(null); const hideReciptModal = () => { setIsAttachmentInvalid(false); @@ -169,6 +170,24 @@ function ReceiptSelector({route, transactionID, iou, report}) { }), ).current; + /** + * On phones that have ultra-wide lens, default camera is ultra-wide. + * The last deviceId is of regular len camera. + */ + useEffect(() => { + if (!navigator || !navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { + return; + } + + navigator.mediaDevices.enumerateDevices().then((devices) => { + normalCameraDeviceIdRef.current = _.chain(devices) + .filter((device) => device.kind === 'videoinput') + .last() + .get('deviceId', '') + .value(); + }); + }, []); + const mobileCameraView = () => ( <> @@ -197,7 +216,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} ref={cameraRef} screenshotFormat="image/png" - videoConstraints={{facingMode: {exact: 'environment'}}} + videoConstraints={{facingMode: {exact: 'environment'}, deviceId: normalCameraDeviceIdRef.current}} torchOn={isFlashLightOn} onTorchAvailability={setIsTorchAvailable} forceScreenshotSourceSize From 3d3bd77d7c1d633ed42adef361952bc588db050f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 27 Oct 2023 16:54:07 +0700 Subject: [PATCH 002/674] useState approach --- src/pages/iou/ReceiptSelector/index.js | 47 +++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 9181d0bd2701..601a1abd4773 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -84,7 +84,32 @@ function ReceiptSelector({route, transactionID, iou, report}) { const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); const [isTorchAvailable, setIsTorchAvailable] = useState(true); const cameraRef = useRef(null); - const normalCameraDeviceIdRef = useRef(null); + const [videoConstraints, setVideoConstraints] = useState({facingMode: {exact: 'environment'}}); + + /** + * On phones that have ultra-wide lens, react-webcam uses ultra-wide by default. + * The last deviceId is of regular len camera. + */ + useEffect(() => { + navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(() => { + if (!navigator.mediaDevices.enumerateDevices) { + return; + } + + navigator.mediaDevices.enumerateDevices().then((devices) => { + const lastBackDeviceId = _.chain(devices) + .reverse() + .find((item) => item.label && item.label.endsWith('facing back')) + .get('deviceId', '') + .value(); + + if (!lastBackDeviceId) { + return; + } + setVideoConstraints({deviceId: lastBackDeviceId}); + }); + }); + }, []); const hideReciptModal = () => { setIsAttachmentInvalid(false); @@ -170,24 +195,6 @@ function ReceiptSelector({route, transactionID, iou, report}) { }), ).current; - /** - * On phones that have ultra-wide lens, default camera is ultra-wide. - * The last deviceId is of regular len camera. - */ - useEffect(() => { - if (!navigator || !navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { - return; - } - - navigator.mediaDevices.enumerateDevices().then((devices) => { - normalCameraDeviceIdRef.current = _.chain(devices) - .filter((device) => device.kind === 'videoinput') - .last() - .get('deviceId', '') - .value(); - }); - }, []); - const mobileCameraView = () => ( <> @@ -216,7 +223,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} ref={cameraRef} screenshotFormat="image/png" - videoConstraints={{facingMode: {exact: 'environment'}, deviceId: normalCameraDeviceIdRef.current}} + videoConstraints={videoConstraints} torchOn={isFlashLightOn} onTorchAvailability={setIsTorchAvailable} forceScreenshotSourceSize From 56f99e6edd04dbf09188dce97e9c61820be40f8c Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 30 Oct 2023 09:44:17 +0700 Subject: [PATCH 003/674] remove getUserMedia --- src/pages/iou/ReceiptSelector/index.js | 56 +++++++++++++------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 1ead9ca45076..168ee8230ff1 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -84,30 +84,30 @@ function ReceiptSelector({route, transactionID, iou, report}) { const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); const [isTorchAvailable, setIsTorchAvailable] = useState(true); const cameraRef = useRef(null); - const [videoConstraints, setVideoConstraints] = useState({facingMode: {exact: 'environment'}}); + const [videoConstraints, setVideoConstraints] = useState(null); /** * On phones that have ultra-wide lens, react-webcam uses ultra-wide by default. * The last deviceId is of regular len camera. */ useEffect(() => { - navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(() => { - if (!navigator.mediaDevices.enumerateDevices) { + if (!navigator.mediaDevices.enumerateDevices) { + setVideoConstraints({facingMode: {exact: 'environment'}}); + return; + } + + navigator.mediaDevices.enumerateDevices().then((devices) => { + const lastBackDeviceId = _.chain(devices) + .filter((item) => item.label && item.label.endsWith('facing back')) + .last() + .get('deviceId', '') + .value(); + + if (!lastBackDeviceId) { + setVideoConstraints({facingMode: {exact: 'environment'}}); return; } - - navigator.mediaDevices.enumerateDevices().then((devices) => { - const lastBackDeviceId = _.chain(devices) - .reverse() - .find((item) => item.label && item.label.endsWith('facing back')) - .get('deviceId', '') - .value(); - - if (!lastBackDeviceId) { - return; - } - setVideoConstraints({deviceId: lastBackDeviceId}); - }); + setVideoConstraints({deviceId: lastBackDeviceId}); }); }, []); @@ -217,17 +217,19 @@ function ReceiptSelector({route, transactionID, iou, report}) { {translate('receipt.cameraAccess')} )} - setCameraPermissionState('granted')} - onUserMediaError={() => setCameraPermissionState('denied')} - style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} - ref={cameraRef} - screenshotFormat="image/png" - videoConstraints={videoConstraints} - torchOn={isFlashLightOn} - onTorchAvailability={setIsTorchAvailable} - forceScreenshotSourceSize - /> + {videoConstraints && ( + setCameraPermissionState('granted')} + onUserMediaError={() => setCameraPermissionState('denied')} + style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} + ref={cameraRef} + screenshotFormat="image/png" + videoConstraints={videoConstraints} + torchOn={isFlashLightOn} + onTorchAvailability={setIsTorchAvailable} + forceScreenshotSourceSize + /> + )} From 3180d32f66ebd7cc30603e17cae250d3bb705368 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 30 Oct 2023 18:11:32 +0700 Subject: [PATCH 004/674] close active stream --- src/pages/iou/ReceiptSelector/index.js | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 168ee8230ff1..304d7c44da43 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -91,23 +91,27 @@ function ReceiptSelector({route, transactionID, iou, report}) { * The last deviceId is of regular len camera. */ useEffect(() => { - if (!navigator.mediaDevices.enumerateDevices) { - setVideoConstraints({facingMode: {exact: 'environment'}}); - return; - } + navigator.mediaDevices.getUserMedia({audio: true, video: true}).then((stream) => { + _.forEach(stream.getTracks(), (videoStream) => videoStream.stop()); - navigator.mediaDevices.enumerateDevices().then((devices) => { - const lastBackDeviceId = _.chain(devices) - .filter((item) => item.label && item.label.endsWith('facing back')) - .last() - .get('deviceId', '') - .value(); - - if (!lastBackDeviceId) { + if (!navigator.mediaDevices.enumerateDevices) { setVideoConstraints({facingMode: {exact: 'environment'}}); return; } - setVideoConstraints({deviceId: lastBackDeviceId}); + + navigator.mediaDevices.enumerateDevices().then((devices) => { + const lastBackDeviceId = _.chain(devices) + .filter((item) => item.kind === 'videoinput') + .last() + .get('deviceId', '') + .value(); + + if (!lastBackDeviceId) { + setVideoConstraints({facingMode: {exact: 'environment'}}); + return; + } + setVideoConstraints({deviceId: lastBackDeviceId}); + }); }); }, []); From c853f697eafa8d3b070bf4bf518aae31f769e720 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 6 Nov 2023 12:22:43 +0700 Subject: [PATCH 005/674] do not request permission on desktop --- src/pages/iou/ReceiptSelector/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index fdef1a7af941..17323b3ae687 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -91,6 +91,10 @@ function ReceiptSelector({route, transactionID, iou, report}) { * The last deviceId is of regular len camera. */ useEffect(() => { + if (!Browser.isMobile()) { + return; + } + navigator.mediaDevices.getUserMedia({video: true}).then((stream) => { _.forEach(stream.getTracks(), (videoStream) => videoStream.stop()); From c7bbfd4cf1529e6ba40bf206e96bc70eee26c9a9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 8 Nov 2023 16:59:28 +0700 Subject: [PATCH 006/674] only request permission on Scan tab focus --- src/pages/iou/ReceiptSelector/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 4e482d13fb44..6d65d250f1a6 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -1,8 +1,10 @@ +import {useIsFocused} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, Text, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; import Shutter from '@assets/images/shutter.svg'; @@ -84,13 +86,14 @@ function ReceiptSelector({route, transactionID, iou, report}) { const [isTorchAvailable, setIsTorchAvailable] = useState(true); const cameraRef = useRef(null); const [videoConstraints, setVideoConstraints] = useState(null); + const isCameraActive = useIsFocused(); /** * On phones that have ultra-wide lens, react-webcam uses ultra-wide by default. * The last deviceId is of regular len camera. */ useEffect(() => { - if (!Browser.isMobile()) { + if (!_.isEmpty(videoConstraints) || !isCameraActive || !Browser.isMobile()) { return; } @@ -116,7 +119,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { setVideoConstraints({deviceId: lastBackDeviceId}); }); }); - }, []); + }, [isCameraActive]); const hideReciptModal = () => { setIsAttachmentInvalid(false); From 586259d96d984f6fdeb0dfab37700a8248e841f2 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 8 Nov 2023 17:02:22 +0700 Subject: [PATCH 007/674] fix lint --- src/pages/iou/ReceiptSelector/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 6d65d250f1a6..63f382baa7ed 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -119,6 +119,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { setVideoConstraints({deviceId: lastBackDeviceId}); }); }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isCameraActive]); const hideReciptModal = () => { From 6b6afd8326c14044681838a44c5e4b929ef77e26 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 15 Nov 2023 15:06:15 +0100 Subject: [PATCH 008/674] [TS migration] Migrate 'Onfido' component --- .../{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} | 50 ++++++++++--------- src/components/Onfido/index.desktop.js | 11 ---- .../{index.native.js => index.native.tsx} | 16 +++--- .../Onfido/{index.website.js => index.tsx} | 11 ++-- src/components/Onfido/onfidoPropTypes.js | 15 ------ src/components/Onfido/types.ts | 20 ++++++++ 6 files changed, 58 insertions(+), 65 deletions(-) rename src/components/Onfido/{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} (81%) delete mode 100644 src/components/Onfido/index.desktop.js rename src/components/Onfido/{index.native.js => index.native.tsx} (79%) rename src/components/Onfido/{index.website.js => index.tsx} (64%) delete mode 100644 src/components/Onfido/onfidoPropTypes.js create mode 100644 src/components/Onfido/types.ts diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.tsx similarity index 81% rename from src/components/Onfido/BaseOnfidoWeb.js rename to src/components/Onfido/BaseOnfidoWeb.tsx index 5c0f83902e55..79842823a975 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,7 +1,6 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; -import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import React, {ForwardedRef, forwardRef, useEffect} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import fontFamily from '@styles/fontFamily'; @@ -10,9 +9,15 @@ import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}) { +type LocaleProps = Pick; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}: OnfidoProps & LocaleProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -22,7 +27,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: fontWeightBold, + fontWeightTitle: Number(fontWeightBold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: themeColors.text, @@ -47,7 +52,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: themeColors.link, colorBackgroundLinkHover: themeColors.link, colorBackgroundLinkActive: themeColors.link, - authAccentColor: themeColors.link, colorBackgroundInfoPill: themeColors.link, colorBackgroundSelector: themeColors.appBG, colorBackgroundDocTypeButton: themeColors.success, @@ -59,11 +63,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -78,17 +81,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (!Object.keys(data).length) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -101,32 +102,33 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, }); @@ -143,8 +145,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.desktop.js b/src/components/Onfido/index.desktop.js deleted file mode 100644 index e455eaf78d32..000000000000 --- a/src/components/Onfido/index.desktop.js +++ /dev/null @@ -1,11 +0,0 @@ -import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; - -// On desktop, we do not want to teardown onfido, because it causes a crash. -// See https://github.com/Expensify/App/issues/6082 -const Onfido = BaseOnfidoWeb; - -Onfido.propTypes = onfidoPropTypes; -Onfido.displayName = 'Onfido'; - -export default Onfido; diff --git a/src/components/Onfido/index.native.js b/src/components/Onfido/index.native.tsx similarity index 79% rename from src/components/Onfido/index.native.js rename to src/components/Onfido/index.native.tsx index ed0578187d3c..e09eeec4f322 100644 --- a/src/components/Onfido/index.native.js +++ b/src/components/Onfido/index.native.tsx @@ -1,15 +1,13 @@ import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; -import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import CONST from '@src/CONST'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoProps} from './types'; -function Onfido({sdkToken, onUserExit, onSuccess, onError}) { +function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { const {translate} = useLocalize(); useEffect(() => { @@ -28,19 +26,20 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { }) .then(onSuccess) .catch((error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; + Log.hmmm('Onfido error on native', {errorType, errorMessage}); // If the user cancels the Onfido flow we won't log this error as it's normal. In the React Native SDK the user exiting the flow will trigger this error which we can use as // our "user exited the flow" callback. On web, this event has it's own callback passed as a config so we don't need to bother with this there. - if (_.contains([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED].includes(errorMessage)) { onUserExit(); return; } // Handle user camera permission on iOS and Android - if (_.contains([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED].includes(errorMessage)) { Alert.alert( translate('onfidoStep.cameraPermissionsNotGranted'), translate('onfidoStep.cameraRequestMessage'), @@ -71,7 +70,6 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { return ; } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/index.website.js b/src/components/Onfido/index.tsx similarity index 64% rename from src/components/Onfido/index.website.js rename to src/components/Onfido/index.tsx index 12ad1edd8fb9..139dc3cec405 100644 --- a/src/components/Onfido/index.website.js +++ b/src/components/Onfido/index.tsx @@ -1,14 +1,14 @@ -import lodashGet from 'lodash/get'; import React, {useEffect, useRef} from 'react'; import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function Onfido({sdkToken, onSuccess, onError, onUserExit}) { - const baseOnfidoRef = useRef(null); +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps) { + const baseOnfidoRef = useRef(null); useEffect( () => () => { - const onfidoOut = lodashGet(baseOnfidoRef.current, 'onfidoOut'); + const onfidoOut = baseOnfidoRef.current?.onfidoOut; + if (!onfidoOut) { return; } @@ -29,7 +29,6 @@ function Onfido({sdkToken, onSuccess, onError, onUserExit}) { ); } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/onfidoPropTypes.js b/src/components/Onfido/onfidoPropTypes.js deleted file mode 100644 index ff0023c70058..000000000000 --- a/src/components/Onfido/onfidoPropTypes.js +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - /** Token used to initialize the Onfido SDK */ - sdkToken: PropTypes.string.isRequired, - - /** Called when the user intentionally exits the flow without completing it */ - onUserExit: PropTypes.func.isRequired, - - /** Called when the user is totally done with Onfido */ - onSuccess: PropTypes.func.isRequired, - - /** Called when Onfido throws an error */ - onError: PropTypes.func.isRequired, -}; diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts new file mode 100644 index 000000000000..a4fe3d93f05e --- /dev/null +++ b/src/components/Onfido/types.ts @@ -0,0 +1,20 @@ +import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; +import * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; + +type OnfidoProps = { + /** Token used to initialize the Onfido SDK */ + sdkToken: string; + + /** Called when the user intentionally exits the flow without completing it */ + onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; + + /** Called when the user is totally done with Onfido */ + onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + + /** Called when Onfido throws an error */ + onError: (error?: string) => void; +}; + +export type {OnfidoProps, OnfidoElement}; From 8b7f74c22a935f44879af65c652dec2c32360bbf Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 4 Dec 2023 15:58:16 +0700 Subject: [PATCH 009/674] bool check videoConstraints --- src/pages/iou/ReceiptSelector/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 67211f5c42ee..c68c20b5c643 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -229,7 +229,7 @@ function ReceiptSelector({route, transactionID, iou, report}) { {translate('receipt.cameraAccess')} )} - {videoConstraints && ( + {!_.isEmpty(videoConstraints) && ( setCameraPermissionState('granted')} onUserMediaError={() => setCameraPermissionState('denied')} From 167775a4d69447f674cad364a891105e53b83e09 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Jan 2024 14:19:45 +0700 Subject: [PATCH 010/674] Reapply changes --- .../request/step/IOURequestStepScan/index.js | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index c0c96826d124..3aa4235dea5d 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -1,6 +1,7 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, Text, View} from 'react-native'; +import _ from 'underscore'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; import Shutter from '@assets/images/shutter.svg'; @@ -14,6 +15,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; +import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -74,6 +76,44 @@ function IOURequestStepScan({ const [isTorchAvailable, setIsTorchAvailable] = useState(false); const cameraRef = useRef(null); + const [videoConstraints, setVideoConstraints] = useState(null); + const tabIndex = 1; + const isScanTabActive = useTabNavigatorFocus({tabIndex}); + + /** + * On phones that have ultra-wide lens, react-webcam uses ultra-wide by default. + * The last deviceId is of regular len camera. + */ + useEffect(() => { + if (!_.isEmpty(videoConstraints) || !isScanTabActive || !Browser.isMobile()) { + return; + } + + navigator.mediaDevices.getUserMedia({video: true}).then((stream) => { + _.forEach(stream.getTracks(), (videoStream) => videoStream.stop()); + + if (!navigator.mediaDevices.enumerateDevices) { + setVideoConstraints({facingMode: {exact: 'environment'}}); + return; + } + + navigator.mediaDevices.enumerateDevices().then((devices) => { + const lastBackDeviceId = _.chain(devices) + .filter((item) => item.kind === 'videoinput') + .last() + .get('deviceId', '') + .value(); + + if (!lastBackDeviceId) { + setVideoConstraints({facingMode: {exact: 'environment'}}); + return; + } + setVideoConstraints({deviceId: lastBackDeviceId}); + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isScanTabActive]); + const hideRecieptModal = () => { setIsAttachmentInvalid(false); }; @@ -211,18 +251,20 @@ function IOURequestStepScan({ {translate('receipt.cameraAccess')} )} - setCameraPermissionState('granted')} - onUserMediaError={() => setCameraPermissionState('denied')} - style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} - ref={cameraRef} - screenshotFormat="image/png" - videoConstraints={{facingMode: {exact: 'environment'}}} - torchOn={isFlashLightOn} - onTorchAvailability={setIsTorchAvailable} - forceScreenshotSourceSize - cameraTabIndex={1} - /> + {!_.isEmpty(videoConstraints) && ( + setCameraPermissionState('granted')} + onUserMediaError={() => setCameraPermissionState('denied')} + style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} + ref={cameraRef} + screenshotFormat="image/png" + videoConstraints={videoConstraints} + torchOn={isFlashLightOn} + onTorchAvailability={setIsTorchAvailable} + forceScreenshotSourceSize + cameraTabIndex={tabIndex} + /> + )} From 6afd422e26ec35c5912b7b00945dc9c3d57e2804 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Jan 2024 14:30:37 +0700 Subject: [PATCH 011/674] fix lint --- src/pages/iou/request/step/IOURequestStepScan/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 3aa4235dea5d..2a7c8be25950 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -78,14 +78,14 @@ function IOURequestStepScan({ const [videoConstraints, setVideoConstraints] = useState(null); const tabIndex = 1; - const isScanTabActive = useTabNavigatorFocus({tabIndex}); + const isTabActive = useTabNavigatorFocus({tabIndex}); /** * On phones that have ultra-wide lens, react-webcam uses ultra-wide by default. * The last deviceId is of regular len camera. */ useEffect(() => { - if (!_.isEmpty(videoConstraints) || !isScanTabActive || !Browser.isMobile()) { + if (!_.isEmpty(videoConstraints) || !isTabActive || !Browser.isMobile()) { return; } @@ -112,7 +112,7 @@ function IOURequestStepScan({ }); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isScanTabActive]); + }, [isTabActive]); const hideRecieptModal = () => { setIsAttachmentInvalid(false); From d95ba1dc8ceee16866056e5f806e308aa0cb9460 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 19 Jan 2024 11:43:06 +0700 Subject: [PATCH 012/674] fix: Inconsistency of flashlight/torch behavior in Scan tab --- .../NavigationAwareCamera/index.js | 47 +------------------ .../request/step/IOURequestStepScan/index.js | 30 ++++++++++-- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js index 10b16da13b6e..37223915f4a2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js @@ -1,61 +1,20 @@ import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; +import React from 'react'; import {View} from 'react-native'; import Webcam from 'react-webcam'; import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus'; const propTypes = { - /** Flag to turn on/off the torch/flashlight - if available */ - torchOn: PropTypes.bool, - /** The index of the tab that contains this camera */ cameraTabIndex: PropTypes.number.isRequired, - - /** Callback function when media stream becomes available - user granted camera permissions and camera starts to work */ - onUserMedia: PropTypes.func, - - /** Callback function passing torch/flashlight capability as bool param of the browser */ - onTorchAvailability: PropTypes.func, -}; - -const defaultProps = { - onUserMedia: undefined, - onTorchAvailability: undefined, - torchOn: false, }; // Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, cameraTabIndex, ...props}, ref) => { - const trackRef = useRef(null); +const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) => { const shouldShowCamera = useTabNavigatorFocus({ tabIndex: cameraTabIndex, }); - const handleOnUserMedia = (stream) => { - if (props.onUserMedia) { - props.onUserMedia(stream); - } - - const [track] = stream.getVideoTracks(); - const capabilities = track.getCapabilities(); - if (capabilities.torch) { - trackRef.current = track; - } - if (onTorchAvailability) { - onTorchAvailability(!!capabilities.torch); - } - }; - - useEffect(() => { - if (!trackRef.current) { - return; - } - - trackRef.current.applyConstraints({ - advanced: [{torch: torchOn}], - }); - }, [torchOn]); - if (!shouldShowCamera) { return null; } @@ -67,7 +26,6 @@ const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, c // eslint-disable-next-line react/jsx-props-no-spreading {...props} ref={ref} - onUserMedia={handleOnUserMedia} /> ); @@ -75,6 +33,5 @@ const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, c NavigationAwareCamera.propTypes = propTypes; NavigationAwareCamera.displayName = 'NavigationAwareCamera'; -NavigationAwareCamera.defaultProps = defaultProps; export default NavigationAwareCamera; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 7c6efca4a32f..c2e9882d5288 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -74,6 +74,7 @@ function IOURequestStepScan({ const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); const [isTorchAvailable, setIsTorchAvailable] = useState(false); const cameraRef = useRef(null); + const trackRef = useRef(null); const hideRecieptModal = () => { setIsAttachmentInvalid(false); @@ -162,11 +163,34 @@ function IOURequestStepScan({ navigateToConfirmationStep(); }; + const handleOnUserMedia = (stream) => { + setCameraPermissionState('granted'); + + const [track] = stream.getVideoTracks(); + const capabilities = track.getCapabilities(); + if (capabilities.torch) { + trackRef.current = track; + } + setIsTorchAvailable(!!capabilities.torch); + }; + const capturePhoto = useCallback(() => { if (!cameraRef.current.getScreenshot) { return; } + if (trackRef.current && isFlashLightOn) { + trackRef.current.applyConstraints({ + advanced: [{torch: true}], + }); + } const imageBase64 = cameraRef.current.getScreenshot(); + + if (trackRef.current && isFlashLightOn) { + trackRef.current.applyConstraints({ + advanced: [{torch: false}], + }); + } + const filename = `receipt_${Date.now()}.png`; const file = FileUtils.base64ToFile(imageBase64, filename); const source = URL.createObjectURL(file); @@ -178,7 +202,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(); - }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); + }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep, isFlashLightOn]); const panResponder = useRef( PanResponder.create({ @@ -209,14 +233,12 @@ function IOURequestStepScan({ )} setCameraPermissionState('granted')} + onUserMedia={handleOnUserMedia} onUserMediaError={() => setCameraPermissionState('denied')} style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} ref={cameraRef} screenshotFormat="image/png" videoConstraints={{facingMode: {exact: 'environment'}}} - torchOn={isFlashLightOn} - onTorchAvailability={setIsTorchAvailable} forceScreenshotSourceSize cameraTabIndex={1} /> From bd5f17703af96409056a743804362e7f7efbf149 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 24 Nov 2023 16:07:50 -0800 Subject: [PATCH 013/674] Include comment in report name for amounts owing --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/languages/types.ts | 2 +- src/libs/ReportUtils.ts | 38 +++++++++++++++++++++++--------------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b6da38df21a0..87b12f631631 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -601,7 +601,7 @@ export default { splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `split ${formattedAmount}${comment ? ` for ${comment}` : ''}`, amountEach: ({amount}: AmountEachParams) => `${amount} each`, - payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} owes ${amount}`, + payerOwesAmount: ({payer, amount, comment}: PayerOwesAmountParams) => `${payer} owes ${amount}${comment ? ` for ${comment}` : ''}`, payerOwes: ({payer}: PayerOwesParams) => `${payer} owes: `, payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}paid ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} paid: `, diff --git a/src/languages/es.ts b/src/languages/es.ts index 2478c8ba8bd2..2c7e58655ca8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -594,7 +594,7 @@ export default { splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, didSplitAmount: ({formattedAmount, comment}: DidSplitAmountMessageParams) => `dividió ${formattedAmount}${comment ? ` para ${comment}` : ''}`, amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`, - payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} debe ${amount}`, + payerOwesAmount: ({payer, amount, comment}: PayerOwesAmountParams) => `${payer} debe ${amount}${comment ? ` para ${comment}` : ''}`, payerOwes: ({payer}: PayerOwesParams) => `${payer} debe: `, payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} pagó: `, diff --git a/src/languages/types.ts b/src/languages/types.ts index 3185b7a8f6f1..eab9991b73d8 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -115,7 +115,7 @@ type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; type AmountEachParams = {amount: number}; -type PayerOwesAmountParams = {payer: string; amount: number | string}; +type PayerOwesAmountParams = {payer: string; amount: number | string; comment?: string}; type PayerOwesParams = {payer: string}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d48567ebdaf3..32fb047d9126 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2178,26 +2178,28 @@ function getReportPreviewMessage( return reportActionMessage; } + let linkedTransaction: Transaction | EmptyObject = {}; + if (!isEmptyObject(reportAction)) { + linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + } + if (!isEmptyObject(reportAction) && !isIOUReport(report) && reportAction && ReportActionsUtils.isSplitBillAction(reportAction)) { // This covers group chats where the last action is a split bill action - const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); if (isEmptyObject(linkedTransaction)) { return reportActionMessage; } - if (!isEmptyObject(linkedTransaction)) { - if (TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { - return Localize.translateLocal('iou.receiptScanning'); - } - - if (TransactionUtils.hasMissingSmartscanFields(linkedTransaction)) { - return Localize.translateLocal('iou.receiptMissingDetails'); - } + if (TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } - const transactionDetails = getTransactionDetails(linkedTransaction); - const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency ?? ''); - return Localize.translateLocal('iou.didSplitAmount', {formattedAmount, comment: transactionDetails?.comment ?? ''}); + if (TransactionUtils.hasMissingSmartscanFields(linkedTransaction)) { + return Localize.translateLocal('iou.receiptMissingDetails'); } + + const transactionDetails = getTransactionDetails(linkedTransaction); + const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency ?? ''); + return Localize.translateLocal('iou.didSplitAmount', {formattedAmount, comment: transactionDetails?.comment ?? ''}); } const totalAmount = getMoneyRequestReimbursableTotal(report); @@ -2214,8 +2216,6 @@ function getReportPreviewMessage( } if (!isEmptyObject(reportAction) && shouldConsiderReceiptBeingScanned && reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction)) { - const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); - if (!isEmptyObject(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { return Localize.translateLocal('iou.receiptScanning'); } @@ -2261,7 +2261,15 @@ function getReportPreviewMessage( return `${requestorName ? `${requestorName}: ` : ''}${Localize.translateLocal('iou.requestedAmount', {formattedAmount: amountToDisplay})}`; } - return Localize.translateLocal(containsNonReimbursable ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount}); + if (containsNonReimbursable) { + return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName ?? '', amount: formattedAmount}); + } + + if (!isEmptyObject(linkedTransaction)) { + const comment = TransactionUtils.getDescription(linkedTransaction); + return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount, comment}); + } + return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount}); } /** From ee2c15d47495cc7a81ffc8e52319cd0bcd817fdc Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 09:30:14 -0800 Subject: [PATCH 014/674] Reuse translateLocal call --- src/libs/ReportUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 32fb047d9126..4cdcdc863aa3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2265,11 +2265,11 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName ?? '', amount: formattedAmount}); } + let comment: string | undefined if (!isEmptyObject(linkedTransaction)) { - const comment = TransactionUtils.getDescription(linkedTransaction); - return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount, comment}); + comment = TransactionUtils.getDescription(linkedTransaction); } - return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount}); + return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount, comment}); } /** From eeca194eaaddc0ef1ae75312d29b4552e8024aab Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 10:29:53 -0800 Subject: [PATCH 015/674] Remove redundant type declaration --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4cdcdc863aa3..d1d38bbff305 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2265,7 +2265,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName ?? '', amount: formattedAmount}); } - let comment: string | undefined + let comment if (!isEmptyObject(linkedTransaction)) { comment = TransactionUtils.getDescription(linkedTransaction); } From db0d8d1ecc0436fbba67479a230a9de0809850f4 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 10:51:27 -0800 Subject: [PATCH 016/674] Avoid variable reassignments --- src/libs/ReportUtils.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d1d38bbff305..e57dff6b4ae2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2178,11 +2178,7 @@ function getReportPreviewMessage( return reportActionMessage; } - let linkedTransaction: Transaction | EmptyObject = {}; - if (!isEmptyObject(reportAction)) { - linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); - } - + const linkedTransaction = !isEmptyObject(reportAction) ? TransactionUtils.getLinkedTransaction(reportAction) : {}; if (!isEmptyObject(reportAction) && !isIOUReport(report) && reportAction && ReportActionsUtils.isSplitBillAction(reportAction)) { // This covers group chats where the last action is a split bill action if (isEmptyObject(linkedTransaction)) { @@ -2265,10 +2261,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName ?? '', amount: formattedAmount}); } - let comment - if (!isEmptyObject(linkedTransaction)) { - comment = TransactionUtils.getDescription(linkedTransaction); - } + const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount, comment}); } From c2731c056e804b06b93cbe63c0ed79f9ab8d9ea6 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 10:52:31 -0800 Subject: [PATCH 017/674] Remove unrelated code change --- src/libs/ReportUtils.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e57dff6b4ae2..ee85c1aca958 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2185,17 +2185,19 @@ function getReportPreviewMessage( return reportActionMessage; } - if (TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { - return Localize.translateLocal('iou.receiptScanning'); - } + if (!isEmptyObject(linkedTransaction)) { + if (TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } - if (TransactionUtils.hasMissingSmartscanFields(linkedTransaction)) { - return Localize.translateLocal('iou.receiptMissingDetails'); - } + if (TransactionUtils.hasMissingSmartscanFields(linkedTransaction)) { + return Localize.translateLocal('iou.receiptMissingDetails'); + } - const transactionDetails = getTransactionDetails(linkedTransaction); - const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency ?? ''); - return Localize.translateLocal('iou.didSplitAmount', {formattedAmount, comment: transactionDetails?.comment ?? ''}); + const transactionDetails = getTransactionDetails(linkedTransaction); + const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency ?? ''); + return Localize.translateLocal('iou.didSplitAmount', {formattedAmount, comment: transactionDetails?.comment ?? ''}); + } } const totalAmount = getMoneyRequestReimbursableTotal(report); From 036ac5c5a87fb72784b60fb899c656d346b29a74 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 14:51:11 -0800 Subject: [PATCH 018/674] Update editRegularMoneyTransaction --- src/libs/actions/IOU.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 7ee752a1f0ef..410da027351a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2272,10 +2272,16 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // Update the last message of the chat report const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); - const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { - payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || '', - amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), - }); + const payer = ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || '' + const formattedAmount = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency) + let messageText + if (hasNonReimbursableTransactions) { + messageText = Localize.translateLocal('iou.payerSpentAmount', { payer, amount: formattedAmount }); + } else { + const comment = TransactionUtils.getDescription(updatedTransaction) + messageText = Localize.translateLocal('iou.payerOwesAmount', { payer, amount: formattedAmount, comment }); + } + updatedChatReport.lastMessageText = messageText; updatedChatReport.lastMessageHtml = messageText; } From 40073f04b4eed15987aa815497b75776cefc2e5b Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 16:00:57 -0800 Subject: [PATCH 019/674] Include request description when request deleted --- src/libs/actions/IOU.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 410da027351a..f861a37b1051 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -28,6 +28,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import * as Policy from './Policy'; import * as Report from './Report'; +import { isEmptyObject } from '@src/types/utils/EmptyObject'; let betas; Onyx.connect({ @@ -2569,10 +2570,16 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); - const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { - payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || '', - amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency), - }); + const payer = ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || ''; + const formattedAmount = CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency); + let messageText + if (hasNonReimbursableTransactions) { + messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}) + } else { + const comment = !isEmptyObject(transaction) ? TransactionUtils.getDescription(transaction) : undefined; + messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}) + } + updatedReportPreviewAction.message[0].text = messageText; updatedReportPreviewAction.message[0].html = messageText; From f7032dd759854ce6da538e79dd855894814c9b12 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 16:03:13 -0800 Subject: [PATCH 020/674] Include IOU description in report name --- src/libs/ReportUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ee85c1aca958..2928e54a79ee 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1928,7 +1928,9 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< } if (isProcessingReport(report) || isDraftExpenseReport(report) || moneyRequestTotal === 0) { - return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount}); + const reportTransactions = !isEmptyObject(report) ? TransactionUtils.getAllReportTransactions(report.reportID) : [] + const comment = reportTransactions.length === 1 ? TransactionUtils.getDescription(reportTransactions[0]) : undefined + return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount, comment}); } return payerPaidAmountMessage; From 2e2ac1f645337fa4d5255fc2fbfe350384a2571a Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 19 Jan 2024 16:06:50 -0800 Subject: [PATCH 021/674] Run prettier and lint --- src/libs/ReportUtils.ts | 4 ++-- src/libs/actions/IOU.js | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2928e54a79ee..004488d3a151 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1928,8 +1928,8 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< } if (isProcessingReport(report) || isDraftExpenseReport(report) || moneyRequestTotal === 0) { - const reportTransactions = !isEmptyObject(report) ? TransactionUtils.getAllReportTransactions(report.reportID) : [] - const comment = reportTransactions.length === 1 ? TransactionUtils.getDescription(reportTransactions[0]) : undefined + const reportTransactions = !isEmptyObject(report) ? TransactionUtils.getAllReportTransactions(report.reportID) : []; + const comment = reportTransactions.length === 1 ? TransactionUtils.getDescription(reportTransactions[0]) : undefined; return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount, comment}); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index f861a37b1051..e7a579d7b671 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -26,9 +26,9 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; import * as Report from './Report'; -import { isEmptyObject } from '@src/types/utils/EmptyObject'; let betas; Onyx.connect({ @@ -2273,14 +2273,14 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // Update the last message of the chat report const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); - const payer = ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || '' - const formattedAmount = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency) - let messageText + const payer = ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || ''; + const formattedAmount = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency); + let messageText; if (hasNonReimbursableTransactions) { - messageText = Localize.translateLocal('iou.payerSpentAmount', { payer, amount: formattedAmount }); + messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}); } else { - const comment = TransactionUtils.getDescription(updatedTransaction) - messageText = Localize.translateLocal('iou.payerOwesAmount', { payer, amount: formattedAmount, comment }); + const comment = TransactionUtils.getDescription(updatedTransaction); + messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}); } updatedChatReport.lastMessageText = messageText; @@ -2572,12 +2572,12 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); const payer = ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || ''; const formattedAmount = CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency); - let messageText + let messageText; if (hasNonReimbursableTransactions) { - messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}) + messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}); } else { const comment = !isEmptyObject(transaction) ? TransactionUtils.getDescription(transaction) : undefined; - messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}) + messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}); } updatedReportPreviewAction.message[0].text = messageText; From c08d9057304624e415ec9fd006e6c6df3c75aa94 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 15:35:55 +0700 Subject: [PATCH 022/674] add settimeout --- src/CONST.ts | 1 + .../request/step/IOURequestStepScan/index.js | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0b10e5767328..264810572030 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -47,6 +47,7 @@ const CONST = { OUT: 'out', }, ARROW_HIDE_DELAY: 3000, + TORCH_EFFECT: 1000, API_ATTACHMENT_VALIDATIONS: { // 24 megabytes in bytes, this is limit set on servers, do not update without wider internal discussion diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index c2e9882d5288..035db5dbb5a1 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -174,23 +174,9 @@ function IOURequestStepScan({ setIsTorchAvailable(!!capabilities.torch); }; - const capturePhoto = useCallback(() => { - if (!cameraRef.current.getScreenshot) { - return; - } - if (trackRef.current && isFlashLightOn) { - trackRef.current.applyConstraints({ - advanced: [{torch: true}], - }); - } + const getScreenshot = useCallback(() => { const imageBase64 = cameraRef.current.getScreenshot(); - if (trackRef.current && isFlashLightOn) { - trackRef.current.applyConstraints({ - advanced: [{torch: false}], - }); - } - const filename = `receipt_${Date.now()}.png`; const file = FileUtils.base64ToFile(imageBase64, filename); const source = URL.createObjectURL(file); @@ -202,7 +188,28 @@ function IOURequestStepScan({ } navigateToConfirmationStep(); - }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep, isFlashLightOn]); + }, [action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); + + const capturePhoto = useCallback(() => { + if (!cameraRef.current.getScreenshot) { + return; + } + + if (trackRef.current && isFlashLightOn) { + trackRef.current.applyConstraints({ + advanced: [{torch: true}], + }); + setTimeout(() => { + getScreenshot(); + trackRef.current.applyConstraints({ + advanced: [{torch: false}], + }); + }, CONST.TORCH_EFFECT); + return; + } + + getScreenshot(); + }, [cameraRef, isFlashLightOn, getScreenshot]); const panResponder = useRef( PanResponder.create({ From c5d03504be589333d2ff8ddcf6b1762daf38ee93 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jan 2024 16:35:30 +0700 Subject: [PATCH 023/674] remove settimeout --- src/CONST.ts | 2 -- .../request/step/IOURequestStepScan/index.js | 17 +++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 264810572030..ae5fbed6dafc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -47,8 +47,6 @@ const CONST = { OUT: 'out', }, ARROW_HIDE_DELAY: 3000, - TORCH_EFFECT: 1000, - API_ATTACHMENT_VALIDATIONS: { // 24 megabytes in bytes, this is limit set on servers, do not update without wider internal discussion MAX_SIZE: 25165824, diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 035db5dbb5a1..cd262ff24906 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -196,15 +196,16 @@ function IOURequestStepScan({ } if (trackRef.current && isFlashLightOn) { - trackRef.current.applyConstraints({ - advanced: [{torch: true}], - }); - setTimeout(() => { - getScreenshot(); - trackRef.current.applyConstraints({ - advanced: [{torch: false}], + trackRef.current + .applyConstraints({ + advanced: [{torch: true}], + }) + .then(() => { + getScreenshot(); + trackRef.current.applyConstraints({ + advanced: [{torch: false}], + }); }); - }, CONST.TORCH_EFFECT); return; } From 719d63d473ecb93ecc8894b9598c47bbae4d7158 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Fri, 26 Jan 2024 10:29:54 -0800 Subject: [PATCH 024/674] Undo changes to text outside LHN preview --- src/libs/ReportUtils.ts | 4 +--- src/libs/actions/IOU.js | 29 ++++++++--------------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ac1686f88544..edcd57078e0c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1931,9 +1931,7 @@ function getMoneyRequestReportName(report: OnyxEntry, policy: OnyxEntry< } if (isProcessingReport(report) || isDraftExpenseReport(report) || moneyRequestTotal === 0) { - const reportTransactions = !isEmptyObject(report) ? TransactionUtils.getAllReportTransactions(report.reportID) : []; - const comment = reportTransactions.length === 1 ? TransactionUtils.getDescription(reportTransactions[0]) : undefined; - return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount, comment}); + return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount}); } return payerPaidAmountMessage; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e7a579d7b671..7ee752a1f0ef 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -26,7 +26,6 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; import * as Report from './Report'; @@ -2273,16 +2272,10 @@ function editRegularMoneyRequest(transactionID, transactionThreadReportID, trans // Update the last message of the chat report const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); - const payer = ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || ''; - const formattedAmount = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency); - let messageText; - if (hasNonReimbursableTransactions) { - messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}); - } else { - const comment = TransactionUtils.getDescription(updatedTransaction); - messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}); - } - + const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { + payer: ReportUtils.getPersonalDetailsForAccountID(updatedMoneyRequestReport.managerID).login || '', + amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), + }); updatedChatReport.lastMessageText = messageText; updatedChatReport.lastMessageHtml = messageText; } @@ -2570,16 +2563,10 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport); - const payer = ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || ''; - const formattedAmount = CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency); - let messageText; - if (hasNonReimbursableTransactions) { - messageText = Localize.translateLocal('iou.payerSpentAmount', {payer, amount: formattedAmount}); - } else { - const comment = !isEmptyObject(transaction) ? TransactionUtils.getDescription(transaction) : undefined; - messageText = Localize.translateLocal('iou.payerOwesAmount', {payer, amount: formattedAmount, comment}); - } - + const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', { + payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || '', + amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency), + }); updatedReportPreviewAction.message[0].text = messageText; updatedReportPreviewAction.message[0].html = messageText; From 2060d5e2bfc161a08b7058ce88b3603add93a3bc Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 30 Jan 2024 20:15:47 +0530 Subject: [PATCH 025/674] TS-migration: TermsStep Page --- .../{TermsStep.js => TermsStep.tsx} | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) rename src/pages/EnablePayments/{TermsStep.js => TermsStep.tsx} (67%) diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.tsx similarity index 67% rename from src/pages/EnablePayments/TermsStep.js rename to src/pages/EnablePayments/TermsStep.tsx index a09e1801c3b0..a03f3607d56e 100644 --- a/src/pages/EnablePayments/TermsStep.js +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -6,39 +6,34 @@ import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import LongTermsForm from './TermsPage/LongTermsForm'; import ShortTermsForm from './TermsPage/ShortTermsForm'; -import userWalletPropTypes from './userWalletPropTypes'; -import walletTermsPropTypes from './walletTermsPropTypes'; - -const propTypes = { - /** The user's wallet */ - userWallet: userWalletPropTypes, +import useLocalize from '@hooks/useLocalize'; +import type {OnyxEntry} from 'react-native-onyx'; +import {WalletTerms, UserWallet} from '@src/types/onyx'; +type TermsStepOnyxProps = { /** Comes from Onyx. Information about the terms for the wallet */ - walletTerms: walletTermsPropTypes, - - ...withLocalizePropTypes, -}; + walletTerms: OnyxEntry; +} -const defaultProps = { - userWallet: {}, - walletTerms: {}, +type TermsStepProps = TermsStepOnyxProps & { + /** The user's wallet */ + userWallet: OnyxEntry; }; -function TermsStep(props) { +function TermsStep(props: TermsStepProps) { const styles = useThemeStyles(); const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false); const [hasAcceptedPrivacyPolicyAndWalletAgreement, setHasAcceptedPrivacyPolicyAndWalletAgreement] = useState(false); const [error, setError] = useState(false); + const {translate} = useLocalize(); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms) || ''; + const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms??{}) || ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); @@ -59,7 +54,7 @@ function TermsStep(props) { return ( <> - + ( - {`${props.translate('termsStep.haveReadAndAgree')}`} - {`${props.translate('termsStep.electronicDisclosures')}.`} + {`${translate('termsStep.haveReadAndAgree')}`} + {`${translate('termsStep.electronicDisclosures')}.`} )} /> ( - {`${props.translate('termsStep.agreeToThe')} `} + {`${translate('termsStep.agreeToThe')} `} - {`${props.translate('common.privacy')} `} + {`${translate('common.privacy')} `} - {`${props.translate('common.and')} `} + {`${translate('common.and')} `} - {`${props.translate('termsStep.walletAgreement')}.`} + {`${translate('termsStep.walletAgreement')}.`} )} /> { if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { setError(true); @@ -104,12 +99,12 @@ function TermsStep(props) { setError(false); BankAccounts.acceptWalletTerms({ hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, - reportID: props.walletTerms.chatReportID, + reportID: props.walletTerms?.chatReportID??'', }); }} message={errorMessage} isAlertVisible={error || Boolean(errorMessage)} - isLoading={!!props.walletTerms.isLoading} + isLoading={!!props.walletTerms?.isLoading} containerStyles={[styles.mh0, styles.mv4]} /> @@ -118,13 +113,9 @@ function TermsStep(props) { } TermsStep.displayName = 'TermsPage'; -TermsStep.propTypes = propTypes; -TermsStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - }), -)(TermsStep); + +export default withOnyx({ + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, +})(TermsStep); From 744a3b5f380317b65ac91925a7e1f37d36273a79 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 30 Jan 2024 20:19:34 +0530 Subject: [PATCH 026/674] TS-migration: ShortTermsForm Page --- .../{ShortTermsForm.js => ShortTermsForm.tsx} | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) rename src/pages/EnablePayments/TermsPage/{ShortTermsForm.js => ShortTermsForm.tsx} (93%) diff --git a/src/pages/EnablePayments/TermsPage/ShortTermsForm.js b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx similarity index 93% rename from src/pages/EnablePayments/TermsPage/ShortTermsForm.js rename to src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx index 40824f47b036..1c9d37bb30e9 100644 --- a/src/pages/EnablePayments/TermsPage/ShortTermsForm.js +++ b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx @@ -5,19 +5,16 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes'; import CONST from '@src/CONST'; +import type {OnyxEntry} from 'react-native-onyx'; +import {UserWallet} from '@src/types/onyx'; -const propTypes = { - /** The user's wallet */ - userWallet: userWalletPropTypes, +type ShortTermsFormProps = { + /** The user's wallet */ + userWallet: OnyxEntry; }; -const defaultProps = { - userWallet: {}, -}; - -function ShortTermsForm(props) { +function ShortTermsForm(props: ShortTermsFormProps) { const styles = useThemeStyles(); const {translate, numberFormat} = useLocalize(); return ( @@ -25,7 +22,7 @@ function ShortTermsForm(props) { {translate('termsStep.shortTermsForm.expensifyPaymentsAccount', { walletProgram: - props.userWallet.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, + props.userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, })} @@ -150,8 +147,6 @@ function ShortTermsForm(props) { ); } -ShortTermsForm.propTypes = propTypes; -ShortTermsForm.defaultProps = defaultProps; ShortTermsForm.displayName = 'ShortTermsForm'; export default ShortTermsForm; From 3f86b2eb3d06eef77cfc51c34a51b4945dddc1a3 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 30 Jan 2024 20:22:23 +0530 Subject: [PATCH 027/674] TS-migration: LongTermsForm Page --- .../TermsPage/{LongTermsForm.js => LongTermsForm.tsx} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/pages/EnablePayments/TermsPage/{LongTermsForm.js => LongTermsForm.tsx} (98%) diff --git a/src/pages/EnablePayments/TermsPage/LongTermsForm.js b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx similarity index 98% rename from src/pages/EnablePayments/TermsPage/LongTermsForm.js rename to src/pages/EnablePayments/TermsPage/LongTermsForm.tsx index fad19c5ecf6f..ec89856642d9 100644 --- a/src/pages/EnablePayments/TermsPage/LongTermsForm.js +++ b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx @@ -66,7 +66,7 @@ function LongTermsForm() { ]; const getLongTermsSections = () => - _.map(termsData, (section, index) => ( + termsData.map((section, index) => ( // eslint-disable-next-line react/no-array-index-key @@ -105,7 +105,6 @@ function LongTermsForm() { Date: Tue, 30 Jan 2024 20:24:10 +0530 Subject: [PATCH 028/674] TS-migration: FailedKYC Page --- .../{FailedKYC.js => FailedKYC.tsx} | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) rename src/pages/EnablePayments/{FailedKYC.js => FailedKYC.tsx} (65%) diff --git a/src/pages/EnablePayments/FailedKYC.js b/src/pages/EnablePayments/FailedKYC.tsx similarity index 65% rename from src/pages/EnablePayments/FailedKYC.js rename to src/pages/EnablePayments/FailedKYC.tsx index fc54ea9c1074..25672772c216 100644 --- a/src/pages/EnablePayments/FailedKYC.js +++ b/src/pages/EnablePayments/FailedKYC.tsx @@ -2,35 +2,31 @@ import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; +import useLocalize from '@hooks/useLocalize'; -const propTypes = { - ...withLocalizePropTypes, -}; - -function FailedKYC(props) { +function FailedKYC() { + const {translate} = useLocalize(); const styles = useThemeStyles(); return ( - {props.translate('additionalDetailsStep.failedKYCTextBefore')} + {translate('additionalDetailsStep.failedKYCTextBefore')} {CONST.EMAIL.CONCIERGE} - {props.translate('additionalDetailsStep.failedKYCTextAfter')} + {translate('additionalDetailsStep.failedKYCTextAfter')} ); } -FailedKYC.propTypes = propTypes; FailedKYC.displayName = 'FailedKYC'; -export default withLocalize(FailedKYC); +export default FailedKYC; From b4c5a8a9d66a629167114fd56c2bb68850a27d11 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 30 Jan 2024 20:38:51 +0530 Subject: [PATCH 029/674] TS-migration: ActivateStep Page --- src/pages/EnablePayments/ActivateStep.js | 72 ----------------------- src/pages/EnablePayments/ActivateStep.tsx | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 72 deletions(-) delete mode 100644 src/pages/EnablePayments/ActivateStep.js create mode 100644 src/pages/EnablePayments/ActivateStep.tsx diff --git a/src/pages/EnablePayments/ActivateStep.js b/src/pages/EnablePayments/ActivateStep.js deleted file mode 100644 index 92342c28af73..000000000000 --- a/src/pages/EnablePayments/ActivateStep.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import ConfirmationPage from '@components/ConfirmationPage'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import LottieAnimations from '@components/LottieAnimations'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import compose from '@libs/compose'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import userWalletPropTypes from './userWalletPropTypes'; -import walletTermsPropTypes from './walletTermsPropTypes'; - -const propTypes = { - ...withLocalizePropTypes, - - /** The user's wallet */ - userWallet: userWalletPropTypes, - - /** Information about the user accepting the terms for payments */ - walletTerms: walletTermsPropTypes, -}; - -const defaultProps = { - userWallet: {}, - walletTerms: { - source: '', - chatReportID: 0, - }, -}; - -function ActivateStep(props) { - const isActivatedWallet = _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], props.userWallet.tierName); - const animation = isActivatedWallet ? LottieAnimations.Fireworks : LottieAnimations.ReviewingBankInfo; - let continueButtonText = ''; - - if (props.walletTerms.chatReportID) { - continueButtonText = props.translate('activateStep.continueToPayment'); - } else if (props.walletTerms.source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET) { - continueButtonText = props.translate('common.continue'); - } else { - continueButtonText = props.translate('activateStep.continueToTransfer'); - } - - return ( - <> - - PaymentMethods.continueSetup()} - /> - - ); -} - -ActivateStep.propTypes = propTypes; -ActivateStep.defaultProps = defaultProps; -ActivateStep.displayName = 'ActivateStep'; - -export default compose( - withLocalize, - withOnyx({ - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - }), -)(ActivateStep); diff --git a/src/pages/EnablePayments/ActivateStep.tsx b/src/pages/EnablePayments/ActivateStep.tsx new file mode 100644 index 000000000000..d16190f0c0af --- /dev/null +++ b/src/pages/EnablePayments/ActivateStep.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import ConfirmationPage from '@components/ConfirmationPage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import LottieAnimations from '@components/LottieAnimations'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import useLocalize from '@hooks/useLocalize'; +import type {OnyxEntry} from 'react-native-onyx'; +import {WalletTerms, UserWallet} from '@src/types/onyx'; + +type ActivateStepOnyxProps = { + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; +}; + +type ActivateStepProps = ActivateStepOnyxProps & { + /** The user's wallet */ + userWallet: OnyxEntry; +}; + +function ActivateStep({ + userWallet, + walletTerms +}: ActivateStepProps) { + const {translate} = useLocalize(); + const isActivatedWallet = userWallet?.tierName && [CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM].some((name) => name === userWallet.tierName); + + const animation = isActivatedWallet ? LottieAnimations.Fireworks : LottieAnimations.ReviewingBankInfo; + let continueButtonText = ''; + + if (walletTerms?.chatReportID) { + continueButtonText = translate('activateStep.continueToPayment'); + } else if (walletTerms?.source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET) { + continueButtonText = translate('common.continue'); + } else { + continueButtonText = translate('activateStep.continueToTransfer'); + } + + return ( + <> + + PaymentMethods.continueSetup()} + /> + + ); +} + +ActivateStep.displayName = 'ActivateStep'; + +export default withOnyx({ + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, +})(ActivateStep); \ No newline at end of file From e48ebb1d332c795947548c4027161b791b5869ff Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 30 Jan 2024 09:46:28 -0800 Subject: [PATCH 030/674] Display report description for 1-to-1 money request --- src/libs/ReportUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index edcd57078e0c..eeebe7e8b78a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2247,7 +2247,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName}); } - const containsNonReimbursable = hasNonReimbursableTransactions(report.reportID); + const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; const lastActorID = reportAction?.actorAccountID; @@ -2259,14 +2259,14 @@ function getReportPreviewMessage( // We only want to show the actor name in the preview if it's not the current user who took the action const requestorName = lastActorID && lastActorID !== currentUserAccountID ? getDisplayNameForParticipant(lastActorID, !isPreviewMessageForParentChatReport) : ''; - return `${requestorName ? `${requestorName}: ` : ''}${Localize.translateLocal('iou.requestedAmount', {formattedAmount: amountToDisplay})}`; + return `${requestorName ? `${requestorName}: ` : ''}${Localize.translateLocal('iou.requestedAmount', {formattedAmount: amountToDisplay, comment})}`; } + const containsNonReimbursable = hasNonReimbursableTransactions(report.reportID); if (containsNonReimbursable) { return Localize.translateLocal('iou.payerSpentAmount', {payer: payerName ?? '', amount: formattedAmount}); } - const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName ?? '', amount: formattedAmount, comment}); } From 0ac0bedbd17cf7e86935e71828efd1175a7ac131 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 31 Jan 2024 01:59:28 +0530 Subject: [PATCH 031/674] TS-migration: OnfidoStep & OnfidoPrivacy Page --- src/pages/EnablePayments/ActivateStep.tsx | 13 ++-- src/pages/EnablePayments/FailedKYC.tsx | 2 +- .../{OnfidoPrivacy.js => OnfidoPrivacy.tsx} | 64 +++++++++---------- .../{OnfidoStep.js => OnfidoStep.tsx} | 35 +++++----- .../TermsPage/ShortTermsForm.tsx | 8 ++- src/pages/EnablePayments/TermsStep.tsx | 12 ++-- 6 files changed, 64 insertions(+), 70 deletions(-) rename src/pages/EnablePayments/{OnfidoPrivacy.js => OnfidoPrivacy.tsx} (74%) rename src/pages/EnablePayments/{OnfidoStep.js => OnfidoStep.tsx} (69%) diff --git a/src/pages/EnablePayments/ActivateStep.tsx b/src/pages/EnablePayments/ActivateStep.tsx index d16190f0c0af..0dbb98e53a5f 100644 --- a/src/pages/EnablePayments/ActivateStep.tsx +++ b/src/pages/EnablePayments/ActivateStep.tsx @@ -1,14 +1,14 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import ConfirmationPage from '@components/ConfirmationPage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import LottieAnimations from '@components/LottieAnimations'; +import useLocalize from '@hooks/useLocalize'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import useLocalize from '@hooks/useLocalize'; -import type {OnyxEntry} from 'react-native-onyx'; -import {WalletTerms, UserWallet} from '@src/types/onyx'; +import {UserWallet, WalletTerms} from '@src/types/onyx'; type ActivateStepOnyxProps = { /** Information about the user accepting the terms for payments */ @@ -20,10 +20,7 @@ type ActivateStepProps = ActivateStepOnyxProps & { userWallet: OnyxEntry; }; -function ActivateStep({ - userWallet, - walletTerms -}: ActivateStepProps) { +function ActivateStep({userWallet, walletTerms}: ActivateStepProps) { const {translate} = useLocalize(); const isActivatedWallet = userWallet?.tierName && [CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM].some((name) => name === userWallet.tierName); @@ -59,4 +56,4 @@ export default withOnyx({ walletTerms: { key: ONYXKEYS.WALLET_TERMS, }, -})(ActivateStep); \ No newline at end of file +})(ActivateStep); diff --git a/src/pages/EnablePayments/FailedKYC.tsx b/src/pages/EnablePayments/FailedKYC.tsx index 25672772c216..6b393229d62f 100644 --- a/src/pages/EnablePayments/FailedKYC.tsx +++ b/src/pages/EnablePayments/FailedKYC.tsx @@ -2,9 +2,9 @@ import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import useLocalize from '@hooks/useLocalize'; function FailedKYC() { const {translate} = useLocalize(); diff --git a/src/pages/EnablePayments/OnfidoPrivacy.js b/src/pages/EnablePayments/OnfidoPrivacy.tsx similarity index 74% rename from src/pages/EnablePayments/OnfidoPrivacy.js rename to src/pages/EnablePayments/OnfidoPrivacy.tsx index 77b884fb2934..d8e9f616b8a7 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.js +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -1,7 +1,8 @@ import lodashGet from 'lodash/get'; import React, {useRef} from 'react'; -import {View} from 'react-native'; +import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import _ from 'underscore'; import FixedFooter from '@components/FixedFooter'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; @@ -9,43 +10,41 @@ import FormScrollView from '@components/FormScrollView'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; -import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; +import {WalletOnfido} from '@src/types/onyx'; -const propTypes = { - /** Stores various information used to build the UI and call any APIs */ - walletOnfidoData: walletOnfidoDataPropTypes, - - ...withLocalizePropTypes, +const DEFAULT_WALLET_ONFIDO_DATA = { + applicantID: '', + sdkToken: '', + loading: false, + errors: {}, + fixableErrors: [], + hasAcceptedPrivacyPolicy: false, }; -const defaultProps = { - walletOnfidoData: { - applicantID: '', - sdkToken: '', - loading: false, - errors: {}, - fixableErrors: [], - hasAcceptedPrivacyPolicy: false, - }, +type OnfidoPrivacyOnyxProps = { + /** Stores various information used to build the UI and call any APIs */ + walletOnfidoData: OnyxEntry; }; -function OnfidoPrivacy({walletOnfidoData, translate, form}) { +type OnfidoPrivacyProps = OnfidoPrivacyOnyxProps & {}; + +function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPrivacyProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); - const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData; + const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData ?? {}; - const formRef = useRef(null); + const formRef = useRef(null); const openOnfidoFlow = () => { BankAccounts.openOnfidoFlow(); }; - let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) || ''; + let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData ?? {}) || ''; const onfidoFixableErrors = lodashGet(walletOnfidoData, 'fixableErrors', []); onfidoError += !_.isEmpty(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; @@ -70,7 +69,7 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { isAlertVisible={Boolean(onfidoError)} onSubmit={openOnfidoFlow} onFixTheErrorsLinkPressed={() => { - form.scrollTo({y: 0, animated: true}); + formRef.current?.scrollTo({y: 0, animated: true}); }} message={onfidoError} isLoading={isLoading} @@ -85,18 +84,13 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { ); } -OnfidoPrivacy.propTypes = propTypes; -OnfidoPrivacy.defaultProps = defaultProps; OnfidoPrivacy.displayName = 'OnfidoPrivacy'; -export default compose( - withLocalize, - withOnyx({ - walletOnfidoData: { - key: ONYXKEYS.WALLET_ONFIDO, +export default withOnyx({ + walletOnfidoData: { + key: ONYXKEYS.WALLET_ONFIDO, - // Let's get a new onfido token each time the user hits this flow (as it should only be once) - initWithStoredValues: false, - }, - }), -)(OnfidoPrivacy); + // Let's get a new onfido token each time the user hits this flow (as it should only be once) + initWithStoredValues: false, + }, +})(OnfidoPrivacy); diff --git a/src/pages/EnablePayments/OnfidoStep.js b/src/pages/EnablePayments/OnfidoStep.tsx similarity index 69% rename from src/pages/EnablePayments/OnfidoStep.js rename to src/pages/EnablePayments/OnfidoStep.tsx index 8b40c88f62fb..5e36f2f302a6 100644 --- a/src/pages/EnablePayments/OnfidoStep.js +++ b/src/pages/EnablePayments/OnfidoStep.tsx @@ -1,7 +1,9 @@ import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +// @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; import useLocalize from '@hooks/useLocalize'; import Growl from '@libs/Growl'; @@ -11,25 +13,25 @@ import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {WalletOnfido} from '@src/types/onyx'; import OnfidoPrivacy from './OnfidoPrivacy'; -import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; -const propTypes = { - /** Stores various information used to build the UI and call any APIs */ - walletOnfidoData: walletOnfidoDataPropTypes, +const DEFAULT_WALLET_ONFIDO_DATA = { + loading: false, + hasAcceptedPrivacyPolicy: false, }; -const defaultProps = { - walletOnfidoData: { - loading: false, - hasAcceptedPrivacyPolicy: false, - }, +type OnfidoStepOnyxProps = { + /** Stores various information used to build the UI and call any APIs */ + walletOnfidoData: OnyxEntry; }; -function OnfidoStep({walletOnfidoData}) { +type OnfidoStepProps = OnfidoStepOnyxProps; + +function OnfidoStep({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoStepProps) { const {translate} = useLocalize(); - const shouldShowOnfido = walletOnfidoData.hasAcceptedPrivacyPolicy && !walletOnfidoData.isLoading && !walletOnfidoData.error && walletOnfidoData.sdkToken; + const shouldShowOnfido = walletOnfidoData?.hasAcceptedPrivacyPolicy && !walletOnfidoData.isLoading && !walletOnfidoData.errors && walletOnfidoData.sdkToken; const goBack = useCallback(() => { Navigation.goBack(ROUTES.HOME); @@ -44,15 +46,16 @@ function OnfidoStep({walletOnfidoData}) { }, [translate]); const verifyIdentity = useCallback( + // @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. (data) => { BankAccounts.verifyIdentity({ onfidoData: JSON.stringify({ ...data, - applicantID: walletOnfidoData.applicantID, + applicantID: walletOnfidoData?.applicantID, }), }); }, - [walletOnfidoData.applicantID], + [walletOnfidoData?.applicantID], ); return ( @@ -70,18 +73,16 @@ function OnfidoStep({walletOnfidoData}) { onSuccess={verifyIdentity} /> ) : ( - + )} ); } -OnfidoStep.propTypes = propTypes; -OnfidoStep.defaultProps = defaultProps; OnfidoStep.displayName = 'OnfidoStep'; -export default withOnyx({ +export default withOnyx({ walletOnfidoData: { key: ONYXKEYS.WALLET_ONFIDO, diff --git a/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx index 1c9d37bb30e9..b45b8b657a75 100644 --- a/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx +++ b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx @@ -1,16 +1,16 @@ import React from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import CONST from '@src/CONST'; -import type {OnyxEntry} from 'react-native-onyx'; import {UserWallet} from '@src/types/onyx'; type ShortTermsFormProps = { - /** The user's wallet */ + /** The user's wallet */ userWallet: OnyxEntry; }; @@ -22,7 +22,9 @@ function ShortTermsForm(props: ShortTermsFormProps) { {translate('termsStep.shortTermsForm.expensifyPaymentsAccount', { walletProgram: - props.userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, + props.userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID + ? CONST.WALLET.PROGRAM_ISSUERS.EXPENSIFY_PAYMENTS + : CONST.WALLET.PROGRAM_ISSUERS.BANCORP_BANK, })} diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index a03f3607d56e..cef54ba463e6 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -1,25 +1,25 @@ import React, {useEffect, useState} from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; +import {UserWallet, WalletTerms} from '@src/types/onyx'; import LongTermsForm from './TermsPage/LongTermsForm'; import ShortTermsForm from './TermsPage/ShortTermsForm'; -import useLocalize from '@hooks/useLocalize'; -import type {OnyxEntry} from 'react-native-onyx'; -import {WalletTerms, UserWallet} from '@src/types/onyx'; type TermsStepOnyxProps = { /** Comes from Onyx. Information about the terms for the wallet */ walletTerms: OnyxEntry; -} +}; type TermsStepProps = TermsStepOnyxProps & { /** The user's wallet */ @@ -33,7 +33,7 @@ function TermsStep(props: TermsStepProps) { const [error, setError] = useState(false); const {translate} = useLocalize(); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms??{}) || ''; + const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) || ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); @@ -99,7 +99,7 @@ function TermsStep(props: TermsStepProps) { setError(false); BankAccounts.acceptWalletTerms({ hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, - reportID: props.walletTerms?.chatReportID??'', + reportID: props.walletTerms?.chatReportID ?? '', }); }} message={errorMessage} From 0feb8c4e71dad1c3cd3d11f7a2c7670387bd09c7 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 31 Jan 2024 04:23:28 +0530 Subject: [PATCH 032/674] TS-migration: EnablePaymentsPage Page --- ...PaymentsPage.js => EnablePaymentsPage.tsx} | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) rename src/pages/EnablePayments/{EnablePaymentsPage.js => EnablePaymentsPage.tsx} (78%) diff --git a/src/pages/EnablePayments/EnablePaymentsPage.js b/src/pages/EnablePayments/EnablePaymentsPage.tsx similarity index 78% rename from src/pages/EnablePayments/EnablePaymentsPage.js rename to src/pages/EnablePayments/EnablePaymentsPage.tsx index 257eab1d38d3..6d2defc82df0 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.js +++ b/src/pages/EnablePayments/EnablePaymentsPage.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -11,28 +11,27 @@ import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {UserWallet} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ActivateStep from './ActivateStep'; import AdditionalDetailsStep from './AdditionalDetailsStep'; import FailedKYC from './FailedKYC'; // Steps import OnfidoStep from './OnfidoStep'; import TermsStep from './TermsStep'; -import userWalletPropTypes from './userWalletPropTypes'; -const propTypes = { +type EnablePaymentsPageOnyxProps = { /** The user's wallet */ - userWallet: userWalletPropTypes, + userWallet: OnyxEntry; }; -const defaultProps = { - userWallet: {}, -}; +type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps & {}; -function EnablePaymentsPage({userWallet}) { +function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const {isPendingOnfidoResult, hasFailedOnfido} = userWallet; + const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; useEffect(() => { if (isOffline) { @@ -47,18 +46,18 @@ function EnablePaymentsPage({userWallet}) { Wallet.openEnablePaymentsPage(); }, [isOffline, isPendingOnfidoResult, hasFailedOnfido]); - if (_.isEmpty(userWallet)) { + if (isEmptyObject(userWallet)) { return ; } return ( {() => { - if (userWallet.errorCode === CONST.WALLET.ERROR.KYC) { + if (userWallet?.errorCode === CONST.WALLET.ERROR.KYC) { return ( <> ({ userWallet: { key: ONYXKEYS.USER_WALLET, From dae08c96c8d7d3008858edf666fa452c6269d310 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 31 Jan 2024 12:56:40 +0530 Subject: [PATCH 033/674] Remove MoneyRequestSelectorPage.js and copy any changes since Nov 27 into IOURequestStartPage.js. Signed-off-by: Krishna Gupta --- ...oraryForRefactorRequestConfirmationList.js | 4 +-- .../AttachmentPickerWithMenuItems.js | 3 +- .../FloatingActionButtonAndPopover.js | 11 ++++-- src/pages/iou/request/IOURequestStartPage.js | 36 ++++++++++--------- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepConfirmation.js | 17 ++++++--- .../step/IOURequestStepParticipants.js | 12 +++++-- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 2aff0444a59e..f48820654768 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -266,8 +266,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // Do not hide fields in case of send money request const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; - const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; - const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; + const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend; // Fetches the first tag list of the policy const policyTag = PolicyUtils.getTag(policyTags); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 444dd939142b..4091016baeeb 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -20,7 +20,6 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; @@ -154,7 +153,7 @@ function AttachmentPickerWithMenuItems({ [CONST.IOU.TYPE.SEND]: { icon: Expensicons.Send, text: translate('iou.sendMoney'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND, report.reportID), + onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), }, }; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index bbcdc5cebef4..4ab4e5fbb081 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -15,7 +15,6 @@ import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; -import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -182,7 +181,15 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: props.translate('iou.sendMoney'), - onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + onSelected: () => + interceptAnonymousUser(() => + Navigation.navigate( + // When starting to create a send money request from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()), + ), + ), }, ...[ { diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 3d80ab89347d..c790b30c5f7d 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -156,22 +156,26 @@ function IOURequestStartPage({ title={tabTitles[iouType]} onBackButtonPress={navigateBack} /> - ( - - )} - > - {() => } - {() => } - {shouldDisplayDistanceRequest && {() => }} - + {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( + ( + + )} + > + {() => } + {() => } + {shouldDisplayDistanceRequest && {() => }} + + ) : ( + + )} diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 15f98205839e..5d84b0d81b68 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -256,7 +256,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE; + const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 6028a735d132..bdee619dba9f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -92,7 +92,15 @@ function IOURequestStepConfirmation({ const transactionTaxCode = transaction.taxRate && transaction.taxRate.keyForList; const transactionTaxAmount = transaction.taxAmount; const requestType = TransactionUtils.getRequestType(transaction); - const headerTitle = iouType === CONST.IOU.TYPE.SPLIT ? translate('iou.split') : translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + const headerTitle = () => { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const participants = useMemo( () => _.map(transaction.participants, (participant) => { @@ -287,7 +295,7 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = transaction.comment.trim(); + const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -299,8 +307,9 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction.amount, transaction.comment, participants, transaction.currency, currentUserPersonalDetails.accountID, report], + [transaction, participants, currentUserPersonalDetails.accountID, report], ); + const addNewParticipant = (option) => { const newParticipants = _.map(transaction.participants, (participant) => { if (participant.accountID === option.accountID) { @@ -327,7 +336,7 @@ function IOURequestStepConfirmation({ {({safeAreaPaddingBottomStyle}) => ( { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -81,7 +89,7 @@ function IOURequestStepParticipants({ return ( Date: Sat, 3 Feb 2024 16:27:01 +0530 Subject: [PATCH 034/674] remove redundant code and old component. Signed-off-by: Krishna Gupta --- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 18 -- src/libs/Navigation/types.ts | 1 - .../FloatingActionButtonAndPopover.js | 2 +- src/pages/iou/MoneyRequestSelectorPage.js | 169 ------------------ ...yForRefactorRequestParticipantsSelector.js | 1 - 6 files changed, 1 insertion(+), 191 deletions(-) delete mode 100644 src/pages/iou/MoneyRequestSelectorPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 4606f867c3fc..60b87fd85f3d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -93,7 +93,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_TAG]: () => require('../../../pages/iou/request/step/IOURequestStepTag').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..72c2c9d305f3 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -379,24 +379,6 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SCAN]: ROUTES.MONEY_REQUEST_STEP_SCAN.route, [SCREENS.MONEY_REQUEST.STEP_TAG]: ROUTES.MONEY_REQUEST_STEP_TAG.route, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: ROUTES.MONEY_REQUEST_STEP_WAYPOINT.route, - [SCREENS.MONEY_REQUEST.ROOT]: { - path: ROUTES.MONEY_REQUEST.route, - exact: true, - screens: { - [SCREENS.MONEY_REQUEST.MANUAL_TAB]: { - path: ROUTES.MONEY_REQUEST_MANUAL_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.SCAN_TAB]: { - path: ROUTES.MONEY_REQUEST_SCAN_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.DISTANCE_TAB]: { - path: ROUTES.MONEY_REQUEST_DISTANCE_TAB.route, - exact: true, - }, - }, - }, [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3c4cf17853f1..13eeb972305a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -193,7 +193,6 @@ type RoomInviteNavigatorParamList = { }; type MoneyRequestNavigatorParamList = { - [SCREENS.MONEY_REQUEST.ROOT]: undefined; [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 7a3390ab5478..b2844374b6ae 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -174,7 +174,7 @@ function FloatingActionButtonAndPopover(props) { }, { icon: Expensicons.Send, - text: props.translate('iou.sendMoney'), + text: translate('iou.sendMoney'), // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js deleted file mode 100644 index 0a0efc38313a..000000000000 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ /dev/null @@ -1,169 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import DragAndDropProvider from '@components/DragAndDrop/Provider'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import TabSelector from '@components/TabSelector/TabSelector'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as IOUUtils from '@libs/IOUUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; -import * as ReportUtils from '@libs/ReportUtils'; -import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import NewDistanceRequestPage from './NewDistanceRequestPage'; -import IOURequestStepScan from './request/step/IOURequestStepScan'; -import NewRequestAmountPage from './steps/NewRequestAmountPage'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Report on which the money request is being created */ - report: reportPropTypes, - - /** The policy tied to the report */ - policy: PropTypes.shape({ - /** Type of the policy */ - type: PropTypes.string, - }), - - /** Which tab has been selected */ - selectedTab: PropTypes.string, -}; - -const defaultProps = { - selectedTab: CONST.TAB_REQUEST.SCAN, - report: {}, - policy: {}, -}; - -function MoneyRequestSelectorPage(props) { - const styles = useThemeStyles(); - const [isDraggingOver, setIsDraggingOver] = useState(false); - - const iouType = lodashGet(props.route, 'params.iouType', ''); - const reportID = lodashGet(props.route, 'params.reportID', ''); - const {translate} = useLocalize(); - - const title = { - [CONST.IOU.TYPE.REQUEST]: translate('iou.requestMoney'), - [CONST.IOU.TYPE.SEND]: translate('iou.sendMoney'), - [CONST.IOU.TYPE.SPLIT]: translate('iou.splitBill'), - }; - const isFromGlobalCreate = !reportID; - const isExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); - const isExpenseReport = ReportUtils.isExpenseReport(props.report); - const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; - - const resetMoneyRequestInfo = () => { - const moneyRequestID = `${iouType}${reportID}`; - IOU.resetMoneyRequestInfo(moneyRequestID); - }; - - // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(props.report.reportID) || ReportUtils.canCreateRequest(props.report, props.policy, iouType); - const prevSelectedTab = usePrevious(props.selectedTab); - - useEffect(() => { - if (prevSelectedTab === props.selectedTab) { - return; - } - - resetMoneyRequestInfo(); - // resetMoneyRequestInfo function is not added as dependencies since they don't change between renders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.selectedTab, prevSelectedTab]); - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - - - {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( - ( - - )} - > - - {() => } - {shouldDisplayDistanceRequest && ( - - )} - - ) : ( - - )} - - - - )} - - ); -} - -MoneyRequestSelectorPage.propTypes = propTypes; -MoneyRequestSelectorPage.defaultProps = defaultProps; -MoneyRequestSelectorPage.displayName = 'MoneyRequestSelectorPage'; - -export default compose( - withReportOrNotFound(false), - withOnyx({ - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, - }, - }), -)(MoneyRequestSelectorPage); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 6e9de5b62a7e..a4807572bf56 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -258,7 +258,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; - const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { if (shouldShowSplitBillErrorMessage) { From b915c4ef809709a29734f0a67a51d423b1eb8d2b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 3 Feb 2024 16:53:52 +0530 Subject: [PATCH 035/674] remove redundant code and comment Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.js | 11 ----------- .../SidebarScreen/FloatingActionButtonAndPopover.js | 1 - 2 files changed, 12 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4db89a1e926b..053d48b7ed0f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3572,16 +3572,6 @@ function setMoneyRequestParticipantsFromReport(transactionID, report) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** - * Initialize money request info and navigate to the MoneyRequest page - * @param {String} iouType - * @param {String} reportID - */ -function startMoneyRequest(iouType, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - /** * @param {String} id */ @@ -3799,7 +3789,6 @@ export { submitReport, payMoneyRequest, sendMoneyWithWallet, - startMoneyRequest, startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b2844374b6ae..52251da7a67a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -175,7 +175,6 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: translate('iou.sendMoney'), - // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => Navigation.navigate( From 9bd67d67aee253e4614ef64854169229282f806d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 5 Feb 2024 16:14:46 +0530 Subject: [PATCH 036/674] merge main, fix lint --- src/pages/EnablePayments/ActivateStep.tsx | 2 +- .../EnablePayments/EnablePaymentsPage.tsx | 3 ++- src/pages/EnablePayments/OnfidoPrivacy.tsx | 23 +++++++++++-------- src/pages/EnablePayments/OnfidoStep.tsx | 2 +- .../TermsPage/LongTermsForm.tsx | 1 - .../TermsPage/ShortTermsForm.tsx | 2 +- src/pages/EnablePayments/TermsStep.tsx | 2 +- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/pages/EnablePayments/ActivateStep.tsx b/src/pages/EnablePayments/ActivateStep.tsx index 0dbb98e53a5f..e0bea7488140 100644 --- a/src/pages/EnablePayments/ActivateStep.tsx +++ b/src/pages/EnablePayments/ActivateStep.tsx @@ -8,7 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {UserWallet, WalletTerms} from '@src/types/onyx'; +import type {UserWallet, WalletTerms} from '@src/types/onyx'; type ActivateStepOnyxProps = { /** Information about the user accepting the terms for payments */ diff --git a/src/pages/EnablePayments/EnablePaymentsPage.tsx b/src/pages/EnablePayments/EnablePaymentsPage.tsx index 6d2defc82df0..1384875fe031 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.tsx +++ b/src/pages/EnablePayments/EnablePaymentsPage.tsx @@ -25,7 +25,7 @@ type EnablePaymentsPageOnyxProps = { userWallet: OnyxEntry; }; -type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps & {}; +type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps; function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { const {translate} = useLocalize(); @@ -38,6 +38,7 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { return; } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (isPendingOnfidoResult || hasFailedOnfido) { Navigation.navigate(ROUTES.SETTINGS_WALLET, CONST.NAVIGATION.TYPE.UP); return; diff --git a/src/pages/EnablePayments/OnfidoPrivacy.tsx b/src/pages/EnablePayments/OnfidoPrivacy.tsx index d8e9f616b8a7..80fa08c009ca 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.tsx +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -1,9 +1,8 @@ -import lodashGet from 'lodash/get'; import React, {useRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; +import type {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; -import _ from 'underscore'; import FixedFooter from '@components/FixedFooter'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import FormScrollView from '@components/FormScrollView'; @@ -15,7 +14,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; -import {WalletOnfido} from '@src/types/onyx'; +import type {WalletOnfido} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const DEFAULT_WALLET_ONFIDO_DATA = { applicantID: '', @@ -31,22 +31,25 @@ type OnfidoPrivacyOnyxProps = { walletOnfidoData: OnyxEntry; }; -type OnfidoPrivacyProps = OnfidoPrivacyOnyxProps & {}; +type OnfidoPrivacyProps = OnfidoPrivacyOnyxProps; function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPrivacyProps) { const {translate} = useLocalize(); - const styles = useThemeStyles(); - const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData ?? {}; - const formRef = useRef(null); + const styles = useThemeStyles(); + if (!walletOnfidoData) { + return; + } + const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData; const openOnfidoFlow = () => { BankAccounts.openOnfidoFlow(); }; let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData ?? {}) || ''; - const onfidoFixableErrors = lodashGet(walletOnfidoData, 'fixableErrors', []); - onfidoError += !_.isEmpty(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; + + const onfidoFixableErrors = walletOnfidoData?.fixableErrors ?? []; + onfidoError += !isEmptyObject(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; return ( diff --git a/src/pages/EnablePayments/OnfidoStep.tsx b/src/pages/EnablePayments/OnfidoStep.tsx index 5e36f2f302a6..04cadb24fecf 100644 --- a/src/pages/EnablePayments/OnfidoStep.tsx +++ b/src/pages/EnablePayments/OnfidoStep.tsx @@ -13,7 +13,7 @@ import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {WalletOnfido} from '@src/types/onyx'; +import type {WalletOnfido} from '@src/types/onyx'; import OnfidoPrivacy from './OnfidoPrivacy'; const DEFAULT_WALLET_ONFIDO_DATA = { diff --git a/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx index ec89856642d9..81d18c5dfc44 100644 --- a/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx +++ b/src/pages/EnablePayments/TermsPage/LongTermsForm.tsx @@ -1,6 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import CollapsibleSection from '@components/CollapsibleSection'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; diff --git a/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx index b45b8b657a75..f4db904c07f3 100644 --- a/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx +++ b/src/pages/EnablePayments/TermsPage/ShortTermsForm.tsx @@ -7,7 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import CONST from '@src/CONST'; -import {UserWallet} from '@src/types/onyx'; +import type {UserWallet} from '@src/types/onyx'; type ShortTermsFormProps = { /** The user's wallet */ diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index cef54ba463e6..fe463f88792d 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -12,7 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; -import {UserWallet, WalletTerms} from '@src/types/onyx'; +import type {UserWallet, WalletTerms} from '@src/types/onyx'; import LongTermsForm from './TermsPage/LongTermsForm'; import ShortTermsForm from './TermsPage/ShortTermsForm'; From c0f4f030db1ff9e61da920b9c4393a82cf42a59b Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 5 Feb 2024 19:22:28 +0530 Subject: [PATCH 037/674] TS-migration: IdologyQuestions Page --- src/ONYXKEYS.ts | 4 + ...ologyQuestions.js => IdologyQuestions.tsx} | 81 +++++++------------ src/types/onyx/Form.ts | 5 ++ src/types/onyx/index.ts | 2 + 4 files changed, 39 insertions(+), 53 deletions(-) rename src/pages/EnablePayments/{IdologyQuestions.js => IdologyQuestions.tsx} (69%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7328fb2543ad..3046b5a16bdd 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -367,6 +367,8 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm', PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', + IDOLOGY_QUESTIONS_FORM: 'idologyQuestions', + IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsDraft', }, } as const; @@ -556,6 +558,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_DRAFT]: OnyxTypes.PersonalBankAccount; + [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM]: OnyxTypes.IdologyQuestionsForm; + [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM_DRAFT]: OnyxTypes.IdologyQuestionsForm; }; type OnyxKeyValue = OnyxEntry; diff --git a/src/pages/EnablePayments/IdologyQuestions.js b/src/pages/EnablePayments/IdologyQuestions.tsx similarity index 69% rename from src/pages/EnablePayments/IdologyQuestions.js rename to src/pages/EnablePayments/IdologyQuestions.tsx index a0c202b0bbbc..4a5ef72019c0 100644 --- a/src/pages/EnablePayments/IdologyQuestions.js +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -1,10 +1,10 @@ -import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {WalletAdditionalQuestionDetails} from 'src/types/onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; +import type {Choice} from '@components/RadioButtons'; import SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; @@ -12,53 +12,36 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; const MAX_SKIP = 1; const SKIP_QUESTION_TEXT = 'Skip Question'; -const propTypes = { +type IdologyQuestionsProps = { /** Questions returned by Idology */ /** example: [{"answer":["1251","6253","113","None of the above","Skip Question"],"prompt":"Which number goes with your address on MASONIC AVE?","type":"street.number.b"}, ...] */ - questions: PropTypes.arrayOf( - PropTypes.shape({ - prompt: PropTypes.string, - type: PropTypes.string, - answer: PropTypes.arrayOf(PropTypes.string), - }), - ), + questions: WalletAdditionalQuestionDetails[]; /** ID from Idology, referencing those questions */ - idNumber: PropTypes.string, - - walletAdditionalDetails: PropTypes.shape({ - /** Are we waiting for a response? */ - isLoading: PropTypes.bool, - - /** Any additional error message to show */ - errors: PropTypes.objectOf(PropTypes.string), - - /** What error do we need to handle */ - errorCode: PropTypes.string, - }), + idNumber: string; }; -const defaultProps = { - questions: [], - idNumber: '', - walletAdditionalDetails: {}, +type Answer = { + question: string; + answer: string; }; -function IdologyQuestions({questions, idNumber}) { +function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [shouldHideSkipAnswer, setShouldHideSkipAnswer] = useState(false); - const [userAnswers, setUserAnswers] = useState([]); + const [userAnswers, setUserAnswers] = useState([]); const currentQuestion = questions[currentQuestionIndex] || {}; - const possibleAnswers = _.filter( - _.map(currentQuestion.answer, (answer) => { + const possibleAnswers: Choice[] = currentQuestion.answer + .map((answer) => { if (shouldHideSkipAnswer && answer === SKIP_QUESTION_TEXT) { return; } @@ -67,15 +50,11 @@ function IdologyQuestions({questions, idNumber}) { label: answer, value: answer, }; - }), - ); + }) + .filter((answer): answer is Choice => answer !== undefined); - /** - * Put question answer in the state. - * @param {String} answer - */ - const chooseAnswer = (answer) => { - const tempAnswers = _.map(userAnswers, _.clone); + const chooseAnswer = (answer: string) => { + const tempAnswers: Answer[] = userAnswers.map((userAnswer) => ({...userAnswer})); tempAnswers[currentQuestionIndex] = {question: currentQuestion.type, answer}; @@ -90,11 +69,11 @@ function IdologyQuestions({questions, idNumber}) { return; } // Get the number of questions that were skipped by the user. - const skippedQuestionsCount = _.filter(userAnswers, (answer) => answer.answer === SKIP_QUESTION_TEXT).length; + const skippedQuestionsCount = userAnswers.filter((answer) => answer.answer === SKIP_QUESTION_TEXT).length; // We have enough answers, let's call expectID KBA to verify them if (userAnswers.length - skippedQuestionsCount >= questions.length - MAX_SKIP) { - const tempAnswers = _.map(userAnswers, _.clone); + const tempAnswers: Answer[] = userAnswers.map((answer) => ({...answer})); // Auto skip any remaining questions if (tempAnswers.length < questions.length) { @@ -112,8 +91,8 @@ function IdologyQuestions({questions, idNumber}) { } }; - const validate = (values) => { - const errors = {}; + const validate = (values: OnyxFormValuesFields) => { + const errors: Errors = {}; if (!values.answer) { errors.answer = translate('additionalDetailsStep.selectAnswer'); } @@ -132,7 +111,7 @@ function IdologyQuestions({questions, idNumber}) { @@ -155,11 +136,5 @@ function IdologyQuestions({questions, idNumber}) { } IdologyQuestions.displayName = 'IdologyQuestions'; -IdologyQuestions.propTypes = propTypes; -IdologyQuestions.defaultProps = defaultProps; - -export default withOnyx({ - walletAdditionalDetails: { - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, - }, -})(IdologyQuestions); + +export default IdologyQuestions; diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 48f386afcbb0..79190aa3e6f6 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -68,6 +68,10 @@ type CloseAccountForm = Form<{ phoneOrEmail: string; }>; +type IdologyQuestionsForm = Form<{ + answer: string; +}>; + export default Form; export type { @@ -84,4 +88,5 @@ export type { WorkspaceSettingsForm, ReportFieldEditForm, CloseAccountForm, + IdologyQuestionsForm, }; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index d0ac2ce395fa..13c305abe384 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -14,6 +14,7 @@ import type { CloseAccountForm, DateOfBirthForm, DisplayNameForm, + IdologyQuestionsForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, @@ -165,4 +166,5 @@ export type { IntroSchoolPrincipalForm, PrivateNotesForm, ReportFieldEditForm, + IdologyQuestionsForm, }; From 61d24b89c0132dd7ce1049a62b0e88e9814b3c5f Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 5 Feb 2024 21:29:42 +0530 Subject: [PATCH 038/674] TS-migration: AdditionalDetailsStep Page --- src/ONYXKEYS.ts | 4 + .../withCurrentUserPersonalDetails.tsx | 2 +- src/libs/actions/PersonalDetails.ts | 3 +- ...tailsStep.js => AdditionalDetailsStep.tsx} | 120 ++++++------------ src/types/onyx/Form.ts | 13 ++ src/types/onyx/index.ts | 2 + 6 files changed, 64 insertions(+), 80 deletions(-) rename src/pages/EnablePayments/{AdditionalDetailsStep.js => AdditionalDetailsStep.tsx} (73%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3046b5a16bdd..2e4e5b564616 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -369,6 +369,8 @@ const ONYXKEYS = { PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', IDOLOGY_QUESTIONS_FORM: 'idologyQuestions', IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsDraft', + ADDITIONAL_DETAILS_FORM: 'additionalDetailStep', + ADDITIONAL_DETAILS_FORM_DRAFT: 'additionalDetailStepDraft', }, } as const; @@ -560,6 +562,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_DRAFT]: OnyxTypes.PersonalBankAccount; [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM]: OnyxTypes.IdologyQuestionsForm; [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM_DRAFT]: OnyxTypes.IdologyQuestionsForm; + [ONYXKEYS.FORMS.ADDITIONAL_DETAILS_FORM]: OnyxTypes.AdditionalDetailStepForm; + [ONYXKEYS.FORMS.ADDITIONAL_DETAILS_FORM_DRAFT]: OnyxTypes.AdditionalDetailStepForm; }; type OnyxKeyValue = OnyxEntry; diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index 9406c8634c1b..75bdb03ea6d8 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -64,4 +64,4 @@ export default function ; }; -const defaultProps = { - walletAdditionalDetails: { - errorFields: {}, - isLoading: false, - errors: {}, - questions: [], - idNumber: '', - errorCode: '', - }, - ...withCurrentUserPersonalDetailsDefaultProps, -}; +type AdditionalDetailsStepProps = AdditionalDetailsStepOnyxProps & WithCurrentUserPersonalDetailsProps; const fieldNameTranslationKeys = { legalFirstName: 'additionalDetailsStep.legalFirstNameLabel', @@ -77,20 +50,17 @@ const fieldNameTranslationKeys = { dob: 'common.dob', ssn: 'common.ssnLast4', ssnFull9: 'common.ssnFull9', -}; +} as const; -function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserPersonalDetails}) { +function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIONAL_DETAILS, currentUserPersonalDetails}: AdditionalDetailsStepProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); const currentDate = new Date(); const minDate = subYears(currentDate, CONST.DATE_BIRTH.MAX_AGE); const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); - const shouldAskForFullSSN = walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN; + const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; - /** - * @param {Object} values The values object is passed from FormProvider and contains info for each form element that has an inputID - * @returns {Object} - */ - const validate = (values) => { + const validate = (values: OnyxFormValuesFields) => { const requiredFields = ['legalFirstName', 'legalLastName', 'addressStreet', 'addressCity', 'addressZipCode', 'phoneNumber', 'dob', 'ssn', 'addressState']; const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); @@ -116,7 +86,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP // walletAdditionalDetails stores errors returned by the server. If the server returns an SSN error // then the user needs to provide the full 9 digit SSN. - if (walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN) { + if (walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN) { if (values.ssn && !ValidationUtils.isValidSSNFullNine(values.ssn)) { errors.ssn = 'additionalDetailsStep.ssnFull9Error'; } @@ -127,26 +97,23 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP return errors; }; - /** - * @param {Object} values The values object is passed from FormProvider and contains info for each form element that has an inputID - */ - const activateWallet = (values) => { + const activateWallet = (values: OnyxFormValuesFields) => { const personalDetails = { - phoneNumber: parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number.significant || '', - legalFirstName: values.legalFirstName || '', - legalLastName: values.legalLastName || '', - addressStreet: values.addressStreet || '', - addressCity: values.addressCity || '', - addressState: values.addressState || '', - addressZip: values.addressZipCode || '', - dob: values.dob || '', - ssn: values.ssn || '', + phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', + legalFirstName: values.legalFirstName ?? '', + legalLastName: values.legalLastName ?? '', + addressStreet: values.addressStreet ?? '', + addressCity: values.addressCity ?? '', + addressState: values.addressState ?? '', + addressZip: values.addressZipCode ?? '', + dob: values.dob ?? '', + ssn: values.ssn ?? '', }; // Attempt to set the personal details Wallet.updatePersonalDetails(personalDetails); }; - if (!_.isEmpty(walletAdditionalDetails.questions)) { + if (walletAdditionalDetails?.questions && walletAdditionalDetails.questions.length > 0) { return ( Wallet.setAdditionalDetailsQuestions(null)} + onBackButtonPress={() => Wallet.setAdditionalDetailsQuestions([], walletAdditionalDetails?.idNumber ?? '')} /> ); @@ -180,7 +147,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP ({ walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, }, - }), -)(AdditionalDetailsStep); + })(AdditionalDetailsStep), +); diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 79190aa3e6f6..646950dcb986 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -72,6 +72,18 @@ type IdologyQuestionsForm = Form<{ answer: string; }>; +type AdditionalDetailStepForm = Form<{ + legalFirstName: string; + legalLastName: string; + addressStreet: string; + addressCity: string; + addressZipCode: string; + phoneNumber: string; + dob: string; + ssn: string; + addressState: string; +}>; + export default Form; export type { @@ -89,4 +101,5 @@ export type { ReportFieldEditForm, CloseAccountForm, IdologyQuestionsForm, + AdditionalDetailStepForm, }; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 13c305abe384..ff6c782a37e1 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -11,6 +11,7 @@ import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; import type { AddDebitCardForm, + AdditionalDetailStepForm, CloseAccountForm, DateOfBirthForm, DisplayNameForm, @@ -167,4 +168,5 @@ export type { PrivateNotesForm, ReportFieldEditForm, IdologyQuestionsForm, + AdditionalDetailStepForm, }; From 0824fb95fa3df6b9f06e96d4d42266deb000c50d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:08:40 +0530 Subject: [PATCH 039/674] update startMoneyRequest_temporaryForRefactor to startMoneyRequest. Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.ts | 9 +-------- src/pages/iou/request/IOURequestStartPage.js | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 569b6b26b728..f20e0b0519c7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -225,7 +225,7 @@ Onyx.connect({ * @param iouRequestType one of manual/scan/distance */ // eslint-disable-next-line @typescript-eslint/naming-convention -function startMoneyRequest_temporaryForRefactor(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { +function startMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { // Generate a brand new transactionID const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID; // Disabling this line since currentDate can be an empty string @@ -3530,12 +3530,6 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** Initialize money request info and navigate to the MoneyRequest page */ -function startMoneyRequest(iouType: string, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - function setMoneyRequestId(id: string) { Onyx.merge(ONYXKEYS.IOU, {id}); } @@ -3686,7 +3680,6 @@ export { payMoneyRequest, sendMoneyWithWallet, startMoneyRequest, - startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, resetMoneyRequestInfo, diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index c790b30c5f7d..dd6420bf2c1a 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -106,7 +106,7 @@ function IOURequestStartPage({ if (transaction.reportID === reportID) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, transactionRequestType.current); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current); }, [transaction, reportID, iouType, isFromGlobalCreate]); const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); @@ -125,7 +125,7 @@ function IOURequestStartPage({ if (newIouType === previousIOURequestType) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, newIouType); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, [previousIOURequestType, reportID, isFromGlobalCreate], From 4e69672e311a7509f05b5e2946814c131c5284b2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:35:40 +0530 Subject: [PATCH 040/674] remove: unused routes from ROUTES.ts Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 7 ------- src/pages/iou/request/step/IOURequestStepWaypoint.js | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 016e4267803b..aabed6fdb0a1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -301,13 +301,6 @@ const ROUTES = { route: ':iouType/new/address/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const, }, - MONEY_REQUEST_DISTANCE_TAB: { - route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` as const, - }, - MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', - MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', - MONEY_REQUEST_CREATE: { route: 'create/:iouType/start/:transactionID/:reportID', getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.js b/src/pages/iou/request/step/IOURequestStepWaypoint.js index 4c35951bc297..89e2b2cc297d 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.js +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.js @@ -158,13 +158,13 @@ function IOURequestStepWaypoint({ } // Other flows will be handled by selecting a waypoint with selectWaypoint as this is mainly for the offline flow - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; const deleteStopAndHideModal = () => { Transaction.removeWaypoint(transaction, pageIndex, true); setIsDeleteStopModalOpen(false); - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; /** @@ -200,7 +200,7 @@ function IOURequestStepWaypoint({ title={translate(waypointDescriptionKey)} shouldShowBackButton onBackButtonPress={() => { - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }} shouldShowThreeDotsButton={shouldShowThreeDotsButton} shouldSetModalVisibility={false} From 013376d144397e0335f1eedb794be9fd81f12f88 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 13:59:30 +0530 Subject: [PATCH 041/674] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index daae4bcf0831..a11877739cb5 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -46,7 +46,7 @@ function IOURequestStepParticipants({ if (iouType === CONST.IOU.TYPE.SEND) { return translate('common.send'); } - translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); From 423af4e488e11f3df71ce02f6b295e7292b978bc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 14:14:51 +0530 Subject: [PATCH 042/674] updated sendMoney callback dependencies. Signed-off-by: Krishna Gupta --- .../iou/request/step/IOURequestStepConfirmation.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 70d5e1fceac7..c66dc91f4f64 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -308,7 +308,13 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); + + let trimmedComment = ''; + + if (transaction.comment && transaction.comment.comment) { + trimmedComment = transaction.comment.comment.trim(); + } + const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -320,7 +326,7 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction, participants, currentUserPersonalDetails.accountID, report], + [transaction.amount, transaction.comment, transaction.currency, participants, currentUserPersonalDetails.accountID, report], ); const addNewParticipant = (option) => { From 1ad742de9d5b27113e74d85691016089ff0d89ae Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 6 Feb 2024 08:08:34 -0800 Subject: [PATCH 043/674] Fix prettier formatting --- src/libs/ReportUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 09496c6b18f1..e485cf615a41 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2257,7 +2257,14 @@ function getReportPreviewMessage( }); } - if (!isEmptyObject(linkedTransaction) && !isEmptyObject(reportAction) && shouldConsiderReceiptBeingScanned && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction) && ReportActionsUtils.isMoneyRequestAction(reportAction)) { + if ( + !isEmptyObject(linkedTransaction) && + !isEmptyObject(reportAction) && + shouldConsiderReceiptBeingScanned && + TransactionUtils.hasReceipt(linkedTransaction) && + TransactionUtils.isReceiptBeingScanned(linkedTransaction) && + ReportActionsUtils.isMoneyRequestAction(reportAction) + ) { return Localize.translateLocal('iou.receiptScanning'); } From 21e3ec0ad48fc8a99f30c75c27fc809be6a5f1bf Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 6 Feb 2024 23:51:15 +0700 Subject: [PATCH 044/674] fix: app crashes when changing from auditor to employee --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index bfe27910c943..2e2d7251de6f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -418,7 +418,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && _.isEmpty(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && _.isEmpty(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { @@ -643,7 +643,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID) { + if (!parentReportActionID || !parentReportActions) { return {}; } return lodashGet(parentReportActions, parentReportActionID); From 5f89ad6cf8fdcc0a4a5522a35588a36c40670f70 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Thu, 8 Feb 2024 17:16:50 +0530 Subject: [PATCH 045/674] merge main, lint fixed and prettified code --- src/libs/PersonalDetailsUtils.ts | 2 +- src/pages/EnablePayments/OnfidoPrivacy.tsx | 2 +- src/pages/EnablePayments/TermsStep.tsx | 2 +- src/types/onyx/Form.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index aa9a7f79e582..69b0f386a76c 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -1,11 +1,11 @@ import Str from 'expensify-common/lib/str'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import type {CurrentUserPersonalDetails} from '@components/withCurrentUserPersonalDetails'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; -import type {CurrentUserPersonalDetails} from '@components/withCurrentUserPersonalDetails'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as UserUtils from './UserUtils'; diff --git a/src/pages/EnablePayments/OnfidoPrivacy.tsx b/src/pages/EnablePayments/OnfidoPrivacy.tsx index 80fa08c009ca..5d8cdc751e87 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.tsx +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -46,7 +46,7 @@ function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPr BankAccounts.openOnfidoFlow(); }; - let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData ?? {}) || ''; + let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData ?? {}) ?? ''; const onfidoFixableErrors = walletOnfidoData?.fixableErrors ?? []; onfidoError += !isEmptyObject(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index fe463f88792d..4836feae9f9b 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -33,7 +33,7 @@ function TermsStep(props: TermsStepProps) { const [error, setError] = useState(false); const {translate} = useLocalize(); - const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) || ''; + const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(props.walletTerms ?? {}) ?? ''; const toggleDisclosure = () => { setHasAcceptedDisclosure(!hasAcceptedDisclosure); diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index bb78487892bf..7043449ae6cf 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -82,7 +82,7 @@ type AdditionalDetailStepForm = Form<{ dob: string; ssn: string; addressState: string; -}> +}>; type RoomNameForm = Form<{ roomName: string; From ed7c4d21e101a1aeede28c6eaeb20e81101938d7 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Fri, 9 Feb 2024 20:05:31 +0530 Subject: [PATCH 046/674] fix: lint --- src/pages/EnablePayments/OnfidoPrivacy.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/EnablePayments/OnfidoPrivacy.tsx b/src/pages/EnablePayments/OnfidoPrivacy.tsx index 8618499397dc..03f8df3ad82a 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.tsx +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -46,8 +46,8 @@ function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPr BankAccounts.openOnfidoFlow(); }; - const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) || ''; - const onfidoFixableErrors = walletOnfidoData?.fixableErrors??[]; + const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) ?? ''; + const onfidoFixableErrors = walletOnfidoData?.fixableErrors ?? []; if (Array.isArray(onfidoError)) { onfidoError[0] += !isEmptyObject(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; } From e6df088475612cd7578a3c3036bd21036a9701ff Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Sat, 10 Feb 2024 13:11:13 +0530 Subject: [PATCH 047/674] updated key from ADDITIONAL_DETAILS_FORM to WALLET_ADDITIONAL_DETAILS --- src/ONYXKEYS.ts | 8 ++++---- src/libs/actions/Wallet.ts | 9 +++++---- src/pages/EnablePayments/AdditionalDetailsStep.tsx | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 90a6860fb1de..b14f0985bfed 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -369,8 +369,8 @@ const ONYXKEYS = { PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', IDOLOGY_QUESTIONS_FORM: 'idologyQuestions', IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsDraft', - ADDITIONAL_DETAILS_FORM: 'additionalDetailStep', - ADDITIONAL_DETAILS_FORM_DRAFT: 'additionalDetailStepDraft', + WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetailsForm', + WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsFormDraft', }, } as const; @@ -562,8 +562,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_DRAFT]: OnyxTypes.PersonalBankAccount; [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM]: OnyxTypes.IdologyQuestionsForm; [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM_DRAFT]: OnyxTypes.IdologyQuestionsForm; - [ONYXKEYS.FORMS.ADDITIONAL_DETAILS_FORM]: OnyxTypes.AdditionalDetailStepForm; - [ONYXKEYS.FORMS.ADDITIONAL_DETAILS_FORM_DRAFT]: OnyxTypes.AdditionalDetailStepForm; + [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.AdditionalDetailStepForm; + [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT]: OnyxTypes.AdditionalDetailStepForm; }; type OnyxKeyValue = OnyxEntry; diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index b03b5e8f6d3d..4d0ba7a67fd1 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -75,11 +75,12 @@ function setKYCWallSource(source?: ValueOf, chatRe /** * Validates a user's provided details against a series of checks */ + function updatePersonalDetails(personalDetails: UpdatePersonalDetailsForWalletParams) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: true, errors: null, @@ -91,7 +92,7 @@ function updatePersonalDetails(personalDetails: UpdatePersonalDetailsForWalletPa const finallyData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: false, }, @@ -232,7 +233,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: true, }, @@ -242,7 +243,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const finallyData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: false, }, diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index ad802a74c050..c9b557a197e8 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -60,7 +60,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; - const validate = (values: OnyxFormValuesFields) => { + const validate = (values: OnyxFormValuesFields) => { const requiredFields = ['legalFirstName', 'legalLastName', 'addressStreet', 'addressCity', 'addressZipCode', 'phoneNumber', 'dob', 'ssn', 'addressState']; const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); @@ -97,7 +97,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO return errors; }; - const activateWallet = (values: OnyxFormValuesFields) => { + const activateWallet = (values: OnyxFormValuesFields) => { const personalDetails = { phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', legalFirstName: values.legalFirstName ?? '', @@ -147,7 +147,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO Date: Mon, 12 Feb 2024 12:58:28 +0530 Subject: [PATCH 048/674] rearranged import for GetPhysicalCardForm --- src/types/onyx/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index ca3abbca28b9..c9742595272f 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -16,8 +16,8 @@ import type { CloseAccountForm, DateOfBirthForm, DisplayNameForm, - IdologyQuestionsForm, GetPhysicalCardForm, + IdologyQuestionsForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, From ee76e50335a44ed6053292ee8deb78423a3edacf Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 12 Feb 2024 20:17:11 +0530 Subject: [PATCH 049/674] updated IDOLOGY_QUESTIONS_FORM key name --- src/ONYXKEYS.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f19adfbeedd3..29a2955facfa 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -367,8 +367,8 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft', PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm', PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft', - IDOLOGY_QUESTIONS_FORM: 'idologyQuestions', - IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsDraft', + IDOLOGY_QUESTIONS_FORM: 'idologyQuestionsForm', + IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsFormDraft', WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetailsForm', WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsFormDraft', }, From 26e354f83e6c3fc8c7281d12d911ecd8c24ff0d4 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Mon, 12 Feb 2024 09:12:06 -0800 Subject: [PATCH 050/674] Fix formatting error --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e329d6e51a49..d86c838ccccd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2308,7 +2308,6 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName}); } - const lastActorID = reportAction?.actorAccountID; const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; let amount = originalMessage?.amount; From 92789b565db5233a5f6dfdc243f266c92a406aa7 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 13 Feb 2024 20:55:10 +0530 Subject: [PATCH 051/674] Remove redundant constants for the old screen names. Signed-off-by: Krishna Gupta --- src/SCREENS.ts | 4 ---- src/pages/iou/request/step/IOURequestStepConfirmation.js | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 1626fdbd1898..6da12b453ef8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -122,9 +122,6 @@ const SCREENS = { SAML_SIGN_IN: 'SAMLSignIn', MONEY_REQUEST: { - MANUAL_TAB: 'manual', - SCAN_TAB: 'scan', - DISTANCE_TAB: 'distance', CREATE: 'Money_Request_Create', STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', START: 'Money_Request_Start', @@ -141,7 +138,6 @@ const SCREENS = { STEP_WAYPOINT: 'Money_Request_Step_Waypoint', STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', - ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', CONFIRMATION: 'Money_Request_Confirmation', diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 038841027fae..6536541c289b 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -360,11 +360,7 @@ function IOURequestStepConfirmation({ (paymentMethodType) => { const currency = transaction.currency; - let trimmedComment = ''; - - if (transaction.comment && transaction.comment.comment) { - trimmedComment = transaction.comment.comment.trim(); - } + const trimmedComment = transaction.comment && transaction.comment.comment ? transaction.comment.comment.trim() : ''; const participant = participants[0]; From 40cd54db77e7a89c46163a4ba0074fe5c7c114dc Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:01 +0100 Subject: [PATCH 052/674] Migrate ReimbursementAccount to ts --- .../RestartBankAccountSetupParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../deleteFromBankAccountList.ts | 16 ++++ .../actions/ReimbursementAccount/errors.ts | 44 ++++++++++ .../actions/ReimbursementAccount/index.ts | 63 ++++++++++++++ .../ReimbursementAccount/navigation.ts | 24 ++++++ .../resetFreePlanBankAccount.ts | 84 +++++++++++++++++++ .../actions/ReimbursementAccount/store.ts | 69 +++++++++++++++ 9 files changed, 309 insertions(+) create mode 100644 src/libs/API/parameters/RestartBankAccountSetupParams.ts create mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts create mode 100644 src/libs/actions/ReimbursementAccount/errors.ts create mode 100644 src/libs/actions/ReimbursementAccount/index.ts create mode 100644 src/libs/actions/ReimbursementAccount/navigation.ts create mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts create mode 100644 src/libs/actions/ReimbursementAccount/store.ts diff --git a/src/libs/API/parameters/RestartBankAccountSetupParams.ts b/src/libs/API/parameters/RestartBankAccountSetupParams.ts new file mode 100644 index 000000000000..b338eac0dea1 --- /dev/null +++ b/src/libs/API/parameters/RestartBankAccountSetupParams.ts @@ -0,0 +1,6 @@ +type RestartBankAccountSetupParams = { + bankAccountID: number; + ownerEmail: string; +}; + +export default RestartBankAccountSetupParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 482c5e0336c4..90b27d825580 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -2,6 +2,7 @@ export type {default as ActivatePhysicalExpensifyCardParams} from './ActivatePhy export type {default as AddNewContactMethodParams} from './AddNewContactMethodParams'; export type {default as AddPaymentCardParams} from './AddPaymentCardParams'; export type {default as AddPersonalBankAccountParams} from './AddPersonalBankAccountParams'; +export type {default as RestartBankAccountSetupParams} from './RestartBankAccountSetupParams'; export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams'; export type {default as AuthenticatePusherParams} from './AuthenticatePusherParams'; export type {default as BankAccountHandlePlaidErrorParams} from './BankAccountHandlePlaidErrorParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index f5d99d8cf40e..4f811b85e709 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -76,6 +76,7 @@ const WRITE_COMMANDS = { ADD_ATTACHMENT: 'AddAttachment', CONNECT_BANK_ACCOUNT_WITH_PLAID: 'ConnectBankAccountWithPlaid', ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount', + RESTART_BANK_ACCOUNT_SETUP: 'RestartBankAccountSetup', OPT_IN_TO_PUSH_NOTIFICATIONS: 'OptInToPushNotifications', OPT_OUT_OF_PUSH_NOTIFICATIONS: 'OptOutOfPushNotifications', RECONNECT_TO_REPORT: 'ReconnectToReport', @@ -213,6 +214,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ADD_ATTACHMENT]: Parameters.AddCommentOrAttachementParams; [WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID]: Parameters.ConnectBankAccountWithPlaidParams; [WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT]: Parameters.AddPersonalBankAccountParams; + [WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP]: Parameters.RestartBankAccountSetupParams; [WRITE_COMMANDS.OPT_IN_TO_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.OPT_OUT_OF_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.RECONNECT_TO_REPORT]: Parameters.ReconnectToReportParams; diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts new file mode 100644 index 000000000000..d9a2dd130d62 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts @@ -0,0 +1,16 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as store from './store'; + +/** + * Deletes a bank account from bankAccountList + */ +function deleteFromBankAccountList(bankAccountID: number) { + // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx + const bankAccountList = store.getBankAccountList(); + delete bankAccountList?.[bankAccountID]; + + Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); +} + +export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts new file mode 100644 index 000000000000..c65da17690bb --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -0,0 +1,44 @@ +import Onyx from 'react-native-onyx'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; + +/** + * Set the current fields with errors. + */ +function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { + // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); +} + +/** + * Set the current fields with errors. + + */ +function setBankAccountFormValidationErrors(errors: Errors) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +} + +/** + * Clear validation messages from reimbursement account + */ +function resetReimbursementAccount() { + setBankAccountFormValidationErrors({}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: null, + pendingAction: null, + }); +} + +/** + * Set the current error message. + */ +function showBankAccountFormValidationError(error: string | null) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: ErrorUtils.getMicroSecondOnyxError(error), + }); +} + +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts new file mode 100644 index 000000000000..5c9bf1c822d1 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -0,0 +1,63 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReimbursementAccountForm} from '@src/types/form'; +import type {BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; +import deleteFromBankAccountList from './deleteFromBankAccountList'; +import resetFreePlanBankAccount from './resetFreePlanBankAccount'; + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; + +/** + * Set the current sub step in first step of adding withdrawal bank account: + * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually + * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber + * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid + * + * @param subStep + * @returns + */ +function setBankAccountSubStep(subStep: BankAccountSubStep) { + return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); +} + +function hideBankAccountErrors() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); +} + +function setWorkspaceIDForReimbursementAccount(workspaceID: string) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); +} + +/** + * @param bankAccountData + */ +function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { + Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); +} + +/** + * Triggers a modal to open allowing the user to reset their bank account + */ +function requestResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); +} + +/** + * Hides modal allowing the user to reset their bank account + */ +function cancelResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); +} + +export { + resetFreePlanBankAccount, + setBankAccountSubStep, + hideBankAccountErrors, + setWorkspaceIDForReimbursementAccount, + updateReimbursementAccountDraft, + requestResetFreePlanBankAccount, + cancelResetFreePlanBankAccount, + deleteFromBankAccountList, +}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.ts b/src/libs/actions/ReimbursementAccount/navigation.ts new file mode 100644 index 000000000000..2c3eb7cf0384 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/navigation.ts @@ -0,0 +1,24 @@ +import Onyx from 'react-native-onyx'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {BankAccountStep} from '@src/types/onyx/ReimbursementAccount'; + +/** + * Navigate to a specific step in the VBA flow + */ +function goToWithdrawalAccountSetupStep(stepID: BankAccountStep) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); +} + +/** + * Navigate to the correct bank account route based on the bank account state and type + * + * @param policyID - The policy ID associated with the bank account. + * @param [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + */ +function navigateToBankAccountRoute(policyID: string, backTo?: string) { + Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); +} + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts new file mode 100644 index 000000000000..3cc34db0846f --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -0,0 +1,84 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import * as API from '@libs/API'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; +import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +/** + * Reset user's reimbursement account. This will delete the bank account. + */ +function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry) { + if (!bankAccountID) { + throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); + } + if (!session?.email) { + throw new Error('Missing credentials when attempting to reset free plan bank account'); + } + + API.write( + WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP, + { + bankAccountID, + ownerEmail: session.email, + }, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + shouldShowResetModal: false, + isLoading: true, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + achData: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_APPLICANT_ID, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_DATA, + value: PlaidDataProps.plaidDataDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_LINK_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: ReimbursementAccountProps.reimbursementAccountDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, + value: {}, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: {isLoading: false, pendingAction: null}, + }, + ], + }, + ); +} + +export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts new file mode 100644 index 000000000000..bdceb4e2ad5d --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -0,0 +1,69 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import BankAccount from '@libs/models/BankAccount'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {ACHData} from '@src/types/onyx/ReimbursementAccount'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; + +/** Reimbursement account actively being set up */ +let reimbursementAccountInSetup: ACHData | EmptyObject = {}; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + callback: (val) => { + reimbursementAccountInSetup = val?.achData ?? {}; + }, +}); + +let reimbursementAccountWorkspaceID: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, + callback: (val) => { + reimbursementAccountWorkspaceID = val; + }, +}); + +let bankAccountList: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.BANK_ACCOUNT_LIST, + callback: (val) => { + bankAccountList = val; + }, +}); + +let credentials: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.CREDENTIALS, + callback: (val) => { + credentials = val; + }, +}); + +function getReimbursementAccountInSetup() { + return reimbursementAccountInSetup; +} + +function getBankAccountList() { + return bankAccountList; +} + +function hasCreditBankAccount() { + if (!bankAccountList) { + return false; + } + + Object.entries(bankAccountList).some(([, bankAccountJSON]) => { + const bankAccount = new BankAccount(bankAccountJSON); + return bankAccount.isDefaultCredit(); + }); +} + +function getCredentials() { + return credentials; +} + +function getReimbursementAccountWorkspaceID() { + return reimbursementAccountWorkspaceID; +} + +export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 6a886fefb7d026fb767ec41172b226e1dfad4842 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:51 +0100 Subject: [PATCH 053/674] Remove old js implementations --- .../deleteFromBankAccountList.js | 18 ---- .../actions/ReimbursementAccount/errors.js | 47 ----------- .../actions/ReimbursementAccount/index.js | 61 -------------- .../ReimbursementAccount/navigation.js | 25 ------ .../resetFreePlanBankAccount.js | 83 ------------------- .../actions/ReimbursementAccount/store.js | 63 -------------- 6 files changed, 297 deletions(-) delete mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js delete mode 100644 src/libs/actions/ReimbursementAccount/errors.js delete mode 100644 src/libs/actions/ReimbursementAccount/index.js delete mode 100644 src/libs/actions/ReimbursementAccount/navigation.js delete mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js delete mode 100644 src/libs/actions/ReimbursementAccount/store.js diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js deleted file mode 100644 index 6161066c1c69..000000000000 --- a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js +++ /dev/null @@ -1,18 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as store from './store'; - -/** - * Deletes a bank account from bankAccountList - * - * @param {Number} bankAccountID - */ -function deleteFromBankAccountList(bankAccountID) { - // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx - const bankAccountList = store.getBankAccountList(); - delete bankAccountList[bankAccountID]; - - Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); -} - -export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.js b/src/libs/actions/ReimbursementAccount/errors.js deleted file mode 100644 index fd2eaf852bce..000000000000 --- a/src/libs/actions/ReimbursementAccount/errors.js +++ /dev/null @@ -1,47 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Set the current fields with errors. - * @param {Object} errorFields - */ -function setPersonalBankAccountFormValidationErrorFields(errorFields) { - // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); -} - -/** - * Set the current fields with errors. - * - * @param {Object} errorFields - */ -function setBankAccountFormValidationErrors(errorFields) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); -} - -/** - * Clear validation messages from reimbursement account - */ -function resetReimbursementAccount() { - setBankAccountFormValidationErrors({}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: null, - pendingAction: null, - }); -} - -/** - * Set the current error message. - * - * @param {String} error - */ -function showBankAccountFormValidationError(error) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: ErrorUtils.getMicroSecondOnyxError(error), - }); -} - -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js deleted file mode 100644 index 12b5b940a0f2..000000000000 --- a/src/libs/actions/ReimbursementAccount/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import deleteFromBankAccountList from './deleteFromBankAccountList'; -import resetFreePlanBankAccount from './resetFreePlanBankAccount'; - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; - -/** - * Set the current sub step in first step of adding withdrawal bank account: - * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually - * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber - * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param {String | null} subStep - * @returns {Promise} - */ -function setBankAccountSubStep(subStep) { - return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); -} - -function hideBankAccountErrors() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); -} - -function setWorkspaceIDForReimbursementAccount(workspaceID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); -} - -/** - * @param {Object} bankAccountData - */ -function updateReimbursementAccountDraft(bankAccountData) { - Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); -} - -/** - * Triggers a modal to open allowing the user to reset their bank account - */ -function requestResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); -} - -/** - * Hides modal allowing the user to reset their bank account - */ -function cancelResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); -} - -export { - resetFreePlanBankAccount, - setBankAccountSubStep, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, - deleteFromBankAccountList, -}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js deleted file mode 100644 index 6c82561c16ee..000000000000 --- a/src/libs/actions/ReimbursementAccount/navigation.js +++ /dev/null @@ -1,25 +0,0 @@ -import Onyx from 'react-native-onyx'; -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -/** - * Navigate to a specific step in the VBA flow - * - * @param {String} stepID - */ -function goToWithdrawalAccountSetupStep(stepID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); -} - -/** - * Navigate to the correct bank account route based on the bank account state and type - * - * @param {string} policyID - The policy ID associated with the bank account. - * @param {string} [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. - */ -function navigateToBankAccountRoute(policyID, backTo) { - Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); -} - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js deleted file mode 100644 index 962800fb2e55..000000000000 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js +++ /dev/null @@ -1,83 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as API from '@libs/API'; -import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Reset user's reimbursement account. This will delete the bank account. - * @param {Number} bankAccountID - * @param {Object} session - */ -function resetFreePlanBankAccount(bankAccountID, session) { - if (!bankAccountID) { - throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); - } - if (!session.email) { - throw new Error('Missing credentials when attempting to reset free plan bank account'); - } - - API.write( - 'RestartBankAccountSetup', - { - bankAccountID, - ownerEmail: session.email, - }, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - shouldShowResetModal: false, - isLoading: true, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - achData: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_APPLICANT_ID, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_DATA, - value: PlaidDataProps.plaidDataDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: ReimbursementAccountProps.reimbursementAccountDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, - value: {}, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: {isLoading: false, pendingAction: null}, - }, - ], - }, - ); -} - -export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.js b/src/libs/actions/ReimbursementAccount/store.js deleted file mode 100644 index 4b8549b60b2e..000000000000 --- a/src/libs/actions/ReimbursementAccount/store.js +++ /dev/null @@ -1,63 +0,0 @@ -import lodashGet from 'lodash/get'; -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import BankAccount from '@libs/models/BankAccount'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** Reimbursement account actively being set up */ -let reimbursementAccountInSetup = {}; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - callback: (val) => { - reimbursementAccountInSetup = lodashGet(val, 'achData', {}); - }, -}); - -let reimbursementAccountWorkspaceID = null; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, - callback: (val) => { - reimbursementAccountWorkspaceID = val; - }, -}); - -let bankAccountList = null; -Onyx.connect({ - key: ONYXKEYS.BANK_ACCOUNT_LIST, - callback: (val) => { - bankAccountList = val; - }, -}); - -let credentials; -Onyx.connect({ - key: ONYXKEYS.CREDENTIALS, - callback: (val) => { - credentials = val || {}; - }, -}); - -function getReimbursementAccountInSetup() { - return reimbursementAccountInSetup; -} - -function getBankAccountList() { - return bankAccountList; -} - -function hasCreditBankAccount() { - return _.some(bankAccountList, (bankAccountJSON) => { - const bankAccount = new BankAccount(bankAccountJSON); - return bankAccount.isDefaultCredit(); - }); -} - -function getCredentials() { - return credentials; -} - -function getReimbursementAccountWorkspaceID() { - return reimbursementAccountWorkspaceID; -} - -export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 8b6fb5e1e3639f3532c1183e953f416417c6151c Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:58:46 +0100 Subject: [PATCH 054/674] Add ErrorFields to PersonalBankAccount type --- src/libs/actions/ReimbursementAccount/errors.ts | 14 +++++++------- src/types/onyx/PersonalBankAccount.ts | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index c65da17690bb..f85426f8d4fe 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -1,24 +1,24 @@ import Onyx from 'react-native-onyx'; import * as ErrorUtils from '@libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; /** * Set the current fields with errors. */ -function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { +function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } /** * Set the current fields with errors. */ -function setBankAccountFormValidationErrors(errors: Errors) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +function setBankAccountFormValidationErrors(errorFields: ErrorFields) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); } /** diff --git a/src/types/onyx/PersonalBankAccount.ts b/src/types/onyx/PersonalBankAccount.ts index 3714cc9f314b..3e52a3cf59f3 100644 --- a/src/types/onyx/PersonalBankAccount.ts +++ b/src/types/onyx/PersonalBankAccount.ts @@ -5,6 +5,9 @@ type PersonalBankAccount = { /** An error message to display to the user */ errors?: OnyxCommon.Errors; + /** Error objects keyed by field name containing errors keyed by microtime */ + errorFields?: OnyxCommon.ErrorFields; + /** Whether we should show the view that the bank account was successfully added */ shouldShowSuccess?: boolean; From 53fb383b51d8382c2cfc3e8fc9068adbddd82c07 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 16:03:55 +0100 Subject: [PATCH 055/674] Adjust types --- src/libs/actions/ReimbursementAccount/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 5c9bf1c822d1..416c5e956189 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -13,11 +13,8 @@ export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidation * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param subStep - * @returns */ -function setBankAccountSubStep(subStep: BankAccountSubStep) { +function setBankAccountSubStep(subStep: BankAccountSubStep | null) { return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); } @@ -25,13 +22,10 @@ function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); } -function setWorkspaceIDForReimbursementAccount(workspaceID: string) { +function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); } -/** - * @param bankAccountData - */ function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); From dfd6b8bedea3616571ba853945abef2913504d35 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 15 Feb 2024 01:59:35 +0530 Subject: [PATCH 056/674] removed MoneyRequest route. Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 6 ------ src/pages/iou/IOUCurrencySelection.js | 4 +--- src/pages/iou/request/step/IOURequestStepParticipants.js | 9 +++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 615d7c708d1d..691ff5c78551 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -260,12 +260,6 @@ const ROUTES = { route: 'r/:reportID/invite', getRoute: (reportID: string) => `r/${reportID}/invite` as const, }, - - // To see the available iouType, please refer to CONST.IOU.TYPE - MONEY_REQUEST: { - route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` as const, - }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` as const, diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 2a48897bfc85..50833534cb0e 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -71,8 +71,6 @@ function IOUCurrencySelection(props) { const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); - const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); - const reportID = lodashGet(props.route, 'params.reportID', ''); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); // Decides whether to allow or disallow editing a money request @@ -161,7 +159,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))} + onBackButtonPress={() => Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} /> { + const headerTitle = useMemo(() => { if (iouType === CONST.IOU.TYPE.SPLIT) { return translate('iou.split'); } @@ -47,7 +47,8 @@ function IOURequestStepParticipants({ return translate('common.send'); } return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); - }; + }, [iouType, transaction, translate]); + const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -89,7 +90,7 @@ function IOURequestStepParticipants({ return ( Date: Thu, 15 Feb 2024 02:04:51 +0530 Subject: [PATCH 057/674] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/IOUCurrencySelection.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 50833534cb0e..125b95046d67 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -17,7 +17,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import {iouDefaultProps, iouPropTypes} from './propTypes'; /** @@ -72,6 +71,7 @@ function IOUCurrencySelection(props) { const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); + const backTo = lodashGet(props.route, 'params.backTo', ''); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -96,7 +96,6 @@ function IOUCurrencySelection(props) { const confirmCurrencySelection = useCallback( (option) => { - const backTo = lodashGet(props.route, 'params.backTo', ''); Keyboard.dismiss(); // When we refresh the web, the money request route gets cleared from the navigation stack. @@ -108,7 +107,7 @@ function IOUCurrencySelection(props) { Navigation.navigate(`${props.route.params.backTo}?currency=${option.currencyCode}`); } }, - [props.route, props.navigation], + [props.route, props.navigation, backTo], ); const {translate, currencyList} = props; @@ -159,7 +158,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} + onBackButtonPress={() => Navigation.goBack(backTo)} /> Date: Fri, 16 Feb 2024 14:39:23 +0530 Subject: [PATCH 058/674] fixed additional details step and idology questions types errors --- src/ONYXKEYS.ts | 2 ++ .../EnablePayments/AdditionalDetailsStep.tsx | 24 +++++++++++++------ src/pages/EnablePayments/IdologyQuestions.tsx | 4 ++-- src/types/form/index.ts | 2 ++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d94f65954a98..05f064d90d5f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -429,6 +429,8 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: FormTypes.AdditionalDetailStepForm; + [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM]: FormTypes.IdologyQuestionsForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index c9b557a197e8..b347c93e296e 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -6,7 +6,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import DatePicker from '@components/DatePicker'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {OnyxFormValuesFields} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -25,6 +25,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {WalletAdditionalDetails} from '@src/types/onyx'; import IdologyQuestions from './IdologyQuestions'; +import INPUT_IDS from '@src/types/form/AdditionalDetailStepForm'; const DEFAULT_WALLET_ADDITIONAL_DETAILS = { errorFields: {}, @@ -51,7 +52,16 @@ const fieldNameTranslationKeys = { ssn: 'common.ssnLast4', ssnFull9: 'common.ssnFull9', } as const; - +const STEP_FIELDS = [ + INPUT_IDS.LEGAL_FIRST_NAME, + INPUT_IDS.LEGAL_LAST_NAME, + INPUT_IDS.ADDRESS_STREET, + INPUT_IDS.ADDRESS_CITY, + INPUT_IDS.PHONE_NUMBER, + INPUT_IDS.DOB, + INPUT_IDS.ADDRESS_STATE, + INPUT_IDS.SSN +]; function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIONAL_DETAILS, currentUserPersonalDetails}: AdditionalDetailsStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -60,9 +70,9 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO const maxDate = subYears(currentDate, CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; - const validate = (values: OnyxFormValuesFields) => { + const validate = (values: FormOnyxValues): FormInputErrors => { const requiredFields = ['legalFirstName', 'legalLastName', 'addressStreet', 'addressCity', 'addressZipCode', 'phoneNumber', 'dob', 'ssn', 'addressState']; - const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); + const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); if (values.dob) { if (!ValidationUtils.isValidPastDate(values.dob) || !ValidationUtils.meetsMaximumAgeRequirement(values.dob)) { @@ -97,7 +107,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO return errors; }; - const activateWallet = (values: OnyxFormValuesFields) => { + const activateWallet = (values: FormOnyxValues) => { const personalDetails = { phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', legalFirstName: values.legalFirstName ?? '', @@ -197,10 +207,10 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO placeholder={translate('common.phoneNumberPlaceholder')} shouldSaveDraft /> - InputComponent={DatePicker} inputID="dob" - // @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys.dob)} placeholder={translate('common.dob')} diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index 4a5ef72019c0..f54f5a7cccba 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native'; import type {WalletAdditionalQuestionDetails} from 'src/types/onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {OnyxFormValuesFields} from '@components/Form/types'; +import type {FormOnyxValues,FormInputErrors} from '@components/Form/types'; import type {Choice} from '@components/RadioButtons'; import SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import Text from '@components/Text'; @@ -91,7 +91,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { } }; - const validate = (values: OnyxFormValuesFields) => { + const validate = (values: FormOnyxValues) => { const errors: Errors = {}; if (!values.answer) { errors.answer = translate('additionalDetailsStep.selectAnswer'); diff --git a/src/types/form/index.ts b/src/types/form/index.ts index d9263991023c..9e457f83ace6 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -34,4 +34,6 @@ export type {WorkspaceRateAndUnitForm} from './WorkspaceRateAndUnitForm'; export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; export type {WorkspaceProfileDescriptionForm} from './WorkspaceProfileDescriptionForm'; +export type {AdditionalDetailStepForm} from './AdditionalDetailStepForm'; +export type {IdologyQuestionsForm} from './IdologyQuestionsForm'; export type {default as Form} from './Form'; From 98ef6936cc57af1d9b20a24e1053eb8b7dad7a04 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Fri, 16 Feb 2024 14:46:29 +0530 Subject: [PATCH 059/674] code prettified & lint fixed --- .../EnablePayments/AdditionalDetailsStep.tsx | 5 ++--- src/pages/EnablePayments/IdologyQuestions.tsx | 4 ++-- src/types/form/AdditionalDetailStepForm.ts | 2 +- src/types/form/IdologyQuestionsForm.ts | 2 +- src/types/onyx/index.ts | 18 +----------------- 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index b347c93e296e..97041a5b021a 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -23,9 +23,9 @@ import AddressForm from '@pages/ReimbursementAccount/AddressForm'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/AdditionalDetailStepForm'; import type {WalletAdditionalDetails} from '@src/types/onyx'; import IdologyQuestions from './IdologyQuestions'; -import INPUT_IDS from '@src/types/form/AdditionalDetailStepForm'; const DEFAULT_WALLET_ADDITIONAL_DETAILS = { errorFields: {}, @@ -60,7 +60,7 @@ const STEP_FIELDS = [ INPUT_IDS.PHONE_NUMBER, INPUT_IDS.DOB, INPUT_IDS.ADDRESS_STATE, - INPUT_IDS.SSN + INPUT_IDS.SSN, ]; function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIONAL_DETAILS, currentUserPersonalDetails}: AdditionalDetailsStepProps) { const {translate} = useLocalize(); @@ -71,7 +71,6 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN; const validate = (values: FormOnyxValues): FormInputErrors => { - const requiredFields = ['legalFirstName', 'legalLastName', 'addressStreet', 'addressCity', 'addressZipCode', 'phoneNumber', 'dob', 'ssn', 'addressState']; const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS); if (values.dob) { diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index f54f5a7cccba..cadb7092f0c4 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native'; import type {WalletAdditionalQuestionDetails} from 'src/types/onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormOnyxValues,FormInputErrors} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import type {Choice} from '@components/RadioButtons'; import SingleChoiceQuestion from '@components/SingleChoiceQuestion'; import Text from '@components/Text'; @@ -91,7 +91,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { } }; - const validate = (values: FormOnyxValues) => { + const validate = (values: FormOnyxValues): FormInputErrors => { const errors: Errors = {}; if (!values.answer) { errors.answer = translate('additionalDetailsStep.selectAnswer'); diff --git a/src/types/form/AdditionalDetailStepForm.ts b/src/types/form/AdditionalDetailStepForm.ts index b432ae87e2cf..f102d03679ba 100644 --- a/src/types/form/AdditionalDetailStepForm.ts +++ b/src/types/form/AdditionalDetailStepForm.ts @@ -25,4 +25,4 @@ type AdditionalDetailStepForm = Form<{ }>; export type {AdditionalDetailStepForm}; -export default INPUT_IDS; \ No newline at end of file +export default INPUT_IDS; diff --git a/src/types/form/IdologyQuestionsForm.ts b/src/types/form/IdologyQuestionsForm.ts index eb48b9027541..5b8d50c68abf 100644 --- a/src/types/form/IdologyQuestionsForm.ts +++ b/src/types/form/IdologyQuestionsForm.ts @@ -9,4 +9,4 @@ type IdologyQuestionsForm = Form<{ }>; export type {IdologyQuestionsForm}; -export default INPUT_IDS; \ No newline at end of file +export default INPUT_IDS; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e116aa99e95a..de9e96d8590b 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -11,23 +11,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; -import type { - AddDebitCardForm, - AdditionalDetailStepForm, - CloseAccountForm, - DateOfBirthForm, - DisplayNameForm, - GetPhysicalCardForm, - IdologyQuestionsForm, - IKnowATeacherForm, - IntroSchoolPrincipalForm, - NewRoomForm, - PrivateNotesForm, - ReportFieldEditForm, - RoomNameForm, - WorkspaceSettingsForm, -} from './Form'; -import type Form from './Form'; +import type {AdditionalDetailStepForm, IdologyQuestionsForm, PrivateNotesForm, ReportFieldEditForm, RoomNameForm} from './Form'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; import type Fund from './Fund'; From 0c056bed123c0eff882e120b692b04428fa5cf40 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 12:59:10 +0100 Subject: [PATCH 060/674] ref: move SignInOrAvatarWithOptionalStatus to TS --- ...js => SignInOrAvatarWithOptionalStatus.tsx} | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) rename src/pages/home/sidebar/{SignInOrAvatarWithOptionalStatus.js => SignInOrAvatarWithOptionalStatus.tsx} (61%) diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx similarity index 61% rename from src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js rename to src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx index 0ea6195cd713..2a9356d78232 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx @@ -1,6 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import * as Session from '@userActions/Session'; @@ -8,18 +5,13 @@ import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, +type SignInOrAvatarWithOptionalStatusProps = { + isCreateMenuOpen?: boolean; }; -const defaultProps = { - isCreateMenuOpen: false, -}; - -function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { +function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen = false}: SignInOrAvatarWithOptionalStatusProps) { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const emojiStatus = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); + const emojiStatus = currentUserPersonalDetails.status?.emojiCode ?? ''; if (Session.isAnonymousUser()) { return ; @@ -35,7 +27,5 @@ function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { return ; } -SignInOrAvatarWithOptionalStatus.propTypes = propTypes; -SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps; SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus'; export default SignInOrAvatarWithOptionalStatus; From 6bd5633ec4631b4a1ef76b03c5a7e576d7d15ca8 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:04:18 +0100 Subject: [PATCH 061/674] ref: move SignInButton to TS --- src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} | 1 - 1 file changed, 1 deletion(-) rename src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} (95%) diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.tsx similarity index 95% rename from src/pages/home/sidebar/SignInButton.js rename to src/pages/home/sidebar/SignInButton.tsx index f89deb6f65b2..1dc65bfd5050 100644 --- a/src/pages/home/sidebar/SignInButton.js +++ b/src/pages/home/sidebar/SignInButton.tsx @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ import React from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; From 5ed0bc2c9fc59b181d3067e88b7c10c36e0ed497 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:05:47 +0100 Subject: [PATCH 062/674] ref: removed SidebarNavigationContext --- src/pages/home/sidebar/SidebarNavigationContext.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/pages/home/sidebar/SidebarNavigationContext.js diff --git a/src/pages/home/sidebar/SidebarNavigationContext.js b/src/pages/home/sidebar/SidebarNavigationContext.js deleted file mode 100644 index e69de29bb2d1..000000000000 From 5443274f38ae358c00e9013f8100f7195ebe77e5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:19:53 +0100 Subject: [PATCH 063/674] ref: move SidebarLinksData to TS --- src/libs/ReportUtils.ts | 2 +- src/libs/SidebarUtils.ts | 14 +- ...debarLinksData.js => SidebarLinksData.tsx} | 296 +++++++----------- 3 files changed, 127 insertions(+), 185 deletions(-) rename src/pages/home/sidebar/{SidebarLinksData.js => SidebarLinksData.tsx} (53%) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b5da21c0f67e..40581f46e76a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3824,7 +3824,7 @@ function shouldReportBeInOptionList({ report: OnyxEntry; currentReportId: string; isInGSDMode: boolean; - betas: Beta[]; + betas: OnyxEntry; policies: OnyxCollection; excludeEmptyChats: boolean; doesReportHaveViolations: boolean; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5fe646c5ad13..7b757d7fc5b8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -79,10 +79,10 @@ let hasInitialReportActions = false; */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, - betas: Beta[], - policies: Record, - priorityMode: ValueOf, + allReports: OnyxEntry>, + betas: OnyxEntry, + policies: OnyxEntry>, + priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', @@ -110,7 +110,7 @@ function getOrderedReportIDs( const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { @@ -118,7 +118,7 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -126,7 +126,7 @@ function getOrderedReportIDs( betas, policies, excludeEmptyChats: true, - doesReportHaveViolations, + doesReportHaveViolations: !!doesReportHaveViolations, }); }); diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.tsx similarity index 53% rename from src/pages/home/sidebar/SidebarLinksData.js rename to src/pages/home/sidebar/SidebarLinksData.tsx index 3bd538e8beab..b128fcaf33ec 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -1,154 +1,114 @@ +import {useIsFocused} from '@react-navigation/native'; import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import withCurrentReportID from '@components/withCurrentReportID'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withNavigationFocus from '@components/withNavigationFocus'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useCurrentReportID from '@hooks/useCurrentReportID'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import SidebarLinks, {basePropTypes} from './SidebarLinks'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; +import SidebarLinks from './SidebarLinks'; -const propTypes = { - ...basePropTypes, - - /* Onyx Props */ - /** List of reports */ - chatReports: PropTypes.objectOf(reportPropTypes), - - /** All report actions for all reports */ - - /** Object of report actions for this report */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), - - /** Whether the reports are loading. When false it means they are ready to be used. */ - isLoadingApp: PropTypes.bool, - - /** The chat priority mode */ - priorityMode: PropTypes.string, - - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - network: networkPropTypes.isRequired, - - /** The policies which the user has access to */ - // eslint-disable-next-line react/forbid-prop-types - policies: PropTypes.object, - - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), - /** All of the transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), +type SidebarLinksDataOnyxProps = { + chatReports: OnyxEntry< + Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' + > & {isUnreadWithMention: boolean} + >; + isLoadingApp: OnyxEntry; + priorityMode: OnyxEntry>; + betas: OnyxEntry; + allReportActions: OnyxEntry>>; + policies: OnyxEntry>; + policyMembers: OnyxCollection; + transactionViolations: OnyxCollection; }; -const defaultProps = { - chatReports: {}, - allReportActions: {}, - isLoadingApp: true, - priorityMode: CONST.PRIORITY_MODE.DEFAULT, - betas: [], - policies: {}, - policyMembers: {}, - session: { - accountID: '', - }, - transactionViolations: {}, +type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { + onLinkClick: (reportID: number) => void; + insets: EdgeInsets; }; function SidebarLinksData({ - isFocused, allReportActions, betas, chatReports, - currentReportID, insets, - isLoadingApp, + isLoadingApp = true, onLinkClick, policies, - priorityMode, - network, + priorityMode = CONST.PRIORITY_MODE.DEFAULT, policyMembers, - session: {accountID}, + // session: {accountID}, transactionViolations, -}) { +}: SidebarLinksDataProps) { + const {currentReportID} = useCurrentReportID() ?? {}; + const {accountID} = useCurrentUserPersonalDetails(); + const network = useNetwork(); + const isFocused = useIsFocused(); const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); + useEffect(() => Policy.openWorkspace(activeWorkspaceID ?? '', policyMemberAccountIDs), [activeWorkspaceID]); - const reportIDsRef = useRef(null); + const reportIDsRef = useRef(null); const isLoading = isLoadingApp; const optionListItems = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -161,7 +121,7 @@ function SidebarLinksData({ // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { + if (!isLoading || !reportIDsRef.current || !!network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; @@ -173,14 +133,14 @@ function SidebarLinksData({ // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportID && !_.contains(optionListItems, currentReportID)) { + if (currentReportID && !optionListItems?.includes(currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportID, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -191,7 +151,7 @@ function SidebarLinksData({ const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; - const isActiveReport = useCallback((reportID) => currentReportIDRef.current === reportID, []); + const isActiveReport = useCallback((reportID: string) => currentReportIDRef.current === reportID, []); return ( +const chatReportSelector = (report: OnyxEntry) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, @@ -233,7 +190,7 @@ const chatReportSelector = (report) => isHidden: report.isHidden, notificationPreference: report.notificationPreference, errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, }, lastMessageText: report.lastMessageText, lastVisibleActionCreated: report.lastVisibleActionCreated, @@ -264,78 +221,63 @@ const chatReportSelector = (report) => isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }; -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => +const reportActionsSelector = (reportActions: OnyxEntry) => reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; return { reportActionID, - parentReportActionID, actionName, errors, message: [ { moderationDecision: {decision}, }, - ], + ] as Message[], }; }); -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => +const policySelector = (policy: OnyxEntry) => policy && { type: policy.type, name: policy.name, avatar: policy.avatar, }; -export default compose( - withCurrentReportID, - withCurrentUserPersonalDetails, - withNavigationFocus, - withNetwork(), - withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, - }), -)(SidebarLinksData); +export default withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, +})(SidebarLinksData); From bc5cd4df25c3dbd9e8a07e14a8abc44d61ec32b0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:46:36 +0100 Subject: [PATCH 064/674] ref: move SidebarLinks to TS --- src/components/LHNOptionsList/types.ts | 2 +- .../{SidebarLinks.js => SidebarLinks.tsx} | 64 +++++++++---------- src/pages/home/sidebar/SidebarLinksData.tsx | 4 +- 3 files changed, 32 insertions(+), 38 deletions(-) rename src/pages/home/sidebar/{SidebarLinks.js => SidebarLinks.tsx} (78%) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..f3d6bde9d41c 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -45,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[]; + data: string[] | null; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.tsx similarity index 78% rename from src/pages/home/sidebar/SidebarLinks.js rename to src/pages/home/sidebar/SidebarLinks.tsx index 9431bae68d8a..52165c148727 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -1,10 +1,9 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import Breadcrumbs from '@components/Breadcrumbs'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -16,37 +15,33 @@ import KeyboardShortcut from '@libs/KeyboardShortcut'; import Navigation from '@libs/Navigation/Navigation'; import onyxSubscribe from '@libs/onyxSubscribe'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import safeAreaInsetPropTypes from '@pages/safeAreaInsetPropTypes'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Modal, Policy, Report} from '@src/types/onyx'; -const basePropTypes = { - /** Toggles the navigation menu open and closed */ - onLinkClick: PropTypes.func.isRequired, - - /** Safe area insets required for mobile devices margins */ - insets: safeAreaInsetPropTypes.isRequired, +type SidebarLinksOnyxProps = { + activePolicy: OnyxEntry; }; -const propTypes = { - ...basePropTypes, - - optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, - - isLoading: PropTypes.bool.isRequired, - - // eslint-disable-next-line react/require-default-props - priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)), - - isActiveReport: PropTypes.func.isRequired, +type SidebarLinksProps = SidebarLinksOnyxProps & { + onLinkClick: () => void; + insets: EdgeInsets; + optionListItems: string[] | null; + isLoading: OnyxEntry; + priorityMode?: OnyxEntry>; + isActiveReport: (reportID: string) => boolean; + isCreateMenuOpen?: boolean; + + // eslint-disable-next-line react/no-unused-prop-types -- its used in withOnyx + activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const modal = useRef({}); + const modal = useRef({}); const {translate, updateLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -67,7 +62,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { - if (_.isNull(modalArg) || typeof modalArg !== 'object') { + if (modalArg === null || typeof modalArg !== 'object') { return; } modal.current = modalArg; @@ -105,18 +100,19 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority /** * Show Report page with selected report id - * - * @param {Object} option - * @param {String} option.reportID */ const showReportPage = useCallback( - (option) => { + (option: Report) => { // Prevent opening Report page when clicking LHN row quickly after clicking FAB icon // or when clicking the active LHN row on large screens // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices const reportActionID = Navigation.getTopmostReportActionId(); - if (isCreateMenuOpen || (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID)) { + if ( + !!isCreateMenuOpen || + (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || + (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID) + ) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); @@ -137,7 +133,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority activePolicy ? { type: CONST.BREADCRUMB_TYPE.STRONG, - text: lodashGet(activePolicy, 'name', ''), + text: activePolicy.name ?? '', } : { type: CONST.BREADCRUMB_TYPE.ROOT, @@ -158,7 +154,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority optionMode={viewMode} onFirstItemRendered={App.setSidebarLoaded} /> - {isLoading && optionListItems.length === 0 && ( + {isLoading && optionListItems?.length === 0 && ( @@ -168,12 +164,10 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority ); } -SidebarLinks.propTypes = propTypes; SidebarLinks.displayName = 'SidebarLinks'; -export default withOnyx({ +export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, })(SidebarLinks); -export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index b128fcaf33ec..ddd232e99275 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -69,7 +69,7 @@ type SidebarLinksDataOnyxProps = { }; type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - onLinkClick: (reportID: number) => void; + onLinkClick: () => void; insets: EdgeInsets; }; @@ -101,7 +101,7 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const optionListItems = useMemo(() => { + const optionListItems: string[] | null = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, chatReports as OnyxEntry>, From 3fb3715847c5c82958147940cdcb68073456aedf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:54:54 +0100 Subject: [PATCH 065/674] ref: move PressableAvatarWithIndicator to TS --- ...or.js => PressableAvatarWithIndicator.tsx} | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) rename src/pages/home/sidebar/{PressableAvatarWithIndicator.js => PressableAvatarWithIndicator.tsx} (56%) diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx similarity index 56% rename from src/pages/home/sidebar/PressableAvatarWithIndicator.js rename to src/pages/home/sidebar/PressableAvatarWithIndicator.tsx index 63c5936e957b..e07b6e856823 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx @@ -1,44 +1,30 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, - - /** The personal details of the person who is logged in */ - currentUserPersonalDetails: personalDetailsPropType, - +type PressableAvatarWithIndicatorOnyxProps = { /** Indicates whether the app is loading initial data */ - isLoading: PropTypes.bool, + isLoading: OnyxEntry; }; -const defaultProps = { - isCreateMenuOpen: false, - currentUserPersonalDetails: { - pendingFields: {avatar: ''}, - accountID: '', - avatar: '', - }, - isLoading: true, +type PressableAvatarWithIndicatorProps = PressableAvatarWithIndicatorOnyxProps & { + /** Whether the create menu is open or not */ + isCreateMenuOpen: boolean; }; -function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDetails, isLoading}) { +function PressableAvatarWithIndicator({isCreateMenuOpen = false, isLoading = true}: PressableAvatarWithIndicatorProps) { const {translate} = useLocalize(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const showSettingsPage = useCallback(() => { if (isCreateMenuOpen) { @@ -55,26 +41,22 @@ function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDeta role={CONST.ROLE.BUTTON} onPress={showSettingsPage} > - + ); } -PressableAvatarWithIndicator.propTypes = propTypes; -PressableAvatarWithIndicator.defaultProps = defaultProps; PressableAvatarWithIndicator.displayName = 'PressableAvatarWithIndicator'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(PressableAvatarWithIndicator); + +export default withOnyx({ + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(PressableAvatarWithIndicator); From 5fa814c327d1e9767afa896e3304487885039547 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:57:26 +0100 Subject: [PATCH 066/674] ref: move AvatarWithOptionalStatus to TS --- ...alStatus.js => AvatarWithOptionalStatus.tsx} | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) rename src/pages/home/sidebar/{AvatarWithOptionalStatus.js => AvatarWithOptionalStatus.tsx} (81%) diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx similarity index 81% rename from src/pages/home/sidebar/AvatarWithOptionalStatus.js rename to src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index e1ff3982a0cc..5597d46c29bc 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -1,5 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -12,20 +10,15 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; -const propTypes = { +type AvatarWithOptionalStatusProps = { /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, + isCreateMenuOpen: boolean; /** Emoji status */ - emojiStatus: PropTypes.string, + emojiStatus: string; }; -const defaultProps = { - isCreateMenuOpen: false, - emojiStatus: '', -}; - -function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { +function AvatarWithOptionalStatus({emojiStatus = '', isCreateMenuOpen = false}: AvatarWithOptionalStatusProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -61,7 +54,5 @@ function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { ); } -AvatarWithOptionalStatus.propTypes = propTypes; -AvatarWithOptionalStatus.defaultProps = defaultProps; AvatarWithOptionalStatus.displayName = 'AvatarWithOptionalStatus'; export default AvatarWithOptionalStatus; From 6733581a39b700a147c1708f606cdc6fede9b1ee Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 17:02:16 +0100 Subject: [PATCH 067/674] ref: move BaseSidebarScreen to TS --- src/pages/home/sidebar/SidebarLinks.tsx | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 2 +- .../{BaseSidebarScreen.js => BaseSidebarScreen.tsx} | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{BaseSidebarScreen.js => BaseSidebarScreen.tsx} (94%) diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index 52165c148727..f01ed07ea476 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -27,7 +27,7 @@ type SidebarLinksOnyxProps = { type SidebarLinksProps = SidebarLinksOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; optionListItems: string[] | null; isLoading: OnyxEntry; priorityMode?: OnyxEntry>; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index ddd232e99275..ab84f69e824b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -70,7 +70,7 @@ type SidebarLinksDataOnyxProps = { type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; }; function SidebarLinksData({ diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx similarity index 94% rename from src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js rename to src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 9188a859d175..314b3921cc0b 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -17,7 +17,7 @@ const startTimer = () => { Performance.markStart(CONST.TIMING.SWITCH_REPORT); }; -function BaseSidebarScreen(props) { +function BaseSidebarScreen() { const styles = useThemeStyles(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); @@ -37,7 +37,6 @@ function BaseSidebarScreen(props) { )} From b258238c5c7150ad34fef64b086d1f649be3266d Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 16 Feb 2024 17:34:00 -0700 Subject: [PATCH 068/674] Add back some HybridApp logic after TS migration to fix Authentication --- src/pages/LogOutPreviousUserPage.tsx | 36 +++++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index f68344604dfa..5191756ccf0b 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect} from 'react'; -import {Linking} from 'react-native'; +import React, {useContext, useEffect} from 'react'; +import {Linking, NativeModules} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -9,11 +9,17 @@ import type {AuthScreensParamList} from '@navigation/types'; import * as SessionActions from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type {Session} from '@src/types/onyx'; +import type {Account, Session} from '@src/types/onyx'; +import InitialUrlContext from "@libs/InitialUrlContext"; +import CONST from "@src/CONST"; +import lodashGet from "lodash/get"; +import ROUTES, {type Route} from "@src/ROUTES"; +import Navigation from "@navigation/Navigation"; type LogOutPreviousUserPageOnyxProps = { /** The data about the current session which will be set once the user is authenticated and we return to this component as an AuthScreen */ session: OnyxEntry; + account: OnyxEntry; }; type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreenProps; @@ -22,10 +28,12 @@ type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreen // out if the transition is for another user. // // This component should not do any other navigation as that handled in App.setUpPoliciesAndNavigate -function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { +function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPageProps) { + const initUrl = useContext(InitialUrlContext); useEffect(() => { - Linking.getInitialURL().then((transitionURL) => { + Linking.getInitialURL().then((url) => { const sessionEmail = session?.email; + const transitionURL = NativeModules.HybridAppModule ? CONST.DEEPLINK_BASE_URL + initUrl : url; const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); if (isLoggingInAsNewUser) { @@ -42,11 +50,20 @@ function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { const shortLivedAuthToken = route.params.shortLivedAuthToken ?? ''; SessionActions.signInWithShortLivedAuthToken(email, shortLivedAuthToken); } + const exitTo = route.params.exitTo as Route | null; + // We don't want to navigate to the exitTo route when creating a new workspace from a deep link, + // because we already handle creating the optimistic policy and navigating to it in App.setUpPoliciesAndNavigate, + // which is already called when AuthScreens mounts. + if (exitTo && exitTo !== ROUTES.WORKSPACE_NEW && !account?.isLoading && !isLoggingInAsNewUser) { + Navigation.isNavigationReady().then(() => { + // remove this screen and navigate to exit route + const exitUrl = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + Navigation.goBack(); + Navigation.navigate(exitUrl); + }); + } }); - - // We only want to run this effect once on mount (when the page first loads after transitioning from OldDot) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [initUrl]); return ; } @@ -54,6 +71,7 @@ function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { LogOutPreviousUserPage.displayName = 'LogOutPreviousUserPage'; export default withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, session: { key: ONYXKEYS.SESSION, }, From 2961243d5da20f2336fe43adbfa15d9de9ffb469 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 16 Feb 2024 17:42:50 -0700 Subject: [PATCH 069/674] Fix lint --- src/pages/LogOutPreviousUserPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 5191756ccf0b..297a9166956c 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -12,8 +12,8 @@ import type SCREENS from '@src/SCREENS'; import type {Account, Session} from '@src/types/onyx'; import InitialUrlContext from "@libs/InitialUrlContext"; import CONST from "@src/CONST"; -import lodashGet from "lodash/get"; -import ROUTES, {type Route} from "@src/ROUTES"; +import ROUTES from "@src/ROUTES"; +import type {Route} from "@src/ROUTES"; import Navigation from "@navigation/Navigation"; type LogOutPreviousUserPageOnyxProps = { From 87817e505679354928b93145edf8a450b109e4fe Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 16 Feb 2024 17:54:14 -0700 Subject: [PATCH 070/674] More lint --- src/pages/LogOutPreviousUserPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 297a9166956c..c6144c70db3e 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -63,7 +63,7 @@ function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPag }); } }); - }, [initUrl]); + }, [initUrl, account, route, session]); return ; } From d72cda0382c4d6f35d7f040bf14d31aa7c062d55 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 16 Feb 2024 18:00:05 -0700 Subject: [PATCH 071/674] Prettier fixes --- src/pages/LogOutPreviousUserPage.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index c6144c70db3e..a10988945c4e 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -4,17 +4,17 @@ import {Linking, NativeModules} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import InitialUrlContext from '@libs/InitialUrlContext'; import * as SessionUtils from '@libs/SessionUtils'; +import Navigation from '@navigation/Navigation'; import type {AuthScreensParamList} from '@navigation/types'; import * as SessionActions from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Account, Session} from '@src/types/onyx'; -import InitialUrlContext from "@libs/InitialUrlContext"; -import CONST from "@src/CONST"; -import ROUTES from "@src/ROUTES"; -import type {Route} from "@src/ROUTES"; -import Navigation from "@navigation/Navigation"; type LogOutPreviousUserPageOnyxProps = { /** The data about the current session which will be set once the user is authenticated and we return to this component as an AuthScreen */ From 2fad64eddb709a5bdde140f53ce65beb63b64578 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 10:02:39 +0100 Subject: [PATCH 072/674] ref: move FloatingActionButtonAndPopover to TS --- src/libs/actions/Policy.ts | 6 +- src/libs/actions/Task.ts | 2 +- ....js => FloatingActionButtonAndPopover.tsx} | 139 +++++++----------- 3 files changed, 58 insertions(+), 89 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{FloatingActionButtonAndPopover.js => FloatingActionButtonAndPopover.tsx} (70%) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9e6745bbc291..911837cd3bf5 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record>; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; @@ -206,8 +206,8 @@ function updateLastAccessedWorkspace(policyID: OnyxEntry) { /** * Check if the user has any active free policies (aka workspaces) */ -function hasActiveFreePolicy(policies: Array> | PoliciesRecord): boolean { - const adminFreePolicies = Object.values(policies).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); +function hasActiveFreePolicy(policies: OnyxEntry): boolean { + const adminFreePolicies = Object.values(policies ?? {}).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); if (adminFreePolicies.length === 0) { return false; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 28cecf460a5f..b05e7cd6b2d9 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -645,7 +645,7 @@ function setParentReportID(parentReportID: string) { /** * Clears out the task info from the store and navigates to the NewTaskDetails page */ -function clearOutTaskInfoAndNavigate(reportID: string) { +function clearOutTaskInfoAndNavigate(reportID?: string) { clearOutTaskInfo(); if (reportID && reportID !== '0') { setParentReportID(reportID); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx similarity index 70% rename from src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js rename to src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 0df490fa4466..83cc719f2e90 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,17 +1,16 @@ -import PropTypes from 'prop-types'; +import {useIsFocused, useNavigation} from '@react-navigation/native'; +import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; -import withNavigation from '@components/withNavigation'; -import withNavigationFocus from '@components/withNavigationFocus'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -22,74 +21,53 @@ import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => - policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; - -const propTypes = { - ...windowDimensionsPropTypes, +type FloatingActionButtonAndPopoverOnyxProps = { + /** The list of policies the user has access to. */ + allPolicies: OnyxEntry>; + isLoading: OnyxEntry; +}; +type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: PropTypes.func, + onShowCreateMenu: () => void; /* Callback function before the menu is hidden */ - onHideCreateMenu: PropTypes.func, - - /** The list of policies the user has access to. */ - allPolicies: PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, - }), - - /** Indicated whether the report data is loading */ - isLoading: PropTypes.bool, - - /** Forwarded ref to FloatingActionButtonAndPopover */ - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + onHideCreateMenu: () => void; }; -const defaultProps = { - onHideCreateMenu: () => {}, - onShowCreateMenu: () => {}, - allPolicies: {}, - isLoading: false, - innerRef: null, + +type FloatingActionButtonAndPopoverRef = { + hideCreateMenu: () => void; }; /** * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. - * @param {Object} props - * @returns {JSX.Element} */ -function FloatingActionButtonAndPopover(props) { +function FloatingActionButtonAndPopover( + {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, + ref: ForwardedRef, +) { + console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); - const fabRef = useRef(null); + const fabRef = useRef(null); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const isFocused = useIsFocused(); - const prevIsFocused = usePrevious(props.isFocused); + const prevIsFocused = usePrevious(isFocused); /** * Check if LHN status changed from active to inactive. * Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web). - * - * @param {Object} prevProps - * @return {Boolean} */ const didScreenBecomeInactive = useCallback( () => // When any other page is opened over LHN - !props.isFocused && prevIsFocused, - [props.isFocused, prevIsFocused], + !isFocused && prevIsFocused, + [isFocused, prevIsFocused], ); /** @@ -97,14 +75,14 @@ function FloatingActionButtonAndPopover(props) { */ const showCreateMenu = useCallback( () => { - if (!props.isFocused && props.isSmallScreenWidth) { + if (!isFocused && isSmallScreenWidth) { return; } setIsCreateMenuActive(true); - props.onShowCreateMenu(); + onShowCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [props.isFocused, props.isSmallScreenWidth], + [isFocused, isSmallScreenWidth], ); /** @@ -118,7 +96,7 @@ function FloatingActionButtonAndPopover(props) { return; } setIsCreateMenuActive(false); - props.onHideCreateMenu(); + onHideCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isCreateMenuActive], @@ -133,7 +111,7 @@ function FloatingActionButtonAndPopover(props) { hideCreateMenu(); }, [didScreenBecomeInactive, hideCreateMenu]); - useImperativeHandle(props.innerRef, () => ({ + useImperativeHandle(ref, () => ({ hideCreateMenu() { hideCreateMenu(); }, @@ -151,10 +129,10 @@ function FloatingActionButtonAndPopover(props) { interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!props.isLoading && !Policy.hasActiveFreePolicy(props.allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) ? [ { displayInDefaultIconColor: true, - contentFit: 'contain', + contentFit: 'contain' as const, icon: Expensicons.NewWorkspace, iconWidth: 46, iconHeight: 40, @@ -219,31 +197,22 @@ function FloatingActionButtonAndPopover(props) { ); } -FloatingActionButtonAndPopover.propTypes = propTypes; -FloatingActionButtonAndPopover.defaultProps = defaultProps; FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; -const FloatingActionButtonAndPopoverWithRef = forwardRef((props, ref) => ( - -)); - -FloatingActionButtonAndPopoverWithRef.displayName = 'FloatingActionButtonAndPopoverWithRef'; - -export default compose( - withNavigation, - withNavigationFocus, - withWindowDimensions, - withOnyx({ - allPolicies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - }, - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(FloatingActionButtonAndPopoverWithRef); +const policySelector = (policy: OnyxEntry) => + policy && { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + }; + +export default withOnyx({ + allPolicies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + }, + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(forwardRef(FloatingActionButtonAndPopover)); From c9158cbd92eaed9d32315fb38fda611e4c182079 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 14:48:56 +0100 Subject: [PATCH 073/674] fix: wip --- src/libs/actions/Policy.ts | 2 +- .../sidebar/BottomTabBarFloatingActionButton/index.tsx | 1 - .../home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 2 -- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 9 ++++----- .../home/sidebar/SidebarScreen/{index.js => index.tsx} | 8 +++++--- src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js | 7 ------- 6 files changed, 10 insertions(+), 19 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{index.js => index.tsx} (75%) delete mode 100644 src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e780001a7ecc..261e15af8d48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; diff --git a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx index 788dd4ae5bc8..33b89be8fd17 100644 --- a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx +++ b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx @@ -32,7 +32,6 @@ function BottomTabBarFloatingActionButton() { return ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 314b3921cc0b..b3901e1ae06f 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -7,7 +7,6 @@ import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; -import sidebarPropTypes from './sidebarPropTypes'; /** * Function called when a pinned chat is selected. @@ -44,7 +43,6 @@ function BaseSidebarScreen() { ); } -BaseSidebarScreen.propTypes = sidebarPropTypes; BaseSidebarScreen.displayName = 'BaseSidebarScreen'; export default BaseSidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 83cc719f2e90..01d5ddaff475 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,4 +1,4 @@ -import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -25,13 +25,13 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ - allPolicies: OnyxEntry>; + allPolicies: OnyxEntry>>; isLoading: OnyxEntry; }; type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: () => void; + onShowCreateMenu?: () => void; /* Callback function before the menu is hidden */ onHideCreateMenu: () => void; @@ -49,7 +49,6 @@ function FloatingActionButtonAndPopover( {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef, ) { - console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); @@ -200,7 +199,7 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - policy && { + !!policy && { type: policy.type, role: policy.role, isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.tsx similarity index 75% rename from src/pages/home/sidebar/SidebarScreen/index.js rename to src/pages/home/sidebar/SidebarScreen/index.tsx index 7086e8a8561a..f017750c912c 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,10 +1,13 @@ import React from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -import sidebarPropTypes from './sidebarPropTypes'; -function SidebarScreen(props) { +type SidebarScreenProps = { + onLayout: (event: LayoutChangeEvent) => void; +}; +function SidebarScreen(props: SidebarScreenProps) { const {isSmallScreenWidth} = useWindowDimensions(); return ( @@ -17,7 +20,6 @@ function SidebarScreen(props) { ); } -SidebarScreen.propTypes = sidebarPropTypes; SidebarScreen.displayName = 'SidebarScreen'; export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js deleted file mode 100644 index 61a9194bb1e5..000000000000 --- a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js +++ /dev/null @@ -1,7 +0,0 @@ -import PropTypes from 'prop-types'; - -const sidebarPropTypes = { - /** Callback when onLayout of sidebar is called */ - onLayout: PropTypes.func, -}; -export default sidebarPropTypes; From dc58fff4022813cc70e994bc2f1f97f4e427cebf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 15:42:06 +0100 Subject: [PATCH 074/674] fix: wip --- .../FloatingActionButtonAndPopover.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 01d5ddaff475..9291401d4569 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -26,6 +26,8 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ allPolicies: OnyxEntry>>; + + /** Wheater app is in loading state */ isLoading: OnyxEntry; }; @@ -199,12 +201,14 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - !!policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; + policy + ? { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + } + : null; export default withOnyx({ allPolicies: { From cb7bfac8c0e73a6ef1814436e7fe35237dc3f5de Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 21 Feb 2024 12:12:14 +0100 Subject: [PATCH 075/674] TS updates after merging main --- src/components/Onfido/BaseOnfidoWeb.tsx | 54 +++++++++++-------- src/components/Onfido/index.native.tsx | 13 ++--- src/components/Onfido/types.ts | 10 ++-- .../VerifyIdentity/VerifyIdentity.tsx | 6 +-- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/components/Onfido/BaseOnfidoWeb.tsx b/src/components/Onfido/BaseOnfidoWeb.tsx index ee206b15fc24..2722b9e9be79 100644 --- a/src/components/Onfido/BaseOnfidoWeb.tsx +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,17 +1,28 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import type {ForwardedRef} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import Log from '@libs/Log'; +import type {ThemeColors} from '@styles/theme/types'; import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}) { +type InitializeOnfidoProps = OnfidoProps & + Pick & { + theme: ThemeColors; + }; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}: InitializeOnfidoProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -21,7 +32,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: FontUtils.fontWeight.bold, + fontWeightTitle: Number(FontUtils.fontWeight.bold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: theme.text, @@ -46,7 +57,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: theme.link, colorBackgroundLinkHover: theme.link, colorBackgroundLinkActive: theme.link, - authAccentColor: theme.link, colorBackgroundInfoPill: theme.link, colorBackgroundSelector: theme.appBG, colorBackgroundDocTypeButton: theme.success, @@ -58,11 +68,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -77,17 +86,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (isEmptyObject(data)) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -100,33 +107,34 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); const theme = useTheme(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, theme, @@ -144,8 +152,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.native.tsx b/src/components/Onfido/index.native.tsx index eae01d14869e..a7e7a277fff9 100644 --- a/src/components/Onfido/index.native.tsx +++ b/src/components/Onfido/index.native.tsx @@ -1,13 +1,13 @@ -import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; +import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK, OnfidoTheme} from '@onfido/react-native-sdk'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; import {checkMultiple, PERMISSIONS, RESULTS} from 'react-native-permissions'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import type {OnfidoProps} from './types'; function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { @@ -16,6 +16,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { useEffect(() => { OnfidoSDK.start({ sdkToken, + theme: OnfidoTheme.AUTOMATIC, flowSteps: { welcome: true, captureFace: { @@ -41,13 +42,13 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { return; } - if (!_.isEmpty(errorMessage) && getPlatform() === CONST.PLATFORM.IOS) { + if (!!errorMessage && getPlatform() === CONST.PLATFORM.IOS) { checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA]) .then((statuses) => { const isMicAllowed = statuses[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED; const isCameraAllowed = statuses[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED; - let alertTitle = ''; - let alertMessage = ''; + let alertTitle: TranslationPaths | '' = ''; + let alertMessage: TranslationPaths | '' = ''; if (!isCameraAllowed) { alertTitle = 'onfidoStep.cameraPermissionsNotGranted'; alertMessage = 'onfidoStep.cameraRequestMessage'; @@ -56,7 +57,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { alertMessage = 'onfidoStep.microphoneRequestMessage'; } - if (!_.isEmpty(alertTitle) && !_.isEmpty(alertMessage)) { + if (!!alertTitle && !!alertMessage) { Alert.alert( translate(alertTitle), translate(alertMessage), diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index a4fe3d93f05e..e341dfd64960 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,5 +1,7 @@ -import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; -import * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnfidoResult} from '@onfido/react-native-sdk'; +import type * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; @@ -11,10 +13,10 @@ type OnfidoProps = { onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; /** Called when the user is totally done with Onfido */ - onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + onSuccess: (data: OnfidoData) => void; /** Called when Onfido throws an error */ onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement}; +export type {OnfidoProps, OnfidoElement, OnfidoData}; diff --git a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx index d17166365a39..f7c4df6fd915 100644 --- a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx @@ -5,8 +5,8 @@ import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -// @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; +import type {OnfidoData} from '@components/Onfido/types'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -40,7 +40,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican const policyID = reimbursementAccount?.achData?.policyID ?? ''; const handleOnfidoSuccess = useCallback( - (onfidoData: Record) => { + (onfidoData: OnfidoData) => { BankAccounts.verifyIdentityForBankAccount(Number(reimbursementAccount?.achData?.bankAccountID ?? '0'), {...onfidoData, applicantID: onfidoApplicantID}, policyID); BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); }, @@ -74,7 +74,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican Date: Wed, 21 Feb 2024 12:29:27 +0100 Subject: [PATCH 076/674] Add OnfidoDataWithApplicantID type --- src/components/Onfido/types.ts | 7 ++++++- src/libs/actions/BankAccounts.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index e341dfd64960..90403f1ab179 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,8 +1,13 @@ import type {OnfidoResult} from '@onfido/react-native-sdk'; import type * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnyxEntry} from 'react-native-onyx'; type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; +type OnfidoDataWithApplicantID = OnfidoData & { + applicantID: OnyxEntry; +}; + type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; type OnfidoProps = { @@ -19,4 +24,4 @@ type OnfidoProps = { onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement, OnfidoData}; +export type {OnfidoProps, OnfidoElement, OnfidoData, OnfidoDataWithApplicantID}; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 30dd03b6e780..90e4a6c4aaed 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnfidoDataWithApplicantID} from '@components/Onfido/types'; import * as API from '@libs/API'; import type { AddPersonalBankAccountParams, @@ -436,7 +437,7 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan /** * Verify the user's identity via Onfido */ -function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: Record, policyID: string) { +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID: string) { const parameters: VerifyIdentityForBankAccountParams = { bankAccountID, onfidoData: JSON.stringify(onfidoData), From 4bb8acb56b6f3995a58eebf0f96cb975d6a94baa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 12:42:45 +0100 Subject: [PATCH 077/674] fix: removed unused props spreading --- src/pages/home/sidebar/SidebarScreen/index.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/index.tsx b/src/pages/home/sidebar/SidebarScreen/index.tsx index f017750c912c..e448a9cad332 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.tsx +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,21 +1,14 @@ import React from 'react'; -import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -type SidebarScreenProps = { - onLayout: (event: LayoutChangeEvent) => void; -}; -function SidebarScreen(props: SidebarScreenProps) { +function SidebarScreen() { const {isSmallScreenWidth} = useWindowDimensions(); return ( - + ); } From 24fb379d31f4fa6647a12795ff7308be4cdd8f47 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 21 Feb 2024 19:48:37 +0530 Subject: [PATCH 078/674] fix loading issue for idology questions and fixed onValueChange for SingleChoiceQuestion --- src/libs/actions/Wallet.ts | 4 ++-- src/pages/EnablePayments/IdologyQuestions.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index 9cb4b28bef20..ffc68c562c4c 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -233,7 +233,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM, value: { isLoading: true, }, @@ -243,7 +243,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const finallyData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, + key: ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM, value: { isLoading: false, }, diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index cadb7092f0c4..b00010671518 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -126,9 +126,10 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { prompt={currentQuestion.prompt} possibleAnswers={possibleAnswers} currentQuestionIndex={currentQuestionIndex} - onInputChange={chooseAnswer} - // NOTEME: check the PR where this was added - // onValueChange={chooseAnswer} + onValueChange={(value) => { + chooseAnswer(String(value)); + }} + onInputChange={() => {}} /> From a8097cd6a6be5b86a7d045304d1d8cf6e668c00c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 21 Feb 2024 16:33:00 +0200 Subject: [PATCH 079/674] Initial historical action to show --- src/CONST.ts | 1 + src/pages/home/report/ReportActionItem.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6a57738d06ec..dade8443bfce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -653,6 +653,7 @@ const CONST = { LEAVE_ROOM: 'LEAVEROOM', UPDATE_ROOM_DESCRIPTION: 'UPDATEROOMDESCRIPTION', }, + UNAPPROVED: 'UNAPPROVED', UNHOLD: 'UNHOLD', }, THREAD_DISABLED: ['CREATED'], diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 66394190fde6..dac0353623a5 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -464,7 +464,11 @@ function ReportActionItem(props) { children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; - } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) { + } else if ([ + CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED + ].includes(props.action.actionName)) { + // This handles all historical actions from OldDot that we just want to display the message text children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; From 708e818589c46508701cf0c6bfc0c93bbef437c8 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 21 Feb 2024 20:46:53 +0530 Subject: [PATCH 080/674] extracted URLs to CONST and deleted unused files --- src/CONST.ts | 4 ++++ .../EnablePayments/AdditionalDetailsStep.tsx | 2 +- src/pages/EnablePayments/IdologyQuestions.tsx | 3 ++- src/pages/EnablePayments/OnfidoPrivacy.tsx | 7 ++++--- src/pages/EnablePayments/TermsStep.tsx | 8 ++++--- .../walletAdditionalDetailsDraftPropTypes.js | 13 ------------ .../walletOnfidoDataPropTypes.js | 21 ------------------- .../ReimbursementAccount/BankAccountStep.js | 2 +- .../substeps/ConfirmAgreements.tsx | 3 ++- 9 files changed, 19 insertions(+), 44 deletions(-) delete mode 100644 src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js delete mode 100644 src/pages/EnablePayments/walletOnfidoDataPropTypes.js diff --git a/src/CONST.ts b/src/CONST.ts index ac8533c620b9..228f8bb97953 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -505,6 +505,10 @@ const CONST = { TERMS_URL: `${USE_EXPENSIFY_URL}/terms`, PRIVACY_URL: `${USE_EXPENSIFY_URL}/privacy`, LICENSES_URL: `${USE_EXPENSIFY_URL}/licenses`, + ACH_TERMS_URL: `${USE_EXPENSIFY_URL}/achterms`, + WALLET_AGREEMENT_URL: `${USE_EXPENSIFY_URL}/walletagreement`, + HELP_LINK_URL: `${USE_EXPENSIFY_URL}/usa-patriot-act`, + ELECTRONIC_DISCLOSURES_URL: `${USE_EXPENSIFY_URL}/esignagreement`, GITHUB_RELEASE_URL: 'https://api.github.com/repos/expensify/app/releases/latest', ADD_SECONDARY_LOGIN_URL: encodeURI('settings?param={"section":"account","openModal":"secondaryLogin"}'), MANAGE_CARDS_URL: 'domain_companycards', diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index 97041a5b021a..cdf166536c5f 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -150,7 +150,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO {translate('additionalDetailsStep.helpText')} {translate('additionalDetailsStep.helpLink')} diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index b00010671518..13c71f7005a9 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -11,6 +11,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Errors} from '@src/types/onyx/OnyxCommon'; @@ -105,7 +106,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { {translate('additionalDetailsStep.helpTextIdologyQuestions')} {translate('additionalDetailsStep.helpLink')} diff --git a/src/pages/EnablePayments/OnfidoPrivacy.tsx b/src/pages/EnablePayments/OnfidoPrivacy.tsx index 03f8df3ad82a..99117e3f1e99 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.tsx +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -13,6 +13,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {WalletOnfido} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -60,11 +61,11 @@ function OnfidoPrivacy({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: OnfidoPr {translate('onfidoStep.acceptTerms')} - {translate('onfidoStep.facialScan')} + {translate('onfidoStep.facialScan')} {', '} - {translate('common.privacy')} + {translate('common.privacy')} {` ${translate('common.and')} `} - {translate('common.termsOfService')}. + {translate('common.termsOfService')}. diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index 4836feae9f9b..ecb636e854f0 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -11,6 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {UserWallet, WalletTerms} from '@src/types/onyx'; import LongTermsForm from './TermsPage/LongTermsForm'; @@ -69,7 +70,8 @@ function TermsStep(props: TermsStepProps) { LabelComponent={() => ( {`${translate('termsStep.haveReadAndAgree')}`} - {`${translate('termsStep.electronicDisclosures')}.`} + + {`${translate('termsStep.electronicDisclosures')}.`} )} /> @@ -80,11 +82,11 @@ function TermsStep(props: TermsStepProps) { {`${translate('termsStep.agreeToThe')} `} - {`${translate('common.privacy')} `} + {`${translate('common.privacy')} `} {`${translate('common.and')} `} - {`${translate('termsStep.walletAgreement')}.`} + {`${translate('termsStep.walletAgreement')}.`} )} /> diff --git a/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js b/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js deleted file mode 100644 index 747fa82f9fa3..000000000000 --- a/src/pages/EnablePayments/walletAdditionalDetailsDraftPropTypes.js +++ /dev/null @@ -1,13 +0,0 @@ -import PropTypes from 'prop-types'; - -export default PropTypes.shape({ - legalFirstName: PropTypes.string, - legalLastName: PropTypes.string, - addressStreet: PropTypes.string, - addressCity: PropTypes.string, - addressState: PropTypes.string, - addressZip: PropTypes.string, - phoneNumber: PropTypes.string, - dob: PropTypes.string, - ssn: PropTypes.string, -}); diff --git a/src/pages/EnablePayments/walletOnfidoDataPropTypes.js b/src/pages/EnablePayments/walletOnfidoDataPropTypes.js deleted file mode 100644 index cedc1f2777b5..000000000000 --- a/src/pages/EnablePayments/walletOnfidoDataPropTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -export default PropTypes.shape({ - /** Unique identifier returned from openOnfidoFlow then re-sent to ActivateWallet with Onfido response data */ - applicantID: PropTypes.string, - - /** Token used to initialize the Onfido SDK token */ - sdkToken: PropTypes.string, - - /** Loading state to provide feedback when we are waiting for a request to finish */ - loading: PropTypes.bool, - - /** Error message to inform the user of any problem that might occur */ - error: PropTypes.string, - - /** A list of Onfido errors that the user can fix in order to attempt the Onfido flow again */ - fixableErrors: PropTypes.arrayOf(PropTypes.string), - - /** Whether the user has accepted the privacy policy of Onfido or not */ - hasAcceptedPrivacyPolicy: PropTypes.bool, -}); diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 278036430dbf..2c40c5a2ed2f 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -179,7 +179,7 @@ function BankAccountStep(props) { )} - {props.translate('common.privacy')} + {props.translate('common.privacy')} Link.openExternalLink('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} style={[styles.flexRow, styles.alignItemsCenter]} diff --git a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx index aa554ca87eb2..fae7702053b7 100644 --- a/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx +++ b/src/pages/ReimbursementAccount/CompleteVerification/substeps/ConfirmAgreements.tsx @@ -11,6 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type {ReimbursementAccount} from '@src/types/onyx'; @@ -90,7 +91,7 @@ function ConfirmAgreements({onNext, reimbursementAccount}: ConfirmAgreementsProp LabelComponent={() => ( {translate('common.iAcceptThe')} - {`${translate('completeVerificationStep.termsAndConditions')}`} + {`${translate('completeVerificationStep.termsAndConditions')}`} )} defaultValue={defaultValues.acceptTermsAndConditions} From 7064cfe6e4487972ff995c5886994cd45f80e5ea Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:52:22 +0000 Subject: [PATCH 081/674] [TS migration] Migrate postTestBuildComment to typescript --- ...uildComment.js => postTestBuildComment.ts} | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) rename tests/unit/{postTestBuildComment.js => postTestBuildComment.ts} (83%) diff --git a/tests/unit/postTestBuildComment.js b/tests/unit/postTestBuildComment.ts similarity index 83% rename from tests/unit/postTestBuildComment.js rename to tests/unit/postTestBuildComment.ts index ff77ca190c08..df0a623aaced 100644 --- a/tests/unit/postTestBuildComment.js +++ b/tests/unit/postTestBuildComment.ts @@ -1,10 +1,11 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; +import type {Writable} from 'type-fest'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; const mockGetInput = jest.fn(); -const mockCreateComment = jest.fn(); +const createCommentMock = jest.spyOn(GithubUtils, 'createComment'); const mockListComments = jest.fn(); const mockGraphql = jest.fn(); jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ @@ -12,7 +13,11 @@ jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ listComments: mockListComments, }, }); -jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint, params) => endpoint(params).then(({data}) => data)); + +jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint: (params: Record) => Promise<{data: TData}>, params: Record) => + endpoint(params).then((response) => response.data), +); + jest.spyOn(GithubUtils, 'graphql', 'get').mockReturnValue(mockGraphql); jest.mock('@actions/github', () => ({ @@ -49,11 +54,12 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; +const asMutable = (value: T): Writable => value as Writable; + describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module - core.getInput = mockGetInput; - GithubUtils.createComment = mockCreateComment; + asMutable(core).getInput = mockGetInput; }); test('Test GH action', async () => { @@ -66,11 +72,12 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('IOS_LINK').mockReturnValue('https://expensify.app/IOS_LINK'); when(core.getInput).calledWith('WEB_LINK').mockReturnValue('https://expensify.app/WEB_LINK'); when(core.getInput).calledWith('DESKTOP_LINK').mockReturnValue('https://expensify.app/DESKTOP_LINK'); - GithubUtils.createComment.mockResolvedValue(true); + createCommentMock.mockResolvedValue(true); mockListComments.mockResolvedValue({ data: [ { body: ':test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing!', + // eslint-disable-next-line @typescript-eslint/naming-convention node_id: 'IC_abcd', }, ], @@ -86,7 +93,7 @@ describe('Post test build comments action tests', () => { } } `); - expect(GithubUtils.createComment).toBeCalledTimes(1); - expect(GithubUtils.createComment).toBeCalledWith('App', 12, message); + expect(createCommentMock).toBeCalledTimes(1); + expect(createCommentMock).toBeCalledWith('App', 12, message); }); }); From 0ead9f440a8ff6d5bd8167a27dc0bf2e3def6ca8 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:58:26 +0000 Subject: [PATCH 082/674] [TS migration][postTestBuildComment] Added last ts issue fix --- tests/unit/postTestBuildComment.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index df0a623aaced..808e85d5c8a8 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -63,7 +63,9 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); + when(core.getInput) + .calledWith('PR_NUMBER', {required: true}) + .mockReturnValue(12 as unknown as string); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); From 09e448f7ddbe7992a943a63abc17f960e71cd647 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 17:37:35 +0100 Subject: [PATCH 083/674] fix: added ugly fix for onyx selectors --- src/pages/home/sidebar/SidebarLinksData.tsx | 74 +++++++++---------- .../FloatingActionButtonAndPopover.tsx | 10 ++- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 29f58d679760..182f413272f0 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -23,42 +23,42 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; +type PickedReport = Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' +>; + type SidebarLinksDataOnyxProps = { - chatReports: OnyxCollection< - Pick< - OnyxTypes.Report, - | 'reportID' - | 'participantAccountIDs' - | 'hasDraft' - | 'isPinned' - | 'isHidden' - | 'notificationPreference' - | 'errorFields' - | 'lastMessageText' - | 'lastVisibleActionCreated' - | 'iouReportID' - | 'total' - | 'nonReimbursableTotal' - | 'hasOutstandingChildRequest' - | 'isWaitingOnBankAccount' - | 'statusNum' - | 'stateNum' - | 'chatType' - | 'type' - | 'policyID' - | 'visibility' - | 'lastReadTime' - | 'reportName' - | 'policyName' - | 'oldPolicyName' - | 'ownerAccountID' - | 'currency' - | 'managerID' - | 'parentReportActionID' - | 'parentReportID' - | 'isDeletedParentAction' - > & {isUnreadWithMention: boolean} - >; + chatReports: OnyxCollection; isLoadingApp: OnyxEntry; priorityMode: OnyxEntry>; betas: OnyxEntry; @@ -248,7 +248,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, isLoadingApp: { @@ -269,7 +269,7 @@ export default withOnyx({ }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, initialValue: {}, }, policyMembers: { diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 2a0682b776cb..208a9c0a9deb 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -170,7 +170,7 @@ function FloatingActionButtonAndPopover( text: translate('sidebarScreen.saveTheWorld'), onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as Record) ? [ { displayInDefaultIconColor: true, @@ -211,10 +211,12 @@ const policySelector = (policy: OnyxEntry) => } : null; -export default withOnyx({ +export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as ( + policy: OnyxEntry, + ) => OnyxEntry>>, }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, From ce8832837a03264d7a95085f06a247a03fdd0ff2 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Wed, 21 Feb 2024 09:21:25 -0800 Subject: [PATCH 084/674] Get linked transaction only when absent --- src/libs/ReportUtils.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9f150c241540..1a2a435199d0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2305,9 +2305,9 @@ function getReportPreviewMessage( return reportActionMessage; } - const linkedTransaction = !isEmptyObject(reportAction) ? TransactionUtils.getLinkedTransaction(reportAction) : {}; if (!isEmptyObject(reportAction) && !isIOUReport(report) && reportAction && ReportActionsUtils.isSplitBillAction(reportAction)) { // This covers group chats where the last action is a split bill action + const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); if (isEmptyObject(linkedTransaction)) { return reportActionMessage; } @@ -2341,14 +2341,12 @@ function getReportPreviewMessage( }); } - if ( - !isEmptyObject(linkedTransaction) && - !isEmptyObject(reportAction) && - shouldConsiderScanningReceiptOrPendingRoute && - TransactionUtils.hasReceipt(linkedTransaction) && - TransactionUtils.isReceiptBeingScanned(linkedTransaction) && - ReportActionsUtils.isMoneyRequestAction(reportAction) - ) { + let linkedTransaction; + if (!isEmptyObject(reportAction) && shouldConsiderScanningReceiptOrPendingRoute && reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction)) { + linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + } + + if (!isEmptyObject(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { return Localize.translateLocal('iou.receiptScanning'); } @@ -2385,7 +2383,6 @@ function getReportPreviewMessage( } const lastActorID = reportAction?.actorAccountID; - const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; let amount = originalMessage?.amount; let currency = originalMessage?.currency ? originalMessage?.currency : report.currency; @@ -2394,6 +2391,11 @@ function getReportPreviewMessage( currency = TransactionUtils.getCurrency(linkedTransaction); } + if (isEmptyObject(linkedTransaction) && !isEmptyObject(reportAction)) { + linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + } + const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; + // if we have the amount in the originalMessage and lastActorID, we can use that to display the preview message for the latest request if (amount !== undefined && lastActorID && !isPreviewMessageForParentChatReport) { const amountToDisplay = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency); From a5ea70b6898e063ef27b2e11884cfaf716d633e7 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:05:21 +0000 Subject: [PATCH 085/674] [TS migration][postTestBuildComment] Updated asMutable to the util --- src/utils/asMutable.ts | 5 +++++ tests/unit/postTestBuildComment.ts | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/utils/asMutable.ts diff --git a/src/utils/asMutable.ts b/src/utils/asMutable.ts new file mode 100644 index 000000000000..57c49058cd14 --- /dev/null +++ b/src/utils/asMutable.ts @@ -0,0 +1,5 @@ +import type {Writable} from 'type-fest'; + +const asMutable = (value: T): Writable => value as Writable; + +export default asMutable; diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 808e85d5c8a8..1c1e9ad35564 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import type {Writable} from 'type-fest'; +import asMutable from '@src/utils/asMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; @@ -54,8 +54,6 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; -const asMutable = (value: T): Writable => value as Writable; - describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module From dfad42627dc4711bd9311b30b397f4468c414ee3 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:17:26 +0000 Subject: [PATCH 086/674] [TS migration][postTestBuildComment] Changed file location and name --- src/{utils/asMutable.ts => types/utils/AsMutable.ts} | 0 tests/unit/postTestBuildComment.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{utils/asMutable.ts => types/utils/AsMutable.ts} (100%) diff --git a/src/utils/asMutable.ts b/src/types/utils/AsMutable.ts similarity index 100% rename from src/utils/asMutable.ts rename to src/types/utils/AsMutable.ts diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 1c1e9ad35564..26c0711a34cd 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import asMutable from '@src/utils/asMutable'; +import asMutable from '@src/types/utils/AsMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; From 8b2a982a0d31ca5348a4b29a38accd1b2fc90519 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 22 Feb 2024 19:16:54 +0530 Subject: [PATCH 087/674] minor fic. Signed-off-by: Krishna Gupta --- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 3 +++ .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 1 + 2 files changed, 4 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 3828a43cd54d..af8f31e8c6b4 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -18,11 +18,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** The report currently being looked at */ diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b514c374e34c..5b9dce43ddcc 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -16,6 +16,7 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; +import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; From 840685854cf85f535be41e9df7792673d3572504 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 23 Feb 2024 16:15:20 +0100 Subject: [PATCH 088/674] Fix typescript errors --- .../RestartBankAccountSetupParams.ts | 1 + .../actions/ReimbursementAccount/index.ts | 2 +- .../resetFreePlanBankAccount.ts | 42 ++++++++++++++++++- .../BankInfo/BankInfo.tsx | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libs/API/parameters/RestartBankAccountSetupParams.ts b/src/libs/API/parameters/RestartBankAccountSetupParams.ts index b338eac0dea1..5cb4fa132d10 100644 --- a/src/libs/API/parameters/RestartBankAccountSetupParams.ts +++ b/src/libs/API/parameters/RestartBankAccountSetupParams.ts @@ -1,6 +1,7 @@ type RestartBankAccountSetupParams = { bankAccountID: number; ownerEmail: string; + policyID: string; }; export default RestartBankAccountSetupParams; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 416c5e956189..dd1c784d2218 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -26,7 +26,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); } -function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { +function updateReimbursementAccountDraft(bankAccountData: Partial) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); } diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts index 9eea9504d604..b3effa220e59 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -6,6 +6,7 @@ import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes' import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type * as OnyxTypes from '@src/types/onyx'; /** @@ -60,6 +61,7 @@ function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry Date: Fri, 23 Feb 2024 16:21:05 +0100 Subject: [PATCH 089/674] Fix comment typo --- src/libs/actions/ReimbursementAccount/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index f85426f8d4fe..05c375364329 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -7,7 +7,7 @@ import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; * Set the current fields with errors. */ function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { - // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } From fe351822d7fcf92f73ea1cf27f81ddee245d2391 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 23 Feb 2024 16:24:33 +0100 Subject: [PATCH 090/674] fix: tests --- src/pages/home/sidebar/SidebarLinksData.tsx | 106 ++++++++++++-------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 182f413272f0..5a5e7a8e0db2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -6,8 +6,9 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; +import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import withCurrentReportID from '@components/withCurrentReportID'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; -import useCurrentReportID from '@hooks/useCurrentReportID'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -58,21 +59,40 @@ type PickedReport = Pick< >; type SidebarLinksDataOnyxProps = { + /** List of reports */ chatReports: OnyxCollection; + + /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoadingApp: OnyxEntry; + + /** The chat priority mode */ priorityMode: OnyxEntry>; + + /** Beta features list */ betas: OnyxEntry; + + /** All report actions for all reports */ allReportActions: OnyxEntry>>; + + /** The policies which the user has access to */ policies: OnyxCollection>; - policyMembers: OnyxCollection; + + /** All of the transaction violations */ transactionViolations: OnyxCollection; -}; -type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - onLinkClick: () => void; - insets: EdgeInsets | undefined; + /** All policy members */ + policyMembers: OnyxCollection; }; +type SidebarLinksDataProps = CurrentReportIDContextValue & + SidebarLinksDataOnyxProps & { + /** Toggles the navigation menu open and closed */ + onLinkClick: () => void; + + /** Safe area insets required for mobile devices margins */ + insets: EdgeInsets | undefined; + }; + function SidebarLinksData({ allReportActions, betas, @@ -84,8 +104,8 @@ function SidebarLinksData({ priorityMode = CONST.PRIORITY_MODE.DEFAULT, policyMembers, transactionViolations, + currentReportID, }: SidebarLinksDataProps) { - const {currentReportID} = useCurrentReportID() ?? {}; const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -245,38 +265,40 @@ const policySelector = (policy: OnyxEntry) => avatar: policy.avatar, }; -export default withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, - initialValue: {}, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, -})(SidebarLinksData); +export default withCurrentReportID( + withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + initialValue: {}, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, + })(SidebarLinksData), +); From a78c3f8f66403c475c9468cdeafc460352f174ad Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 13:51:28 +0700 Subject: [PATCH 091/674] remove redundant change --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 7f923b20864e..fe5ed9bb1150 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -648,7 +648,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID || !parentReportActions) { + if (!parentReportActionID) { return {}; } return lodashGet(parentReportActions, parentReportActionID, {}); From 229e1561643f0df80486d414a8f9136cf5584007 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 14:44:40 +0700 Subject: [PATCH 092/674] fix: infinite loading in policy expense chat when changing from auditor to employee --- src/pages/home/ReportScreen.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index fe5ed9bb1150..997989c97aa4 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -137,6 +137,17 @@ function getReportID(route) { return String(lodashGet(route, 'params.reportID') || 0); } +/** + * Check is the report is deleted. + * We currently use useMemo to memorize every properties of the report + * so we can't check using isEmpty. + * + * @param {Object} report + */ +function isDeletedReport(report) { + return _.every(report, _.isUndefined); +} + function ReportScreen({ betas, route, @@ -421,7 +432,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && _.isEmpty(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isDeletedReport(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { From feeceae1bcbd1d11ef2d8d0f94c1815f8fbcc810 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 15:38:10 +0700 Subject: [PATCH 093/674] fix lint --- src/pages/home/ReportScreen.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 997989c97aa4..9c3d31fda9e7 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -143,6 +143,7 @@ function getReportID(route) { * so we can't check using isEmpty. * * @param {Object} report + * @returns {Boolean} */ function isDeletedReport(report) { return _.every(report, _.isUndefined); From d2b3674759cd5be4572b09228d5a87d9801f4661 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 11:58:43 +0100 Subject: [PATCH 094/674] Change Object.entries to Object.values --- src/libs/actions/ReimbursementAccount/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts index bdceb4e2ad5d..30005b6fdc04 100644 --- a/src/libs/actions/ReimbursementAccount/store.ts +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -52,7 +52,7 @@ function hasCreditBankAccount() { return false; } - Object.entries(bankAccountList).some(([, bankAccountJSON]) => { + Object.values(bankAccountList).some((bankAccountJSON) => { const bankAccount = new BankAccount(bankAccountJSON); return bankAccount.isDefaultCredit(); }); From 60ea1a16422b9452a62464600f73d89da28b8beb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 26 Feb 2024 16:03:36 +0500 Subject: [PATCH 095/674] perf: remove redundant translations --- src/CONST.ts | 1 + src/libs/PersonalDetailsUtils.ts | 9 +++++++-- src/libs/ReportUtils.ts | 23 +++++++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8abd4c087b16..bb167bb5d8d8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -41,6 +41,7 @@ const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { + MERGED_ACCOUNT_PREFIX: 'MERGED_', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 55aee10e611a..7cd6ac5bdedd 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -24,9 +24,14 @@ Onyx.connect({ }, }); +const hiddenText = Localize.translateLocal('common.hidden'); +const substringStartIndex = 8; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { - const displayName = passedPersonalDetails?.displayName ? passedPersonalDetails.displayName.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '') : ''; - const fallbackValue = shouldFallbackToHidden ? Localize.translateLocal('common.hidden') : ''; + let displayName = passedPersonalDetails?.displayName ?? ''; + if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { + displayName = displayName.substring(substringStartIndex); + } + const fallbackValue = shouldFallbackToHidden ? hiddenText : ''; return displayName || defaultValue || fallbackValue; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8813501e2b3f..adfefec932e5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -575,17 +575,18 @@ function getPolicyType(report: OnyxEntry, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable'); + const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableWorkspaceText; if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return Localize.translateLocal('workspace.common.unavailable'); + return unavailableWorkspaceText; } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -773,11 +774,12 @@ function isAnnounceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE; } +const chatTypes = [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL]; /** * Whether the provided report is a default room */ function isDefaultRoom(report: OnyxEntry): boolean { - return [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL].some((type) => type === getChatType(report)); + return chatTypes.some((type) => type === getChatType(report)); } /** @@ -1625,6 +1627,7 @@ function getPersonalDetailsForAccountID(accountID: number): Partial, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return Localize.translateLocal('parentReportAction.deletedMessage'); + return deletedMessageText; } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${Localize.translateLocal('common.attachment')}]`; + return `[${attachmentText}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2529,7 +2536,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return Localize.translateLocal('parentReportAction.deletedTask'); + return deletedTaskText; } if (isChatRoom(report) || isTaskReport(report)) { @@ -2545,7 +2552,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${Localize.translateLocal('common.archived')})`; + formattedName += ` (${archivedText})`; } if (formattedName) { From 22f0ffa46f75a5828e6edbf6774c0b53e688fbda Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 26 Feb 2024 16:29:41 +0500 Subject: [PATCH 096/674] perf: only parse the number if it matches the pattern --- src/CONST.ts | 1 + src/libs/LocalePhoneNumber.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index bb167bb5d8d8..fce096a9957a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -42,6 +42,7 @@ const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', + SMS_DOMAIN_PATTERN: 'expensify.sms', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 933aa7937560..4fc13322b0cf 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {parsePhoneNumber} from './PhoneNumber'; @@ -18,6 +19,10 @@ function formatPhoneNumber(number: string): string { return ''; } + // do not parse the string, if it's not a phone number + if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1) { + return number; + } const numberWithoutSMSDomain = Str.removeSMSDomain(number); const parsedPhoneNumber = parsePhoneNumber(numberWithoutSMSDomain); From c34bee5fd6a110df9a4a34ea4e7d28f7084aed28 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 12:44:47 +0100 Subject: [PATCH 097/674] Add missing isOnfidoSetupComplete --- .../actions/ReimbursementAccount/resetFreePlanBankAccount.ts | 1 + src/types/form/ReimbursementAccountForm.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts index b3effa220e59..3d529ce54cd6 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -96,6 +96,7 @@ function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry Date: Mon, 26 Feb 2024 18:54:01 +0700 Subject: [PATCH 098/674] fix: telephoto camera on safari 17+ --- .../request/step/IOURequestStepScan/index.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 2f7ca1519144..09eb85348ef7 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -90,14 +90,24 @@ function IOURequestStepScan({ return; } - navigator.mediaDevices.getUserMedia({video: true}).then((stream) => { - _.forEach(stream.getTracks(), (videoStream) => videoStream.stop()); - + navigator.mediaDevices.getUserMedia({video: {facingMode: {exact: 'environment'}, zoom: {ideal: 1}}}).then((stream) => { + // Only Safari 17+ supports zoom constraint + if (Browser.isMobileSafari() && stream.getTracks().length > 0) { + const deviceId = _.chain(stream.getTracks()) + .map((track) => track.getSettings()) + .find((setting) => setting.zoom === 1) + .get('deviceId') + .value(); + if (deviceId) { + setVideoConstraints({deviceId}); + return; + } + } + _.forEach(stream.getTracks(), (track) => track.stop()); if (!navigator.mediaDevices.enumerateDevices) { setVideoConstraints({facingMode: {exact: 'environment'}}); return; } - navigator.mediaDevices.enumerateDevices().then((devices) => { const lastBackDeviceId = _.chain(devices) .filter((item) => item.kind === 'videoinput') From f7ec94a46916c897df9c29ff34151db2a4d5f45a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 26 Feb 2024 19:01:09 +0700 Subject: [PATCH 099/674] change function name --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 9c3d31fda9e7..157be9118c6c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -145,7 +145,7 @@ function getReportID(route) { * @param {Object} report * @returns {Boolean} */ -function isDeletedReport(report) { +function isEmpty(report) { return _.every(report, _.isUndefined); } @@ -433,7 +433,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isDeletedReport(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isEmpty(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { From 643c7e150cd9e7f42f559c5df0e384182c7a0c0d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 12:44:01 +0500 Subject: [PATCH 100/674] perf: return early if displayName exists --- src/libs/PersonalDetailsUtils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 7cd6ac5bdedd..912e9aba36aa 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -25,14 +25,22 @@ Onyx.connect({ }); const hiddenText = Localize.translateLocal('common.hidden'); -const substringStartIndex = 8; +const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } + + /** + * If displayName exists, return it early so we don't have to allocate + * memory for the fallback string. + */ + if (displayName) { + return displayName; + } const fallbackValue = shouldFallbackToHidden ? hiddenText : ''; - return displayName || defaultValue || fallbackValue; + return defaultValue || fallbackValue; } /** From c683db7392b2ad06320785a78661bf56c5c2bc4e Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 13:10:35 +0500 Subject: [PATCH 101/674] test: fix failing test for formatPhoneNumber --- src/libs/LocalePhoneNumber.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 4fc13322b0cf..ec704ec4d44b 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -10,6 +10,28 @@ Onyx.connect({ callback: (val) => (countryCodeByIP = val ?? 1), }); +/** + * Checks whether the given string contains any numbers. + * It uses indexOf instead of regex and includes for performance reasons. + * + * @param text + * @returns boolean + */ +function containsNumbers(text: string) { + return ( + text.indexOf('0') !== -1 || + text.indexOf('1') !== -1 || + text.indexOf('2') !== -1 || + text.indexOf('3') !== -1 || + text.indexOf('4') !== -1 || + text.indexOf('5') !== -1 || + text.indexOf('6') !== -1 || + text.indexOf('7') !== -1 || + text.indexOf('8') !== -1 || + text.indexOf('9') !== -1 + ); +} + /** * Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions @@ -20,7 +42,7 @@ function formatPhoneNumber(number: string): string { } // do not parse the string, if it's not a phone number - if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1) { + if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1 && !containsNumbers(number)) { return number; } const numberWithoutSMSDomain = Str.removeSMSDomain(number); From 454a9b258fee05e310d2378b5bfd2cdaf915b251 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 15:24:10 +0500 Subject: [PATCH 102/674] refactor: move translations to utils --- src/libs/CommonTranslationUtils.ts | 49 ++++++++++++++++++++++++++++++ src/libs/PersonalDetailsUtils.ts | 11 +++++-- src/libs/ReportUtils.ts | 21 +++++-------- 3 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 src/libs/CommonTranslationUtils.ts diff --git a/src/libs/CommonTranslationUtils.ts b/src/libs/CommonTranslationUtils.ts new file mode 100644 index 000000000000..80a06e51ce9f --- /dev/null +++ b/src/libs/CommonTranslationUtils.ts @@ -0,0 +1,49 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as Localize from './Localize'; + +/** + * This file contains common translations that are used in multiple places in the app. + * This is done to avoid duplicate translations and to keep the translations consistent. + * This also allows us to not repeatedly translate the same string which may happen due + * to translations being done for eg, in a loop. + * + * This was identified as part of a performance audit. + * details: https://github.com/Expensify/App/issues/35234#issuecomment-1926911643 + */ + +let deletedTaskText = ''; +let deletedMessageText = ''; +let attachmentText = ''; +let archivedText = ''; +let hiddenText = ''; +let unavailableWorkspaceText = ''; + +function isTranslationAvailable() { + return deletedTaskText && deletedMessageText && attachmentText && archivedText && hiddenText && unavailableWorkspaceText; +} + +Onyx.connect({ + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + callback: (val) => { + if (!val && isTranslationAvailable()) { + return; + } + + deletedTaskText = Localize.translateLocal('parentReportAction.deletedTask'); + deletedMessageText = Localize.translateLocal('parentReportAction.deletedMessage'); + attachmentText = Localize.translateLocal('common.attachment'); + archivedText = Localize.translateLocal('common.archived'); + hiddenText = Localize.translateLocal('common.hidden'); + unavailableWorkspaceText = Localize.translateLocal('workspace.common.unavailable'); + }, +}); + +export default { + deletedTaskText: () => deletedTaskText, + deletedMessageText: () => deletedMessageText, + attachmentText: () => attachmentText, + archivedText: () => archivedText, + hiddenText: () => hiddenText, + unavailableWorkspaceText: () => unavailableWorkspaceText, +}; diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 912e9aba36aa..7a9b3fe96598 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -5,6 +5,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; +import CommonTranslationUtils from './CommonTranslationUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as UserUtils from './UserUtils'; @@ -24,10 +25,16 @@ Onyx.connect({ }, }); -const hiddenText = Localize.translateLocal('common.hidden'); +/** + * Index for the substring method to remove the merged account prefix. + */ const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; + function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; + /** + * If the displayName starts with the merged account prefix, remove it. + */ if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } @@ -39,7 +46,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableWorkspaceText; + const noPolicyFound = returnEmptyIfNotFound ? '' : CommonTranslationUtils.unavailableWorkspaceText(); if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return unavailableWorkspaceText; + return CommonTranslationUtils.unavailableWorkspaceText(); } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -1627,7 +1627,6 @@ function getPersonalDetailsForAccountID(accountID: number): Partial, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return deletedMessageText; + return CommonTranslationUtils.deletedMessageText(); } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${attachmentText}]`; + return `[${CommonTranslationUtils.attachmentText()}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2536,7 +2531,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return deletedTaskText; + return CommonTranslationUtils.deletedTaskText(); } if (isChatRoom(report) || isTaskReport(report)) { @@ -2552,7 +2547,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${archivedText})`; + formattedName += ` (${CommonTranslationUtils.archivedText()})`; } if (formattedName) { From eb8f26ccb2fabb54fbb7f6c054e13dd53127c9ee Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 15:24:28 +0500 Subject: [PATCH 103/674] fix: linting --- src/libs/LocalePhoneNumber.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index ec704ec4d44b..798e0a50c43e 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -13,7 +13,7 @@ Onyx.connect({ /** * Checks whether the given string contains any numbers. * It uses indexOf instead of regex and includes for performance reasons. - * + * * @param text * @returns boolean */ From 58d5b933dbe07128cf33ee4d161b4c78318bc00b Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 27 Feb 2024 17:54:29 +0700 Subject: [PATCH 104/674] add settimeout --- src/pages/iou/request/step/IOURequestStepScan/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 13d6b2c4e243..179138e8bd50 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -201,10 +201,12 @@ function IOURequestStepScan({ advanced: [{torch: true}], }) .then(() => { - getScreenshot(); - trackRef.current.applyConstraints({ - advanced: [{torch: false}], - }); + setTimeout(() => { + getScreenshot(); + trackRef.current.applyConstraints({ + advanced: [{torch: false}], + }); + }, 2000); }); return; } From 57370376e524abafc3d6e9e0c97bb649df40ffdf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 27 Feb 2024 14:01:08 +0100 Subject: [PATCH 105/674] fix: resolve comments --- src/components/LHNOptionsList/types.ts | 2 +- src/libs/ReportUtils.ts | 9 +-- src/libs/SidebarUtils.ts | 28 +++++---- src/libs/actions/Policy.ts | 2 +- .../home/sidebar/AvatarWithOptionalStatus.tsx | 4 +- src/pages/home/sidebar/SidebarLinks.tsx | 27 +++++---- src/pages/home/sidebar/SidebarLinksData.tsx | 59 +++++-------------- .../FloatingActionButtonAndPopover.tsx | 19 +++--- .../SignInOrAvatarWithOptionalStatus.tsx | 1 + 9 files changed, 67 insertions(+), 84 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f3d6bde9d41c..58bea97f04c9 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -45,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[] | null; + data: string[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b19cd837a033..b23da56bcd8f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -704,8 +704,8 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean /** * Checks if the supplied report has a common policy member with the array passed in params. */ -function hasParticipantInArray(report: Report, policyMemberAccountIDs: number[]) { - if (!report.participantAccountIDs) { +function hasParticipantInArray(report: OnyxEntry, policyMemberAccountIDs: number[]) { + if (!report?.participantAccountIDs) { return false; } @@ -921,9 +921,10 @@ function isConciergeChatReport(report: OnyxEntry): boolean { * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ -function doesReportBelongToWorkspace(report: Report, policyMemberAccountIDs: number[], policyID?: string) { +function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID?: string) { return ( - isConciergeChatReport(report) || (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report.policyID === policyID) + isConciergeChatReport(report) || + (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report?.policyID === policyID) ); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 44a17a6d50a0..463c796fb5ce 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -64,9 +64,9 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: OnyxEntry>, + allReports: OnyxCollection, betas: OnyxEntry, - policies: OnyxEntry>, + policies: OnyxCollection, priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, @@ -83,7 +83,7 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + !!betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -91,7 +91,7 @@ function getOrderedReportIDs( betas, policies, excludeEmptyChats: true, - doesReportHaveViolations: !!doesReportHaveViolations, + doesReportHaveViolations, }); }); @@ -114,7 +114,7 @@ function getOrderedReportIDs( // - Sorted by reportDisplayName in GSD (focus) view mode const pinnedAndGBRReports: Report[] = []; const draftReports: Report[] = []; - const nonArchivedReports: Report[] = []; + const nonArchivedReports: Array> = []; const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { @@ -125,16 +125,18 @@ function getOrderedReportIDs( // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. - // eslint-disable-next-line no-param-reassign - report.displayName = ReportUtils.getReportName(report); + if (report) { + // eslint-disable-next-line no-param-reassign + report.displayName = ReportUtils.getReportName(report); + } - const isPinned = report.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); - if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { + const isPinned = report?.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + if ((isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) && report) { pinnedAndGBRReports.push(report); - } else if (report.hasDraft) { + } else if (report?.hasDraft) { draftReports.push(report); - } else if (ReportUtils.isArchivedRoom(report)) { + } else if (ReportUtils.isArchivedRoom(report) && report) { archivedReports.push(report); } else { nonArchivedReports.push(report); @@ -160,7 +162,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); return LHNReports; } diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 850d21e4858e..0296b1a8ea6f 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -84,7 +84,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record; +type PoliciesRecord = Record>; type NewCustomUnit = { customUnitID: string; diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index 5597d46c29bc..0e1dedaf3651 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -12,10 +12,10 @@ import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; type AvatarWithOptionalStatusProps = { /** Whether the create menu is open or not */ - isCreateMenuOpen: boolean; + isCreateMenuOpen?: boolean; /** Emoji status */ - emojiStatus: string; + emojiStatus?: string; }; function AvatarWithOptionalStatus({emojiStatus = '', isCreateMenuOpen = false}: AvatarWithOptionalStatusProps) { diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index b0dd6f5ac067..5e0e37b1a164 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -26,19 +26,30 @@ type SidebarLinksOnyxProps = { }; type SidebarLinksProps = SidebarLinksOnyxProps & { + /** Toggles the navigation menu open and closed */ onLinkClick: () => void; - insets: EdgeInsets | undefined; - optionListItems: string[] | null; + + /** Safe area insets required for mobile devices margins */ + insets: EdgeInsets; + + /** List of options to display */ + optionListItems: string[]; + + /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoading: OnyxEntry; + + /** The chat priority mode */ priorityMode?: OnyxEntry>; + + /** Method to change currently active report */ isActiveReport: (reportID: string) => boolean; - isCreateMenuOpen?: boolean; + /** ID of currently active workspace */ // eslint-disable-next-line react/no-unused-prop-types -- its used in withOnyx activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}: SidebarLinksProps) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, activePolicy}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const modal = useRef({}); @@ -105,17 +116,13 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices const reportActionID = Navigation.getTopmostReportActionId(); - if ( - !!isCreateMenuOpen || - (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || - (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID) - ) { + if ((option.reportID === Navigation.getTopmostReportId() && !reportActionID) || (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); onLinkClick(); }, - [isCreateMenuOpen, isSmallScreenWidth, isActiveReport, onLinkClick], + [isSmallScreenWidth, isActiveReport, onLinkClick], ); const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index ee99b0c9cf59..6e71e30ac1b6 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -24,43 +24,13 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; -type PickedReport = Pick< - OnyxTypes.Report, - | 'reportID' - | 'participantAccountIDs' - | 'hasDraft' - | 'isPinned' - | 'isHidden' - | 'notificationPreference' - | 'errorFields' - | 'lastMessageText' - | 'lastVisibleActionCreated' - | 'iouReportID' - | 'total' - | 'nonReimbursableTotal' - | 'hasOutstandingChildRequest' - | 'isWaitingOnBankAccount' - | 'statusNum' - | 'stateNum' - | 'chatType' - | 'type' - | 'policyID' - | 'visibility' - | 'lastReadTime' - | 'reportName' - | 'policyName' - | 'oldPolicyName' - | 'ownerAccountID' - | 'currency' - | 'managerID' - | 'parentReportActionID' - | 'parentReportID' - | 'isDeletedParentAction' ->; +type ChatReportSelector = ReturnType & {isUnreadWithMention: boolean}; +type PolicySelector = ReturnType; +type ReportActionsSelector = ReturnType; type SidebarLinksDataOnyxProps = { /** List of reports */ - chatReports: OnyxCollection; + chatReports: OnyxCollection; /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoadingApp: OnyxEntry; @@ -72,10 +42,10 @@ type SidebarLinksDataOnyxProps = { betas: OnyxEntry; /** All report actions for all reports */ - allReportActions: OnyxEntry>>; + allReportActions: OnyxEntry; /** The policies which the user has access to */ - policies: OnyxCollection>; + policies: OnyxCollection; /** All of the transaction violations */ transactionViolations: OnyxCollection; @@ -90,7 +60,7 @@ type SidebarLinksDataProps = CurrentReportIDContextValue & onLinkClick: () => void; /** Safe area insets required for mobile devices margins */ - insets: EdgeInsets | undefined; + insets: EdgeInsets; }; function SidebarLinksData({ @@ -120,7 +90,7 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const optionListItems: string[] | null = useMemo(() => { + const optionListItems: string[] = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, chatReports as OnyxEntry>, @@ -133,7 +103,7 @@ function SidebarLinksData({ policyMemberAccountIDs, ); - if (deepEqual(reportIDsRef.current, reportIDs)) { + if (reportIDsRef.current && deepEqual(reportIDsRef.current, reportIDs)) { return reportIDsRef.current; } @@ -170,7 +140,7 @@ function SidebarLinksData({ const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; - const isActiveReport = useCallback((reportID: string) => currentReportIDRef.current === reportID, []); + const isActiveReport = useCallback((reportID: string): boolean => currentReportIDRef.current === reportID, []); return ( ) => report && { @@ -270,7 +239,8 @@ export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, isLoadingApp: { @@ -291,7 +261,8 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, initialValue: {}, }, policyMembers: { @@ -303,3 +274,5 @@ export default withCurrentReportID( }, })(SidebarLinksData), ); + +export type {PolicySelector}; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 208a9c0a9deb..c080272acbd2 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -14,6 +14,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; @@ -25,7 +26,7 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ - allPolicies: OnyxEntry>>; + allPolicies: OnyxCollection; /** Wheater app is in loading state */ isLoading: OnyxEntry; @@ -36,7 +37,7 @@ type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxPro onShowCreateMenu?: () => void; /* Callback function before the menu is hidden */ - onHideCreateMenu: () => void; + onHideCreateMenu?: () => void; }; type FloatingActionButtonAndPopoverRef = { @@ -48,7 +49,7 @@ type FloatingActionButtonAndPopoverRef = { * FAB that can open or close the menu. */ function FloatingActionButtonAndPopover( - {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, + {onHideCreateMenu, onShowCreateMenu, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); @@ -80,7 +81,7 @@ function FloatingActionButtonAndPopover( return; } setIsCreateMenuActive(true); - onShowCreateMenu(); + onShowCreateMenu?.(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isFocused, isSmallScreenWidth], @@ -97,7 +98,7 @@ function FloatingActionButtonAndPopover( return; } setIsCreateMenuActive(false); - onHideCreateMenu(); + onHideCreateMenu?.(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isCreateMenuActive], @@ -170,7 +171,7 @@ function FloatingActionButtonAndPopover( text: translate('sidebarScreen.saveTheWorld'), onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as Record) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as OnyxEntry>) ? [ { displayInDefaultIconColor: true, @@ -214,9 +215,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as ( - policy: OnyxEntry, - ) => OnyxEntry>>, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx index 2a9356d78232..e8c90bb6eb08 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx @@ -6,6 +6,7 @@ import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; type SignInOrAvatarWithOptionalStatusProps = { + /** Whether the create menu is open or not */ isCreateMenuOpen?: boolean; }; From f011e58cac8812b6fd09c2c03a198c5f6ba0b32c Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 27 Feb 2024 08:12:02 -0800 Subject: [PATCH 106/674] Hide description when last message is multi-request preview --- src/libs/OptionsListUtils.ts | 1 + src/libs/ReportUtils.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 3d11795f5452..3e2f89f274a1 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -561,6 +561,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails ReportUtils.isChatReport(report), null, true, + lastReportAction, ); } else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index be1fd977c145..e6775ef59bd3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2289,6 +2289,7 @@ function getReportPreviewMessage( isPreviewMessageForParentChatReport = false, policy: OnyxEntry = null, isForListPreview = false, + originalReportAction: OnyxEntry | EmptyObject = reportAction, ): string { const reportActionMessage = reportAction?.message?.[0].html ?? ''; @@ -2387,7 +2388,9 @@ function getReportPreviewMessage( if (isEmptyObject(linkedTransaction) && !isEmptyObject(reportAction)) { linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); } - const comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; + const hideComment = + !isEmptyObject(originalReportAction) && ReportActionsUtils.isReportPreviewAction(originalReportAction) && ReportActionsUtils.getNumberOfMoneyRequests(originalReportAction) !== 1; + const comment = !isEmptyObject(linkedTransaction) && !hideComment ? TransactionUtils.getDescription(linkedTransaction) : undefined; // if we have the amount in the originalMessage and lastActorID, we can use that to display the preview message for the latest request if (amount !== undefined && lastActorID && !isPreviewMessageForParentChatReport) { From a818f52490e9b109c408c5d7349f987fcfc206a1 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 27 Feb 2024 09:51:03 -0800 Subject: [PATCH 107/674] Tidy up conditions for request descriptions --- src/libs/ReportUtils.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9ecfd2e92beb..4dcbc60dd849 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2395,9 +2395,11 @@ function getReportPreviewMessage( if (isEmptyObject(linkedTransaction) && !isEmptyObject(reportAction)) { linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); } - const hideComment = - !isEmptyObject(originalReportAction) && ReportActionsUtils.isReportPreviewAction(originalReportAction) && ReportActionsUtils.getNumberOfMoneyRequests(originalReportAction) !== 1; - const comment = !isEmptyObject(linkedTransaction) && !hideComment ? TransactionUtils.getDescription(linkedTransaction) : undefined; + + let comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; + if (!isEmptyObject(originalReportAction) && ReportActionsUtils.isReportPreviewAction(originalReportAction) && ReportActionsUtils.getNumberOfMoneyRequests(originalReportAction) !== 1) { + comment = undefined; + } // if we have the amount in the originalMessage and lastActorID, we can use that to display the preview message for the latest request if (amount !== undefined && lastActorID && !isPreviewMessageForParentChatReport) { From 3060c1e778249b8b755c584fa76aebb9e08ebebe Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 10:59:40 +0700 Subject: [PATCH 108/674] clear timeout --- .../iou/request/step/IOURequestStepScan/index.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 179138e8bd50..add7e888fd58 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; @@ -76,6 +76,8 @@ function IOURequestStepScan({ const cameraRef = useRef(null); const trackRef = useRef(null); + const getScreenshotTimeoutRef = useRef(null); + const hideRecieptModal = () => { setIsAttachmentInvalid(false); }; @@ -201,7 +203,7 @@ function IOURequestStepScan({ advanced: [{torch: true}], }) .then(() => { - setTimeout(() => { + getScreenshotTimeoutRef.current = setTimeout(() => { getScreenshot(); trackRef.current.applyConstraints({ advanced: [{torch: false}], @@ -220,6 +222,15 @@ function IOURequestStepScan({ }), ).current; + useEffect(() => { + return () => { + if (!getScreenshotTimeoutRef.current) { + return; + } + clearTimeout(getScreenshotTimeoutRef.current); + }; + }, []); + const mobileCameraView = () => ( <> From 34cd2e8402fe6fab0135e503114b7d514360a76b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 28 Feb 2024 13:03:14 +0500 Subject: [PATCH 109/674] refactor: use regex and dedicated const for sms domain --- src/CONST.ts | 1 - src/libs/LocalePhoneNumber.ts | 26 ++------------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fce096a9957a..bb167bb5d8d8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -42,7 +42,6 @@ const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', - SMS_DOMAIN_PATTERN: 'expensify.sms', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 798e0a50c43e..460d5fc0fe9f 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -10,28 +10,6 @@ Onyx.connect({ callback: (val) => (countryCodeByIP = val ?? 1), }); -/** - * Checks whether the given string contains any numbers. - * It uses indexOf instead of regex and includes for performance reasons. - * - * @param text - * @returns boolean - */ -function containsNumbers(text: string) { - return ( - text.indexOf('0') !== -1 || - text.indexOf('1') !== -1 || - text.indexOf('2') !== -1 || - text.indexOf('3') !== -1 || - text.indexOf('4') !== -1 || - text.indexOf('5') !== -1 || - text.indexOf('6') !== -1 || - text.indexOf('7') !== -1 || - text.indexOf('8') !== -1 || - text.indexOf('9') !== -1 - ); -} - /** * Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions @@ -41,8 +19,8 @@ function formatPhoneNumber(number: string): string { return ''; } - // do not parse the string, if it's not a phone number - if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1 && !containsNumbers(number)) { + // do not parse the string, if it doesn't contain the SMS domain and it's not a phone number + if (number.indexOf(CONST.SMS.DOMAIN) === -1 && !CONST.REGEX.DIGITS_AND_PLUS.test(number)) { return number; } const numberWithoutSMSDomain = Str.removeSMSDomain(number); From 707f56c7f913dcf6e7e764933c7e3b7f78021294 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 28 Feb 2024 13:03:42 +0500 Subject: [PATCH 110/674] refactor: remove allocating a variable and return the evaluated expresssion --- src/libs/PersonalDetailsUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 7a9b3fe96598..d5946feb2f74 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -46,8 +46,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial Date: Wed, 28 Feb 2024 08:33:16 +0000 Subject: [PATCH 111/674] Rename AsMutable.ts to asMutable.ts --- src/types/utils/{AsMutable.ts => asMutable.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/types/utils/{AsMutable.ts => asMutable.ts} (100%) diff --git a/src/types/utils/AsMutable.ts b/src/types/utils/asMutable.ts similarity index 100% rename from src/types/utils/AsMutable.ts rename to src/types/utils/asMutable.ts From 5a33f683158f2282b2134478411c30671d4b8492 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 28 Feb 2024 08:35:38 +0000 Subject: [PATCH 112/674] [TS migration][postTestBuildComments] Feedback --- tests/unit/postTestBuildComment.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 26c0711a34cd..24028ee4f1bd 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import asMutable from '@src/types/utils/AsMutable'; +import asMutable from '@src/types/utils/asMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; @@ -61,9 +61,7 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput) - .calledWith('PR_NUMBER', {required: true}) - .mockReturnValue(12 as unknown as string); + when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); @@ -94,6 +92,6 @@ describe('Post test build comments action tests', () => { } `); expect(createCommentMock).toBeCalledTimes(1); - expect(createCommentMock).toBeCalledWith('App', 12, message); + expect(createCommentMock).toBeCalledWith('App', '12', message); }); }); From 518153347c6760462a92370b066dbace32a56781 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 18:01:29 +0700 Subject: [PATCH 113/674] add return condition --- .../request/step/IOURequestStepScan/index.js | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index add7e888fd58..e18bc861333f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -165,7 +165,7 @@ function IOURequestStepScan({ navigateToConfirmationStep(); }; - const handleOnUserMedia = (stream) => { + const setupCameraPermissionsAndCapabilities = (stream) => { setCameraPermissionState('granted'); const [track] = stream.getVideoTracks(); @@ -177,6 +177,10 @@ function IOURequestStepScan({ }; const getScreenshot = useCallback(() => { + if (!cameraRef.current || !cameraRef.current.getScreenshot) { + return; + } + const imageBase64 = cameraRef.current.getScreenshot(); const filename = `receipt_${Date.now()}.png`; @@ -192,11 +196,16 @@ function IOURequestStepScan({ navigateToConfirmationStep(); }, [action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); - const capturePhoto = useCallback(() => { - if (!cameraRef.current.getScreenshot) { + const clearTorchConstraints = useCallback(() => { + if (!trackRef.current) { return; } + trackRef.current.applyConstraints({ + advanced: [{torch: false}], + }); + }, []); + const capturePhoto = useCallback(() => { if (trackRef.current && isFlashLightOn) { trackRef.current .applyConstraints({ @@ -205,16 +214,14 @@ function IOURequestStepScan({ .then(() => { getScreenshotTimeoutRef.current = setTimeout(() => { getScreenshot(); - trackRef.current.applyConstraints({ - advanced: [{torch: false}], - }); + clearTorchConstraints(); }, 2000); }); return; } getScreenshot(); - }, [cameraRef, isFlashLightOn, getScreenshot]); + }, [isFlashLightOn, getScreenshot, clearTorchConstraints]); const panResponder = useRef( PanResponder.create({ @@ -222,14 +229,15 @@ function IOURequestStepScan({ }), ).current; - useEffect(() => { - return () => { + useEffect( + () => () => { if (!getScreenshotTimeoutRef.current) { return; } clearTimeout(getScreenshotTimeoutRef.current); - }; - }, []); + }, + [], + ); const mobileCameraView = () => ( <> @@ -254,7 +262,7 @@ function IOURequestStepScan({ )} setCameraPermissionState('denied')} style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} ref={cameraRef} From f2d910b045b28d8d95a0e8e0dce3b8f3959f00ac Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 28 Feb 2024 19:25:41 +0530 Subject: [PATCH 114/674] fix: updated IdologyQuestionsForm and AdditionalDetailStepForm values usage of the updated Form --- src/pages/EnablePayments/TermsStep.tsx | 2 -- src/types/form/AdditionalDetailStepForm.ts | 28 +++++++++++++--------- src/types/form/IdologyQuestionsForm.ts | 12 +++++++--- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/pages/EnablePayments/TermsStep.tsx b/src/pages/EnablePayments/TermsStep.tsx index 7a4fc76d87c5..df9b14cffca4 100644 --- a/src/pages/EnablePayments/TermsStep.tsx +++ b/src/pages/EnablePayments/TermsStep.tsx @@ -7,8 +7,6 @@ import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; - -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; diff --git a/src/types/form/AdditionalDetailStepForm.ts b/src/types/form/AdditionalDetailStepForm.ts index f102d03679ba..0d2c96487392 100644 --- a/src/types/form/AdditionalDetailStepForm.ts +++ b/src/types/form/AdditionalDetailStepForm.ts @@ -1,3 +1,4 @@ +import type {ValueOf} from 'type-fest'; import type Form from './Form'; const INPUT_IDS = { @@ -12,17 +13,22 @@ const INPUT_IDS = { SSN: 'ssn', } as const; -type AdditionalDetailStepForm = Form<{ - [INPUT_IDS.LEGAL_FIRST_NAME]: string; - [INPUT_IDS.LEGAL_LAST_NAME]: string; - [INPUT_IDS.PHONE_NUMBER]: string; - [INPUT_IDS.ADDRESS_STREET]: string; - [INPUT_IDS.ADDRESS_CITY]: string; - [INPUT_IDS.ADDRESS_ZIP_CODE]: string; - [INPUT_IDS.ADDRESS_STATE]: string; - [INPUT_IDS.DOB]: string; - [INPUT_IDS.SSN]: string; -}>; +type InputID = ValueOf; + +type AdditionalDetailStepForm = Form< + InputID, + { + [INPUT_IDS.LEGAL_FIRST_NAME]: string; + [INPUT_IDS.LEGAL_LAST_NAME]: string; + [INPUT_IDS.PHONE_NUMBER]: string; + [INPUT_IDS.ADDRESS_STREET]: string; + [INPUT_IDS.ADDRESS_CITY]: string; + [INPUT_IDS.ADDRESS_ZIP_CODE]: string; + [INPUT_IDS.ADDRESS_STATE]: string; + [INPUT_IDS.DOB]: string; + [INPUT_IDS.SSN]: string; + } +>; export type {AdditionalDetailStepForm}; export default INPUT_IDS; diff --git a/src/types/form/IdologyQuestionsForm.ts b/src/types/form/IdologyQuestionsForm.ts index 5b8d50c68abf..250e8c6f7766 100644 --- a/src/types/form/IdologyQuestionsForm.ts +++ b/src/types/form/IdologyQuestionsForm.ts @@ -1,12 +1,18 @@ +import type {ValueOf} from 'type-fest'; import type Form from './Form'; const INPUT_IDS = { ANSWER: 'answer', } as const; -type IdologyQuestionsForm = Form<{ - [INPUT_IDS.ANSWER]: string; -}>; +type InputID = ValueOf; + +type IdologyQuestionsForm = Form< + InputID, + { + [INPUT_IDS.ANSWER]: string; + } +>; export type {IdologyQuestionsForm}; export default INPUT_IDS; From 8cfade3b04b12520ce93d33cdd35c2e0edad53d8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 29 Feb 2024 00:19:36 +0700 Subject: [PATCH 115/674] remove unnecessary check --- src/pages/iou/request/step/IOURequestStepScan/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index e18bc861333f..a11a9086e135 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -177,7 +177,7 @@ function IOURequestStepScan({ }; const getScreenshot = useCallback(() => { - if (!cameraRef.current || !cameraRef.current.getScreenshot) { + if (!cameraRef.current) { return; } From 06ca091a5fef7552178ee4d767f7bf709a87abf4 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Thu, 29 Feb 2024 09:49:30 +0530 Subject: [PATCH 116/674] fix: prevent tab switch while swiping horizontally on map --- src/components/SwipeInterceptPanResponder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SwipeInterceptPanResponder.tsx b/src/components/SwipeInterceptPanResponder.tsx index fe1545d2f14b..48cfe4f90c5c 100644 --- a/src/components/SwipeInterceptPanResponder.tsx +++ b/src/components/SwipeInterceptPanResponder.tsx @@ -1,7 +1,7 @@ import {PanResponder} from 'react-native'; const SwipeInterceptPanResponder = PanResponder.create({ - onMoveShouldSetPanResponder: () => true, + onStartShouldSetPanResponder: () => true, onPanResponderTerminationRequest: () => false, }); From 4607adda4ca244422ffce548780750df2d01610a Mon Sep 17 00:00:00 2001 From: Aswin S Date: Thu, 29 Feb 2024 10:13:51 +0530 Subject: [PATCH 117/674] misc: remove redundant file --- src/components/MapView/responder/index.android.ts | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/components/MapView/responder/index.android.ts diff --git a/src/components/MapView/responder/index.android.ts b/src/components/MapView/responder/index.android.ts deleted file mode 100644 index a0fce71d8ef5..000000000000 --- a/src/components/MapView/responder/index.android.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {PanResponder} from 'react-native'; - -const responder = PanResponder.create({ - onStartShouldSetPanResponder: () => true, - onPanResponderTerminationRequest: () => false, -}); - -export default responder; From f36550f1001bba5e995b1ef682dc22da19b28605 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 29 Feb 2024 13:00:44 +0500 Subject: [PATCH 118/674] refactor: use single-line comments --- src/libs/PersonalDetailsUtils.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index d5946feb2f74..97e2dc91492b 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -25,24 +25,19 @@ Onyx.connect({ }, }); -/** - * Index for the substring method to remove the merged account prefix. - */ +// Index for the substring method to remove the merged account prefix. const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; - /** - * If the displayName starts with the merged account prefix, remove it. - */ + + // If the displayName starts with the merged account prefix, remove it. if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } - /** - * If displayName exists, return it early so we don't have to allocate - * memory for the fallback string. - */ + // If displayName exists, return it early so we don't have to allocate + // memory for the fallback string. if (displayName) { return displayName; } From e01e18bccbd26929b3a49d211a523c4723989d92 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 29 Feb 2024 09:54:53 +0100 Subject: [PATCH 119/674] fix: resolve comments --- src/libs/SidebarUtils.ts | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 4 ++-- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 463c796fb5ce..41a0f4b87a6c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -136,7 +136,7 @@ function getOrderedReportIDs( pinnedAndGBRReports.push(report); } else if (report?.hasDraft) { draftReports.push(report); - } else if (ReportUtils.isArchivedRoom(report) && report) { + } else if (report && ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); } else { nonArchivedReports.push(report); diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 6e71e30ac1b6..b4d118c5278b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -239,7 +239,7 @@ export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, @@ -261,7 +261,7 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, initialValue: {}, }, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index c080272acbd2..a550dbd91657 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -215,6 +215,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, }, isLoading: { From 3484b06e87d161c3534329418a047a29cd28db82 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 29 Feb 2024 10:28:10 +0100 Subject: [PATCH 120/674] fix: typecheck --- tests/utils/LHNTestUtils.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 80f28002f975..f6bd01bbee74 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -282,7 +282,6 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( {}} insets={{ top: 0, @@ -290,7 +289,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { right: 0, bottom: 0, }} - isSmallScreenWidth={false} + // @ts-expect-error - normally this comes from withCurrentReportID hoc , but here we are just mocking this currentReportID={currentReportID} /> From f9dd242735e6e64019807b4c5c981b0c80c89add Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 29 Feb 2024 10:51:11 +0100 Subject: [PATCH 121/674] migrate group 5 tests to ts --- .../actions/{ReportTest.js => ReportTest.ts} | 135 +++++++++--------- tests/unit/{APITest.js => APITest.ts} | 93 ++++++++---- .../{MigrationTest.js => MigrationTest.ts} | 61 ++++---- tests/unit/{NetworkTest.js => NetworkTest.ts} | 32 +++-- 4 files changed, 193 insertions(+), 128 deletions(-) rename tests/actions/{ReportTest.js => ReportTest.ts} (86%) rename tests/unit/{APITest.js => APITest.ts} (87%) rename tests/unit/{MigrationTest.js => MigrationTest.ts} (76%) rename tests/unit/{NetworkTest.js => NetworkTest.ts} (92%) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.ts similarity index 86% rename from tests/actions/ReportTest.js rename to tests/actions/ReportTest.ts index a94db507637b..43ceaaad607e 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.ts @@ -1,8 +1,9 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {afterEach, beforeAll, beforeEach, describe, expect, it} from '@jest/globals'; import {utcToZonedTime} from 'date-fns-tz'; -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type * as OnyxTypes from '@src/types/onyx'; import CONST from '../../src/CONST'; import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; @@ -21,7 +22,7 @@ import waitForNetworkPromises from '../utils/waitForNetworkPromises'; const UTC = 'UTC'; jest.mock('../../src/libs/actions/Report', () => { - const originalModule = jest.requireActual('../../src/libs/actions/Report'); + const originalModule: typeof Report = jest.requireActual('../../src/libs/actions/Report'); return { ...originalModule, @@ -35,7 +36,7 @@ describe('actions/Report', () => { PusherHelper.setup(); Onyx.init({ keys: ONYXKEYS, - registerStorageEventListener: () => {}, + // registerStorageEventListener: () => {}, }); }); @@ -52,12 +53,12 @@ describe('actions/Report', () => { afterEach(PusherHelper.teardown); it('should store a new report action in Onyx when onyxApiUpdate event is handled via Pusher', () => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; - const REPORT_ID = 1; - let reportActionID; + const REPORT_ID = '1'; + let reportActionID: string; const REPORT_ACTION = { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, actorAccountID: TEST_USER_ACCOUNT_ID, @@ -68,7 +69,7 @@ describe('actions/Report', () => { shouldShow: true, }; - let reportActions; + let reportActions: OnyxEntry; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, callback: (val) => (reportActions = val), @@ -88,7 +89,7 @@ describe('actions/Report', () => { return waitForBatchedUpdates(); }) .then(() => { - const resultAction = _.first(_.values(reportActions)); + const resultAction: OnyxEntry = Object.values(reportActions ?? [])[0]; reportActionID = resultAction.reportActionID; expect(resultAction.message).toEqual(REPORT_ACTION.message); @@ -125,12 +126,12 @@ describe('actions/Report', () => { }) .then(() => { // Verify there is only one action and our optimistic comment has been removed - expect(_.size(reportActions)).toBe(1); + expect(Object.keys(reportActions ?? {}).length).toBe(1); - const resultAction = reportActions[reportActionID]; + const resultAction = reportActions?.[reportActionID]; // Verify that our action is no longer in the loading state - expect(resultAction.pendingAction).toBeUndefined(); + expect(resultAction?.pendingAction).toBeUndefined(); }); }); @@ -139,10 +140,10 @@ describe('actions/Report', () => { const TEST_USER_LOGIN = 'test@test.com'; const REPORT_ID = '1'; - let reportIsPinned; + let reportIsPinned: boolean; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, - callback: (val) => (reportIsPinned = lodashGet(val, 'isPinned')), + callback: (val) => (reportIsPinned = val?.isPinned ?? false), }); // Set up Onyx with some test user data @@ -167,7 +168,7 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; // WHEN we add enough logs to send a packet for (let i = 0; i <= LOGGER_MAX_LOG_LINES; i++) { @@ -186,27 +187,27 @@ describe('actions/Report', () => { .then(() => { // THEN only ONE call to AddComment will happen const URL_ARGUMENT_INDEX = 0; - const addCommentCalls = _.filter(global.fetch.mock.calls, (callArguments) => callArguments[URL_ARGUMENT_INDEX].includes('AddComment')); + const addCommentCalls = (global.fetch as jest.Mock).mock.calls.filter((callArguments) => callArguments[URL_ARGUMENT_INDEX].includes('AddComment')); expect(addCommentCalls.length).toBe(1); }); }); it('should be updated correctly when new comments are added, deleted or marked as unread', () => { jest.useFakeTimers(); - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; const REPORT_ID = '1'; - let report; - let reportActionCreatedDate; - let currentTime; + let report: OnyxEntry; + let reportActionCreatedDate: string; + let currentTime: string; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, callback: (val) => (report = val), }); - let reportActions; + let reportActions: OnyxTypes.ReportActions; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, - callback: (val) => (reportActions = val), + callback: (val) => (reportActions = val ?? {}), }); const USER_1_LOGIN = 'user@test.com'; @@ -276,7 +277,7 @@ describe('actions/Report', () => { .then(() => { // The report will be read expect(ReportUtils.isUnread(report)).toBe(false); - expect(utcToZonedTime(report.lastReadTime, UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); + expect(utcToZonedTime(report?.lastReadTime ?? '', UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); // And no longer show the green dot for unread mentions in the LHN expect(ReportUtils.isUnreadWithMention(report)).toBe(false); @@ -290,7 +291,7 @@ describe('actions/Report', () => { // Then the report will be unread and show the green dot for unread mentions in LHN expect(ReportUtils.isUnread(report)).toBe(true); expect(ReportUtils.isUnreadWithMention(report)).toBe(true); - expect(report.lastReadTime).toBe(DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1)); + expect(report?.lastReadTime).toBe(DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1)); // When a new comment is added by the current user jest.advanceTimersByTime(10); @@ -302,8 +303,8 @@ describe('actions/Report', () => { // The report will be read, the green dot for unread mentions will go away, and the lastReadTime updated expect(ReportUtils.isUnread(report)).toBe(false); expect(ReportUtils.isUnreadWithMention(report)).toBe(false); - expect(utcToZonedTime(report.lastReadTime, UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); - expect(report.lastMessageText).toBe('Current User Comment 1'); + expect(utcToZonedTime(report?.lastReadTime ?? '', UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); + expect(report?.lastMessageText).toBe('Current User Comment 1'); // When another comment is added by the current user jest.advanceTimersByTime(10); @@ -314,8 +315,8 @@ describe('actions/Report', () => { .then(() => { // The report will be read and the lastReadTime updated expect(ReportUtils.isUnread(report)).toBe(false); - expect(utcToZonedTime(report.lastReadTime, UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); - expect(report.lastMessageText).toBe('Current User Comment 2'); + expect(utcToZonedTime(report?.lastReadTime ?? '', UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); + expect(report?.lastMessageText).toBe('Current User Comment 2'); // When another comment is added by the current user jest.advanceTimersByTime(10); @@ -326,8 +327,8 @@ describe('actions/Report', () => { .then(() => { // The report will be read and the lastReadTime updated expect(ReportUtils.isUnread(report)).toBe(false); - expect(utcToZonedTime(report.lastReadTime, UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); - expect(report.lastMessageText).toBe('Current User Comment 3'); + expect(utcToZonedTime(report?.lastReadTime ?? '', UTC).getTime()).toBeGreaterThanOrEqual(utcToZonedTime(currentTime, UTC).getTime()); + expect(report?.lastMessageText).toBe('Current User Comment 3'); const USER_1_BASE_ACTION = { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, @@ -350,12 +351,14 @@ describe('actions/Report', () => { created: DateUtils.getDBTime(Date.now() - 2), reportActionID: '200', }, + 300: { ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 2', text: 'Current User Comment 2'}], created: DateUtils.getDBTime(Date.now() - 1), reportActionID: '300', }, + 400: { ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 3', text: 'Current User Comment 3'}], @@ -394,7 +397,7 @@ describe('actions/Report', () => { }) .then(() => { // Then no change will occur - expect(report.lastReadTime).toBe(reportActionCreatedDate); + expect(report?.lastReadTime).toBe(reportActionCreatedDate); expect(ReportUtils.isUnread(report)).toBe(false); // When the user manually marks a message as "unread" @@ -404,7 +407,7 @@ describe('actions/Report', () => { .then(() => { // Then we should expect the report to be to be unread expect(ReportUtils.isUnread(report)).toBe(true); - expect(report.lastReadTime).toBe(DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1)); + expect(report?.lastReadTime).toBe(DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1)); // If the user deletes the last comment after the lastReadTime the lastMessageText will reflect the new last comment Report.deleteReportComment(REPORT_ID, {...reportActions[400]}); @@ -412,7 +415,7 @@ describe('actions/Report', () => { }) .then(() => { expect(ReportUtils.isUnread(report)).toBe(false); - expect(report.lastMessageText).toBe('Current User Comment 2'); + expect(report?.lastMessageText).toBe('Current User Comment 2'); }); waitForBatchedUpdates(); // flushing onyx.set as it will be batched return setPromise; @@ -424,7 +427,7 @@ describe('actions/Report', () => { * already in the comment and the user deleted it on purpose. */ - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; // User edits comment to add link // We should generate link @@ -536,11 +539,11 @@ describe('actions/Report', () => { }); it('should properly toggle reactions on a message', () => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; - const REPORT_ID = 1; + const REPORT_ID = '1'; const EMOJI_CODE = '👍'; const EMOJI_SKIN_TONE = 2; const EMOJI_NAME = '+1'; @@ -550,20 +553,20 @@ describe('actions/Report', () => { types: ['👍🏿', '👍🏾', '👍🏽', '👍🏼', '👍🏻'], }; - let reportActions; + let reportActions: OnyxTypes.ReportActions; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, - callback: (val) => (reportActions = val), + callback: (val) => (reportActions = val ?? {}), }); - const reportActionsReactions = {}; + const reportActionsReactions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS, callback: (val, key) => { - reportActionsReactions[key] = val; + reportActionsReactions[key] = val ?? {}; }, }); - let reportAction; - let reportActionID; + let reportAction: OnyxTypes.ReportAction; + let reportActionID: string; // Set up Onyx with some test user data return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) @@ -579,15 +582,15 @@ describe('actions/Report', () => { return waitForBatchedUpdates(); }) .then(() => { - reportAction = _.first(_.values(reportActions)); + reportAction = Object.values(reportActions)[0]; reportActionID = reportAction.reportActionID; // Add a reaction to the comment - Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI); + Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0]); return waitForBatchedUpdates(); }) .then(() => { - reportAction = _.first(_.values(reportActions)); + reportAction = Object.values(reportActions)[0]; // Expect the reaction to exist in the reportActionsReactions collection expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); @@ -597,8 +600,8 @@ describe('actions/Report', () => { expect(reportActionReaction).toHaveProperty(EMOJI.name); // Expect the emoji to have the user accountID - const reportActionReactionEmoji = reportActionReaction[EMOJI.name]; - expect(reportActionReactionEmoji.users).toHaveProperty(`${TEST_USER_ACCOUNT_ID}`); + const reportActionReactionEmoji = reportActionReaction?.[EMOJI.name]; + expect(reportActionReactionEmoji?.users).toHaveProperty(`${TEST_USER_ACCOUNT_ID}`); // Now we remove the reaction Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction); @@ -608,23 +611,23 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); + expect(reportActionReaction?.[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }) .then(() => { - reportAction = _.first(_.values(reportActions)); + reportAction = Object.values(reportActions)[0]; // Add the same reaction to the same report action with a different skintone - Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI); + Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0]); return waitForBatchedUpdates() .then(() => { - reportAction = _.first(_.values(reportActions)); + reportAction = Object.values(reportActions)[0]; const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, EMOJI_SKIN_TONE); return waitForBatchedUpdates(); }) .then(() => { - reportAction = _.first(_.values(reportActions)); + reportAction = Object.values(reportActions)[0]; // Expect the reaction to exist in the reportActionsReactions collection expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); @@ -634,11 +637,11 @@ describe('actions/Report', () => { expect(reportActionReaction).toHaveProperty(EMOJI.name); // Expect the emoji to have the user accountID - const reportActionReactionEmoji = reportActionReaction[EMOJI.name]; - expect(reportActionReactionEmoji.users).toHaveProperty(`${TEST_USER_ACCOUNT_ID}`); + const reportActionReactionEmoji = reportActionReaction?.[EMOJI.name]; + expect(reportActionReactionEmoji?.users).toHaveProperty(`${TEST_USER_ACCOUNT_ID}`); // Expect two different skintone reactions - const reportActionReactionEmojiUserSkinTones = reportActionReactionEmoji.users[TEST_USER_ACCOUNT_ID].skinTones; + const reportActionReactionEmojiUserSkinTones = reportActionReactionEmoji?.users[TEST_USER_ACCOUNT_ID].skinTones; expect(reportActionReactionEmojiUserSkinTones).toHaveProperty('-1'); expect(reportActionReactionEmojiUserSkinTones).toHaveProperty('2'); @@ -650,17 +653,17 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); + expect(reportActionReaction?.[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }); }); }); it("shouldn't add the same reaction twice when changing preferred skin color and reaction doesn't support skin colors", () => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; - const REPORT_ID = 1; + const REPORT_ID = '1'; const EMOJI_CODE = '😄'; const EMOJI_NAME = 'smile'; const EMOJI = { @@ -668,20 +671,20 @@ describe('actions/Report', () => { name: EMOJI_NAME, }; - let reportActions; + let reportActions: OnyxTypes.ReportActions = {}; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, - callback: (val) => (reportActions = val), + callback: (val) => (reportActions = val ?? {}), }); - const reportActionsReactions = {}; + const reportActionsReactions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS, callback: (val, key) => { - reportActionsReactions[key] = val; + reportActionsReactions[key] = val ?? {}; }, }); - let resultAction; + let resultAction: OnyxTypes.ReportAction; // Set up Onyx with some test user data return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) @@ -697,14 +700,14 @@ describe('actions/Report', () => { return waitForBatchedUpdates(); }) .then(() => { - resultAction = _.first(_.values(reportActions)); + resultAction = Object.values(reportActions)[0]; // Add a reaction to the comment Report.toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, {}); return waitForBatchedUpdates(); }) .then(() => { - resultAction = _.first(_.values(reportActions)); + resultAction = Object.values(reportActions)[0]; // Now we toggle the reaction while the skin tone has changed. // As the emoji doesn't support skin tones, the emoji @@ -717,7 +720,7 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction.reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction.reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); + expect(reportActionReaction?.[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }); }); }); diff --git a/tests/unit/APITest.js b/tests/unit/APITest.ts similarity index 87% rename from tests/unit/APITest.js rename to tests/unit/APITest.ts index 30c935c48571..9c94730fb4cc 100644 --- a/tests/unit/APITest.js +++ b/tests/unit/APITest.ts @@ -1,5 +1,6 @@ -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +// import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import reactNativeOnyxMock from '../../__mocks__/react-native-onyx'; import CONST from '../../src/CONST'; import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; import * as API from '../../src/libs/API'; @@ -14,16 +15,26 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; +const Onyx = reactNativeOnyxMock; + jest.mock('../../src/libs/Log'); Onyx.init({ keys: ONYXKEYS, }); +type Response = { + ok?: boolean; + status?: ValueOf | ValueOf; + jsonCode?: ValueOf; + title?: ValueOf; + type?: ValueOf; +}; + const originalXHR = HttpUtils.xhr; beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; HttpUtils.xhr = originalXHR; MainQueue.clear(); HttpUtils.cancelPendingRequests(); @@ -53,8 +64,11 @@ describe('APITests', () => { return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Writes and Reads are called + // @ts-expect-error - mocking the parameter API.write('mock command', {param1: 'value1'}); + // @ts-expect-error - mocking the parameter API.read('mock command', {param2: 'value2'}); + // @ts-expect-error - mocking the parameter API.write('mock command', {param3: 'value3'}); return waitForBatchedUpdates(); }) @@ -89,7 +103,9 @@ describe('APITests', () => { }) .then(() => { // When API Write commands are made + // @ts-expect-error - mocking the parameter API.write('mock command', {param1: 'value1'}); + // @ts-expect-error - mocking the parameter API.write('mock command', {param2: 'value2'}); return waitForBatchedUpdates(); }) @@ -120,8 +136,11 @@ describe('APITests', () => { test('Write request should not be cleared until a backend response occurs', () => { // We're setting up xhr handler that will resolve calls programmatically - const xhrCalls = []; - const promises = []; + const xhrCalls: Array<{ + resolve: (value: Response | PromiseLike) => void; + reject: (value: unknown) => void; + }> = []; + const promises: Array> = []; jest.spyOn(HttpUtils, 'xhr').mockImplementation(() => { promises.push( @@ -130,7 +149,7 @@ describe('APITests', () => { }), ); - return _.last(promises); + return promises.slice(-1)[0]; }); // Given we have some requests made while we're offline @@ -138,7 +157,9 @@ describe('APITests', () => { Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Write commands are made + // @ts-expect-error - mocking the parameter API.write('mock command', {param1: 'value1'}); + // @ts-expect-error - mocking the parameter API.write('mock command', {param2: 'value2'}); return waitForBatchedUpdates(); }) @@ -148,14 +169,14 @@ describe('APITests', () => { .then(waitForBatchedUpdates) .then(() => { // Then requests should remain persisted until the xhr call is resolved - expect(_.size(PersistedRequests.getAll())).toEqual(2); + expect(PersistedRequests.getAll().length).toEqual(2); xhrCalls[0].resolve({jsonCode: CONST.JSON_CODE.SUCCESS}); return waitForBatchedUpdates(); }) .then(waitForBatchedUpdates) .then(() => { - expect(_.size(PersistedRequests.getAll())).toEqual(1); + expect(PersistedRequests.getAll().length).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param2: 'value2'})})]); // When a request fails it should be retried @@ -163,7 +184,7 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) .then(() => { - expect(_.size(PersistedRequests.getAll())).toEqual(1); + expect(PersistedRequests.getAll().length).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param2: 'value2'})})]); // We need to advance past the request throttle back off timer because the request won't be retried until then @@ -177,32 +198,30 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) .then(() => { - expect(_.size(PersistedRequests.getAll())).toEqual(0); + expect(PersistedRequests.getAll().length).toEqual(0); }) ); }); // Given a retry response create a mock and run some expectations for retrying requests - const retryExpectations = (retryResponse) => { - let successfulResponse = { + + const retryExpectations = (Response: Response) => { + const successfulResponse = { ok: true, jsonCode: CONST.JSON_CODE.SUCCESS, - }; - - // We have to mock response.json() too - successfulResponse = { - ...successfulResponse, + // We have to mock response.json() too json: () => Promise.resolve(successfulResponse), }; // Given a mock where a retry response is returned twice before a successful response - global.fetch = jest.fn().mockResolvedValueOnce(retryResponse).mockResolvedValueOnce(retryResponse).mockResolvedValueOnce(successfulResponse); + global.fetch = jest.fn().mockResolvedValueOnce(Response).mockResolvedValueOnce(Response).mockResolvedValueOnce(successfulResponse); // Given we have a request made while we're offline return ( Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Write commands are made + // @ts-expect-error - mocking the parameter API.write('mock command', {param1: 'value1'}); return waitForNetworkPromises(); }) @@ -215,7 +234,7 @@ describe('APITests', () => { expect(global.fetch).toHaveBeenCalledTimes(1); // And we still have 1 persisted request since it failed - expect(_.size(PersistedRequests.getAll())).toEqual(1); + expect(PersistedRequests.getAll().length).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param1: 'value1'})})]); // We let the SequentialQueue process again after its wait time @@ -228,7 +247,7 @@ describe('APITests', () => { expect(global.fetch).toHaveBeenCalledTimes(2); // And we still have 1 persisted request since it failed - expect(_.size(PersistedRequests.getAll())).toEqual(1); + expect(PersistedRequests.getAll().length).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param1: 'value1'})})]); // We let the SequentialQueue process again after its wait time @@ -241,7 +260,7 @@ describe('APITests', () => { expect(global.fetch).toHaveBeenCalledTimes(3); // The request succeeds so the queue is empty - expect(_.size(PersistedRequests.getAll())).toEqual(0); + expect(PersistedRequests.getAll().length).toEqual(0); }) ); }; @@ -258,7 +277,7 @@ describe('APITests', () => { // Given the response data returned when auth is down const responseData = { ok: true, - status: 200, + status: CONST.JSON_CODE.SUCCESS, jsonCode: CONST.JSON_CODE.EXP_ERROR, title: CONST.ERROR_TITLE.SOCKET, type: CONST.ERROR_TYPE.SOCKET, @@ -289,6 +308,7 @@ describe('APITests', () => { waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})) .then(() => { + // @ts-expect-error - mocking the parameter API.write('Mock', {param1: 'value1'}); return waitForBatchedUpdates(); }) @@ -297,7 +317,7 @@ describe('APITests', () => { .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) .then(waitForBatchedUpdates) .then(() => { - const nonLogCalls = _.filter(xhr.mock.calls, ([commandName]) => commandName !== 'Log'); + const nonLogCalls = xhr.mock.calls.filter(([commandName]) => commandName !== 'Log'); // The request should be retried once and reauthenticate should be called the second time // expect(xhr).toHaveBeenCalledTimes(3); @@ -322,12 +342,19 @@ describe('APITests', () => { }) .then(() => { // When we queue 6 persistable commands and one not persistable + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value1'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value2'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value3'}); + // @ts-expect-error - mocking the parameter API.read('MockCommand', {content: 'not-persisted'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value4'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value5'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value6'}); return waitForBatchedUpdates(); @@ -359,11 +386,17 @@ describe('APITests', () => { }) .then(() => { // When we queue 6 persistable commands + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value1'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value2'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value3'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value4'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value5'}); + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value6'}); return waitForBatchedUpdates(); }) @@ -402,7 +435,14 @@ describe('APITests', () => { }) .then(() => { // When we queue both non-persistable and persistable commands that will trigger reauthentication and go offline at the same time - API.makeRequestWithSideEffects('AuthenticatePusher', {content: 'value1'}); + API.makeRequestWithSideEffects('AuthenticatePusher', { + // eslint-disable-next-line @typescript-eslint/naming-convention + socket_id: 'socket_id', + // eslint-disable-next-line @typescript-eslint/naming-convention + channel_name: 'channel_name', + shouldRetry: false, + forceNetworkRequest: false, + }); Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); expect(NetworkStore.isOffline()).toBe(false); @@ -410,6 +450,7 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) .then(() => { + // @ts-expect-error - mocking the parameter API.write('MockCommand'); expect(PersistedRequests.getAll().length).toBe(1); expect(NetworkStore.isOffline()).toBe(true); @@ -479,6 +520,7 @@ describe('APITests', () => { NetworkStore.resetHasReadRequiredDataFromStorage(); // And queue a Write request while offline + // @ts-expect-error - mocking the parameter API.write('MockCommand', {content: 'value1'}); // Then we should expect the request to get persisted @@ -515,8 +557,11 @@ describe('APITests', () => { expect(NetworkStore.isOffline()).toBe(false); // WHEN we make a request that should be retried, one that should not, and another that should + // @ts-expect-error - mocking the parameter API.write('MockCommandOne'); + // @ts-expect-error - mocking the parameter API.read('MockCommandTwo'); + // @ts-expect-error - mocking the parameter API.write('MockCommandThree'); // THEN the retryable requests should immediately be added to the persisted requests diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.ts similarity index 76% rename from tests/unit/MigrationTest.js rename to tests/unit/MigrationTest.ts index 65ab921ac9e1..6d18ec2f0c68 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; import Log from '../../src/libs/Log'; import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; @@ -7,13 +8,13 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; jest.mock('../../src/libs/getPlatform'); -let LogSpy; +let LogSpy: unknown; describe('Migrations', () => { beforeAll(() => { Onyx.init({keys: ONYXKEYS}); LogSpy = jest.spyOn(Log, 'info'); - Log.serverLoggingCallback = () => {}; + Log.serverLoggingCallback = () => Promise.resolve({requestID: '123'}); return waitForBatchedUpdates(); }); @@ -32,6 +33,7 @@ describe('Migrations', () => { it('Should remove all report actions given that a previousReportActionID does not exist', () => Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + // @ts-expect-error Preset necessary values 1: { reportActionID: '1', }, @@ -51,7 +53,7 @@ describe('Migrations', () => { callback: (allReportActions) => { Onyx.disconnect(connectionID); const expectedReportAction = {}; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); }, }); })); @@ -59,6 +61,7 @@ describe('Migrations', () => { it('Should not remove any report action given that previousReportActionID exists in first valid report action', () => Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + // @ts-expect-error Preset necessary values 1: { reportActionID: '1', previousReportActionID: '0', @@ -87,12 +90,13 @@ describe('Migrations', () => { previousReportActionID: '1', }, }; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); }, }); })); it('Should skip zombie report actions and proceed to remove all reportActions given that a previousReportActionID does not exist', () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, @@ -117,15 +121,16 @@ describe('Migrations', () => { callback: (allReportActions) => { Onyx.disconnect(connectionID); const expectedReportAction = {}; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); }, }); })); it('Should skip zombie report actions and should not remove any report action given that previousReportActionID exists in first valid report action', () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, @@ -160,15 +165,16 @@ describe('Migrations', () => { previousReportActionID: '23', }, }; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction1); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction1); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); }, }); })); it('Should skip if no valid reportActions', () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: {}, @@ -184,10 +190,10 @@ describe('Migrations', () => { callback: (allReportActions) => { Onyx.disconnect(connectionID); const expectedReportAction = {}; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeUndefined(); }, }); })); @@ -200,6 +206,7 @@ describe('Migrations', () => { )); it('Should move individual draft to a draft collection of report', () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: 'a', [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]: 'b', @@ -221,16 +228,17 @@ describe('Migrations', () => { 3: 'c', 4: 'd', }; - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]).toBeUndefined(); - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]).toBeUndefined(); - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`]).toMatchObject(expectedReportActionDraft1); - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft2); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]).toBeUndefined(); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]).toBeUndefined(); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`]).toMatchObject(expectedReportActionDraft1); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft2); }, }); })); it('Should skip if nothing to migrate', () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]: null, @@ -246,15 +254,16 @@ describe('Migrations', () => { callback: (allReportActions) => { Onyx.disconnect(connectionID); const expectedReportActionDraft = {}; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]).toBeUndefined(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]).toBeUndefined(); + expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft); }, }); })); it("Shouldn't move empty individual draft to a draft collection of report", () => + // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: '', [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`]: {}, @@ -266,7 +275,7 @@ describe('Migrations', () => { waitForCollectionCallback: true, callback: (allReportActionsDrafts) => { Onyx.disconnect(connectionID); - expect(allReportActionsDrafts[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); + expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); }, }); })); diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.ts similarity index 92% rename from tests/unit/NetworkTest.js rename to tests/unit/NetworkTest.ts index 29f5e344b35a..f8b5b6a7d345 100644 --- a/tests/unit/NetworkTest.js +++ b/tests/unit/NetworkTest.ts @@ -1,5 +1,6 @@ -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import type {Mock} from 'jest-mock'; +import reactNativeOnyxMock from '../../__mocks__/react-native-onyx'; +// import Onyx from 'react-native-onyx'; import CONST from '../../src/CONST'; import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; @@ -15,6 +16,8 @@ import ONYXKEYS from '../../src/ONYXKEYS'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +const Onyx = reactNativeOnyxMock; + jest.mock('../../src/libs/Log'); Onyx.init({ @@ -25,7 +28,7 @@ OnyxUpdateManager(); const originalXHR = HttpUtils.xhr; beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; HttpUtils.xhr = originalXHR; MainQueue.clear(); HttpUtils.cancelPendingRequests(); @@ -50,7 +53,7 @@ describe('NetworkTests', () => { const TEST_USER_LOGIN = 'test@testguy.com'; const TEST_USER_ACCOUNT_ID = 1; - let isOffline; + let isOffline: boolean | null = null; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -67,8 +70,9 @@ describe('NetworkTests', () => { global.fetch = jest.fn().mockRejectedValue(new TypeError(CONST.ERROR.FAILED_TO_FETCH)); const actualXhr = HttpUtils.xhr; - HttpUtils.xhr = jest.fn(); - HttpUtils.xhr + + const mockedXhr = jest.fn(); + mockedXhr .mockImplementationOnce(() => Promise.resolve({ jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED, @@ -100,6 +104,8 @@ describe('NetworkTests', () => { }), ); + HttpUtils.xhr = mockedXhr; + // This should first trigger re-authentication and then a Failed to fetch PersonalDetails.openPersonalDetails(); return waitForBatchedUpdates() @@ -113,8 +119,8 @@ describe('NetworkTests', () => { }) .then(() => { // Then we will eventually have 1 call to OpenPersonalDetailsPage and 1 calls to Authenticate - const callsToOpenPersonalDetails = _.filter(HttpUtils.xhr.mock.calls, ([command]) => command === 'OpenPersonalDetailsPage'); - const callsToAuthenticate = _.filter(HttpUtils.xhr.mock.calls, ([command]) => command === 'Authenticate'); + const callsToOpenPersonalDetails = (HttpUtils.xhr as Mock).mock.calls.filter(([command]) => command === 'OpenPersonalDetailsPage'); + const callsToAuthenticate = (HttpUtils.xhr as Mock).mock.calls.filter(([command]) => command === 'Authenticate'); expect(callsToOpenPersonalDetails.length).toBe(1); expect(callsToAuthenticate.length).toBe(1); @@ -133,8 +139,8 @@ describe('NetworkTests', () => { // When we sign in return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { - HttpUtils.xhr = jest.fn(); - HttpUtils.xhr + const mockedXhr = jest.fn(); + mockedXhr // And mock the first call to openPersonalDetails return with an expired session code .mockImplementationOnce(() => @@ -164,6 +170,8 @@ describe('NetworkTests', () => { }), ); + HttpUtils.xhr = mockedXhr; + // And then make 3 API READ requests in quick succession with an expired authToken and handle the response // It doesn't matter which requests these are really as all the response is mocked we just want to see // that we get re-authenticated @@ -175,8 +183,8 @@ describe('NetworkTests', () => { .then(() => { // We should expect to see the three calls to OpenApp, but only one call to Authenticate. // And we should also see the reconnection callbacks triggered. - const callsToOpenPersonalDetails = _.filter(HttpUtils.xhr.mock.calls, ([command]) => command === 'OpenPersonalDetailsPage'); - const callsToAuthenticate = _.filter(HttpUtils.xhr.mock.calls, ([command]) => command === 'Authenticate'); + const callsToOpenPersonalDetails = (HttpUtils.xhr as Mock).mock.calls.filter(([command]) => command === 'OpenPersonalDetailsPage'); + const callsToAuthenticate = (HttpUtils.xhr as Mock).mock.calls.filter(([command]) => command === 'Authenticate'); expect(callsToOpenPersonalDetails.length).toBe(3); expect(callsToAuthenticate.length).toBe(1); expect(reconnectionCallbacksSpy.mock.calls.length).toBe(3); From 1512c35d5f251ddd20c26a277319ff7b390ce61e Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:54:35 +0100 Subject: [PATCH 122/674] Fix: Workspace - Member and Role can be clicked to select all the members in Members list --- .../SelectionList/BaseSelectionList.tsx | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1c69d00b3910..843c7ee1fc28 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -429,28 +429,31 @@ function BaseSelectionList( ) : ( <> {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - e.preventDefault() : undefined} - > - + - {customListHeader ?? ( - - {translate('workspace.people.selectAll')} - - )} - + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + onMouseDown={shouldPreventDefaultFocusOnSelectRow ? (e) => e.preventDefault() : undefined} + > + + {!customListHeader ? ( + + {translate('workspace.people.selectAll')} + + ) : null} + + {customListHeader} + )} {!headerMessage && !canSelectMultiple && customListHeader} Date: Thu, 29 Feb 2024 15:51:05 -0300 Subject: [PATCH 123/674] Migrate NVPs to their new keys --- src/ONYXKEYS.ts | 24 +++++++----- src/libs/migrateOnyx.ts | 3 +- src/libs/migrations/NVPMigration.ts | 61 +++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/libs/migrations/NVPMigration.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d4a0b8a21d66..d0b73c963ce1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -17,7 +17,7 @@ const ONYXKEYS = { ACCOUNT_MANAGER_REPORT_ID: 'accountManagerReportID', /** Boolean flag only true when first set */ - NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'isFirstTimeNewExpensifyUser', + NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser', /** Holds an array of client IDs which is used for multi-tabs on web in order to know * which tab is the leader, and which ones are the followers */ @@ -109,22 +109,25 @@ const ONYXKEYS = { NVP_PRIORITY_MODE: 'nvp_priorityMode', /** Contains the users's block expiration (if they have one) */ - NVP_BLOCKED_FROM_CONCIERGE: 'private_blockedFromConcierge', + NVP_BLOCKED_FROM_CONCIERGE: 'nvp_private_blockedFromConcierge', /** A unique identifier that each user has that's used to send notifications */ - NVP_PRIVATE_PUSH_NOTIFICATION_ID: 'private_pushNotificationID', + NVP_PRIVATE_PUSH_NOTIFICATION_ID: 'nvp_private_pushNotificationID', /** The NVP with the last payment method used per policy */ - NVP_LAST_PAYMENT_METHOD: 'nvp_lastPaymentMethod', + NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod', /** This NVP holds to most recent waypoints that a person has used when creating a distance request */ NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints', /** This NVP will be `true` if the user has ever dismissed the engagement modal on either OldDot or NewDot. If it becomes true it should stay true forever. */ - NVP_HAS_DISMISSED_IDLE_PANEL: 'hasDismissedIdlePanel', + NVP_HAS_DISMISSED_IDLE_PANEL: 'nvp_hasDismissedIdlePanel', /** This NVP contains the choice that the user made on the engagement modal */ - NVP_INTRO_SELECTED: 'introSelected', + NVP_INTRO_SELECTED: 'nvp_introSelected', + + /** This NVP contains the active policyID */ + NVP_ACTIVE_POLICY_ID: 'nvp_expensify_activePolicyID', /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -146,7 +149,7 @@ const ONYXKEYS = { ONFIDO_APPLICANT_ID: 'onfidoApplicantID', /** Indicates which locale should be used */ - NVP_PREFERRED_LOCALE: 'preferredLocale', + NVP_PREFERRED_LOCALE: 'nvp_preferredLocale', /** User's Expensify Wallet */ USER_WALLET: 'userWallet', @@ -170,7 +173,7 @@ const ONYXKEYS = { CARD_LIST: 'cardList', /** Whether the user has tried focus mode yet */ - NVP_TRY_FOCUS_MODE: 'tryFocusMode', + NVP_TRY_FOCUS_MODE: 'nvp_tryFocusMode', /** Whether the user has been shown the hold educational interstitial yet */ NVP_HOLD_USE_EXPLAINED: 'holdUseExplained', @@ -188,10 +191,10 @@ const ONYXKEYS = { REIMBURSEMENT_ACCOUNT: 'reimbursementAccount', /** Store preferred skintone for emoji */ - PREFERRED_EMOJI_SKIN_TONE: 'preferredEmojiSkinTone', + PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', /** Store frequently used emojis for this user */ - FREQUENTLY_USED_EMOJIS: 'frequentlyUsedEmojis', + FREQUENTLY_USED_EMOJIS: 'expensify_frequentlyUsedEmojis', /** Stores Workspace ID that will be tied to reimbursement account during setup */ REIMBURSEMENT_ACCOUNT_WORKSPACE_ID: 'reimbursementAccountWorkspaceID', @@ -568,6 +571,7 @@ type OnyxValuesMapping = { [ONYXKEYS.LOGS]: Record; [ONYXKEYS.SHOULD_STORE_LOGS]: boolean; [ONYXKEYS.CACHED_PDF_PATHS]: Record; + [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: string; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/libs/migrateOnyx.ts b/src/libs/migrateOnyx.ts index 1202275067a5..5ce899cdd316 100644 --- a/src/libs/migrateOnyx.ts +++ b/src/libs/migrateOnyx.ts @@ -1,5 +1,6 @@ import Log from './Log'; import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; +import NVPMigration from './migrations/NVPMigration'; import RemoveEmptyReportActionsDrafts from './migrations/RemoveEmptyReportActionsDrafts'; import RenameReceiptFilename from './migrations/RenameReceiptFilename'; import TransactionBackupsToCollection from './migrations/TransactionBackupsToCollection'; @@ -10,7 +11,7 @@ export default function (): Promise { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order - const migrationPromises = [RenameReceiptFilename, KeyReportActionsDraftByReportActionID, TransactionBackupsToCollection, RemoveEmptyReportActionsDrafts]; + const migrationPromises = [RenameReceiptFilename, KeyReportActionsDraftByReportActionID, TransactionBackupsToCollection, RemoveEmptyReportActionsDrafts, NVPMigration]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the // previous promise to finish before moving onto the next one. diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts new file mode 100644 index 000000000000..1c3465a492a9 --- /dev/null +++ b/src/libs/migrations/NVPMigration.ts @@ -0,0 +1,61 @@ +import after from 'lodash/after'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; + +const migrations = { + // eslint-disable-next-line @typescript-eslint/naming-convention + nvp_lastPaymentMethod: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, + isFirstTimeNewExpensifyUser: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, + preferredLocale: ONYXKEYS.NVP_PREFERRED_LOCALE, + preferredEmojiSkinTone: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + frequentlyUsedEmojis: ONYXKEYS.FREQUENTLY_USED_EMOJIS, + // eslint-disable-next-line @typescript-eslint/naming-convention + private_blockedFromConcierge: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, + // eslint-disable-next-line @typescript-eslint/naming-convention + private_pushNotificationID: ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID, + tryFocusMode: ONYXKEYS.NVP_TRY_FOCUS_MODE, + introSelected: ONYXKEYS.NVP_INTRO_SELECTED, + hasDismissedIdlePanel: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, +}; + +// This migration changes the keys of all the NVP related keys so that they are standardized +export default function () { + return new Promise((resolve) => { + // It's 1 more because activePolicyID is not in the migrations object above as it is nested inside an object + const resolveWhenDone = after(Object.entries(migrations).length + 1, () => resolve()); + + for (const [oldKey, newKey] of Object.entries(migrations)) { + const connectionID = Onyx.connect({ + // @ts-expect-error oldKey is a variable + key: oldKey, + callback: (value) => { + Onyx.disconnect(connectionID); + if (value !== null) { + // @ts-expect-error These keys are variables, so we can't check the type + Onyx.multiSet({ + [newKey]: value, + [oldKey]: null, + }); + } + resolveWhenDone(); + }, + }); + } + const connectionID = Onyx.connect({ + key: ONYXKEYS.ACCOUNT, + callback: (value) => { + Onyx.disconnect(connectionID); + if (value?.activePolicyID) { + const activePolicyID = value.activePolicyID; + const newValue = value; + delete newValue.activePolicyID; + Onyx.multiSet({ + [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: activePolicyID, + [ONYXKEYS.ACCOUNT]: newValue, + }); + } + resolveWhenDone(); + }, + }); + }); +} From c4205502e9c039f5c6a4825052a51b18c1100150 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 29 Feb 2024 20:39:56 +0100 Subject: [PATCH 124/674] Fix: Category - Checkbox is clickable outside near the right of checkbox --- src/components/SelectionList/BaseListItem.tsx | 2 +- src/styles/utils/index.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 98b1999625ee..5ea451c12f11 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -79,7 +79,7 @@ function BaseListItem({ accessibilityLabel={item.text} role={CONST.ROLE.BUTTON} onPress={handleCheckboxPress} - style={StyleUtils.getCheckboxPressableStyle()} + style={[StyleUtils.getCheckboxPressableStyle(), styles.mr3]} > {item.isSelected && ( diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 72719e4795c4..5470d976eafe 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1481,7 +1481,6 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ getFullscreenCenteredContentStyles: () => [StyleSheet.absoluteFill, styles.justifyContentCenter, styles.alignItemsCenter], getMultiselectListStyles: (isSelected: boolean, isDisabled: boolean): ViewStyle => ({ - ...styles.mr3, ...(isSelected && styles.checkedContainer), ...(isSelected && styles.borderColorFocus), ...(isDisabled && styles.cursorDisabled), From 9ce6a3cf5ff6a901889a83a2e3d4e0a0149f572b Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 29 Feb 2024 17:03:22 -0300 Subject: [PATCH 125/674] Remove nvp props from inside account --- src/ONYXKEYS.ts | 4 ++++ src/components/ReferralProgramCTA.tsx | 5 ++--- src/pages/NewChatPage.tsx | 5 ++--- ...poraryForRefactorRequestParticipantsSelector.js | 3 +-- .../MoneyRequestParticipantsSelector.js | 3 +-- src/pages/workspace/WorkspaceNewRoomPage.tsx | 8 +++----- src/types/onyx/Account.ts | 14 +------------- src/types/onyx/DismissedReferralBanners.ts | 11 +++++++++++ src/types/onyx/index.ts | 2 ++ 9 files changed, 27 insertions(+), 28 deletions(-) create mode 100644 src/types/onyx/DismissedReferralBanners.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d0b73c963ce1..304c091a48a2 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -129,6 +129,9 @@ const ONYXKEYS = { /** This NVP contains the active policyID */ NVP_ACTIVE_POLICY_ID: 'nvp_expensify_activePolicyID', + /** This NVP contains the referral banners the user dismissed */ + NVP_DISMISSED_REFERRAL_BANNERS: 'dismissedReferralBanners', + /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -572,6 +575,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_STORE_LOGS]: boolean; [ONYXKEYS.CACHED_PDF_PATHS]: Record; [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: string; + [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index 6db37ce1320a..40c3c8683578 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; import Navigation from '@src/libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {DismissedReferralBanners} from '@src/types/onyx/Account'; +import type DismissedReferralBanners from '@src/types/onyx/DismissedReferralBanners'; import Icon from './Icon'; import {Close} from './Icon/Expensicons'; import {PressableWithoutFeedback} from './Pressable'; @@ -82,7 +82,6 @@ function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: Ref export default withOnyx({ dismissedReferralBanners: { - key: ONYXKEYS.ACCOUNT, - selector: (data) => data?.dismissedReferralBanners ?? {}, + key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS, }, })(ReferralProgramCTA); diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 72393e89ae1a..a1de24da12d4 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -22,7 +22,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {DismissedReferralBanners} from '@src/types/onyx/Account'; +import type DismissedReferralBanners from '@src/types/onyx/DismissedReferralBanners'; type NewChatPageWithOnyxProps = { /** All reports shared with the user */ @@ -287,8 +287,7 @@ NewChatPage.displayName = 'NewChatPage'; export default withOnyx({ dismissedReferralBanners: { - key: ONYXKEYS.ACCOUNT, - selector: (data) => data?.dismissedReferralBanners ?? {}, + key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 2865316b7fd5..1c31806086bd 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -360,8 +360,7 @@ MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTempora export default withOnyx({ dismissedReferralBanners: { - key: ONYXKEYS.ACCOUNT, - selector: (data) => data.dismissedReferralBanners || {}, + key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 3fde970327d7..85feafc76fe8 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -371,8 +371,7 @@ MoneyRequestParticipantsSelector.defaultProps = defaultProps; export default withOnyx({ dismissedReferralBanners: { - key: ONYXKEYS.ACCOUNT, - selector: (data) => data.dismissedReferralBanners || {}, + key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index b9236b0e7252..e4d319313136 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -35,7 +35,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {NewRoomForm} from '@src/types/form/NewRoomForm'; import INPUT_IDS from '@src/types/form/NewRoomForm'; -import type {Account, Policy, Report as ReportType, Session} from '@src/types/onyx'; +import type {Policy, Report as ReportType, Session} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -53,7 +53,7 @@ type WorkspaceNewRoomPageOnyxProps = { session: OnyxEntry; /** policyID for main workspace */ - activePolicyID: OnyxEntry['activePolicyID']>; + activePolicyID: OnyxEntry>; }; type WorkspaceNewRoomPageProps = WorkspaceNewRoomPageOnyxProps; @@ -144,7 +144,6 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli return; } Navigation.dismissModal(newRoomReportID); - // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to update on changing the form State }, [isLoading, errorFields]); useEffect(() => { @@ -342,8 +341,7 @@ export default withOnyx account?.activePolicyID ?? null, + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, initialValue: null, }, })(WorkspaceNewRoomPage); diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 534a8ad0f2bc..98ce460a7669 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -4,14 +4,6 @@ import type * as OnyxCommon from './OnyxCommon'; type TwoFactorAuthStep = ValueOf | ''; -type DismissedReferralBanners = { - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]?: boolean; - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]?: boolean; - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]?: boolean; - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]?: boolean; - [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]?: boolean; -}; - type Account = { /** Whether SAML is enabled for the current account */ isSAMLEnabled?: boolean; @@ -64,15 +56,11 @@ type Account = { /** Whether a sign is loading */ isLoading?: boolean; - /** The active policy ID. Initiating a SmartScan will create an expense on this policy by default. */ - activePolicyID?: string; - errors?: OnyxCommon.Errors | null; success?: string; codesAreCopied?: boolean; twoFactorAuthStep?: TwoFactorAuthStep; - dismissedReferralBanners?: DismissedReferralBanners; }; export default Account; -export type {TwoFactorAuthStep, DismissedReferralBanners}; +export type {TwoFactorAuthStep}; diff --git a/src/types/onyx/DismissedReferralBanners.ts b/src/types/onyx/DismissedReferralBanners.ts new file mode 100644 index 000000000000..43fa6472a6ae --- /dev/null +++ b/src/types/onyx/DismissedReferralBanners.ts @@ -0,0 +1,11 @@ +import type CONST from '@src/CONST'; + +type DismissedReferralBanners = { + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]?: boolean; +}; + +export default DismissedReferralBanners; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6846fc302639..cc9c3cd44831 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -11,6 +11,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; +import type DismissedReferralBanners from './DismissedReferralBanners'; import type Download from './Download'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; @@ -85,6 +86,7 @@ export type { Currency, CurrencyList, CustomStatusDraft, + DismissedReferralBanners, Download, FrequentlyUsedEmoji, Fund, From 55f816dd080f2aaf5be2c3dfd90c9ffcb6ebfabd Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 29 Feb 2024 17:10:46 -0300 Subject: [PATCH 126/674] Fix usage of referral banners in account --- src/libs/actions/User.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 5d089ed6e393..ec5991346872 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -961,11 +961,9 @@ function dismissReferralBanner(type: ValueOf Date: Thu, 29 Feb 2024 19:49:19 -0300 Subject: [PATCH 127/674] Suppress some errors --- src/libs/migrations/NVPMigration.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index 1c3465a492a9..22bdd4a03615 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -45,9 +45,12 @@ export default function () { key: ONYXKEYS.ACCOUNT, callback: (value) => { Onyx.disconnect(connectionID); + // @ts-expect-error we are removing this property, so it is not in the type anymore if (value?.activePolicyID) { + // @ts-expect-error we are removing this property, so it is not in the type anymore const activePolicyID = value.activePolicyID; - const newValue = value; + const newValue = {...value}; + // @ts-expect-error we are removing this property, so it is not in the type anymore delete newValue.activePolicyID; Onyx.multiSet({ [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: activePolicyID, From b17b23cb8306b8820f8d6ab547afb207ec2ab0f3 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 29 Feb 2024 20:05:31 -0300 Subject: [PATCH 128/674] Readd suppression --- src/pages/workspace/WorkspaceNewRoomPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index e4d319313136..9771f8bccae2 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -144,6 +144,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli return; } Navigation.dismissModal(newRoomReportID); + // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to update on changing the form State }, [isLoading, errorFields]); useEffect(() => { From 57370c83d1ea54e9a7f4753bcdd47f7fc42e58de Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 1 Mar 2024 10:10:16 +0100 Subject: [PATCH 129/674] Migrate web proxy to TS --- config/{proxyConfig.js => proxyConfig.ts} | 2 +- package.json | 2 +- web/{proxy.js => proxy.ts} | 22 +++++++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) rename config/{proxyConfig.js => proxyConfig.ts} (92%) rename web/{proxy.js => proxy.ts} (78%) diff --git a/config/proxyConfig.js b/config/proxyConfig.ts similarity index 92% rename from config/proxyConfig.js rename to config/proxyConfig.ts index fa09c436461f..6a74d145df85 100644 --- a/config/proxyConfig.js +++ b/config/proxyConfig.ts @@ -3,7 +3,7 @@ * We only specify for staging URLs as API requests are sent to the production * servers by default. */ -module.exports = { +export default { STAGING: '/staging/', STAGING_SECURE: '/staging-secure/', }; diff --git a/package.json b/package.json index e3c23d4538d3..e757f000f5be 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ipad-sm": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (11-inch) (4th generation)\\\" --mode=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", "start": "npx react-native start", "web": "scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", - "web-proxy": "ts-node web/proxy.js", + "web-proxy": "ts-node web/proxy.ts", "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.js", "build": "webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "build-staging": "webpack --config config/webpack/webpack.common.js --env envFile=.env.staging", diff --git a/web/proxy.js b/web/proxy.ts similarity index 78% rename from web/proxy.js rename to web/proxy.ts index 0d82ae60b678..130f5a67e51a 100644 --- a/web/proxy.js +++ b/web/proxy.ts @@ -1,7 +1,10 @@ -const http = require('http'); -const https = require('https'); -const proxyConfig = require('../config/proxyConfig'); -require('dotenv').config(); +import dotenv from 'dotenv'; +import http from 'http'; +import type {IncomingMessage, ServerResponse} from 'http'; +import https from 'https'; +import proxyConfig from '../config/proxyConfig'; + +dotenv.config(); if (process.env.USE_WEB_PROXY === 'false') { process.stdout.write('Skipping proxy as USE_WEB_PROXY was set to false.\n'); @@ -20,7 +23,7 @@ console.log(`Creating proxy with host: ${host} for production API and ${stagingH * possible to work on the app within a limited development * environment that has no local API. */ -const server = http.createServer((request, response) => { +const server = http.createServer((request: IncomingMessage, response: ServerResponse) => { let hostname = host; let requestPath = request.url; @@ -37,10 +40,10 @@ const server = http.createServer((request, response) => { * /receipts/w_... => request sent to production server * /staging/chat-attachments/46545... => request sent to staging server */ - if (request.url.startsWith(proxyConfig.STAGING_SECURE)) { + if (request.url?.startsWith(proxyConfig.STAGING_SECURE)) { hostname = stagingSecureHost; requestPath = request.url.replace(proxyConfig.STAGING_SECURE, '/'); - } else if (request.url.startsWith(proxyConfig.STAGING)) { + } else if (request.url?.startsWith(proxyConfig.STAGING)) { hostname = stagingHost; requestPath = request.url.replace(proxyConfig.STAGING, '/'); } @@ -52,14 +55,15 @@ const server = http.createServer((request, response) => { headers: { ...request.headers, host: hostname, - 'user-agent': request.headers['user-agent'].concat(' Development-NewDot/1.0'), + // eslint-disable-next-line @typescript-eslint/naming-convention + 'user-agent': request.headers['user-agent']?.concat(' Development-NewDot/1.0'), }, port: 443, }); request.pipe(proxyRequest); proxyRequest.on('response', (proxyResponse) => { - response.writeHead(proxyResponse.statusCode, proxyResponse.headers); + response.writeHead(proxyResponse.statusCode ?? 0, proxyResponse.headers); proxyResponse.pipe(response); }); From 3c5b6a272a89cda8d9a17d50008f24a84b4ea91d Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 1 Mar 2024 10:22:24 +0100 Subject: [PATCH 130/674] Fix GH actions --- .github/actions/javascript/bumpVersion/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/actions/javascript/bumpVersion/index.js b/.github/actions/javascript/bumpVersion/index.js index d17760baa91f..8fe84446ba82 100644 --- a/.github/actions/javascript/bumpVersion/index.js +++ b/.github/actions/javascript/bumpVersion/index.js @@ -2657,12 +2657,17 @@ createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) // Coercion. // Extract anything that could conceivably be a part of a valid semver -createToken('COERCE', `${'(^|[^\\d])' + +createToken('COERCEPLAIN', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`) +createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`) +createToken('COERCEFULL', src[t.COERCEPLAIN] + + `(?:${src[t.PRERELEASE]})?` + + `(?:${src[t.BUILD]})?` + `(?:$|[^\\d])`) createToken('COERCERTL', src[t.COERCE], true) +createToken('COERCERTLFULL', src[t.COERCEFULL], true) // Tilde ranges. // Meaning is "reasonably at or greater than" From c19ccde1e07006228b2eb26752c03680e5580564 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Fri, 1 Mar 2024 10:25:34 +0100 Subject: [PATCH 131/674] Revert unnecessary changes --- config/{proxyConfig.ts => proxyConfig.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename config/{proxyConfig.ts => proxyConfig.js} (92%) diff --git a/config/proxyConfig.ts b/config/proxyConfig.js similarity index 92% rename from config/proxyConfig.ts rename to config/proxyConfig.js index 6a74d145df85..fa09c436461f 100644 --- a/config/proxyConfig.ts +++ b/config/proxyConfig.js @@ -3,7 +3,7 @@ * We only specify for staging URLs as API requests are sent to the production * servers by default. */ -export default { +module.exports = { STAGING: '/staging/', STAGING_SECURE: '/staging-secure/', }; From b99c2cf228e15d8e138a23d7b3dae277ed89d8e3 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 1 Mar 2024 17:25:20 +0100 Subject: [PATCH 132/674] [TS migration] Migrate 'Desktop' files --- config/webpack/webpack.desktop.js | 4 +- ...{ELECTRON_EVENTS.js => ELECTRON_EVENTS.ts} | 4 +- desktop/README.md | 4 +- desktop/contextBridge.js | 98 ---------- desktop/contextBridge.ts | 82 ++++++++ desktop/{main.js => main.ts} | 178 ++++++++++-------- desktop/{start.js => start.ts} | 9 +- package.json | 2 +- src/types/modules/electron.d.ts | 9 +- 9 files changed, 195 insertions(+), 195 deletions(-) rename desktop/{ELECTRON_EVENTS.js => ELECTRON_EVENTS.ts} (90%) delete mode 100644 desktop/contextBridge.js create mode 100644 desktop/contextBridge.ts rename desktop/{main.js => main.ts} (82%) rename desktop/{start.js => start.ts} (88%) diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.js index 2612e2b190fa..20ee4a4025df 100644 --- a/config/webpack/webpack.desktop.js +++ b/config/webpack/webpack.desktop.js @@ -28,8 +28,8 @@ module.exports = (env) => { name: 'desktop-main', target: 'electron-main', entry: { - main: './desktop/main.js', - contextBridge: './desktop/contextBridge.js', + main: './desktop/main.ts', + contextBridge: './desktop/contextBridge.ts', }, output: { filename: '[name].js', diff --git a/desktop/ELECTRON_EVENTS.js b/desktop/ELECTRON_EVENTS.ts similarity index 90% rename from desktop/ELECTRON_EVENTS.js rename to desktop/ELECTRON_EVENTS.ts index ee8c0521892e..de0bd655e12c 100644 --- a/desktop/ELECTRON_EVENTS.js +++ b/desktop/ELECTRON_EVENTS.ts @@ -9,6 +9,6 @@ const ELECTRON_EVENTS = { KEYBOARD_SHORTCUTS_PAGE: 'keyboard-shortcuts-page', START_UPDATE: 'start-update', UPDATE_DOWNLOADED: 'update-downloaded', -}; +} as const; -module.exports = ELECTRON_EVENTS; +export default ELECTRON_EVENTS; diff --git a/desktop/README.md b/desktop/README.md index 77abff97a898..4493196b5ed4 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -31,7 +31,7 @@ The New Expensify desktop app is built using [Electron.js](https://www.electronj The desktop app is organized in three pieces: 1. The Electron main process - - Implemented in https://github.com/Expensify/App/blob/main/desktop/main.js. + - Implemented in https://github.com/Expensify/App/blob/main/desktop/main.ts. - This file has access to the full set of Electron and Node.JS APIs. 2. The Electron renderer process - This is the webpack-bundled version of our react-native-web app (except using `index.desktop.js` files instead of `index.website.js`, where applicable) @@ -131,7 +131,7 @@ The root [package.json](../package.json) serves for `devDependencies` and shared The [desktop/package.json](./package.json) serves for desktop (electron-main) specific dependencies We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.js) to bundle our js code Half of the config takes care of packaging root package dependencies - everything related to rendering App in the Electron window. Packaged under `dist/www` -The other half is about bundling the `main.js` script which initializes Electron and renders `www` +The other half is about bundling the `main.ts` script which initializes Electron and renders `www` ## See what is getting packaged in the app If you suspect unnecessary items might be getting packaged you can inspect the package content in `desktop-build/` diff --git a/desktop/contextBridge.js b/desktop/contextBridge.js deleted file mode 100644 index a8b89cdc0b64..000000000000 --- a/desktop/contextBridge.js +++ /dev/null @@ -1,98 +0,0 @@ -const _ = require('underscore'); -const {contextBridge, ipcRenderer} = require('electron'); -const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); - -const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ - ELECTRON_EVENTS.REQUEST_DEVICE_ID, - ELECTRON_EVENTS.REQUEST_FOCUS_APP, - ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, - ELECTRON_EVENTS.REQUEST_VISIBILITY, - ELECTRON_EVENTS.START_UPDATE, - ELECTRON_EVENTS.LOCALE_UPDATED, -]; - -const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; - -const getErrorMessage = (channel) => `Electron context bridge cannot be used with channel '${channel}'`; - -/** - * The following methods will be available in the renderer process under `window.electron`. - */ -contextBridge.exposeInMainWorld('electron', { - /** - * Send data asynchronously from renderer process to main process. - * Note that this is a one-way channel – main will not respond. In order to get a response from main, either: - * - * - Use `sendSync` - * - Or implement `invoke` if you want to maintain asynchronous communication: https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args - * - * @param {String} channel - * @param {*} data - */ - send: (channel, data) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - ipcRenderer.send(channel, data); - }, - - /** - * Send data synchronously from renderer process to main process. Main process may return a result. - * - * @param {String} channel - * @param {*} data - * @returns {*} - */ - sendSync: (channel, data) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - return ipcRenderer.sendSync(channel, data); - }, - - /** - * Execute a function in the main process and return a promise that resolves with its response. - * - * @param {String} channel - * @param {*} args - * @returns {Promise} - */ - invoke: (channel, ...args) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - return ipcRenderer.invoke(channel, ...args); - }, - - /** - * Set up a listener for events emitted from the main process and sent to the renderer process. - * - * @param {String} channel - * @param {Function} func - */ - on: (channel, func) => { - if (!_.contains(WHITELIST_CHANNELS_MAIN_TO_RENDERER, channel)) { - throw new Error(getErrorMessage(channel)); - } - - // Deliberately strip event as it includes `sender` - ipcRenderer.on(channel, (event, ...args) => func(...args)); - }, - - /** - * Remove listeners for a single channel from the main process and sent to the renderer process. - * - * @param {String} channel - * @param {Function} func - */ - removeAllListeners: (channel) => { - if (!_.contains(WHITELIST_CHANNELS_MAIN_TO_RENDERER, channel)) { - throw new Error(getErrorMessage(channel)); - } - - ipcRenderer.removeAllListeners(channel); - }, -}); diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts new file mode 100644 index 000000000000..f2693259e51a --- /dev/null +++ b/desktop/contextBridge.ts @@ -0,0 +1,82 @@ +import {contextBridge, ipcRenderer} from 'electron'; +import ELECTRON_EVENTS from './ELECTRON_EVENTS'; + +type ContextBridgeApi = { + send: (channel: string, data?: unknown) => void; + sendSync: (channel: string, data?: unknown) => unknown; + invoke: (channel: string, ...args: unknown[]) => Promise; + on: (channel: string, func: (...args: unknown[]) => void) => void; + removeAllListeners: (channel: string) => void; +}; + +const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ + ELECTRON_EVENTS.REQUEST_DEVICE_ID, + ELECTRON_EVENTS.REQUEST_FOCUS_APP, + ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, + ELECTRON_EVENTS.REQUEST_VISIBILITY, + ELECTRON_EVENTS.START_UPDATE, + ELECTRON_EVENTS.LOCALE_UPDATED, +]; + +const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; + +const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`; + +/** + * The following methods will be available in the renderer process under `window.electron`. + */ +contextBridge.exposeInMainWorld('electron', { + /** + * Send data asynchronously from renderer process to main process. + * Note that this is a one-way channel – main will not respond. In order to get a response from main, either: + * + * - Use `sendSync` + * - Or implement `invoke` if you want to maintain asynchronous communication: https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args + */ + send: (channel: string, data: unknown) => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + ipcRenderer.send(channel, data); + }, + + /** Send data synchronously from renderer process to main process. Main process may return a result. */ + sendSync: (channel: string, data: unknown): unknown => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + return ipcRenderer.sendSync(channel, data); + }, + + /** Execute a function in the main process and return a promise that resolves with its response. */ + invoke: (channel: string, ...args: unknown[]): Promise => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + return ipcRenderer.invoke(channel, ...args); + }, + + /** Set up a listener for events emitted from the main process and sent to the renderer process. */ + on: (channel: string, func: (...args: unknown[]) => void) => { + if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + // Deliberately strip event as it includes `sender` + ipcRenderer.on(channel, (event, ...args) => func(...args)); + }, + + /** Remove listeners for a single channel from the main process and sent to the renderer process. */ + removeAllListeners: (channel: string) => { + if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + ipcRenderer.removeAllListeners(channel); + }, +}); + +export default ContextBridgeApi; diff --git a/desktop/main.js b/desktop/main.ts similarity index 82% rename from desktop/main.js rename to desktop/main.ts index 4b38c5d36ab3..071ddb7c7a41 100644 --- a/desktop/main.js +++ b/desktop/main.ts @@ -1,17 +1,21 @@ -const {app, dialog, clipboard, BrowserWindow, Menu, MenuItem, shell, ipcMain} = require('electron'); -const _ = require('underscore'); -const serve = require('electron-serve'); -const contextMenu = require('electron-context-menu'); -const {autoUpdater} = require('electron-updater'); -const log = require('electron-log'); -const {machineId} = require('node-machine-id'); -const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); -const checkForUpdates = require('../src/libs/checkForUpdates'); -const CONFIG = require('../src/CONFIG').default; -const CONST = require('../src/CONST').default; -const Localize = require('../src/libs/Localize'); - -const port = process.env.PORT || 8082; +import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItem, shell} from 'electron'; +import type {BrowserView, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; +import contextMenu from 'electron-context-menu'; +import log from 'electron-log'; +import type {ElectronLog} from 'electron-log'; +import serve from 'electron-serve'; +import {autoUpdater} from 'electron-updater'; +import {machineId} from 'node-machine-id'; +import checkForUpdates from '@libs/checkForUpdates'; +import * as Localize from '@libs/Localize'; +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; +import type {Locale} from '@src/types/onyx'; +import ELECTRON_EVENTS from './ELECTRON_EVENTS'; + +const port = process.env.PORT ?? 8082; const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST; // Setup google api key in process environment, we are setting it this way intentionally. It is required by the @@ -34,43 +38,46 @@ app.commandLine.appendSwitch('enable-network-information-downlink-max'); /** * Inserts the plain text from the clipboard into the provided browser window's web contents. * - * @param {BrowserWindow} browserWindow - The Electron BrowserWindow instance where the text should be inserted. + * @param browserWindow - The Electron BrowserWindow instance where the text should be inserted. */ -function pasteAsPlainText(browserWindow) { +function pasteAsPlainText(browserWindow: BrowserWindow | BrowserView | WebviewTag | WebContents) { const text = clipboard.readText(); - browserWindow.webContents.insertText(text); + + if ('webContents' in browserWindow) { + browserWindow.webContents.insertText(text); + } } /** * Initialize the right-click menu * See https://github.com/sindresorhus/electron-context-menu * - * @param {String} preferredLocale - The current user language to be used for translating menu labels. - * @returns {Function} A dispose function to clean up the created context menu. + * @param preferredLocale - The current user language to be used for translating menu labels. + * @returns A dispose function to clean up the created context menu. */ - -function createContextMenu(preferredLocale = LOCALES.DEFAULT) { +function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => void { return contextMenu({ labels: { cut: Localize.translate(preferredLocale, 'desktopApplicationMenu.cut'), paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), }, - append: (defaultActions, parameters, browserWindow) => [ - new MenuItem({ - // Only enable the menu item for Editable context which supports paste - visible: parameters.isEditable && parameters.editFlags.canPaste, - role: 'pasteAndMatchStyle', - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), - }), - new MenuItem({ - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), - visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, - click: () => pasteAsPlainText(browserWindow), - }), - ], + append: (defaultActions, parameters, browserWindow) => + [ + new MenuItem({ + // Only enable the menu item for Editable context which supports paste + visible: parameters.isEditable && parameters.editFlags.canPaste, + role: 'pasteAndMatchStyle', + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + }), + new MenuItem({ + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, + click: () => pasteAsPlainText(browserWindow), + }), + ] as unknown as MenuItemConstructorOptions[], }); } @@ -79,11 +86,11 @@ let disposeContextMenu = createContextMenu(); // Send all autoUpdater logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log // See https://www.npmjs.com/package/electron-log autoUpdater.logger = log; -autoUpdater.logger.transports.file.level = 'info'; +(autoUpdater.logger as ElectronLog).transports.file.level = 'info'; // Send all Console logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log // See https://www.npmjs.com/package/electron-log -_.assign(console, log.functions); +Object.assign(console, log.functions); // This sets up the command line arguments used to manage the update. When // the --expected-update-version flag is set, the app will open pre-hidden @@ -92,23 +99,24 @@ _.assign(console, log.functions); const EXPECTED_UPDATE_VERSION_FLAG = '--expected-update-version'; const APP_DOMAIN = __DEV__ ? `https://dev.new.expensify.com:${port}` : 'app://-'; -let expectedUpdateVersion; -for (let i = 0; i < process.argv.length; i++) { - const arg = process.argv[i]; - if (arg.startsWith(`${EXPECTED_UPDATE_VERSION_FLAG}=`)) { - expectedUpdateVersion = arg.substr(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); +let expectedUpdateVersion: string; +process.argv.forEach((arg) => { + if (!arg.startsWith(`${EXPECTED_UPDATE_VERSION_FLAG}=`)) { + return; } -} + + expectedUpdateVersion = arg.substr(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); +}); // Add the listeners and variables required to ensure that auto-updating // happens correctly. let hasUpdate = false; -let downloadedVersion; +let downloadedVersion: string; // Note that we have to subscribe to this separately and cannot use Localize.translateLocal, // because the only way code can be shared between the main and renderer processes at runtime is via the context bridge // So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED -const preferredLocale = CONST.LOCALES.DEFAULT; +const preferredLocale: Locale = CONST.LOCALES.DEFAULT; const appProtocol = CONST.DEEPLINK_BASE_URL.replace('://', ''); @@ -120,12 +128,8 @@ const quitAndInstallWithUpdate = () => { autoUpdater.quitAndInstall(); }; -/** - * Menu Item callback to triggers an update check - * @param {MenuItem} menuItem - * @param {BrowserWindow} browserWindow - */ -const manuallyCheckForUpdates = (menuItem, browserWindow) => { +/** Menu Item callback to triggers an update check */ +const manuallyCheckForUpdates = (menuItem: MenuItem, browserWindow?: BrowserWindow) => { // Disable item until the check (and download) is complete // eslint: menu item flags like enabled or visible can be dynamically toggled by mutating the object // eslint-disable-next-line no-param-reassign @@ -135,7 +139,11 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { .checkForUpdates() .catch((error) => ({error})) .then((result) => { - const downloadPromise = result && result.downloadPromise; + const downloadPromise = result && 'downloadPromise' in result ? result.downloadPromise : undefined; + + if (!browserWindow) { + return; + } if (downloadPromise) { dialog.showMessageBox(browserWindow, { @@ -144,7 +152,7 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message'), buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], }); - } else if (result && result.error) { + } else if (result && 'error' in result && result.error) { dialog.showMessageBox(browserWindow, { type: 'error', message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'), @@ -170,25 +178,30 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { }); }; -/** - * Trigger event to show keyboard shortcuts - * @param {BrowserWindow} browserWindow - */ -const showKeyboardShortcutsPage = (browserWindow) => { +/** Trigger event to show keyboard shortcuts */ +const showKeyboardShortcutsPage = (browserWindow: BrowserWindow) => { if (!browserWindow.isVisible()) { return; } browserWindow.webContents.send(ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE); }; -// Actual auto-update listeners -const electronUpdater = (browserWindow) => ({ +/** Actual auto-update listeners */ +const electronUpdater = (browserWindow: BrowserWindow): PlatformSpecificUpdater => ({ init: () => { autoUpdater.on(ELECTRON_EVENTS.UPDATE_DOWNLOADED, (info) => { const systemMenu = Menu.getApplicationMenu(); + const updateMenuItem = systemMenu?.getMenuItemById(`update`); + const checkForUpdatesMenuItem = systemMenu?.getMenuItemById(`checkForUpdates`); + downloadedVersion = info.version; - systemMenu.getMenuItemById(`update`).visible = true; - systemMenu.getMenuItemById(`checkForUpdates`).visible = false; + + if (updateMenuItem) { + updateMenuItem.visible = true; + } + if (checkForUpdatesMenuItem) { + checkForUpdatesMenuItem.visible = false; + } if (browserWindow.isVisible()) { browserWindow.webContents.send(ELECTRON_EVENTS.UPDATE_DOWNLOADED, info.version); } else { @@ -204,29 +217,26 @@ const electronUpdater = (browserWindow) => ({ }, }); -/* - * @param {Menu} systemMenu - */ -const localizeMenuItems = (submenu, updatedLocale) => - _.map(submenu, (menu) => { - const newMenu = _.clone(menu); +const localizeMenuItems = (submenu: MenuItemConstructorOptions[], updatedLocale: Locale): MenuItemConstructorOptions[] => + submenu.map((menu) => { + const newMenu: MenuItemConstructorOptions = {...menu}; if (menu.id) { - const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}`); + const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); if (labelTranslation) { newMenu.label = labelTranslation; } } if (menu.submenu) { - newMenu.submenu = localizeMenuItems(menu.submenu, updatedLocale); + newMenu.submenu = localizeMenuItems(menu.submenu as MenuItemConstructorOptions[], updatedLocale); } return newMenu; }); -const mainWindow = () => { - let deeplinkUrl; - let browserWindow; +const mainWindow = (): Promise => { + let deeplinkUrl: string; + let browserWindow: BrowserWindow; - const loadURL = __DEV__ ? (win) => win.loadURL(`https://dev.new.expensify.com:${port}`) : serve({directory: `${__dirname}/www`}); + const loadURL = __DEV__ ? (win: BrowserWindow): Promise => win.loadURL(`https://dev.new.expensify.com:${port}`) : serve({directory: `${__dirname}/www`}); // Prod and staging set the icon in the electron-builder config, so only update it here for dev if (__DEV__) { @@ -296,7 +306,9 @@ const mainWindow = () => { if (!__DEV__) { // Modify the origin and referer for requests sent to our API webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { + // @ts-expect-error need to confirm if it's used details.requestHeaders.origin = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; + // @ts-expect-error need to confirm if it's used details.requestHeaders.referer = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; callback({requestHeaders: details.requestHeaders}); }); @@ -304,9 +316,11 @@ const mainWindow = () => { // Modify access-control-allow-origin header and CSP for the response webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { - details.responseHeaders['access-control-allow-origin'] = [APP_DOMAIN]; - if (details.responseHeaders['content-security-policy']) { - details.responseHeaders['content-security-policy'] = _.map(details.responseHeaders['content-security-policy'], (value) => + if (details.responseHeaders) { + details.responseHeaders['access-control-allow-origin'] = [APP_DOMAIN]; + } + if (details.responseHeaders?.['content-security-policy']) { + details.responseHeaders['content-security-policy'] = details.responseHeaders['content-security-policy'].map((value) => value.startsWith('frame-ancestors') ? `${value} ${APP_DOMAIN}` : value, ); } @@ -319,7 +333,7 @@ const mainWindow = () => { browserWindow.setTitle('New Expensify'); } - const initialMenuTemplate = [ + const initialMenuTemplate: MenuItemConstructorOptions[] = [ { id: 'mainMenu', label: Localize.translate(preferredLocale, `desktopApplicationMenu.mainMenu`), @@ -404,6 +418,7 @@ const mainWindow = () => { submenu: [ { id: 'back', + // @ts-expect-error role doesn't exist but removing cause problems role: 'back', accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[', click: () => { @@ -411,6 +426,7 @@ const mainWindow = () => { }, }, { + // @ts-expect-error role doesn't exist but removing cause problems role: 'back', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Left' : 'Shift+Left', @@ -420,6 +436,7 @@ const mainWindow = () => { }, { id: 'forward', + // @ts-expect-error role doesn't exist but removing cause problems role: 'forward', accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]', click: () => { @@ -427,6 +444,7 @@ const mainWindow = () => { }, }, { + // @ts-expect-error role doesn't exist but removing cause problems role: 'forward', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Right' : 'Shift+Right', @@ -485,7 +503,7 @@ const mainWindow = () => { // When the user clicks a link that has target="_blank" (which is all external links) // open the default browser instead of a new electron window browserWindow.webContents.setWindowOpenHandler(({url}) => { - const denial = {action: 'deny'}; + const denial = {action: 'deny'} as const; // Make sure local urls stay in electron perimeter if (url.substr(0, 'file://'.length).toLowerCase() === 'file://') { diff --git a/desktop/start.js b/desktop/start.ts similarity index 88% rename from desktop/start.js rename to desktop/start.ts index 05a1b031350d..9efc7e04c9be 100644 --- a/desktop/start.js +++ b/desktop/start.ts @@ -1,7 +1,10 @@ #!/usr/bin/env node -const portfinder = require('portfinder'); +import {config} from 'dotenv'; +import portfinder from 'portfinder'; + const concurrently = require('concurrently'); -require('dotenv').config(); + +config(); const basePort = 8082; @@ -39,6 +42,8 @@ portfinder }, ]; + // concurrently lib problem + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return concurrently(processes, { inputStream: process.stdin, prefix: 'name', diff --git a/package.json b/package.json index e3c23d4538d3..20a9418eb74d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "build": "webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "build-staging": "webpack --config config/webpack/webpack.common.js --env envFile=.env.staging", "build-adhoc": "webpack --config config/webpack/webpack.common.js --env envFile=.env.adhoc", - "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.js", + "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.js", diff --git a/src/types/modules/electron.d.ts b/src/types/modules/electron.d.ts index 09e33f29ba38..7b2ecaa3866d 100644 --- a/src/types/modules/electron.d.ts +++ b/src/types/modules/electron.d.ts @@ -1,11 +1,4 @@ -// TODO: Move this type to desktop/contextBridge.js once it is converted to TS -type ContextBridgeApi = { - send: (channel: string, data?: unknown) => void; - sendSync: (channel: string, data?: unknown) => unknown; - invoke: (channel: string, ...args: unknown) => Promise; - on: (channel: string, func: () => void) => void; - removeAllListeners: (channel: string) => void; -}; +import type ContextBridgeApi from '@desktop/contextBridge'; declare global { // eslint-disable-next-line @typescript-eslint/consistent-type-definitions From 39d33deebea4e1a27bf6a83cf58767755576aaf8 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Mar 2024 14:14:34 -0300 Subject: [PATCH 133/674] Fix type errors --- src/components/ReferralProgramCTA.tsx | 8 ++++---- src/pages/NewChatPage.tsx | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index 40c3c8683578..bd6976c84e3d 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; import Navigation from '@src/libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type DismissedReferralBanners from '@src/types/onyx/DismissedReferralBanners'; +import type * as OnyxTypes from '@src/types/onyx'; import Icon from './Icon'; import {Close} from './Icon/Expensicons'; import {PressableWithoutFeedback} from './Pressable'; @@ -16,7 +16,7 @@ import Text from './Text'; import Tooltip from './Tooltip'; type ReferralProgramCTAOnyxProps = { - dismissedReferralBanners: DismissedReferralBanners; + dismissedReferralBanners: OnyxEntry; }; type ReferralProgramCTAProps = ReferralProgramCTAOnyxProps & { @@ -36,7 +36,7 @@ function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: Ref User.dismissReferralBanner(referralContentType); }; - if (!referralContentType || dismissedReferralBanners[referralContentType]) { + if (!referralContentType || dismissedReferralBanners?.[referralContentType]) { return null; } diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index a1de24da12d4..f4eccd52c78e 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -22,7 +22,6 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type DismissedReferralBanners from '@src/types/onyx/DismissedReferralBanners'; type NewChatPageWithOnyxProps = { /** All reports shared with the user */ @@ -34,7 +33,7 @@ type NewChatPageWithOnyxProps = { betas: OnyxEntry; /** An object that holds data about which referral banners have been dismissed */ - dismissedReferralBanners: DismissedReferralBanners; + dismissedReferralBanners: OnyxEntry; /** Whether we are searching for reports in the server */ isSearchingForReports: OnyxEntry; @@ -265,7 +264,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingF shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} shouldShowOptions={isOptionsDataReady && didScreenTransitionEnd} shouldShowConfirmButton - shouldShowReferralCTA={!dismissedReferralBanners[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]} + shouldShowReferralCTA={!dismissedReferralBanners?.[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]} referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT} confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} textInputAlert={isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} From 75cd434c4c72e8cb77ed5d8872ae136750be894b Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:01 +0000 Subject: [PATCH 134/674] Add relevant oldDot messages we're missing in newDot to const --- src/CONST.ts | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d9e00f0a207e..c02d4e4bd4f5 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -561,27 +561,55 @@ const CONST = { SPLIT_REPORTID: '-2', ACTIONS: { LIMIT: 50, + // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMarkedReimbursedMessage in ReportActionItem.js TYPE: { + ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', ADDCOMMENT: 'ADDCOMMENT', - APPROVED: 'APPROVED', + APPROVED: 'APPROVED', // OldDot Action + CHANGEFIELD: 'CHANGEFIELD', // OldDot Action + CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action + CHANGETYPE: 'CHANGETYPE', // OldDot Action CHRONOSOOOLIST: 'CHRONOSOOOLIST', CLOSED: 'CLOSED', CREATED: 'CREATED', + DELEGATESUBMIT: 'DELEGATESUBMIT', // OldDot Action + DELETEDACCOUNT: 'DELETEDACCOUNT', // OldDot Action + DONATION: 'DONATION', // OldDot Action + EXPORTEDTOINTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action + EXPORTEDTOQUICKBOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action + FORWARDED: 'FORWARDED', // OldDot Action HOLD: 'HOLD', IOU: 'IOU', - MARKEDREIMBURSED: 'MARKEDREIMBURSED', + INTEGRATIONSMESSAGE: 'INTEGRATIONSMESSAGE', // OldDot Action + MANAGERATTACHRECEIPT: 'MANAGERATTACHRECEIPT', // OldDot Action + MANAGERDETACHRECEIPT: 'MANAGERDETACHRECEIPT', // OldDot Action + MARKEDREIMBURSED: 'MARKEDREIMBURSED', // OldDot Action + MARKREIMBURSEDFROMINTEGRATION: 'MARKREIMBURSEDFROMINTEGRATION', // OldDot Action MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', + OUTDATEDBANKACCOUNT: 'OUTDATEDBANKACCOUNT', // OldDot Action + REIMBURSEMENTACHBOUNCE: 'REIMBURSEMENTACHBOUNCE', // OldDot Action + REIMBURSEMENTACHCANCELLED: 'REIMBURSEMENTACHCANCELLED', // OldDot Action + REIMBURSEMENTACCOUNTCHANGED: 'REIMBURSEMENTACCOUNTCHANGED', // OldDot Action + REIMBURSEMENTDELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', + REIMBURSEMENTREQUESTED: 'REIMBURSEMENTREQUESTED', // OldDot Action + REIMBURSEMENTSETUP: 'REIMBURSEMENTSETUP', // OldDot Action RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', + SELECTEDFORRANDOMAUDIT: 'SELECTEDFORRANDOMAUDIT', // OldDot Action + SHARE: 'SHARE', // OldDot Action + STRIPEPAID: 'STRIPEPAID', // OldDot Action SUBMITTED: 'SUBMITTED', + TAKECONTROL: 'TAKECONTROL', // OldDot Action TASKCANCELLED: 'TASKCANCELLED', TASKCOMPLETED: 'TASKCOMPLETED', TASKEDITED: 'TASKEDITED', TASKREOPENED: 'TASKREOPENED', - ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', + UNAPPROVED: 'UNAPPROVED', // OldDot Action + UNHOLD: 'UNHOLD', + UNSHARE: 'UNSHARE', // OldDot Action POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', @@ -655,8 +683,6 @@ const CONST = { LEAVE_ROOM: 'LEAVEROOM', UPDATE_ROOM_DESCRIPTION: 'UPDATEROOMDESCRIPTION', }, - UNAPPROVED: 'UNAPPROVED', - UNHOLD: 'UNHOLD', }, THREAD_DISABLED: ['CREATED'], }, From 9c8833f407324b061f0b079a799bb25225997319 Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:18 +0000 Subject: [PATCH 135/674] Render oldDot messages from const --- src/pages/home/report/ReportActionItem.js | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b07421a66479..8859d0a7c060 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -465,8 +465,33 @@ function ReportActionItem(props) { } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else if ([ + CONST.REPORT.ACTIONS.TYPE.CHANGEFIELD, + CONST.REPORT.ACTIONS.TYPE.CHANGEPOLICY, + CONST.REPORT.ACTIONS.TYPE.CHANGETYPE, + CONST.REPORT.ACTIONS.TYPE.DELEGATESUBMIT, + CONST.REPORT.ACTIONS.TYPE.DELETEDACCOUNT, + CONST.REPORT.ACTIONS.TYPE.DONATION, + CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOINTEGRATION, + CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOQUICKBOOKS, + CONST.REPORT.ACTIONS.TYPE.FORWARDED, + CONST.REPORT.ACTIONS.TYPE.INTEGRATIONSMESSAGE, + CONST.REPORT.ACTIONS.TYPE.MANAGERATTACHRECEIPT, + CONST.REPORT.ACTIONS.TYPE.MANAGERDETACHRECEIPT, CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, - CONST.REPORT.ACTIONS.TYPE.UNAPPROVED + CONST.REPORT.ACTIONS.TYPE.MARKREIMBURSEDFROMINTEGRATION, + CONST.REPORT.ACTIONS.TYPE.OUTDATEDBANKACCOUNT, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACHBOUNCE, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACHCANCELLED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACCOUNTCHANGED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDELAYED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTREQUESTED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTSETUP, + CONST.REPORT.ACTIONS.TYPE.SELECTEDFORRANDOMAUDIT, + CONST.REPORT.ACTIONS.TYPE.SHARE, + CONST.REPORT.ACTIONS.TYPE.STRIPEPAID, + CONST.REPORT.ACTIONS.TYPE.TAKECONTROL, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, + CONST.REPORT.ACTIONS.TYPE.UNSHARE ].includes(props.action.actionName)) { // This handles all historical actions from OldDot that we just want to display the message text children = ; From 53f4289ce51a6bd375e841b098043fd166fafd78 Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:59 +0000 Subject: [PATCH 136/674] Update CONST.ts --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index c02d4e4bd4f5..9e6cf4990188 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -565,7 +565,7 @@ const CONST = { TYPE: { ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', ADDCOMMENT: 'ADDCOMMENT', - APPROVED: 'APPROVED', // OldDot Action + APPROVED: 'APPROVED', CHANGEFIELD: 'CHANGEFIELD', // OldDot Action CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action CHANGETYPE: 'CHANGETYPE', // OldDot Action From 3053b96a9432b9f5161bcfd3a09699e73f8fc86a Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Mar 2024 14:25:28 -0300 Subject: [PATCH 137/674] More lints --- src/components/ReferralProgramCTA.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index bd6976c84e3d..c93b75bf11ad 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; From 9eab6033922d3cade18acc4c1f827776f4c79871 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 1 Mar 2024 11:39:47 -0700 Subject: [PATCH 138/674] Apply suggestions from code review Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> Co-authored-by: Manan --- src/pages/LogOutPreviousUserPage.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index a10988945c4e..522883c38881 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -29,11 +29,11 @@ type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreen // // This component should not do any other navigation as that handled in App.setUpPoliciesAndNavigate function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPageProps) { - const initUrl = useContext(InitialUrlContext); + const initUrlFromOldApp = useContext(InitialUrlContext); useEffect(() => { Linking.getInitialURL().then((url) => { const sessionEmail = session?.email; - const transitionURL = NativeModules.HybridAppModule ? CONST.DEEPLINK_BASE_URL + initUrl : url; + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initUrl}` : url; const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); if (isLoggingInAsNewUser) { @@ -71,7 +71,10 @@ function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPag LogOutPreviousUserPage.displayName = 'LogOutPreviousUserPage'; export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, + isAccountLoading: { + key: ONYXKEYS.ACCOUNT, + selector: (account) => account?.isLoading, + }, session: { key: ONYXKEYS.SESSION, }, From ce048d53cf21d17eb4ae0ce7ca4031ef9f16e699 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Sat, 2 Mar 2024 12:06:35 +0530 Subject: [PATCH 139/674] revert the WALLET_ADDITIONAL_DETAILS key name --- src/ONYXKEYS.ts | 4 ++-- src/pages/EnablePayments/AdditionalDetailsStep.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index afcbd059a9f8..389c270f7510 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -399,8 +399,8 @@ const ONYXKEYS = { EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', IDOLOGY_QUESTIONS_FORM: 'idologyQuestionsForm', IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsFormDraft', - WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetailsForm', - WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsFormDraft', + WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetails', + WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsDraft', }, } as const; diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index cdf166536c5f..0b663261865f 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -237,6 +237,7 @@ AdditionalDetailsStep.displayName = 'AdditionalDetailsStep'; export default withCurrentUserPersonalDetails( withOnyx({ + // @ts-expect-error: ONYXKEYS.WALLET_ADDITIONAL_DETAILS is conflicting with ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS walletAdditionalDetails: { key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS, }, From a3115ba8ba75d23895af7eb1d4e320d3976641ae Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 3 Mar 2024 17:23:19 +0530 Subject: [PATCH 140/674] fix failing typechecks. Signed-off-by: Krishna Gupta --- src/libs/actions/Session/index.ts | 2 +- src/pages/LogInWithShortLivedAuthTokenPage.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 76f335a3bec0..5abe235ca029 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -855,7 +855,7 @@ function handleExitToNavigation(exitTo: Routes | HybridAppRoute) { InteractionManager.runAfterInteractions(() => { waitForUserSignIn().then(() => { Navigation.waitForProtectedRoutes().then(() => { - const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Routes); Navigation.navigate(url); }); }); diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 4e7372f10dc6..17cc971a075f 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -17,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PublicScreensParamList} from '@libs/Navigation/types'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Account} from '@src/types/onyx'; @@ -51,7 +52,7 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA if (exitTo) { Navigation.isNavigationReady().then(() => { - const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Routes); Navigation.navigate(url); }); } From 94452f5d83510bf6dec19be805d2a0b1e492ca2c Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:24:19 +0100 Subject: [PATCH 141/674] line up checkboxes --- src/components/SelectionList/BaseSelectionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 4e19cba00b2f..cde7eb775f23 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -433,7 +433,7 @@ function BaseSelectionList( ) : ( <> {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - + Date: Mon, 4 Mar 2024 07:42:44 +0530 Subject: [PATCH 142/674] fix route iou type for send. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepParticipants.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 954983808c80..38af1af8302d 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -118,7 +118,14 @@ function IOURequestStepParticipants({ const goToNextStep = useCallback( (selectedIouType) => { const isSplit = selectedIouType === CONST.IOU.TYPE.SPLIT; - const nextStepIOUType = !isSplit && iouType !== CONST.IOU.TYPE.REQUEST ? CONST.IOU.TYPE.REQUEST : iouType; + let nextStepIOUType; + + if (isSplit && iouType !== CONST.IOU.TYPE.REQUEST) { + nextStepIOUType = CONST.IOU.TYPE.SPLIT; + } else { + nextStepIOUType = iouType === CONST.IOU.TYPE.SEND ? CONST.IOU.TYPE.SEND : CONST.IOU.TYPE.REQUEST; + } + IOU.setMoneyRequestTag(transactionID, ''); IOU.setMoneyRequestCategory(transactionID, ''); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID)); From e02b55a5f95fe10e4f22a28b16f20aaf75977c5f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 13:02:21 +0500 Subject: [PATCH 143/674] refactor: use default policy room chat types from CONST --- src/CONST.ts | 17 ++++++++++------- src/libs/ReportUtils.ts | 3 +-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bb167bb5d8d8..78c562ea2cde 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -37,11 +37,20 @@ const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInpu // describes if a shortcut key can cause navigation const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; +const chatTypes = { + POLICY_ANNOUNCE: 'policyAnnounce', + POLICY_ADMINS: 'policyAdmins', + DOMAIN_ALL: 'domainAll', + POLICY_ROOM: 'policyRoom', + POLICY_EXPENSE_CHAT: 'policyExpenseChat', +} as const; + // Explicit type annotation is required const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', + DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, @@ -684,13 +693,7 @@ const CONST = { IOU: 'iou', TASK: 'task', }, - CHAT_TYPE: { - POLICY_ANNOUNCE: 'policyAnnounce', - POLICY_ADMINS: 'policyAdmins', - DOMAIN_ALL: 'domainAll', - POLICY_ROOM: 'policyRoom', - POLICY_EXPENSE_CHAT: 'policyExpenseChat', - }, + CHAT_TYPE: chatTypes, WORKSPACE_CHAT_ROOMS: { ANNOUNCE: '#announce', ADMINS: '#admins', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 37fee63780fb..93d2fd5da37d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -774,12 +774,11 @@ function isAnnounceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE; } -const chatTypes = [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL]; /** * Whether the provided report is a default room */ function isDefaultRoom(report: OnyxEntry): boolean { - return chatTypes.some((type) => type === getChatType(report)); + return CONST.DEFAULT_POLICY_ROOM_CHAT_TYPES.some((type) => type === getChatType(report)); } /** From cf1aa20d91c338bedb96cc326ce8997bbc74beb0 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Mon, 4 Mar 2024 15:11:32 +0530 Subject: [PATCH 144/674] fix: revert removal of onMoveShouldSetPanResponder --- src/components/SwipeInterceptPanResponder.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/SwipeInterceptPanResponder.tsx b/src/components/SwipeInterceptPanResponder.tsx index 48cfe4f90c5c..e778f0c49e54 100644 --- a/src/components/SwipeInterceptPanResponder.tsx +++ b/src/components/SwipeInterceptPanResponder.tsx @@ -1,7 +1,8 @@ -import {PanResponder} from 'react-native'; +import { PanResponder } from 'react-native'; const SwipeInterceptPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, onPanResponderTerminationRequest: () => false, }); From aa4d31ab0422c54ee43bf3aa9b8aa925fd19eb03 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Mon, 4 Mar 2024 15:24:58 +0530 Subject: [PATCH 145/674] fix: clean lint --- src/components/SwipeInterceptPanResponder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SwipeInterceptPanResponder.tsx b/src/components/SwipeInterceptPanResponder.tsx index e778f0c49e54..6a3d14b3b24b 100644 --- a/src/components/SwipeInterceptPanResponder.tsx +++ b/src/components/SwipeInterceptPanResponder.tsx @@ -1,4 +1,4 @@ -import { PanResponder } from 'react-native'; +import {PanResponder} from 'react-native'; const SwipeInterceptPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, From 7cbca08a4bd4b93ec49f6277df41f476744a33e7 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 15:55:46 +0500 Subject: [PATCH 146/674] revert: bring back localize.translateLocal --- src/libs/PersonalDetailsUtils.ts | 2 +- src/libs/ReportUtils.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 97e2dc91492b..5a3de690d8f8 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -41,7 +41,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : CommonTranslationUtils.unavailableWorkspaceText(); + const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable'); if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return CommonTranslationUtils.unavailableWorkspaceText(); + return Localize.translateLocal('workspace.common.unavailable'); } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -1647,7 +1647,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f const longName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, formattedLogin, shouldFallbackToHidden); // If the user's personal details (first name) should be hidden, make sure we return "hidden" instead of the short name - if (shouldFallbackToHidden && longName === CommonTranslationUtils.hiddenText()) { + if (shouldFallbackToHidden && longName === Localize.translateLocal('common.hidden')) { return longName; } @@ -2508,13 +2508,13 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return CommonTranslationUtils.deletedMessageText(); + return Localize.translateLocal('parentReportAction.deletedMessage'); } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${CommonTranslationUtils.attachmentText()}]`; + return `[${Localize.translateLocal('common.attachment')}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2530,7 +2530,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return CommonTranslationUtils.deletedTaskText(); + return Localize.translateLocal('parentReportAction.deletedTask'); } if (isChatRoom(report) || isTaskReport(report)) { @@ -2546,7 +2546,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${CommonTranslationUtils.archivedText()})`; + formattedName += ` (${Localize.translateLocal('common.archived')})`; } if (formattedName) { From 4df1d3da5dd9f6cb7862c8c62b105688cbbe9274 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 16:51:30 +0500 Subject: [PATCH 147/674] feat: add cache for translated values in Localize --- src/libs/Localize/index.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 64d07897aa8a..9cadc352ca9a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -93,11 +93,42 @@ function translate(desiredLanguage: 'en' | 'es' | throw new Error(`${phraseKey} was not found in the default language`); } +/** + * Map to store translated values for each locale + * This is used to avoid translating the same phrase multiple times. + * + * The data is stored in the following format: + * + * { + * "name_en": "Name", + * "name_es": "Nombre", + * } + * + * Note: We are not storing any translated values for phrases with variables, + * as they have higher chance of being unique, so we'll end up wasting space + * in our cache. + */ +const translatedValues = new Map(); + /** * Uses the locale in this file updated by the Onyx subscriber. */ function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { - return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); + const preferredLocale = BaseLocaleListener.getPreferredLocale(); + const key = `${phrase}_${preferredLocale}`; + const isVariablesEmpty = variables.length === 0; + + // If the phrase is already translated and there are no variables, return the translated value + if (translatedValues.has(key) && isVariablesEmpty) { + return translatedValues.get(key) as string; + } + const translatedText = translate(preferredLocale, phrase, ...variables); + + // We don't want to store translated values for phrases with variables + if (isVariablesEmpty) { + translatedValues.set(key, translatedText); + } + return translatedText; } /** From 8215b5377db03124eed0e166545c5cd2f9d16605 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 4 Mar 2024 12:57:26 +0100 Subject: [PATCH 148/674] address comments --- src/types/onyx/ReportAction.ts | 6 +- src/types/onyx/ReportActionsDrafts.ts | 5 + tests/actions/ReportTest.ts | 45 +++-- tests/unit/APITest.ts | 50 ++--- tests/unit/MigrationTest.ts | 252 +++++++++++++++----------- tests/unit/NetworkTest.ts | 35 ++-- 6 files changed, 228 insertions(+), 165 deletions(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index bb5bf50ec6cf..0971fb6b77e1 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -2,6 +2,8 @@ import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; +import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as OnyxCommon from './OnyxCommon'; import type {Decision, Reaction} from './OriginalMessage'; @@ -224,5 +226,7 @@ type ReportAction = ReportActionBase & OriginalMessage; type ReportActions = Record; +type ReportActionCollectionDataSet = CollectionDataSet; + export default ReportAction; -export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage}; +export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage, ReportActionCollectionDataSet}; diff --git a/src/types/onyx/ReportActionsDrafts.ts b/src/types/onyx/ReportActionsDrafts.ts index 70d16c62a3bc..e4c51c61ed25 100644 --- a/src/types/onyx/ReportActionsDrafts.ts +++ b/src/types/onyx/ReportActionsDrafts.ts @@ -1,5 +1,10 @@ +import type ONYXKEYS from '@src/ONYXKEYS'; +import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type ReportActionsDraft from './ReportActionsDraft'; type ReportActionsDrafts = Record; +type ReportActionsDraftCollectionDataSet = CollectionDataSet; + export default ReportActionsDrafts; +export type {ReportActionsDraftCollectionDataSet}; diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 43ceaaad607e..251d26932128 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -3,17 +3,17 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it} from '@jest/glob import {utcToZonedTime} from 'date-fns-tz'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import CONST from '@src/CONST'; +import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; +import * as PersistedRequests from '@src/libs/actions/PersistedRequests'; +import * as Report from '@src/libs/actions/Report'; +import * as User from '@src/libs/actions/User'; +import DateUtils from '@src/libs/DateUtils'; +import Log from '@src/libs/Log'; +import * as SequentialQueue from '@src/libs/Network/SequentialQueue'; +import * as ReportUtils from '@src/libs/ReportUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import CONST from '../../src/CONST'; -import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; -import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; -import * as Report from '../../src/libs/actions/Report'; -import * as User from '../../src/libs/actions/User'; -import DateUtils from '../../src/libs/DateUtils'; -import Log from '../../src/libs/Log'; -import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; -import * as ReportUtils from '../../src/libs/ReportUtils'; -import ONYXKEYS from '../../src/ONYXKEYS'; import getIsUsingFakeTimers from '../utils/getIsUsingFakeTimers'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; @@ -21,8 +21,8 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; const UTC = 'UTC'; -jest.mock('../../src/libs/actions/Report', () => { - const originalModule: typeof Report = jest.requireActual('../../src/libs/actions/Report'); +jest.mock('@src/libs/actions/Report', () => { + const originalModule = jest.requireActual('@src/libs/actions/Report'); return { ...originalModule, @@ -36,7 +36,6 @@ describe('actions/Report', () => { PusherHelper.setup(); Onyx.init({ keys: ONYXKEYS, - // registerStorageEventListener: () => {}, }); }); @@ -53,7 +52,8 @@ describe('actions/Report', () => { afterEach(PusherHelper.teardown); it('should store a new report action in Onyx when onyxApiUpdate event is handled via Pusher', () => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; @@ -89,7 +89,7 @@ describe('actions/Report', () => { return waitForBatchedUpdates(); }) .then(() => { - const resultAction: OnyxEntry = Object.values(reportActions ?? [])[0]; + const resultAction: OnyxEntry = Object.values(reportActions ?? {})[0]; reportActionID = resultAction.reportActionID; expect(resultAction.message).toEqual(REPORT_ACTION.message); @@ -168,7 +168,8 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); // WHEN we add enough logs to send a packet for (let i = 0; i <= LOGGER_MAX_LOG_LINES; i++) { @@ -194,7 +195,8 @@ describe('actions/Report', () => { it('should be updated correctly when new comments are added, deleted or marked as unread', () => { jest.useFakeTimers(); - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); const REPORT_ID = '1'; let report: OnyxEntry; let reportActionCreatedDate: string; @@ -427,7 +429,8 @@ describe('actions/Report', () => { * already in the comment and the user deleted it on purpose. */ - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); // User edits comment to add link // We should generate link @@ -539,7 +542,8 @@ describe('actions/Report', () => { }); it('should properly toggle reactions on a message', () => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; @@ -659,7 +663,8 @@ describe('actions/Report', () => { }); it("shouldn't add the same reaction twice when changing preferred skin color and reaction doesn't support skin colors", () => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 9c94730fb4cc..359288b2a1ef 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -1,23 +1,23 @@ -// import Onyx from 'react-native-onyx'; +import MockedOnyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import reactNativeOnyxMock from '../../__mocks__/react-native-onyx'; -import CONST from '../../src/CONST'; -import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; -import * as API from '../../src/libs/API'; -import HttpUtils from '../../src/libs/HttpUtils'; -import * as MainQueue from '../../src/libs/Network/MainQueue'; -import * as NetworkStore from '../../src/libs/Network/NetworkStore'; -import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; -import * as Request from '../../src/libs/Request'; -import * as RequestThrottle from '../../src/libs/RequestThrottle'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import CONST from '@src/CONST'; +import * as PersistedRequests from '@src/libs/actions/PersistedRequests'; +import * as API from '@src/libs/API'; +import HttpUtils from '@src/libs/HttpUtils'; +import * as MainQueue from '@src/libs/Network/MainQueue'; +import * as NetworkStore from '@src/libs/Network/NetworkStore'; +import * as SequentialQueue from '@src/libs/Network/SequentialQueue'; +import * as Request from '@src/libs/Request'; +import * as RequestThrottle from '@src/libs/RequestThrottle'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ReactNativeOnyxMock from '../../__mocks__/react-native-onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; -const Onyx = reactNativeOnyxMock; +const Onyx = MockedOnyx as typeof ReactNativeOnyxMock; -jest.mock('../../src/libs/Log'); +jest.mock('@src/libs/Log'); Onyx.init({ keys: ONYXKEYS, @@ -27,14 +27,21 @@ type Response = { ok?: boolean; status?: ValueOf | ValueOf; jsonCode?: ValueOf; + json?: () => Promise; title?: ValueOf; type?: ValueOf; }; +type XhrCalls = Array<{ + resolve: (value: Response | PromiseLike) => void; + reject: (value: unknown) => void; +}>; + const originalXHR = HttpUtils.xhr; beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); HttpUtils.xhr = originalXHR; MainQueue.clear(); HttpUtils.cancelPendingRequests(); @@ -136,10 +143,7 @@ describe('APITests', () => { test('Write request should not be cleared until a backend response occurs', () => { // We're setting up xhr handler that will resolve calls programmatically - const xhrCalls: Array<{ - resolve: (value: Response | PromiseLike) => void; - reject: (value: unknown) => void; - }> = []; + const xhrCalls: XhrCalls = []; const promises: Array> = []; jest.spyOn(HttpUtils, 'xhr').mockImplementation(() => { @@ -205,8 +209,8 @@ describe('APITests', () => { // Given a retry response create a mock and run some expectations for retrying requests - const retryExpectations = (Response: Response) => { - const successfulResponse = { + const retryExpectations = (response: Response) => { + const successfulResponse: Response = { ok: true, jsonCode: CONST.JSON_CODE.SUCCESS, // We have to mock response.json() too @@ -214,7 +218,7 @@ describe('APITests', () => { }; // Given a mock where a retry response is returned twice before a successful response - global.fetch = jest.fn().mockResolvedValueOnce(Response).mockResolvedValueOnce(Response).mockResolvedValueOnce(successfulResponse); + global.fetch = jest.fn().mockResolvedValueOnce(response).mockResolvedValueOnce(response).mockResolvedValueOnce(successfulResponse); // Given we have a request made while we're offline return ( @@ -275,7 +279,7 @@ describe('APITests', () => { test('write requests are retried when Auth is down', () => { // Given the response data returned when auth is down - const responseData = { + const responseData: Response = { ok: true, status: CONST.JSON_CODE.SUCCESS, jsonCode: CONST.JSON_CODE.EXP_ERROR, diff --git a/tests/unit/MigrationTest.ts b/tests/unit/MigrationTest.ts index 6d18ec2f0c68..bd1f79b8f838 100644 --- a/tests/unit/MigrationTest.ts +++ b/tests/unit/MigrationTest.ts @@ -1,14 +1,17 @@ /* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; -import Log from '../../src/libs/Log'; -import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; -import KeyReportActionsDraftByReportActionID from '../../src/libs/migrations/KeyReportActionsDraftByReportActionID'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import CONST from '@src/CONST'; +import Log from '@src/libs/Log'; +import CheckForPreviousReportActionID from '@src/libs/migrations/CheckForPreviousReportActionID'; +import KeyReportActionsDraftByReportActionID from '@src/libs/migrations/KeyReportActionsDraftByReportActionID'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportActionCollectionDataSet} from '@src/types/onyx/ReportAction'; +import type {ReportActionsDraftCollectionDataSet} from '@src/types/onyx/ReportActionsDrafts'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -jest.mock('../../src/libs/getPlatform'); +jest.mock('@src/libs/getPlatform'); -let LogSpy: unknown; +let LogSpy: jest.SpyInstance>; describe('Migrations', () => { beforeAll(() => { @@ -30,18 +33,23 @@ describe('Migrations', () => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions'), )); - it('Should remove all report actions given that a previousReportActionID does not exist', () => - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { - // @ts-expect-error Preset necessary values - 1: { - reportActionID: '1', - }, - 2: { - reportActionID: '2', - }, + it('Should remove all report actions given that a previousReportActionID does not exist', () => { + const setQueries: ReportActionCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`] = { + 1: { + reportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + }, + 2: { + reportActionID: '2', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, }, - }) + }; + + return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith( @@ -56,22 +64,28 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); }, }); - })); - - it('Should not remove any report action given that previousReportActionID exists in first valid report action', () => - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { - // @ts-expect-error Preset necessary values - 1: { - reportActionID: '1', - previousReportActionID: '0', - }, - 2: { - reportActionID: '2', - previousReportActionID: '1', - }, + }); + }); + + it('Should not remove any report action given that previousReportActionID exists in first valid report action', () => { + const setQueries: ReportActionCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`] = { + 1: { + reportActionID: '1', + previousReportActionID: '0', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + }, + 2: { + reportActionID: '2', + previousReportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, }, - }) + }; + + return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); @@ -93,23 +107,33 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); }, }); - })); - - it('Should skip zombie report actions and proceed to remove all reportActions given that a previousReportActionID does not exist', () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { - 1: { - reportActionID: '1', - }, - 2: { - reportActionID: '2', - }, + }); + }); + + it('Should skip zombie report actions and proceed to remove all reportActions given that a previousReportActionID does not exist', () => { + const setQueries: ReportActionCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`] = {}; + + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`] = null; + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`] = null; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`] = { + 1: { + reportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + }, + 2: { + reportActionID: '2', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, }, - }) + }; + + return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith( @@ -127,25 +151,34 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); }, }); - })); - - it('Should skip zombie report actions and should not remove any report action given that previousReportActionID exists in first valid report action', () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { - 1: { - reportActionID: '1', - previousReportActionID: '10', - }, - 2: { - reportActionID: '2', - previousReportActionID: '23', - }, + }); + }); + + it('Should skip zombie report actions and should not remove any report action given that previousReportActionID exists in first valid report action', () => { + const setQueries: ReportActionCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`] = {}; + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`] = null; + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`] = null; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`] = { + 1: { + reportActionID: '1', + previousReportActionID: '10', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + }, + 2: { + reportActionID: '2', + previousReportActionID: '23', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, }, - }) + }; + + Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); @@ -171,16 +204,20 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); }, }); - })); - - it('Should skip if no valid reportActions', () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: {}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: {}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: null, - }) + }); + }); + + it('Should skip if no valid reportActions', () => { + const setQueries: ReportActionCollectionDataSet = {}; + + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`] = null; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`] = {}; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`] = {}; + // @ts-expect-error preset null value + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`] = null; + + Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions'); @@ -196,7 +233,8 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeUndefined(); }, }); - })); + }); + }); }); describe('KeyReportActionsDraftByReportActionID', () => { @@ -205,14 +243,15 @@ describe('Migrations', () => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there were no reportActionsDrafts'), )); - it('Should move individual draft to a draft collection of report', () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: 'a', - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]: 'b', - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]: {3: 'c'}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]: 'd', - }) + it('Should move individual draft to a draft collection of report', () => { + const setQueries: ReportActionsDraftCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = 'a'; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = 'b'; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`] = {3: 'c'}; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = 'd'; + + Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { const connectionID = Onyx.connect({ @@ -235,16 +274,18 @@ describe('Migrations', () => { expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft2); }, }); - })); - - it('Should skip if nothing to migrate', () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]: {}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`]: null, - }) + }); + }); + + it('Should skip if nothing to migrate', () => { + const setQueries: ReportActionsDraftCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = null; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = null; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`] = {}; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = null; + + Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there are no actions drafts to migrate'); @@ -260,14 +301,16 @@ describe('Migrations', () => { expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`]).toMatchObject(expectedReportActionDraft); }, }); - })); - - it("Shouldn't move empty individual draft to a draft collection of report", () => - // @ts-expect-error Preset necessary values - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]: '', - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`]: {}, - }) + }); + }); + + it("Shouldn't move empty individual draft to a draft collection of report", () => { + const setQueries: ReportActionsDraftCollectionDataSet = {}; + + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = ''; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`] = {}; + + Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { const connectionID = Onyx.connect({ @@ -278,6 +321,7 @@ describe('Migrations', () => { expect(allReportActionsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`]).toBeUndefined(); }, }); - })); + }); + }); }); }); diff --git a/tests/unit/NetworkTest.ts b/tests/unit/NetworkTest.ts index f8b5b6a7d345..63b275a1a6b6 100644 --- a/tests/unit/NetworkTest.ts +++ b/tests/unit/NetworkTest.ts @@ -1,24 +1,24 @@ import type {Mock} from 'jest-mock'; -import reactNativeOnyxMock from '../../__mocks__/react-native-onyx'; -// import Onyx from 'react-native-onyx'; -import CONST from '../../src/CONST'; -import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; -import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; -import * as PersonalDetails from '../../src/libs/actions/PersonalDetails'; -import * as Session from '../../src/libs/actions/Session'; -import HttpUtils from '../../src/libs/HttpUtils'; -import Log from '../../src/libs/Log'; -import * as Network from '../../src/libs/Network'; -import * as MainQueue from '../../src/libs/Network/MainQueue'; -import * as NetworkStore from '../../src/libs/Network/NetworkStore'; -import NetworkConnection from '../../src/libs/NetworkConnection'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import MockedOnyx from 'react-native-onyx'; +import CONST from '@src/CONST'; +import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; +import * as PersistedRequests from '@src/libs/actions/PersistedRequests'; +import * as PersonalDetails from '@src/libs/actions/PersonalDetails'; +import * as Session from '@src/libs/actions/Session'; +import HttpUtils from '@src/libs/HttpUtils'; +import Log from '@src/libs/Log'; +import * as Network from '@src/libs/Network'; +import * as MainQueue from '@src/libs/Network/MainQueue'; +import * as NetworkStore from '@src/libs/Network/NetworkStore'; +import NetworkConnection from '@src/libs/NetworkConnection'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ReactNativeOnyxMock from '../../__mocks__/react-native-onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -const Onyx = reactNativeOnyxMock; +const Onyx = MockedOnyx as typeof ReactNativeOnyxMock; -jest.mock('../../src/libs/Log'); +jest.mock('@src/libs/Log'); Onyx.init({ keys: ONYXKEYS, @@ -28,7 +28,8 @@ OnyxUpdateManager(); const originalXHR = HttpUtils.xhr; beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + global.fetch = TestHelper.getGlobalFetchMock(); HttpUtils.xhr = originalXHR; MainQueue.clear(); HttpUtils.cancelPendingRequests(); From 55eb5d2f765099561de1a8720e2cb1345ca013e3 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:13:32 +0500 Subject: [PATCH 149/674] refactor: delete common translation utils --- src/libs/CommonTranslationUtils.ts | 49 ------------------------------ 1 file changed, 49 deletions(-) delete mode 100644 src/libs/CommonTranslationUtils.ts diff --git a/src/libs/CommonTranslationUtils.ts b/src/libs/CommonTranslationUtils.ts deleted file mode 100644 index 80a06e51ce9f..000000000000 --- a/src/libs/CommonTranslationUtils.ts +++ /dev/null @@ -1,49 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as Localize from './Localize'; - -/** - * This file contains common translations that are used in multiple places in the app. - * This is done to avoid duplicate translations and to keep the translations consistent. - * This also allows us to not repeatedly translate the same string which may happen due - * to translations being done for eg, in a loop. - * - * This was identified as part of a performance audit. - * details: https://github.com/Expensify/App/issues/35234#issuecomment-1926911643 - */ - -let deletedTaskText = ''; -let deletedMessageText = ''; -let attachmentText = ''; -let archivedText = ''; -let hiddenText = ''; -let unavailableWorkspaceText = ''; - -function isTranslationAvailable() { - return deletedTaskText && deletedMessageText && attachmentText && archivedText && hiddenText && unavailableWorkspaceText; -} - -Onyx.connect({ - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - callback: (val) => { - if (!val && isTranslationAvailable()) { - return; - } - - deletedTaskText = Localize.translateLocal('parentReportAction.deletedTask'); - deletedMessageText = Localize.translateLocal('parentReportAction.deletedMessage'); - attachmentText = Localize.translateLocal('common.attachment'); - archivedText = Localize.translateLocal('common.archived'); - hiddenText = Localize.translateLocal('common.hidden'); - unavailableWorkspaceText = Localize.translateLocal('workspace.common.unavailable'); - }, -}); - -export default { - deletedTaskText: () => deletedTaskText, - deletedMessageText: () => deletedMessageText, - attachmentText: () => attachmentText, - archivedText: () => archivedText, - hiddenText: () => hiddenText, - unavailableWorkspaceText: () => unavailableWorkspaceText, -}; From e643960c7ef54a8e2451f052c926ab664be127a4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:43:24 +0500 Subject: [PATCH 150/674] refactor: avoid multiple cache lookups --- src/libs/Localize/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 9cadc352ca9a..ad5e93af24ee 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -118,9 +118,13 @@ function translateLocal(phrase: TKey, ...variable const key = `${phrase}_${preferredLocale}`; const isVariablesEmpty = variables.length === 0; - // If the phrase is already translated and there are no variables, return the translated value - if (translatedValues.has(key) && isVariablesEmpty) { - return translatedValues.get(key) as string; + // Directly access and assign the translated value from the cache, instead of + // going through map.has() and map.get() to avoid multiple lookups. + const valueFromCache = translatedValues.get(key); + + // If the phrase is already translated, return the translated value + if (valueFromCache) { + return valueFromCache; } const translatedText = translate(preferredLocale, phrase, ...variables); From f3d6f1f73255fe186eea218f6600365dbfe06e41 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:43:39 +0500 Subject: [PATCH 151/674] fix: lint --- src/libs/PersonalDetailsUtils.ts | 1 - src/libs/ReportUtils.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index c5de7456629d..391cabef0790 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -5,7 +5,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; -import CommonTranslationUtils from './CommonTranslationUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as UserUtils from './UserUtils'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1c64deff881e..f7d49389e200 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -51,7 +51,6 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import * as CollectionUtils from './CollectionUtils'; -import CommonTranslationUtils from './CommonTranslationUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; From 46d1bf52143d4647dd74737e05cc819b044c7980 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 4 Mar 2024 14:25:29 +0100 Subject: [PATCH 152/674] Improve typing --- desktop/main.ts | 49 +++++++++++++++++++----------------------------- desktop/start.ts | 4 +--- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index 071ddb7c7a41..cbc12d9d2608 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,5 +1,5 @@ -import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItem, shell} from 'electron'; -import type {BrowserView, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; +import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell} from 'electron'; +import type {BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; import contextMenu from 'electron-context-menu'; import log from 'electron-log'; import type {ElectronLog} from 'electron-log'; @@ -62,22 +62,21 @@ function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => voi paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), }, - append: (defaultActions, parameters, browserWindow) => - [ - new MenuItem({ - // Only enable the menu item for Editable context which supports paste - visible: parameters.isEditable && parameters.editFlags.canPaste, - role: 'pasteAndMatchStyle', - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), - }), - new MenuItem({ - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), - visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, - click: () => pasteAsPlainText(browserWindow), - }), - ] as unknown as MenuItemConstructorOptions[], + append: (defaultActions, parameters, browserWindow) => [ + { + // Only enable the menu item for Editable context which supports paste + visible: parameters.isEditable && parameters.editFlags.canPaste, + role: 'pasteAndMatchStyle', + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + }, + { + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, + click: () => pasteAsPlainText(browserWindow), + }, + ], }); } @@ -306,10 +305,6 @@ const mainWindow = (): Promise => { if (!__DEV__) { // Modify the origin and referer for requests sent to our API webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { - // @ts-expect-error need to confirm if it's used - details.requestHeaders.origin = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; - // @ts-expect-error need to confirm if it's used - details.requestHeaders.referer = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; callback({requestHeaders: details.requestHeaders}); }); } @@ -418,16 +413,13 @@ const mainWindow = (): Promise => { submenu: [ { id: 'back', - // @ts-expect-error role doesn't exist but removing cause problems - role: 'back', accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[', click: () => { browserWindow.webContents.goBack(); }, }, { - // @ts-expect-error role doesn't exist but removing cause problems - role: 'back', + label: 'backWithKeyShortcut', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Left' : 'Shift+Left', click: () => { @@ -436,16 +428,13 @@ const mainWindow = (): Promise => { }, { id: 'forward', - // @ts-expect-error role doesn't exist but removing cause problems - role: 'forward', accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]', click: () => { browserWindow.webContents.goForward(); }, }, { - // @ts-expect-error role doesn't exist but removing cause problems - role: 'forward', + label: 'forwardWithKeyShortcut', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Right' : 'Shift+Right', click: () => { diff --git a/desktop/start.ts b/desktop/start.ts index 9efc7e04c9be..f92cd10dc5aa 100644 --- a/desktop/start.ts +++ b/desktop/start.ts @@ -42,8 +42,6 @@ portfinder }, ]; - // concurrently lib problem - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return concurrently(processes, { inputStream: process.stdin, prefix: 'name', @@ -53,6 +51,6 @@ portfinder }).then( () => process.exit(0), () => process.exit(1), - ); + ) as never; }) .catch(() => process.exit(1)); From 0f1fb25ffd036dc02b3e7aba1730a2ee46baeb44 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 4 Mar 2024 20:17:26 +0530 Subject: [PATCH 153/674] removed unused onyx/Form --- src/types/onyx/Form.ts | 140 ---------------------------------------- src/types/onyx/index.ts | 6 -- 2 files changed, 146 deletions(-) delete mode 100644 src/types/onyx/Form.ts diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts deleted file mode 100644 index 3743468b39b0..000000000000 --- a/src/types/onyx/Form.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type * as OnyxCommon from './OnyxCommon'; -import type PersonalBankAccount from './PersonalBankAccount'; - -type FormValueType = string | boolean | Date | OnyxCommon.Errors; - -type BaseForm = { - /** Controls the loading state of the form */ - isLoading?: boolean; - - /** Server side errors keyed by microtime */ - errors?: OnyxCommon.Errors | null; - - /** Field-specific server side errors keyed by microtime */ - errorFields?: OnyxCommon.ErrorFields | null; -}; - -type Form = Record> = TFormValues & BaseForm; - -type AddDebitCardForm = Form<{ - /** Whether the form has been submitted */ - setupComplete: boolean; -}>; - -type DateOfBirthForm = Form<{ - /** Date of birth */ - dob?: string; -}>; - -type DisplayNameForm = Form<{ - firstName: string; - lastName: string; -}>; - -type NewRoomForm = Form<{ - roomName?: string; - reportDescription?: string; - policyID?: string; - writeCapability?: string; - visibility?: string; -}>; - -type IKnowATeacherForm = Form<{ - firstName: string; - lastName: string; - partnerUserID: string; -}>; - -type IntroSchoolPrincipalForm = Form<{ - firstName: string; - lastName: string; - partnerUserID: string; -}>; - -type PrivateNotesForm = Form<{ - privateNotes: string; -}>; - -type GetPhysicalCardForm = Form<{ - /** Address line 1 for delivery */ - addressLine1?: string; - - /** Address line 2 for delivery */ - addressLine2?: string; - - /** City for delivery */ - city?: string; - - /** Country for delivery */ - country?: string; - - /** First name for delivery */ - legalFirstName?: string; - - /** Last name for delivery */ - legalLastName?: string; - - /** Phone number for delivery */ - phoneNumber?: string; - - /** State for delivery */ - state?: string; - - /** Zip code for delivery */ - zipPostCode?: string; -}>; - -type PersonalBankAccountForm = Form; - -type WorkspaceSettingsForm = Form<{ - name: string; -}>; - -type ReportFieldEditForm = Form>; - -type CloseAccountForm = Form<{ - reasonForLeaving: string; - phoneOrEmail: string; -}>; - -type IdologyQuestionsForm = Form<{ - answer: string; -}>; - -type AdditionalDetailStepForm = Form<{ - legalFirstName: string; - legalLastName: string; - addressStreet: string; - addressCity: string; - addressZipCode: string; - phoneNumber: string; - dob: string; - ssn: string; - addressState: string; -}>; - -type RoomNameForm = Form<{ - roomName: string; -}>; - -export default Form; - -export type { - AddDebitCardForm, - DateOfBirthForm, - PrivateNotesForm, - DisplayNameForm, - FormValueType, - GetPhysicalCardForm, - NewRoomForm, - BaseForm, - IKnowATeacherForm, - IntroSchoolPrincipalForm, - PersonalBankAccountForm, - WorkspaceSettingsForm, - ReportFieldEditForm, - CloseAccountForm, - IdologyQuestionsForm, - AdditionalDetailStepForm, - RoomNameForm, -}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 277011602a17..6846fc302639 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -12,7 +12,6 @@ import type Currency from './Currency'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; -import type {AdditionalDetailStepForm, IdologyQuestionsForm, PrivateNotesForm, ReportFieldEditForm, RoomNameForm} from './Form'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; import type Fund from './Fund'; @@ -154,10 +153,5 @@ export type { RecentlyUsedReportFields, LastPaymentMethod, InvitedEmailsToAccountIDs, - PrivateNotesForm, - ReportFieldEditForm, - IdologyQuestionsForm, - AdditionalDetailStepForm, - RoomNameForm, Log, }; From 9f7258c4c6ec7a127be94b704de6d7b95cada235 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 4 Mar 2024 15:50:28 +0100 Subject: [PATCH 154/674] Re-run checks From e0813e48574bc22e8a14844d8fae4afcd7c86f20 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 4 Mar 2024 14:09:18 +0100 Subject: [PATCH 155/674] fix test --- tests/unit/MigrationTest.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/unit/MigrationTest.ts b/tests/unit/MigrationTest.ts index bd1f79b8f838..d60761cd1d89 100644 --- a/tests/unit/MigrationTest.ts +++ b/tests/unit/MigrationTest.ts @@ -178,7 +178,7 @@ describe('Migrations', () => { }, }; - Onyx.multiSet(setQueries) + return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); @@ -217,7 +217,7 @@ describe('Migrations', () => { // @ts-expect-error preset null value setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`] = null; - Onyx.multiSet(setQueries) + return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions'); @@ -225,8 +225,8 @@ describe('Migrations', () => { key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, waitForCollectionCallback: true, callback: (allReportActions) => { - Onyx.disconnect(connectionID); const expectedReportAction = {}; + Onyx.disconnect(connectionID); expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeUndefined(); expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); expect(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); @@ -246,12 +246,15 @@ describe('Migrations', () => { it('Should move individual draft to a draft collection of report', () => { const setQueries: ReportActionsDraftCollectionDataSet = {}; + // @ts-expect-error preset invalid value setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = 'a'; + // @ts-expect-error preset invalid value setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = 'b'; setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`] = {3: 'c'}; + // @ts-expect-error preset invalid value setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = 'd'; - Onyx.multiSet(setQueries) + return Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { const connectionID = Onyx.connect({ @@ -280,12 +283,9 @@ describe('Migrations', () => { it('Should skip if nothing to migrate', () => { const setQueries: ReportActionsDraftCollectionDataSet = {}; - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = null; - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = null; setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`] = {}; - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = null; - Onyx.multiSet(setQueries) + return Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there are no actions drafts to migrate'); @@ -307,10 +307,11 @@ describe('Migrations', () => { it("Shouldn't move empty individual draft to a draft collection of report", () => { const setQueries: ReportActionsDraftCollectionDataSet = {}; + // @ts-expect-error preset empty string value setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = ''; setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`] = {}; - Onyx.multiSet(setQueries) + return Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) .then(() => { const connectionID = Onyx.connect({ From 8a2b943aa3ab57c38d487f7de31307dadd0b28ca Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 4 Mar 2024 22:09:08 +0530 Subject: [PATCH 156/674] removed IdologyQuestionsForm instead used AdditionalDetailStepForm --- src/ONYXKEYS.ts | 3 --- src/libs/actions/Wallet.ts | 4 ++-- src/pages/EnablePayments/IdologyQuestions.tsx | 4 ++-- src/types/form/AdditionalDetailStepForm.ts | 2 ++ src/types/form/IdologyQuestionsForm.ts | 18 ------------------ src/types/form/index.ts | 1 - 6 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 src/types/form/IdologyQuestionsForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 389c270f7510..6834fcaa13ce 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -397,8 +397,6 @@ const ONYXKEYS = { EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', - IDOLOGY_QUESTIONS_FORM: 'idologyQuestionsForm', - IDOLOGY_QUESTIONS_FORM_DRAFT: 'idologyQuestionsFormDraft', WALLET_ADDITIONAL_DETAILS: 'walletAdditionalDetails', WALLET_ADDITIONAL_DETAILS_DRAFT: 'walletAdditionalDetailsDraft', }, @@ -446,7 +444,6 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; [ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: FormTypes.AdditionalDetailStepForm; - [ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM]: FormTypes.IdologyQuestionsForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index ffc68c562c4c..9cb4b28bef20 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -233,7 +233,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: true, }, @@ -243,7 +243,7 @@ function answerQuestionsForWallet(answers: WalletQuestionAnswer[], idNumber: str const finallyData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.FORMS.IDOLOGY_QUESTIONS_FORM, + key: ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS, value: { isLoading: false, }, diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index 13c71f7005a9..6baea2158613 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -92,7 +92,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { } }; - const validate = (values: FormOnyxValues): FormInputErrors => { + const validate = (values: FormOnyxValues): FormInputErrors => { const errors: Errors = {}; if (!values.answer) { errors.answer = translate('additionalDetailsStep.selectAnswer'); @@ -112,7 +112,7 @@ function IdologyQuestions({questions, idNumber}: IdologyQuestionsProps) { ; @@ -27,6 +28,7 @@ type AdditionalDetailStepForm = Form< [INPUT_IDS.ADDRESS_STATE]: string; [INPUT_IDS.DOB]: string; [INPUT_IDS.SSN]: string; + [INPUT_IDS.ANSWER]: string; } >; diff --git a/src/types/form/IdologyQuestionsForm.ts b/src/types/form/IdologyQuestionsForm.ts deleted file mode 100644 index 250e8c6f7766..000000000000 --- a/src/types/form/IdologyQuestionsForm.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type {ValueOf} from 'type-fest'; -import type Form from './Form'; - -const INPUT_IDS = { - ANSWER: 'answer', -} as const; - -type InputID = ValueOf; - -type IdologyQuestionsForm = Form< - InputID, - { - [INPUT_IDS.ANSWER]: string; - } ->; - -export type {IdologyQuestionsForm}; -export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 498265848e37..0ab53ba9ec0e 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -38,5 +38,4 @@ export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; export type {AdditionalDetailStepForm} from './AdditionalDetailStepForm'; -export type {IdologyQuestionsForm} from './IdologyQuestionsForm'; export type {default as Form} from './Form'; From 5e5289ba2f1724241b0f97ba0f669a1d27492392 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 10:40:52 +0100 Subject: [PATCH 157/674] fix: typecheck --- src/libs/SidebarUtils.ts | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 47 ++++++++++----------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6be95b393472..81d8f5a46d53 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -74,7 +74,7 @@ function getOrderedReportIDs( betas: OnyxEntry, policies: OnyxCollection, priorityMode: OnyxEntry>, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 042f3b392d8a..1d0aa9e996ac 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -26,9 +26,13 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; -type ChatReportSelector = ReturnType & {isUnreadWithMention: boolean}; -type PolicySelector = ReturnType; -type ReportActionsSelector = ReturnType; +type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; +type PolicySelector = Pick; +type TransactionSelector = Pick< + OnyxTypes.Transaction, + 'reportID' | 'iouRequestType' | 'comment' | 'receipt' | 'merchant' | 'modifiedMerchant' | 'created' | 'modifiedCreated' | 'amount' | 'modifiedAmount' +>; +type ReportActionsSelector = Array>; type SidebarLinksDataOnyxProps = { /** List of reports */ @@ -44,7 +48,7 @@ type SidebarLinksDataOnyxProps = { betas: OnyxEntry; /** All transactions f */ - allTransactions: OnyxEntry; + allTransactions: OnyxCollection; /** All report actions for all reports */ allReportActions: OnyxEntry; @@ -82,6 +86,7 @@ function SidebarLinksData({ transactionViolations, currentReportID, }: SidebarLinksDataProps) { + console.log(allReportActions); const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -101,7 +106,7 @@ function SidebarLinksData({ return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -118,7 +123,7 @@ function SidebarLinksData({ betas, policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -166,7 +171,7 @@ function SidebarLinksData({ betas, policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -222,15 +227,14 @@ SidebarLinksData.displayName = 'SidebarLinksData'; * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. */ -const chatReportSelector = (report: OnyxEntry) => - report && { +const chatReportSelector = (report: OnyxEntry): ChatReportSelector => + (report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, hasDraft: report.hasDraft, isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, - errors: report.errors, errorFields: { addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, }, @@ -252,9 +256,6 @@ const chatReportSelector = (report: OnyxEntry) => reportName: report.reportName, policyName: report.policyName, oldPolicyName: report.oldPolicyName, - isPolicyExpenseChat: report.isPolicyExpenseChat, - isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat, - isCancelledIOU: report.isCancelledIOU, // Other less obvious properites considered for sorting: ownerAccountID: report.ownerAccountID, currency: report.currency, @@ -264,7 +265,7 @@ const chatReportSelector = (report: OnyxEntry) => parentReportID: report.parentReportID, isDeletedParentAction: report.isDeletedParentAction, isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; + }) as ChatReportSelector; const reportActionsSelector = (reportActions: OnyxEntry) => reportActions && @@ -285,15 +286,15 @@ const reportActionsSelector = (reportActions: OnyxEntry }; }); -const policySelector = (policy: OnyxEntry) => - policy && { +const policySelector = (policy: OnyxEntry): PolicySelector => + (policy && { type: policy.type, name: policy.name, avatar: policy.avatar, - }; + }) as PolicySelector; -const transactionSelector = (transaction) => - transaction && { +const transactionSelector = (transaction: OnyxEntry): TransactionSelector => + (transaction && { reportID: transaction.reportID, iouRequestType: transaction.iouRequestType, comment: transaction.comment, @@ -304,14 +305,13 @@ const transactionSelector = (transaction) => modifiedAmount: transaction.modifiedAmount, created: transaction.created, modifiedCreated: transaction.modifiedCreated, - }; + }) as TransactionSelector; export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + selector: chatReportSelector, initialValue: {}, }, isLoadingApp: { @@ -337,8 +337,7 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, + selector: policySelector, initialValue: {}, }, policyMembers: { From ac55d2f98c493345f0064dc3f6014155aef57a37 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 11:33:16 +0100 Subject: [PATCH 158/674] fix: typecheck --- src/libs/SidebarUtils.ts | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 81d8f5a46d53..8d53e992cb2d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -70,11 +70,11 @@ function filterDisplayName(displayName: string): string { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: OnyxCollection, - betas: OnyxEntry, - policies: OnyxCollection, - priorityMode: OnyxEntry>, - allReportActions: OnyxCollection, + allReports: Record, + betas: Beta[], + policies: Record, + priorityMode: ValueOf, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], @@ -83,17 +83,17 @@ function getOrderedReportIDs( ): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports ?? {}); + const allReportsDictValues = Object.values(allReports); const reportIDsWithViolations = new Set(); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; - const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report?.parentReportActionID ?? '']; + const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? '']; const doesReportHaveViolations = canUseViolations && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); if (doesReportHaveViolations) { - reportIDsWithViolations.add(report?.reportID ?? ''); + reportIDsWithViolations.add(report.reportID); } return ReportUtils.shouldReportBeInOptionList({ report, @@ -124,14 +124,14 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndBrickRoadReports: Array> = []; + const pinnedAndBrickRoadReports: Report[] = []; const draftReports: Report[] = []; - const nonArchivedReports: Array> = []; + const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( - (report) => report?.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), + (report) => report.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), ); } // There are a few properties that need to be calculated for the report which are used when sorting reports. @@ -140,20 +140,17 @@ function getOrderedReportIDs( // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. // eslint-disable-next-line no-param-reassign - if (report) { - // eslint-disable-next-line no-param-reassign - report.displayName = filterDisplayName(ReportUtils.getReportName(report)); - } + report.displayName = filterDisplayName(ReportUtils.getReportName(report)); - const hasRBR = (!!report && report.reportID in reportIDsWithErrors) || reportIDsWithViolations.has(report?.reportID ?? ''); + const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID); - const isPinned = report?.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + const isPinned = report.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { pinnedAndBrickRoadReports.push(report); - } else if (report?.hasDraft) { + } else if (report.hasDraft) { draftReports.push(report); - } else if (report && ReportUtils.isArchivedRoom(report)) { + } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); } else { nonArchivedReports.push(report); @@ -179,7 +176,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); + const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); return LHNReports; } From ab1369805213034680390bd4d572b50047972612 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 5 Mar 2024 11:56:42 +0100 Subject: [PATCH 159/674] Update concurrently lib, minor code improvements --- desktop/contextBridge.ts | 4 +- desktop/start.ts | 17 +- package-lock.json | 621 +++++++++------------------------------ package.json | 2 +- 4 files changed, 145 insertions(+), 499 deletions(-) diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts index f2693259e51a..689c69de0cc8 100644 --- a/desktop/contextBridge.ts +++ b/desktop/contextBridge.ts @@ -16,9 +16,9 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ ELECTRON_EVENTS.REQUEST_VISIBILITY, ELECTRON_EVENTS.START_UPDATE, ELECTRON_EVENTS.LOCALE_UPDATED, -]; +] as const; -const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; +const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR] as const; const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`; diff --git a/desktop/start.ts b/desktop/start.ts index f92cd10dc5aa..17ec690507ed 100644 --- a/desktop/start.ts +++ b/desktop/start.ts @@ -1,10 +1,9 @@ #!/usr/bin/env node -import {config} from 'dotenv'; +import concurrently from 'concurrently'; +import {config as configDotenv} from 'dotenv'; import portfinder from 'portfinder'; -const concurrently = require('concurrently'); - -config(); +configDotenv(); const basePort = 8082; @@ -12,7 +11,7 @@ portfinder .getPortPromise({ port: basePort, }) - .then((port) => { + .then((port): Promise => { const devServer = `webpack-dev-server --config config/webpack/webpack.dev.js --port ${port} --env platform=desktop`; const buildMain = 'webpack watch --config config/webpack/webpack.desktop.js --config-name desktop-main --mode=development'; @@ -42,15 +41,17 @@ portfinder }, ]; - return concurrently(processes, { + const {result} = concurrently(processes, { inputStream: process.stdin, prefix: 'name', // Like Harry Potter and he-who-must-not-be-named, "neither can live while the other survives" killOthers: ['success', 'failure'], - }).then( + }); + + return result.then( () => process.exit(0), () => process.exit(1), - ) as never; + ); }) .catch(() => process.exit(1)); diff --git a/package-lock.json b/package-lock.json index 80d3d1c6e911..db601a4ac37b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -197,7 +197,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", - "concurrently": "^5.3.0", + "concurrently": "^8.2.2", "copy-webpack-plugin": "^6.4.1", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", @@ -2709,20 +2709,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@callstack/reassure-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@callstack/reassure-cli/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2762,42 +2748,6 @@ "node": ">=8" } }, - "node_modules/@callstack/reassure-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/@callstack/reassure-compare": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.6.0.tgz", @@ -26041,75 +25991,16 @@ } }, "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "license": "MIT", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/clone": { @@ -26457,40 +26348,115 @@ } }, "node_modules/concurrently": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz", - "integrity": "sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, - "license": "MIT", "dependencies": { - "chalk": "^2.4.2", - "date-fns": "^2.0.1", - "lodash": "^4.17.15", - "read-pkg": "^4.0.1", - "rxjs": "^6.5.2", - "spawn-command": "^0.0.2-1", - "supports-color": "^6.1.0", + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", "tree-kill": "^1.2.2", - "yargs": "^13.3.0" + "yargs": "^17.7.2" }, "bin": { - "concurrently": "bin/concurrently.js" + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=6.0.0" + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/concurrently/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/config-file-ts": { @@ -28722,20 +28688,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/electron-builder/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-builder/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -28794,42 +28746,6 @@ "node": ">=8" } }, - "node_modules/electron-builder/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/electron-builder/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-builder/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-publish": { "version": "24.5.0", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.5.0.tgz", @@ -35634,20 +35550,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-cli/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -35687,41 +35589,6 @@ "node": ">=8" } }, - "node_modules/jest-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "29.4.1", "license": "MIT", @@ -39736,16 +39603,6 @@ "tslib": "2" } }, - "node_modules/memfs/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -40462,19 +40319,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, - "node_modules/metro/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/metro/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -40600,39 +40444,6 @@ } } }, - "node_modules/metro/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/metro/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/microevent.ts": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", @@ -44871,19 +44682,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/react-native/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/react-native/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -44975,39 +44773,6 @@ "async-limiter": "~1.0.0" } }, - "node_modules/react-native/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/react-native/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/react-native/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/react-pdf": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.3.3.tgz", @@ -46791,25 +46556,14 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, "node_modules/safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -48008,11 +47762,10 @@ } }, "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true, - "license": "MIT" + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true }, "node_modules/spdx-correct": { "version": "3.1.1", @@ -52692,22 +52445,20 @@ } }, "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "license": "MIT", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -52720,126 +52471,20 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/yargs/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" } }, "node_modules/yauzl": { diff --git a/package.json b/package.json index 92afaafc85e7..7f5847d50608 100644 --- a/package.json +++ b/package.json @@ -246,7 +246,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", - "concurrently": "^5.3.0", + "concurrently": "^8.2.2", "copy-webpack-plugin": "^6.4.1", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", From bb932cbed6602f6c6c527e504d0a67e0a429e761 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 5 Mar 2024 12:08:19 +0100 Subject: [PATCH 160/674] Update files extension in the README --- desktop/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/README.md b/desktop/README.md index 4493196b5ed4..2fc074153d87 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -37,9 +37,9 @@ The desktop app is organized in three pieces: - This is the webpack-bundled version of our react-native-web app (except using `index.desktop.js` files instead of `index.website.js`, where applicable) - This is _very_ similar to our web app, and code in this process should assume it will be run in the context of a browser (no access to `require`, Electron, or Node.js APis) 3. The context bridge - - Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.js + - Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.ts - The context bridge enables communication between the main and renderer processes. For example, if the renderer process needs to make use of a Node.js or Electron API it must: - 1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.js + 1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.ts 2. Add that event to the whitelist defined in the context bridge 3. Set up a handler for the event in the main process that can respond to the renderer process back through the bridge, if necessary. From 018516241e2691db789e7e7ff67f171950c2dcb6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 5 Mar 2024 19:37:54 +0700 Subject: [PATCH 161/674] fix: image shrinks while loading in carousel --- src/components/Lightbox/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index a7ed6946fb28..1a69e1f8958b 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -137,7 +137,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { - return DEFAULT_IMAGE_DIMENSION; + return undefined; } const {minScale} = getCanvasFitScale({canvasSize, contentSize}); @@ -217,7 +217,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan > setFallbackImageLoaded(true)} From 379485eaa374db1612b022d35aab75e67e8f818b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 15:13:04 +0100 Subject: [PATCH 162/674] fix: typecheck --- src/libs/OptionsListUtils.ts | 6 ++- src/libs/SidebarUtils.ts | 39 ++++++++-------- src/pages/home/sidebar/SidebarLinksData.tsx | 51 ++++++++++----------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 07f0df962455..1a7ea79e5d59 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -481,7 +481,11 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { +function getAllReportErrors( + report: OnyxEntry, + reportActions: OnyxEntry | ReportAction[] | undefined, + transactions: OnyxCollection = allTransactions, +): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d53e992cb2d..c22226553ffc 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -70,10 +70,10 @@ function filterDisplayName(displayName: string): string { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, - betas: Beta[], - policies: Record, - priorityMode: ValueOf, + allReports: OnyxCollection, + betas: OnyxEntry, + policies: OnyxCollection, + priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', @@ -83,17 +83,17 @@ function getOrderedReportIDs( ): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); const reportIDsWithViolations = new Set(); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; - const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? '']; + const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report?.parentReportActionID ?? '']; const doesReportHaveViolations = canUseViolations && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); if (doesReportHaveViolations) { - reportIDsWithViolations.add(report.reportID); + reportIDsWithViolations.add(report?.reportID ?? ''); } return ReportUtils.shouldReportBeInOptionList({ report, @@ -124,14 +124,14 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndBrickRoadReports: Report[] = []; - const draftReports: Report[] = []; - const nonArchivedReports: Report[] = []; - const archivedReports: Report[] = []; + const pinnedAndBrickRoadReports: Array> = []; + const draftReports: Array> = []; + const nonArchivedReports: Array> = []; + const archivedReports: Array> = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( - (report) => report.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), + (report) => report?.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), ); } // There are a few properties that need to be calculated for the report which are used when sorting reports. @@ -140,15 +140,18 @@ function getOrderedReportIDs( // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. // eslint-disable-next-line no-param-reassign - report.displayName = filterDisplayName(ReportUtils.getReportName(report)); + if (report) { + // eslint-disable-next-line no-param-reassign + report.displayName = filterDisplayName(ReportUtils.getReportName(report)); + } - const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID); + const hasRBR = (!!report && report?.reportID in reportIDsWithErrors) || reportIDsWithViolations.has(report?.reportID ?? ''); - const isPinned = report.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); + const isPinned = report?.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { pinnedAndBrickRoadReports.push(report); - } else if (report.hasDraft) { + } else if (report?.hasDraft) { draftReports.push(report); } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); @@ -176,7 +179,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 1d0aa9e996ac..f9368bac86d2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -51,7 +51,7 @@ type SidebarLinksDataOnyxProps = { allTransactions: OnyxCollection; /** All report actions for all reports */ - allReportActions: OnyxEntry; + allReportActions: OnyxCollection; /** The policies which the user has access to */ policies: OnyxCollection; @@ -86,7 +86,6 @@ function SidebarLinksData({ transactionViolations, currentReportID, }: SidebarLinksDataProps) { - console.log(allReportActions); const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -105,8 +104,8 @@ function SidebarLinksData({ const reportKeys = Object.keys(chatReports ?? {}); return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; - const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions as OnyxCollection) || {}; + const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS) ?? '']; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxTypes.ReportAction[], allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -119,11 +118,11 @@ function SidebarLinksData({ const optionListItems: string[] = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, - chatReports as OnyxEntry>, + chatReports, betas, - policies as OnyxEntry>, + policies as OnyxCollection, priorityMode, - allReportActions as OnyxCollection, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -167,11 +166,11 @@ function SidebarLinksData({ if (currentReportID && !optionListItems?.includes(currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportID, - chatReports as OnyxEntry>, + chatReports as OnyxCollection, betas, - policies as OnyxEntry>, + policies as OnyxCollection, priorityMode, - allReportActions as OnyxCollection, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -268,23 +267,23 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele }) as ChatReportSelector; const reportActionsSelector = (reportActions: OnyxEntry) => - reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors, originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; + (reportActions && + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors, originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; - return { - reportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - } as Message, - ], - originalMessage, - }; - }); + return { + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + } as Message, + ], + originalMessage, + }; + })) as ReportActionsSelector; const policySelector = (policy: OnyxEntry): PolicySelector => (policy && { From dc64a57d4501a1fc99631e73b226f660875e8f24 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 16:43:51 +0100 Subject: [PATCH 163/674] fix: types --- src/libs/OptionsListUtils.ts | 6 +-- src/pages/home/sidebar/SidebarLinksData.tsx | 45 ++++++++++++--------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 1a7ea79e5d59..2b3125db47ce 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -481,11 +481,7 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors( - report: OnyxEntry, - reportActions: OnyxEntry | ReportAction[] | undefined, - transactions: OnyxCollection = allTransactions, -): OnyxCommon.Errors { +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry | undefined, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index f9368bac86d2..0f4daa6f5f76 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -23,7 +23,6 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; @@ -32,7 +31,7 @@ type TransactionSelector = Pick< OnyxTypes.Transaction, 'reportID' | 'iouRequestType' | 'comment' | 'receipt' | 'merchant' | 'modifiedMerchant' | 'created' | 'modifiedCreated' | 'amount' | 'modifiedAmount' >; -type ReportActionsSelector = Array>; +type ReportActionsSelector = Record>; type SidebarLinksDataOnyxProps = { /** List of reports */ @@ -105,7 +104,9 @@ function SidebarLinksData({ return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS) ?? '']; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxTypes.ReportAction[], allTransactions as OnyxCollection) || {}; + + const errors = + OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxEntry, allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -255,6 +256,9 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele reportName: report.reportName, policyName: report.policyName, oldPolicyName: report.oldPolicyName, + isPolicyExpenseChat: report.isPolicyExpenseChat, + isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat, + isCancelledIOU: report.isCancelledIOU, // Other less obvious properites considered for sorting: ownerAccountID: report.ownerAccountID, currency: report.currency, @@ -266,24 +270,29 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }) as ChatReportSelector; -const reportActionsSelector = (reportActions: OnyxEntry) => +const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => (reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors, originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; + Object.fromEntries( + Object.entries(reportActions).map(([key, reportAction]) => { + const {reportActionID, actionName, errors, originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; - return { - reportActionID, - actionName, - errors, - message: [ + return [ + key, { - moderationDecision: {decision}, - } as Message, - ], - originalMessage, - }; - })) as ReportActionsSelector; + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + }, + ], + originalMessage, + }, + ]; + }), + )) as ReportActionsSelector; const policySelector = (policy: OnyxEntry): PolicySelector => (policy && { From e9140e1aafc1e0e9cb836c34294a7fdbcfa9b14d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 5 Mar 2024 22:02:32 +0530 Subject: [PATCH 164/674] updated setAdditionalDetailsQuestions prop to handle null and added missed addressZipCode INPUT_ID to STEP_FIELDS --- src/libs/actions/Wallet.ts | 3 +-- src/pages/EnablePayments/AdditionalDetailsStep.tsx | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index 9cb4b28bef20..097d9ee0419a 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -52,7 +52,7 @@ function openOnfidoFlow() { API.read(READ_COMMANDS.OPEN_ONFIDO_FLOW, {}, {optimisticData, finallyData}); } -function setAdditionalDetailsQuestions(questions: WalletAdditionalQuestionDetails[], idNumber: string) { +function setAdditionalDetailsQuestions(questions: WalletAdditionalQuestionDetails[] | null, idNumber?: string) { Onyx.merge(ONYXKEYS.WALLET_ADDITIONAL_DETAILS, {questions, idNumber}); } @@ -75,7 +75,6 @@ function setKYCWallSource(source?: ValueOf, chatRe /** * Validates a user's provided details against a series of checks */ - function updatePersonalDetails(personalDetails: UpdatePersonalDetailsForWalletParams) { const optimisticData: OnyxUpdate[] = [ { diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx index 0b663261865f..57b9c7c6ade4 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx +++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx @@ -57,6 +57,7 @@ const STEP_FIELDS = [ INPUT_IDS.LEGAL_LAST_NAME, INPUT_IDS.ADDRESS_STREET, INPUT_IDS.ADDRESS_CITY, + INPUT_IDS.ADDRESS_ZIP_CODE, INPUT_IDS.PHONE_NUMBER, INPUT_IDS.DOB, INPUT_IDS.ADDRESS_STATE, @@ -132,7 +133,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO > Wallet.setAdditionalDetailsQuestions([], walletAdditionalDetails?.idNumber ?? '')} + onBackButtonPress={() => Wallet.setAdditionalDetailsQuestions(null)} /> Date: Tue, 5 Mar 2024 14:11:05 -0500 Subject: [PATCH 165/674] Change API to new schema --- package-lock.json | 6 +++--- package.json | 2 +- src/libs/ApiUtils.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81e5b036a5be..c416a788e7c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#45d3b61bb38b4f9a19ddf573ce1e212369b242db", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", @@ -30751,8 +30751,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#45d3b61bb38b4f9a19ddf573ce1e212369b242db", - "integrity": "sha512-R1ykTwH3Pdp2sFqE6AL3ihmo4OjLMDEc8mEqQwD9W+yoIDIScT6Wi5ewO5vZUNsyCiKnD+xvNU7I1d9VNRJkXw==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", + "integrity": "sha512-hsP+sd3jc752y402gAoJbmk8eTkAIbWBhV2nXuAsRl86jt79qQvgkfDheG0TWmcYrO+7RApqkok0OfZJuyI5nA==", "license": "MIT", "dependencies": { "classnames": "2.5.0", diff --git a/package.json b/package.json index 47ea222bfb91..c028c884e243 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#45d3b61bb38b4f9a19ddf573ce1e212369b242db", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index 67feb18b36fa..78bcc000318c 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -52,7 +52,7 @@ function getApiRoot(request?: Request): string { * @param - the name of the API command */ function getCommandURL(request: Request): string { - return `${getApiRoot(request)}api?command=${request.command}`; + return `${getApiRoot(request)}api/${request.command}?`; } /** From a2ada45f0e5ee307f0d8b6073b6dacecabe47256 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Mar 2024 16:36:54 -0300 Subject: [PATCH 166/674] Migrate recently used tags too --- src/ONYXKEYS.ts | 2 +- src/libs/migrations/NVPMigration.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index db9864e6800c..1087312a4acd 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -293,7 +293,7 @@ const ONYXKEYS = { POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', - POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', + POLICY_RECENTLY_USED_TAGS: 'nvp_policyRecentlyUsedTags_', POLICY_REPORT_FIELDS: 'policyReportFields_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index 22bdd4a03615..a6fe81fa0aee 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -21,8 +21,8 @@ const migrations = { // This migration changes the keys of all the NVP related keys so that they are standardized export default function () { return new Promise((resolve) => { - // It's 1 more because activePolicyID is not in the migrations object above as it is nested inside an object - const resolveWhenDone = after(Object.entries(migrations).length + 1, () => resolve()); + // We add the number of manual connections we add below + const resolveWhenDone = after(Object.entries(migrations).length + 2, () => resolve()); for (const [oldKey, newKey] of Object.entries(migrations)) { const connectionID = Onyx.connect({ @@ -41,10 +41,10 @@ export default function () { }, }); } - const connectionID = Onyx.connect({ + const connectionIDAccount = Onyx.connect({ key: ONYXKEYS.ACCOUNT, callback: (value) => { - Onyx.disconnect(connectionID); + Onyx.disconnect(connectionIDAccount); // @ts-expect-error we are removing this property, so it is not in the type anymore if (value?.activePolicyID) { // @ts-expect-error we are removing this property, so it is not in the type anymore @@ -60,5 +60,26 @@ export default function () { resolveWhenDone(); }, }); + const connectionIDRecentlyUsedTags = Onyx.connect({ + // @ts-expect-error The key was renamed, so it does not exist in the type definition + key: 'policyRecentlyUsedTags_', + waitForCollectionCallback: true, + callback: (value) => { + Onyx.disconnect(connectionIDRecentlyUsedTags); + if (!value) { + resolveWhenDone(); + return; + } + const newValue = {}; + for (const key of Object.keys(value)) { + // @ts-expect-error We have no fixed types here + newValue[`nvp_${key}`] = value[key]; + // @ts-expect-error We have no fixed types here + newValue[key] = null; + } + Onyx.multiSet(newValue); + resolveWhenDone(); + }, + }); }); } From f0c591094bbd81300c3c3497750dc871b86830d3 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Mar 2024 16:47:21 -0300 Subject: [PATCH 167/674] Make collection load properly --- src/ONYXKEYS.ts | 2 ++ src/libs/migrations/NVPMigration.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1087312a4acd..d581e515e0f5 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -294,6 +294,7 @@ const ONYXKEYS = { POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', POLICY_RECENTLY_USED_TAGS: 'nvp_policyRecentlyUsedTags_', + OLD_POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', POLICY_REPORT_FIELDS: 'policyReportFields_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', @@ -484,6 +485,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; + [ONYXKEYS.COLLECTION.OLD_POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index a6fe81fa0aee..6be142eb1f2a 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -61,8 +61,7 @@ export default function () { }, }); const connectionIDRecentlyUsedTags = Onyx.connect({ - // @ts-expect-error The key was renamed, so it does not exist in the type definition - key: 'policyRecentlyUsedTags_', + key: ONYXKEYS.COLLECTION.OLD_POLICY_RECENTLY_USED_TAGS, waitForCollectionCallback: true, callback: (value) => { Onyx.disconnect(connectionIDRecentlyUsedTags); From fdadc74041fbcac42c12ee063ab14ded025e2a21 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Mar 2024 16:57:55 -0300 Subject: [PATCH 168/674] Correct onyx key --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d581e515e0f5..13f578dae136 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -199,7 +199,7 @@ const ONYXKEYS = { PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', /** Store frequently used emojis for this user */ - FREQUENTLY_USED_EMOJIS: 'expensify_frequentlyUsedEmojis', + FREQUENTLY_USED_EMOJIS: 'nvp_expensify_frequentlyUsedEmojis', /** Stores Workspace ID that will be tied to reimbursement account during setup */ REIMBURSEMENT_ACCOUNT_WORKSPACE_ID: 'reimbursementAccountWorkspaceID', From 1290c364747c9a61908bb88b36ac75437251204e Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Mar 2024 17:46:36 -0300 Subject: [PATCH 169/674] Add nvp prefix --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 13f578dae136..031759c2b4eb 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -132,7 +132,7 @@ const ONYXKEYS = { NVP_ACTIVE_POLICY_ID: 'nvp_expensify_activePolicyID', /** This NVP contains the referral banners the user dismissed */ - NVP_DISMISSED_REFERRAL_BANNERS: 'dismissedReferralBanners', + NVP_DISMISSED_REFERRAL_BANNERS: 'nvp_dismissedReferralBanners', /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', From 175556071115a7732be109d64241adf732851cf7 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Mar 2024 14:20:49 +0500 Subject: [PATCH 170/674] refactor: use dedicated map for each locale --- src/libs/Localize/index.ts | 39 +++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ad5e93af24ee..f64d39d5e24a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -12,6 +12,7 @@ import type {Locale} from '@src/types/onyx'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import type BaseLocale from './LocaleListener/types'; // Current user mail is needed for handling missing translations let userEmail = ''; @@ -94,33 +95,55 @@ function translate(desiredLanguage: 'en' | 'es' | } /** - * Map to store translated values for each locale + * Map to store translated values for each locale. * This is used to avoid translating the same phrase multiple times. * * The data is stored in the following format: * * { - * "name_en": "Name", - * "name_es": "Nombre", + * "name": "Name", * } * * Note: We are not storing any translated values for phrases with variables, * as they have higher chance of being unique, so we'll end up wasting space * in our cache. */ -const translatedValues = new Map(); +const TRANSLATED_VALUES_EN = new Map(); +const TRANSLATED_VALUES_ES = new Map(); +const TRANSLATED_VALUES_ES_ES = new Map(); +const TRANSLATED_VALUES_ES_ONFIDO = new Map(); + +/** + * Returns the map for the given locale. + */ +function getTranslatedValuesMap(locale: BaseLocale) { + switch (locale) { + case CONST.LOCALES.ES_ES: + return TRANSLATED_VALUES_ES_ES; + case CONST.LOCALES.ES_ES_ONFIDO: + return TRANSLATED_VALUES_ES_ONFIDO; + case CONST.LOCALES.ES: + return TRANSLATED_VALUES_ES; + case CONST.LOCALES.DEFAULT: + default: + return TRANSLATED_VALUES_EN; + } +} /** * Uses the locale in this file updated by the Onyx subscriber. */ function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { const preferredLocale = BaseLocaleListener.getPreferredLocale(); - const key = `${phrase}_${preferredLocale}`; + const key = `${phrase}`; const isVariablesEmpty = variables.length === 0; + // Get the map for the preferred locale + const map = getTranslatedValuesMap(preferredLocale); + // Directly access and assign the translated value from the cache, instead of // going through map.has() and map.get() to avoid multiple lookups. - const valueFromCache = translatedValues.get(key); + const valueFromCache = map.get(key); // If the phrase is already translated, return the translated value if (valueFromCache) { @@ -130,7 +153,9 @@ function translateLocal(phrase: TKey, ...variable // We don't want to store translated values for phrases with variables if (isVariablesEmpty) { - translatedValues.set(key, translatedText); + // We set the translated value in the cache in the next iteration + // of the event loop to make this operation asynchronous. + setImmediate(() => map.set(key, translatedText)); } return translatedText; } From 99dd34cfdf973681a713100ea345ced47911e206 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Mar 2024 14:32:23 +0500 Subject: [PATCH 171/674] refactor: remove setImmediate --- src/libs/Localize/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index f64d39d5e24a..72bb4dd89bb1 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -153,9 +153,8 @@ function translateLocal(phrase: TKey, ...variable // We don't want to store translated values for phrases with variables if (isVariablesEmpty) { - // We set the translated value in the cache in the next iteration - // of the event loop to make this operation asynchronous. - setImmediate(() => map.set(key, translatedText)); + // We set the translated value in the cache + map.set(key, translatedText); } return translatedText; } From 7b3c4134c5f03cca3b8f17c5e140d77ea5db1a83 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 6 Mar 2024 11:20:48 +0100 Subject: [PATCH 172/674] Migrate g15 stories to TS --- src/components/CheckboxWithLabel.tsx | 2 + src/components/OptionRow.tsx | 2 + src/components/PopoverMenu.tsx | 2 +- ...ories.js => CheckboxWithLabel.stories.tsx} | 15 +++++--- ...nuItem.stories.js => MenuItem.stories.tsx} | 38 ++++++++++--------- ...onRow.stories.js => OptionRow.stories.tsx} | 3 +- ...enu.stories.js => PopoverMenu.stories.tsx} | 28 +++++++------- ...stories.js => SubscriptAvatar.stories.tsx} | 14 ++++--- 8 files changed, 59 insertions(+), 45 deletions(-) rename src/stories/{CheckboxWithLabel.stories.js => CheckboxWithLabel.stories.tsx} (73%) rename src/stories/{MenuItem.stories.js => MenuItem.stories.tsx} (77%) rename src/stories/{OptionRow.stories.js => OptionRow.stories.tsx} (94%) rename src/stories/{PopoverMenu.stories.js => PopoverMenu.stories.tsx} (78%) rename src/stories/{SubscriptAvatar.stories.js => SubscriptAvatar.stories.tsx} (77%) diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx index 2919debe9cb1..dd169576186e 100644 --- a/src/components/CheckboxWithLabel.tsx +++ b/src/components/CheckboxWithLabel.tsx @@ -108,3 +108,5 @@ function CheckboxWithLabel( CheckboxWithLabel.displayName = 'CheckboxWithLabel'; export default React.forwardRef(CheckboxWithLabel); + +export type {CheckboxWithLabelProps}; diff --git a/src/components/OptionRow.tsx b/src/components/OptionRow.tsx index 7b45fd963fe7..97ef6885c80f 100644 --- a/src/components/OptionRow.tsx +++ b/src/components/OptionRow.tsx @@ -340,3 +340,5 @@ export default React.memo( prevProps.option.pendingAction === nextProps.option.pendingAction && prevProps.option.customIcon === nextProps.option.customIcon, ); + +export type {OptionRowProps}; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index a391ff061baa..3a211f90bd14 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -243,4 +243,4 @@ function PopoverMenu({ PopoverMenu.displayName = 'PopoverMenu'; export default React.memo(PopoverMenu); -export type {PopoverMenuItem}; +export type {PopoverMenuItem, PopoverMenuProps}; diff --git a/src/stories/CheckboxWithLabel.stories.js b/src/stories/CheckboxWithLabel.stories.tsx similarity index 73% rename from src/stories/CheckboxWithLabel.stories.js rename to src/stories/CheckboxWithLabel.stories.tsx index f978856aaefb..b5e8bc72f380 100644 --- a/src/stories/CheckboxWithLabel.stories.js +++ b/src/stories/CheckboxWithLabel.stories.tsx @@ -1,29 +1,33 @@ +import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React from 'react'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; +import type {CheckboxWithLabelProps} from '@components/CheckboxWithLabel'; import Text from '@components/Text'; // eslint-disable-next-line no-restricted-imports import {defaultStyles} from '@styles/index'; +type CheckboxWithLabelStory = ComponentStory; + /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -const story = { +const story: ComponentMeta = { title: 'Components/CheckboxWithLabel', component: CheckboxWithLabel, }; -function Template(args) { +function Template(args: CheckboxWithLabelProps) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Default = Template.bind({}); -const WithLabelComponent = Template.bind({}); -const WithErrors = Template.bind({}); +const Default: CheckboxWithLabelStory = Template.bind({}); +const WithLabelComponent: CheckboxWithLabelStory = Template.bind({}); +const WithErrors: CheckboxWithLabelStory = Template.bind({}); Default.args = { isChecked: true, label: 'Plain text label', @@ -44,7 +48,6 @@ WithLabelComponent.args = { WithErrors.args = { isChecked: false, - hasError: true, errorText: 'Please accept Terms before continuing.', onInputChange: () => {}, label: 'I accept the Terms & Conditions', diff --git a/src/stories/MenuItem.stories.js b/src/stories/MenuItem.stories.tsx similarity index 77% rename from src/stories/MenuItem.stories.js rename to src/stories/MenuItem.stories.tsx index 0e7260fa4d1a..4e02bcaf785f 100644 --- a/src/stories/MenuItem.stories.js +++ b/src/stories/MenuItem.stories.tsx @@ -1,26 +1,30 @@ +import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React from 'react'; import Chase from '@assets/images/bankicons/chase.svg'; import MenuItem from '@components/MenuItem'; +import type {MenuItemProps} from '@components/MenuItem'; import variables from '@styles/variables'; +type MenuItemStory = ComponentStory; + /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -const story = { +const story: ComponentMeta = { title: 'Components/MenuItem', component: MenuItem, }; -function Template(args) { +function Template(args: MenuItemProps) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Default = Template.bind({}); +const Default: MenuItemStory = Template.bind({}); Default.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -28,7 +32,7 @@ Default.args = { iconWidth: variables.iconSizeExtraLarge, }; -const Description = Template.bind({}); +const Description: MenuItemStory = Template.bind({}); Description.args = { title: 'Alberta Bobbeth Charleson', description: 'Account ending in 1111', @@ -37,7 +41,7 @@ Description.args = { iconWidth: variables.iconSizeExtraLarge, }; -const RightIcon = Template.bind({}); +const RightIcon: MenuItemStory = Template.bind({}); RightIcon.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -46,7 +50,7 @@ RightIcon.args = { shouldShowRightIcon: true, }; -const RightIconAndDescription = Template.bind({}); +const RightIconAndDescription: MenuItemStory = Template.bind({}); RightIconAndDescription.args = { title: 'Alberta Bobbeth Charleson', description: 'Account ending in 1111', @@ -56,7 +60,7 @@ RightIconAndDescription.args = { shouldShowRightIcon: true, }; -const RightIconAndDescriptionWithLabel = Template.bind({}); +const RightIconAndDescriptionWithLabel: MenuItemStory = Template.bind({}); RightIconAndDescriptionWithLabel.args = { label: 'Account number', title: 'Alberta Bobbeth Charleson', @@ -67,7 +71,7 @@ RightIconAndDescriptionWithLabel.args = { shouldShowRightIcon: true, }; -const Selected = Template.bind({}); +const Selected: MenuItemStory = Template.bind({}); Selected.args = { title: 'Alberta Bobbeth Charleson', description: 'Account ending in 1111', @@ -78,7 +82,7 @@ Selected.args = { isSelected: true, }; -const BadgeText = Template.bind({}); +const BadgeText: MenuItemStory = Template.bind({}); BadgeText.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -88,7 +92,7 @@ BadgeText.args = { badgeText: '$0.00', }; -const Focused = Template.bind({}); +const Focused: MenuItemStory = Template.bind({}); Focused.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -98,7 +102,7 @@ Focused.args = { focused: true, }; -const Disabled = Template.bind({}); +const Disabled: MenuItemStory = Template.bind({}); Disabled.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -108,17 +112,17 @@ Disabled.args = { disabled: true, }; -const BrickRoadIndicatorSuccess = Template.bind({}); -BrickRoadIndicatorSuccess.args = { +const BrickRoadIndicatorInfo: MenuItemStory = Template.bind({}); +BrickRoadIndicatorInfo.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, iconHeight: variables.iconSizeExtraLarge, iconWidth: variables.iconSizeExtraLarge, shouldShowRightIcon: true, - brickRoadIndicator: 'success', + brickRoadIndicator: 'info', }; -const BrickRoadIndicatorFailure = Template.bind({}); +const BrickRoadIndicatorFailure: MenuItemStory = Template.bind({}); BrickRoadIndicatorFailure.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -128,7 +132,7 @@ BrickRoadIndicatorFailure.args = { brickRoadIndicator: 'error', }; -const ErrorMessage = Template.bind({}); +const ErrorMessage: MenuItemStory = Template.bind({}); ErrorMessage.args = { title: 'Alberta Bobbeth Charleson', icon: Chase, @@ -149,7 +153,7 @@ export { BadgeText, Focused, Disabled, - BrickRoadIndicatorSuccess, + BrickRoadIndicatorInfo, BrickRoadIndicatorFailure, RightIconAndDescriptionWithLabel, ErrorMessage, diff --git a/src/stories/OptionRow.stories.js b/src/stories/OptionRow.stories.tsx similarity index 94% rename from src/stories/OptionRow.stories.js rename to src/stories/OptionRow.stories.tsx index 3096940dda5f..d2fffcd583dd 100644 --- a/src/stories/OptionRow.stories.js +++ b/src/stories/OptionRow.stories.tsx @@ -2,6 +2,7 @@ import React from 'react'; import * as Expensicons from '@components/Icon/Expensicons'; import OnyxProvider from '@components/OnyxProvider'; import OptionRow from '@components/OptionRow'; +import type {OptionRowProps} from '@components/OptionRow'; /* eslint-disable react/jsx-props-no-spreading */ @@ -42,7 +43,7 @@ export default { }, }; -function Template(args) { +function Template(args: OptionRowProps) { return ( diff --git a/src/stories/PopoverMenu.stories.js b/src/stories/PopoverMenu.stories.tsx similarity index 78% rename from src/stories/PopoverMenu.stories.js rename to src/stories/PopoverMenu.stories.tsx index c03a554741f1..2f1491bdd5f3 100644 --- a/src/stories/PopoverMenu.stories.js +++ b/src/stories/PopoverMenu.stories.tsx @@ -1,36 +1,40 @@ +import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React from 'react'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import PopoverMenu from '@components/PopoverMenu'; +import type {PopoverMenuProps} from '@components/PopoverMenu'; // eslint-disable-next-line no-restricted-imports import themeColors from '@styles/theme/themes/dark'; +type PopoverMenuStory = ComponentStory; + /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -const story = { +const story: ComponentMeta = { title: 'Components/PopoverMenu', component: PopoverMenu, }; -function Template(args) { +function Template(args: PopoverMenuProps) { const [isVisible, setIsVisible] = React.useState(false); const toggleVisibility = () => setIsVisible(!isVisible); return ( <> ; + /** * We use the Component Story Format for writing stories. Follow the docs here: * @@ -23,27 +27,27 @@ export default { }, }; -function Template(args) { +function Template(args: SubscriptAvatarProps) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Default = Template.bind({}); +const Default: SubscriptAvatarStory = Template.bind({}); -const AvatarURLStory = Template.bind({}); +const AvatarURLStory: SubscriptAvatarStory = Template.bind({}); AvatarURLStory.args = { mainAvatar: {source: defaultAvatars.Avatar1, name: '', type: CONST.ICON_TYPE_AVATAR}, secondaryAvatar: {source: defaultAvatars.Avatar3, name: '', type: CONST.ICON_TYPE_AVATAR}, }; -const SubscriptIcon = Template.bind({}); +const SubscriptIcon: SubscriptAvatarStory = Template.bind({}); SubscriptIcon.args = { subscriptIcon: {source: Expensicons.DownArrow, width: 8, height: 8}, }; -const WorkspaceSubscriptIcon = Template.bind({}); +const WorkspaceSubscriptIcon: SubscriptAvatarStory = Template.bind({}); WorkspaceSubscriptIcon.args = { mainAvatar: {source: defaultAvatars.Avatar1, name: '', type: CONST.ICON_TYPE_WORKSPACE}, subscriptIcon: {source: Expensicons.DownArrow, width: 8, height: 8}, From f86fbdb470d32bfd32b95675d807661cbc914faa Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 6 Mar 2024 11:35:06 +0100 Subject: [PATCH 173/674] Add return type and change comment --- src/libs/actions/ReimbursementAccount/index.ts | 2 +- src/libs/actions/ReimbursementAccount/navigation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index dd1c784d2218..9edf1d9dbcd9 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -14,7 +14,7 @@ export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidation * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid */ -function setBankAccountSubStep(subStep: BankAccountSubStep | null) { +function setBankAccountSubStep(subStep: BankAccountSubStep | null): Promise { return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); } diff --git a/src/libs/actions/ReimbursementAccount/navigation.ts b/src/libs/actions/ReimbursementAccount/navigation.ts index 2c3eb7cf0384..49cf17fcc5bf 100644 --- a/src/libs/actions/ReimbursementAccount/navigation.ts +++ b/src/libs/actions/ReimbursementAccount/navigation.ts @@ -15,7 +15,7 @@ function goToWithdrawalAccountSetupStep(stepID: BankAccountStep) { * Navigate to the correct bank account route based on the bank account state and type * * @param policyID - The policy ID associated with the bank account. - * @param [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + * @param [backTo] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. */ function navigateToBankAccountRoute(policyID: string, backTo?: string) { Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); From fe7c953e9fd0206dd5dd308a5cd5e2d8c4309613 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 6 Mar 2024 11:43:30 +0100 Subject: [PATCH 174/674] Rename args to props --- src/stories/CheckboxWithLabel.stories.tsx | 4 ++-- src/stories/MenuItem.stories.tsx | 4 ++-- src/stories/OptionRow.stories.tsx | 4 ++-- src/stories/PopoverMenu.stories.tsx | 4 ++-- src/stories/SubscriptAvatar.stories.tsx | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/stories/CheckboxWithLabel.stories.tsx b/src/stories/CheckboxWithLabel.stories.tsx index b5e8bc72f380..8d3c1610e500 100644 --- a/src/stories/CheckboxWithLabel.stories.tsx +++ b/src/stories/CheckboxWithLabel.stories.tsx @@ -18,9 +18,9 @@ const story: ComponentMeta = { component: CheckboxWithLabel, }; -function Template(args: CheckboxWithLabelProps) { +function Template(props: CheckboxWithLabelProps) { // eslint-disable-next-line react/jsx-props-no-spreading - return ; + return ; } // Arguments can be passed to the component by binding diff --git a/src/stories/MenuItem.stories.tsx b/src/stories/MenuItem.stories.tsx index 4e02bcaf785f..da486656cddf 100644 --- a/src/stories/MenuItem.stories.tsx +++ b/src/stories/MenuItem.stories.tsx @@ -17,9 +17,9 @@ const story: ComponentMeta = { component: MenuItem, }; -function Template(args: MenuItemProps) { +function Template(props: MenuItemProps) { // eslint-disable-next-line react/jsx-props-no-spreading - return ; + return ; } // Arguments can be passed to the component by binding diff --git a/src/stories/OptionRow.stories.tsx b/src/stories/OptionRow.stories.tsx index d2fffcd583dd..ea83816ab340 100644 --- a/src/stories/OptionRow.stories.tsx +++ b/src/stories/OptionRow.stories.tsx @@ -43,10 +43,10 @@ export default { }, }; -function Template(args: OptionRowProps) { +function Template(props: OptionRowProps) { return ( - + ); } diff --git a/src/stories/PopoverMenu.stories.tsx b/src/stories/PopoverMenu.stories.tsx index 2f1491bdd5f3..8396a0ea15b5 100644 --- a/src/stories/PopoverMenu.stories.tsx +++ b/src/stories/PopoverMenu.stories.tsx @@ -20,7 +20,7 @@ const story: ComponentMeta = { component: PopoverMenu, }; -function Template(args: PopoverMenuProps) { +function Template(props: PopoverMenuProps) { const [isVisible, setIsVisible] = React.useState(false); const toggleVisibility = () => setIsVisible(!isVisible); return ( @@ -34,7 +34,7 @@ function Template(args: PopoverMenuProps) { ; + return ; } // Arguments can be passed to the component by binding From 01ca51592899313913c58ad31e5fa072615b0083 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 6 Mar 2024 21:54:40 +0530 Subject: [PATCH 175/674] deleted unused walletTermsPropTypes --- .../EnablePayments/walletTermsPropTypes.js | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/pages/EnablePayments/walletTermsPropTypes.js diff --git a/src/pages/EnablePayments/walletTermsPropTypes.js b/src/pages/EnablePayments/walletTermsPropTypes.js deleted file mode 100644 index 4420a2dd0861..000000000000 --- a/src/pages/EnablePayments/walletTermsPropTypes.js +++ /dev/null @@ -1,18 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import CONST from '@src/CONST'; - -/** Prop types related to the Terms step of KYC flow */ -export default PropTypes.shape({ - /** Any error message to show */ - errors: PropTypes.objectOf(PropTypes.string), - - /** The source that triggered the KYC wall */ - source: PropTypes.oneOf(_.values(CONST.KYC_WALL_SOURCE)), - - /** When the user accepts the Wallet's terms in order to pay an IOU, this is the ID of the chatReport the IOU is linked to */ - chatReportID: PropTypes.string, - - /** Boolean to indicate whether the submission of wallet terms is being processed */ - isLoading: PropTypes.bool, -}); From 54c7a4cb0d2dae6e9f56761c10f67d6040c43b4c Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Wed, 6 Mar 2024 13:34:50 -0300 Subject: [PATCH 176/674] Early return, move NVP constants, only resolve promise when set is done --- src/ONYXKEYS.ts | 39 ++++++++++++++------------- src/libs/migrations/NVPMigration.ts | 42 +++++++++++++++-------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 031759c2b4eb..33f38e0f5c91 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -16,9 +16,6 @@ const ONYXKEYS = { /** Holds the reportID for the report between the user and their account manager */ ACCOUNT_MANAGER_REPORT_ID: 'accountManagerReportID', - /** Boolean flag only true when first set */ - NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser', - /** Holds an array of client IDs which is used for multi-tabs on web in order to know * which tab is the leader, and which ones are the followers */ ACTIVE_CLIENTS: 'activeClients', @@ -106,7 +103,11 @@ const ONYXKEYS = { STASHED_SESSION: 'stashedSession', BETAS: 'betas', - /** NVP keys + /** NVP keys */ + + /** Boolean flag only true when first set */ + NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser', + /** Contains the user preference for the LHN priority mode */ NVP_PRIORITY_MODE: 'nvp_priorityMode', @@ -134,6 +135,21 @@ const ONYXKEYS = { /** This NVP contains the referral banners the user dismissed */ NVP_DISMISSED_REFERRAL_BANNERS: 'nvp_dismissedReferralBanners', + /** Indicates which locale should be used */ + NVP_PREFERRED_LOCALE: 'nvp_preferredLocale', + + /** Whether the user has tried focus mode yet */ + NVP_TRY_FOCUS_MODE: 'nvp_tryFocusMode', + + /** Whether the user has been shown the hold educational interstitial yet */ + NVP_HOLD_USE_EXPLAINED: 'holdUseExplained', + + /** Store preferred skintone for emoji */ + PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', + + /** Store frequently used emojis for this user */ + FREQUENTLY_USED_EMOJIS: 'nvp_expensify_frequentlyUsedEmojis', + /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -153,9 +169,6 @@ const ONYXKEYS = { ONFIDO_TOKEN: 'onfidoToken', ONFIDO_APPLICANT_ID: 'onfidoApplicantID', - /** Indicates which locale should be used */ - NVP_PREFERRED_LOCALE: 'nvp_preferredLocale', - /** User's Expensify Wallet */ USER_WALLET: 'userWallet', @@ -177,12 +190,6 @@ const ONYXKEYS = { /** The user's cash card and imported cards (including the Expensify Card) */ CARD_LIST: 'cardList', - /** Whether the user has tried focus mode yet */ - NVP_TRY_FOCUS_MODE: 'nvp_tryFocusMode', - - /** Whether the user has been shown the hold educational interstitial yet */ - NVP_HOLD_USE_EXPLAINED: 'holdUseExplained', - /** Boolean flag used to display the focus mode notification */ FOCUS_MODE_NOTIFICATION: 'focusModeNotification', @@ -195,12 +202,6 @@ const ONYXKEYS = { /** Stores information about the active reimbursement account being set up */ REIMBURSEMENT_ACCOUNT: 'reimbursementAccount', - /** Store preferred skintone for emoji */ - PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', - - /** Store frequently used emojis for this user */ - FREQUENTLY_USED_EMOJIS: 'nvp_expensify_frequentlyUsedEmojis', - /** Stores Workspace ID that will be tied to reimbursement account during setup */ REIMBURSEMENT_ACCOUNT_WORKSPACE_ID: 'reimbursementAccountWorkspaceID', diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index 6be142eb1f2a..26375c1858eb 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -2,6 +2,7 @@ import after from 'lodash/after'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +// These are the oldKeyName: newKeyName of the NVPs we can migrate without any processing const migrations = { // eslint-disable-next-line @typescript-eslint/naming-convention nvp_lastPaymentMethod: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, @@ -30,14 +31,15 @@ export default function () { key: oldKey, callback: (value) => { Onyx.disconnect(connectionID); - if (value !== null) { - // @ts-expect-error These keys are variables, so we can't check the type - Onyx.multiSet({ - [newKey]: value, - [oldKey]: null, - }); + if (value === null) { + resolveWhenDone(); + return; } - resolveWhenDone(); + // @ts-expect-error These keys are variables, so we can't check the type + Onyx.multiSet({ + [newKey]: value, + [oldKey]: null, + }).then(resolveWhenDone); }, }); } @@ -46,18 +48,19 @@ export default function () { callback: (value) => { Onyx.disconnect(connectionIDAccount); // @ts-expect-error we are removing this property, so it is not in the type anymore - if (value?.activePolicyID) { - // @ts-expect-error we are removing this property, so it is not in the type anymore - const activePolicyID = value.activePolicyID; - const newValue = {...value}; - // @ts-expect-error we are removing this property, so it is not in the type anymore - delete newValue.activePolicyID; - Onyx.multiSet({ - [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: activePolicyID, - [ONYXKEYS.ACCOUNT]: newValue, - }); + if (!value?.activePolicyID) { + resolveWhenDone(); + return; } - resolveWhenDone(); + // @ts-expect-error we are removing this property, so it is not in the type anymore + const activePolicyID = value.activePolicyID; + const newValue = {...value}; + // @ts-expect-error we are removing this property, so it is not in the type anymore + delete newValue.activePolicyID; + Onyx.multiSet({ + [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: activePolicyID, + [ONYXKEYS.ACCOUNT]: newValue, + }).then(resolveWhenDone); }, }); const connectionIDRecentlyUsedTags = Onyx.connect({ @@ -76,8 +79,7 @@ export default function () { // @ts-expect-error We have no fixed types here newValue[key] = null; } - Onyx.multiSet(newValue); - resolveWhenDone(); + Onyx.multiSet(newValue).then(resolveWhenDone); }, }); }); From 56b4a8a3b05ea050c92d477ec344617db3b0b97b Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 6 Mar 2024 17:48:38 +0100 Subject: [PATCH 177/674] RoomHeaderAvatars migrated to ts --- src/ROUTES.ts | 2 +- src/components/Avatar.tsx | 44 +-------------- ...HeaderAvatars.js => RoomHeaderAvatars.tsx} | 54 +++++++++---------- src/components/types.ts | 44 +++++++++++++++ 4 files changed, 71 insertions(+), 73 deletions(-) rename src/components/{RoomHeaderAvatars.js => RoomHeaderAvatars.tsx} (72%) create mode 100644 src/components/types.ts diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cfc287ba2cdc..8c4a9c47f570 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -37,7 +37,7 @@ const ROUTES = { }, PROFILE_AVATAR: { route: 'a/:accountID/avatar', - getRoute: (accountID: string) => `a/${accountID}/avatar` as const, + getRoute: (accountID: string | number) => `a/${accountID}/avatar` as const, }, TRANSITION_BETWEEN_APPS: 'transition', diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 2b2d0a60f657..4ce50ecad0cc 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,55 +1,16 @@ import React, {useEffect, useState} from 'react'; -import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; +import type {ImageStyle, StyleProp} from 'react-native'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; -import type {AvatarSource} from '@libs/UserUtils'; -import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; -import type {AvatarType} from '@src/types/onyx/OnyxCommon'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; - -type AvatarProps = { - /** Source for the avatar. Can be a URL or an icon. */ - source?: AvatarSource; - - /** Extra styles to pass to Image */ - imageStyles?: StyleProp; - - /** Additional styles to pass to Icon */ - iconAdditionalStyles?: StyleProp; - - /** Extra styles to pass to View wrapper */ - containerStyles?: StyleProp; - - /** Set the size of Avatar */ - size?: AvatarSizeName; - - /** - * The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' - * If the avatar is type === workspace, this fill color will be ignored and decided based on the name prop. - */ - fill?: string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. - * If the avatar is type === workspace, this fallback icon will be ignored and decided based on the name prop. - */ - fallbackIcon?: AvatarSource; - - /** Used to locate fallback icon in end-to-end tests. */ - fallbackIconTestID?: string; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: AvatarType; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; -}; +import type AvatarProps from './types'; function Avatar({ source, @@ -124,4 +85,3 @@ function Avatar({ Avatar.displayName = 'Avatar'; export default Avatar; -export {type AvatarProps}; diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.tsx similarity index 72% rename from src/components/RoomHeaderAvatars.js rename to src/components/RoomHeaderAvatars.tsx index 64cc9ac7abf3..836e55e79567 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.tsx @@ -1,63 +1,58 @@ -import PropTypes from 'prop-types'; import React, {memo} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import Avatar from './Avatar'; -import avatarPropTypes from './avatarPropTypes'; import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; import Text from './Text'; +import type AvatarProps from './types'; -const propTypes = { - icons: PropTypes.arrayOf(avatarPropTypes), - reportID: PropTypes.string, +type RoomHeaderAvatarsProps = { + icons: AvatarProps[]; + reportID: string; }; -const defaultProps = { - icons: [], - reportID: '', -}; - -function RoomHeaderAvatars(props) { - const navigateToAvatarPage = (icon) => { +function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) { + const navigateToAvatarPage = (icon: AvatarProps) => { if (icon.type === CONST.ICON_TYPE_WORKSPACE) { - Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(props.reportID)); + Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; } - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); + if (icon.id !== undefined) { + Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); + } }; const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - if (!props.icons.length) { + if (icons.length) { return null; } - if (props.icons.length === 1) { + if (icons.length === 1) { return ( navigateToAvatarPage(props.icons[0])} + onPress={() => navigateToAvatarPage(icons[0])} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={props.icons[0].name} + accessibilityLabel={icons[0].name ?? ''} > ); } - const iconsToDisplay = props.icons.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS); + const iconsToDisplay = icons.slice(0, CONST.REPORT.MAX_PREVIEW_AVATARS); const iconStyle = [ styles.roomHeaderAvatar, @@ -68,8 +63,9 @@ function RoomHeaderAvatars(props) { return ( - {_.map(iconsToDisplay, (icon, index) => ( + {iconsToDisplay.map((icon, index) => ( @@ -77,7 +73,7 @@ function RoomHeaderAvatars(props) { style={[styles.mln4, StyleUtils.getAvatarBorderRadius(CONST.AVATAR_SIZE.LARGE_BORDERED, icon.type)]} onPress={() => navigateToAvatarPage(icon)} accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={icon.name} + accessibilityLabel={icon.name ?? ''} > - {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && props.icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && ( + {index === CONST.REPORT.MAX_PREVIEW_AVATARS - 1 && icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS !== 0 && ( <> - {`+${props.icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`} + {`+${icons.length - CONST.REPORT.MAX_PREVIEW_AVATARS}`} )} @@ -110,8 +106,6 @@ function RoomHeaderAvatars(props) { ); } -RoomHeaderAvatars.defaultProps = defaultProps; -RoomHeaderAvatars.propTypes = propTypes; RoomHeaderAvatars.displayName = 'RoomHeaderAvatars'; export default memo(RoomHeaderAvatars); diff --git a/src/components/types.ts b/src/components/types.ts new file mode 100644 index 000000000000..a1cf9c2a0c4f --- /dev/null +++ b/src/components/types.ts @@ -0,0 +1,44 @@ +import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; +import type {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSizeName} from '@styles/utils'; +import type {AvatarType} from '@src/types/onyx/OnyxCommon'; + +type AvatarProps = { + /** Source for the avatar. Can be a URL or an icon. */ + source?: AvatarSource; + + /** Extra styles to pass to Image */ + imageStyles?: StyleProp; + + /** Additional styles to pass to Icon */ + iconAdditionalStyles?: StyleProp; + + /** Extra styles to pass to View wrapper */ + containerStyles?: StyleProp; + + /** Set the size of Avatar */ + size?: AvatarSizeName; + + /** + * The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' + * If the avatar is type === workspace, this fill color will be ignored and decided based on the name prop. + */ + fill?: string; + + /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. + * If the avatar is type === workspace, this fallback icon will be ignored and decided based on the name prop. + */ + fallbackIcon?: AvatarSource; + + /** Used to locate fallback icon in end-to-end tests. */ + fallbackIconTestID?: string; + + /** Denotes whether it is an avatar or a workspace avatar */ + type?: AvatarType; + + /** Owner of the avatar. If user, displayName. If workspace, policy name */ + name?: string; + id?: string | number; +}; + +export default AvatarProps; From be58c4f67eaf2c6075da772f4554cc8693c99d3b Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Wed, 6 Mar 2024 20:44:41 +0100 Subject: [PATCH 178/674] Update src/libs/migrations/NVPMigration.ts Co-authored-by: Tim Golen --- src/libs/migrations/NVPMigration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index 26375c1858eb..9ab774328f78 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -22,7 +22,7 @@ const migrations = { // This migration changes the keys of all the NVP related keys so that they are standardized export default function () { return new Promise((resolve) => { - // We add the number of manual connections we add below + // Resolve the migration when all the keys have been migrated. The number of keys is the size of the `migrations` object in addition to the ACCOUNT and OLD_POLICY_RECENTLY_USED_TAGS keys (which is why there is a +2). const resolveWhenDone = after(Object.entries(migrations).length + 2, () => resolve()); for (const [oldKey, newKey] of Object.entries(migrations)) { From 89418e8070cbcd569de4a039148fba593efd369a Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Wed, 6 Mar 2024 13:33:17 -0800 Subject: [PATCH 179/674] Add and update comments for report action params --- src/libs/ReportUtils.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 350a08b43821..1508f5362406 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2331,18 +2331,21 @@ function getTransactionReportName(reportAction: OnyxEntry | EmptyObject, - reportAction: OnyxEntry | EmptyObject = {}, + iouReportAction: OnyxEntry | EmptyObject = {}, shouldConsiderScanningReceiptOrPendingRoute = false, isPreviewMessageForParentChatReport = false, policy: OnyxEntry = null, isForListPreview = false, - originalReportAction: OnyxEntry | EmptyObject = reportAction, + originalReportAction: OnyxEntry | EmptyObject = iouReportAction, ): string { - const reportActionMessage = reportAction?.message?.[0].html ?? ''; + const reportActionMessage = iouReportAction?.message?.[0].html ?? ''; if (isEmptyObject(report) || !report?.reportID) { // The iouReport is not found locally after SignIn because the OpenApp API won't return iouReports if they're settled @@ -2350,9 +2353,9 @@ function getReportPreviewMessage( return reportActionMessage; } - if (!isEmptyObject(reportAction) && !isIOUReport(report) && reportAction && ReportActionsUtils.isSplitBillAction(reportAction)) { + if (!isEmptyObject(iouReportAction) && !isIOUReport(report) && iouReportAction && ReportActionsUtils.isSplitBillAction(iouReportAction)) { // This covers group chats where the last action is a split bill action - const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + const linkedTransaction = TransactionUtils.getLinkedTransaction(iouReportAction); if (isEmptyObject(linkedTransaction)) { return reportActionMessage; } @@ -2387,8 +2390,8 @@ function getReportPreviewMessage( } let linkedTransaction; - if (!isEmptyObject(reportAction) && shouldConsiderScanningReceiptOrPendingRoute && reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction)) { - linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + if (!isEmptyObject(iouReportAction) && shouldConsiderScanningReceiptOrPendingRoute && iouReportAction && ReportActionsUtils.isMoneyRequestAction(iouReportAction)) { + linkedTransaction = TransactionUtils.getLinkedTransaction(iouReportAction); } if (!isEmptyObject(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { @@ -2399,7 +2402,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.routePending'); } - const originalMessage = reportAction?.originalMessage as IOUMessage | undefined; + const originalMessage = iouReportAction?.originalMessage as IOUMessage | undefined; // Show Paid preview message if it's settled or if the amount is paid & stuck at receivers end for only chat reports. if (isSettled(report.reportID) || (report.isWaitingOnBankAccount && isPreviewMessageForParentChatReport)) { @@ -2427,7 +2430,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.waitingOnBankAccount', {submitterDisplayName}); } - const lastActorID = reportAction?.actorAccountID; + const lastActorID = iouReportAction?.actorAccountID; let amount = originalMessage?.amount; let currency = originalMessage?.currency ? originalMessage?.currency : report.currency; @@ -2436,8 +2439,8 @@ function getReportPreviewMessage( currency = TransactionUtils.getCurrency(linkedTransaction); } - if (isEmptyObject(linkedTransaction) && !isEmptyObject(reportAction)) { - linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); + if (isEmptyObject(linkedTransaction) && !isEmptyObject(iouReportAction)) { + linkedTransaction = TransactionUtils.getLinkedTransaction(iouReportAction); } let comment = !isEmptyObject(linkedTransaction) ? TransactionUtils.getDescription(linkedTransaction) : undefined; From 71dcc0364383f10e39cd8f0b05f9ba9c8459b1c2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 7 Mar 2024 15:52:09 +0700 Subject: [PATCH 180/674] fix in app sound is played if user not viewing chat --- src/libs/actions/User.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 708fc5e8591d..e347fddfb4a7 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -30,6 +30,7 @@ import PusherUtils from '@libs/PusherUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import playSoundExcludingMobile from '@libs/Sound/playSoundExcludingMobile'; +import Visibility from '@libs/Visibility'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -489,7 +490,11 @@ const isChannelMuted = (reportId: string) => function playSoundForMessageType(pushJSON: OnyxServerUpdate[]) { const reportActionsOnly = pushJSON.filter((update) => update.key?.includes('reportActions_')); // "reportActions_5134363522480668" -> "5134363522480668" - const reportIDs = reportActionsOnly.map((value) => value.key.split('_')[1]); + const reportIDs = reportActionsOnly + .map((value) => value.key.split('_')[1]) + .filter((reportID) => { + return reportID === Navigation.getTopmostReportId() && Visibility.isVisible() && Visibility.hasFocus(); + }); Promise.all(reportIDs.map((reportID) => isChannelMuted(reportID))) .then((muted) => muted.every((isMuted) => isMuted)) From 0873e42968a2ed50b410973afd4687d81e843bb9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 7 Mar 2024 16:16:41 +0700 Subject: [PATCH 181/674] fix lint --- src/libs/actions/User.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index e347fddfb4a7..77efb30ae874 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -492,9 +492,7 @@ function playSoundForMessageType(pushJSON: OnyxServerUpdate[]) { // "reportActions_5134363522480668" -> "5134363522480668" const reportIDs = reportActionsOnly .map((value) => value.key.split('_')[1]) - .filter((reportID) => { - return reportID === Navigation.getTopmostReportId() && Visibility.isVisible() && Visibility.hasFocus(); - }); + .filter((reportID) => reportID === Navigation.getTopmostReportId() && Visibility.isVisible() && Visibility.hasFocus()); Promise.all(reportIDs.map((reportID) => isChannelMuted(reportID))) .then((muted) => muted.every((isMuted) => isMuted)) From fa08a81639143936fe795593ecf386c9500e924d Mon Sep 17 00:00:00 2001 From: smelaa Date: Thu, 7 Mar 2024 11:37:29 +0100 Subject: [PATCH 182/674] Address reviewer's comments --- src/components/Avatar.tsx | 47 ++++++++++++++++++++++++++-- src/components/RoomHeaderAvatars.tsx | 6 ++-- src/components/types.ts | 44 -------------------------- 3 files changed, 48 insertions(+), 49 deletions(-) delete mode 100644 src/components/types.ts diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 4ce50ecad0cc..e5725b779110 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,16 +1,58 @@ import React, {useEffect, useState} from 'react'; -import type {ImageStyle, StyleProp} from 'react-native'; import {View} from 'react-native'; +import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; +import type {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; +import type {AvatarType} from '@src/types/onyx/OnyxCommon'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; -import type AvatarProps from './types'; + +type AvatarProps = { + /** Source for the avatar. Can be a URL or an icon. */ + source?: AvatarSource; + + /** Extra styles to pass to Image */ + imageStyles?: StyleProp; + + /** Additional styles to pass to Icon */ + iconAdditionalStyles?: StyleProp; + + /** Extra styles to pass to View wrapper */ + containerStyles?: StyleProp; + + /** Set the size of Avatar */ + size?: AvatarSizeName; + + /** + * The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' + * If the avatar is type === workspace, this fill color will be ignored and decided based on the name prop. + */ + fill?: string; + + /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. + * If the avatar is type === workspace, this fallback icon will be ignored and decided based on the name prop. + */ + fallbackIcon?: AvatarSource; + + /** Used to locate fallback icon in end-to-end tests. */ + fallbackIconTestID?: string; + + /** Denotes whether it is an avatar or a workspace avatar */ + type?: AvatarType; + + /** Owner of the avatar. If user, displayName. If workspace, policy name */ + name?: string; + // this prop is used in RoomHeaderAvatars + // eslint-disable-next-line react/no-unused-prop-types + id?: string | number; +}; function Avatar({ source, @@ -85,3 +127,4 @@ function Avatar({ Avatar.displayName = 'Avatar'; export default Avatar; +export {type AvatarProps}; diff --git a/src/components/RoomHeaderAvatars.tsx b/src/components/RoomHeaderAvatars.tsx index 836e55e79567..dd986ce39e31 100644 --- a/src/components/RoomHeaderAvatars.tsx +++ b/src/components/RoomHeaderAvatars.tsx @@ -5,10 +5,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type {AvatarProps} from './Avatar'; import Avatar from './Avatar'; import PressableWithoutFocus from './Pressable/PressableWithoutFocus'; import Text from './Text'; -import type AvatarProps from './types'; type RoomHeaderAvatarsProps = { icons: AvatarProps[]; @@ -21,14 +21,14 @@ function RoomHeaderAvatars({icons = [], reportID = ''}: RoomHeaderAvatarsProps) Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID)); return; } - if (icon.id !== undefined) { + if (icon.id) { Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(icon.id)); } }; const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - if (icons.length) { + if (!icons.length) { return null; } diff --git a/src/components/types.ts b/src/components/types.ts deleted file mode 100644 index a1cf9c2a0c4f..000000000000 --- a/src/components/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type {ImageStyle, StyleProp, ViewStyle} from 'react-native'; -import type {AvatarSource} from '@libs/UserUtils'; -import type {AvatarSizeName} from '@styles/utils'; -import type {AvatarType} from '@src/types/onyx/OnyxCommon'; - -type AvatarProps = { - /** Source for the avatar. Can be a URL or an icon. */ - source?: AvatarSource; - - /** Extra styles to pass to Image */ - imageStyles?: StyleProp; - - /** Additional styles to pass to Icon */ - iconAdditionalStyles?: StyleProp; - - /** Extra styles to pass to View wrapper */ - containerStyles?: StyleProp; - - /** Set the size of Avatar */ - size?: AvatarSizeName; - - /** - * The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue' - * If the avatar is type === workspace, this fill color will be ignored and decided based on the name prop. - */ - fill?: string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. - * If the avatar is type === workspace, this fallback icon will be ignored and decided based on the name prop. - */ - fallbackIcon?: AvatarSource; - - /** Used to locate fallback icon in end-to-end tests. */ - fallbackIconTestID?: string; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: AvatarType; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - id?: string | number; -}; - -export default AvatarProps; From 5770a2f2a4cfe66a91f5d2ec39e7d7a09099d053 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 7 Mar 2024 15:10:22 +0100 Subject: [PATCH 183/674] Remove unused functions --- src/libs/actions/BankAccounts.ts | 1 - .../deleteFromBankAccountList.ts | 16 -------- .../actions/ReimbursementAccount/errors.ts | 21 +--------- .../actions/ReimbursementAccount/index.ts | 18 +------- .../actions/ReimbursementAccount/store.ts | 41 +------------------ 5 files changed, 4 insertions(+), 93 deletions(-) delete mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 0f4e1aed36a7..878e1a443181 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -29,7 +29,6 @@ export { resetReimbursementAccount, resetFreePlanBankAccount, hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, setBankAccountSubStep, updateReimbursementAccountDraft, requestResetFreePlanBankAccount, diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts deleted file mode 100644 index d9a2dd130d62..000000000000 --- a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as store from './store'; - -/** - * Deletes a bank account from bankAccountList - */ -function deleteFromBankAccountList(bankAccountID: number) { - // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx - const bankAccountList = store.getBankAccountList(); - delete bankAccountList?.[bankAccountID]; - - Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); -} - -export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index 05c375364329..2ab99c847291 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -1,17 +1,7 @@ import Onyx from 'react-native-onyx'; -import * as ErrorUtils from '@libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; -/** - * Set the current fields with errors. - */ -function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { - // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); -} - /** * Set the current fields with errors. @@ -32,13 +22,4 @@ function resetReimbursementAccount() { }); } -/** - * Set the current error message. - */ -function showBankAccountFormValidationError(error: string | null) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: ErrorUtils.getMicroSecondOnyxError(error), - }); -} - -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; +export {setBankAccountFormValidationErrors, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 9edf1d9dbcd9..57d785c5d1c9 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -2,11 +2,10 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; import type {BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; -import deleteFromBankAccountList from './deleteFromBankAccountList'; import resetFreePlanBankAccount from './resetFreePlanBankAccount'; export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; +export {setBankAccountFormValidationErrors, resetReimbursementAccount} from './errors'; /** * Set the current sub step in first step of adding withdrawal bank account: @@ -22,10 +21,6 @@ function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); } -function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); -} - function updateReimbursementAccountDraft(bankAccountData: Partial) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); @@ -45,13 +40,4 @@ function cancelResetFreePlanBankAccount() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); } -export { - resetFreePlanBankAccount, - setBankAccountSubStep, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, - deleteFromBankAccountList, -}; +export {resetFreePlanBankAccount, setBankAccountSubStep, hideBankAccountErrors, updateReimbursementAccountDraft, requestResetFreePlanBankAccount, cancelResetFreePlanBankAccount}; diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts index 30005b6fdc04..6e14cf72a569 100644 --- a/src/libs/actions/ReimbursementAccount/store.ts +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -3,25 +3,6 @@ import Onyx from 'react-native-onyx'; import BankAccount from '@libs/models/BankAccount'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {ACHData} from '@src/types/onyx/ReimbursementAccount'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; - -/** Reimbursement account actively being set up */ -let reimbursementAccountInSetup: ACHData | EmptyObject = {}; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - callback: (val) => { - reimbursementAccountInSetup = val?.achData ?? {}; - }, -}); - -let reimbursementAccountWorkspaceID: OnyxEntry = null; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, - callback: (val) => { - reimbursementAccountWorkspaceID = val; - }, -}); let bankAccountList: OnyxEntry = null; Onyx.connect({ @@ -31,18 +12,6 @@ Onyx.connect({ }, }); -let credentials: OnyxEntry = null; -Onyx.connect({ - key: ONYXKEYS.CREDENTIALS, - callback: (val) => { - credentials = val; - }, -}); - -function getReimbursementAccountInSetup() { - return reimbursementAccountInSetup; -} - function getBankAccountList() { return bankAccountList; } @@ -58,12 +27,4 @@ function hasCreditBankAccount() { }); } -function getCredentials() { - return credentials; -} - -function getReimbursementAccountWorkspaceID() { - return reimbursementAccountWorkspaceID; -} - -export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; +export {getBankAccountList, hasCreditBankAccount}; From c14fbf85cbf91608e7b7f4ed7b2a6426a3bc57ed Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 7 Mar 2024 15:14:11 +0100 Subject: [PATCH 184/674] feat: PolicyNewDistanceRatePage --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../CreatePolicyDistanceRateParams.ts | 9 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/libs/actions/Policy.ts | 51 +++++++++++ .../distanceRates/PolicyDistanceRatesPage.tsx | 4 +- .../PolicyNewDistanceRatePage.tsx | 86 +++++++++++++++++++ .../form/PolicyCreateDistanceRateForm.ts | 18 ++++ src/types/form/index.ts | 1 + 15 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/libs/API/parameters/CreatePolicyDistanceRateParams.ts create mode 100644 src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx create mode 100644 src/types/form/PolicyCreateDistanceRateForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f6b5c635e4ae..ce2a2be262e9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -331,6 +331,8 @@ const ONYXKEYS = { WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft', WORKSPACE_RATE_AND_UNIT_FORM: 'workspaceRateAndUnitForm', WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft', + POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', + POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', CLOSE_ACCOUNT_FORM: 'closeAccount', CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft', PROFILE_SETTINGS_FORM: 'profileSettingsForm', @@ -443,6 +445,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fee69b9d785a..61dc44ac0432 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -554,6 +554,10 @@ const ROUTES = { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, }, + WORKSPACE_CREATE_DISTANCE_RATE: { + route: 'workspace/:policyID/distance-rates/new', + getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/new` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index caedab241349..4d572a65e24f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -226,6 +226,7 @@ const SCREENS = { CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', DISTANCE_RATES: 'Distance_Rates', + CREATE_DISTANCE_RATE: 'Create_Distance_Rate', }, EDIT_REQUEST: { diff --git a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts new file mode 100644 index 000000000000..ea06408d4402 --- /dev/null +++ b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts @@ -0,0 +1,9 @@ +import type {Rate} from '@src/types/onyx/Policy'; + +type CreatePolicyDistanceRateParams = { + policyID: string; + customUnitID: string; + customUnitRate: Rate; +}; + +export default CreatePolicyDistanceRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 7c4d592fe48d..34cc473b5d50 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -156,3 +156,4 @@ export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './Se export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; +export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4cec18ae646..dce2592eae08 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -156,6 +156,7 @@ const WRITE_COMMANDS = { CANCEL_PAYMENT: 'CancelPayment', ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount', SWITCH_TO_OLD_DOT: 'SwitchToOldDot', + CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', } as const; type WriteCommand = ValueOf; @@ -310,6 +311,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; + [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 545641957c9a..c6cf3d515336 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -251,6 +251,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 7959999ee813..1d2cad900ff1 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -7,6 +7,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 927b5b509277..a99d3e9cb695 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -283,6 +283,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route, }, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + path: ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.route, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f9b70b4b7ac8..73debd9d3897 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -207,6 +207,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b94ae68b04ac..2f9005633b48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -10,6 +10,7 @@ import type {ValueOf} from 'type-fest'; import * as API from '@libs/API'; import type { AddMembersToWorkspaceParams, + CreatePolicyDistanceRateParams, CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, @@ -2514,6 +2515,54 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } +function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + ...customUnitRate, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), + pendingAction: null, + }, + }, + }, + }, + }, + }, + ]; + + const params: CreatePolicyDistanceRateParams = { + policyID, + customUnitID, + customUnitRate, + }; + + API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, failureData}); +} + export { removeMembers, updateWorkspaceMembersRole, @@ -2565,4 +2614,6 @@ export { setWorkspaceRequiresCategory, clearCategoryErrors, openPolicyDistanceRatesPage, + generateCustomUnitID, + createPolicyDistanceRate, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 08cd3dffe709..8c81ff7765b5 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -19,6 +19,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; @@ -26,6 +27,7 @@ import {openPolicyDistanceRatesPage} from '@userActions/Policy'; import ButtonWithDropdownMenu from '@src/components/ButtonWithDropdownMenu'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type Policy from '@src/types/onyx/Policy'; import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; @@ -91,7 +93,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); const addRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); + Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); }; const openSettings = () => { diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx new file mode 100644 index 000000000000..8e794e51674d --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -0,0 +1,86 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import {createPolicyDistanceRate, generateCustomUnitID} from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm'; +import type {Rate} from '@src/types/onyx/Policy'; +import type Policy from '@src/types/onyx/Policy'; + +type PolicyNewDistanceRatePageOnyxProps = { + policy: OnyxEntry; +}; + +type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScreenProps; + +function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; + const customUnits = policy?.customUnits ?? {}; + const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; + const customUnitRateID = generateCustomUnitID(); + + const submit = (values: FormOnyxValues) => { + const newRate: Rate = { + currency, + name: CONST.CUSTOM_UNITS.DEFAULT_RATE, + rate: Number(values.rate), + customUnitRateID, + }; + + createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + Navigation.goBack(); + }; + + return ( + + + + + + + + + + + ); +} + +PolicyNewDistanceRatePage.displayName = 'CreateDistanceRatePage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyNewDistanceRatePage); diff --git a/src/types/form/PolicyCreateDistanceRateForm.ts b/src/types/form/PolicyCreateDistanceRateForm.ts new file mode 100644 index 000000000000..11bb9f4aa83c --- /dev/null +++ b/src/types/form/PolicyCreateDistanceRateForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + RATE: 'rate', +} as const; + +type InputID = ValueOf; + +type PolicyCreateDistanceRateForm = Form< + InputID, + { + [INPUT_IDS.RATE]: string; + } +>; + +export type {PolicyCreateDistanceRateForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 1ff8d0df2031..bf58c78d0b90 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -37,4 +37,5 @@ export type {WorkspaceRateAndUnitForm} from './WorkspaceRateAndUnitForm'; export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; +export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; export type {default as Form} from './Form'; From b8065489197501e2b65d40dd42ee882211dd4a57 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 7 Mar 2024 22:23:01 +0800 Subject: [PATCH 185/674] don't clear the optimistic personal detail if fail when inviting new user --- src/libs/actions/Policy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 38cc11756068..db62c68291a0 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -948,7 +948,6 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount // need to remove the members since that is handled by onClose of OfflineWithFeedback. value: failureMembersState, }, - ...newPersonalDetailsOnyxData.finallyData, ...membersChats.onyxFailureData, ...announceRoomMembers.onyxFailureData, ]; From b7cdc1b51d9ae04112c81c7c468de55dd29fe372 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:26:24 +0100 Subject: [PATCH 186/674] add initial new tax page --- src/CONST.ts | 4 + src/ONYXKEYS.ts | 2 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + src/languages/en.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + .../workspace/taxes/WorkspaceNewTaxPage.tsx | 96 +++++++++++++++++++ .../workspace/taxes/WorkspaceTaxesPage.tsx | 6 +- src/types/form/WorkspaceNewTaxForm.ts | 22 +++++ src/types/form/index.ts | 1 + src/types/onyx/Policy.ts | 2 +- 13 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx create mode 100644 src/types/form/WorkspaceNewTaxForm.ts diff --git a/src/CONST.ts b/src/CONST.ts index 6861fe174ffe..b981d10e5be3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4021,6 +4021,10 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', }, + + TAX_RATES: { + NAME_MAX_LENGTH: 50, + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 188ab5646d30..05b1e1bec1f9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -404,6 +404,7 @@ const ONYXKEYS = { EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', + WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', }, } as const; @@ -449,6 +450,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9704bf047315..f0f6fed658f9 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -558,6 +558,10 @@ const ROUTES = { route: 'workspace/:policyID/taxes', getRoute: (policyID: string) => `workspace/${policyID}/taxes` as const, }, + WORKSPACE_TAXES_NEW: { + route: 'workspace/:policyID/taxes/new', + getRoute: (policyID: string) => `workspace/${policyID}/taxes/new` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 27909319b724..d48cc9c19b83 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -216,6 +216,7 @@ const SCREENS = { CATEGORIES: 'Workspace_Categories', TAGS: 'Workspace_Tags', TAXES: 'Workspace_Taxes', + TAXES_NEW: 'Workspace_Taxes_New', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver', diff --git a/src/languages/en.ts b/src/languages/en.ts index 715640bbb200..56c983ace282 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1787,6 +1787,8 @@ export default { }, taxes: { subtitle: 'Add tax names, rates, and set defaults.', + value: 'Value', + name: 'Name', }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index cee9e31cedea..001891805bb2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -262,6 +262,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_NEW]: () => require('../../../pages/workspace/taxes/WorkspaceNewTaxPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 0cae5e34e958..76f605c648b8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -309,6 +309,9 @@ const config: LinkingOptions['config'] = { [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: { path: ROUTES.SETTINGS_EXIT_SURVEY_CONFIRM.route, }, + [SCREENS.WORKSPACE.TAXES_NEW]: { + path: ROUTES.WORKSPACE_TAXES_NEW.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 5c64dd62fd43..fcd69c49a135 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -225,6 +225,9 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: { backTo: Routes; }; + [SCREENS.WORKSPACE.TAXES_NEW]: { + policyID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx new file mode 100644 index 000000000000..a23b8d3061de --- /dev/null +++ b/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx @@ -0,0 +1,96 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import AmountPicker from '@components/AmountPicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextPicker from '@components/TextPicker'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {createWorkspaceTax} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceNewTaxForm'; +import type {TaxRate} from '@src/types/onyx'; + +type WorkspaceNewTaxPageProps = StackScreenProps; + +function WorkspaceNewTaxPage({ + route: { + params: {policyID}, + }, +}: WorkspaceNewTaxPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const validate = useCallback((values: FormOnyxValues): Partial> => { + const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.VALUE, INPUT_IDS.NAME]); + + const value = Number(values[INPUT_IDS.VALUE]); + if (value > 100 || value < 0) { + errors[INPUT_IDS.VALUE] = 'workspace.taxes.errors.value.percentageRange'; + } + + return errors; + }, []); + + const submitForm = useCallback( + (values: FormOnyxValues) => { + // TODO: Add proper code generation + const taxRate = { + ...values, + code: `tax_${Date.now()}`, + } satisfies TaxRate; + createWorkspaceTax({policyID, taxRate}); + Navigation.goBack(); + }, + [policyID], + ); + + return ( + + + + + + + + + + ); +} + +WorkspaceNewTaxPage.displayName = 'WorkspaceNewTaxPage'; + +export default WorkspaceNewTaxPage; diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index b19a69adebe2..0f4340b97eb8 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -15,12 +15,14 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import type SCREENS from '@src/SCREENS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; type WorkspaceTaxesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -83,7 +85,7 @@ function WorkspaceTaxesPage({policy}: WorkspaceTaxesPageProps) {