From c8692c0887191667c1335814eb05f2a77c90ee2a Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 27 Oct 2023 00:44:15 +0700 Subject: [PATCH 001/345] 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/345] 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/345] 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/345] 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/345] 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/345] 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/345] 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 8b7f74c22a935f44879af65c652dec2c32360bbf Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 4 Dec 2023 15:58:16 +0700 Subject: [PATCH 008/345] 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 009/345] 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 010/345] 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 011/345] 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 012/345] 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 013/345] 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 014/345] 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 015/345] 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 016/345] 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 017/345] 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 018/345] 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 019/345] 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 020/345] 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 021/345] 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 022/345] 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 023/345] 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 024/345] 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 025/345] 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 026/345] 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 027/345] 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 028/345] 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 029/345] 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 030/345] 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 031/345] 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 9bd67d67aee253e4614ef64854169229282f806d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 5 Feb 2024 16:14:46 +0530 Subject: [PATCH 032/345] 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 033/345] 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 034/345] 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 1ad742de9d5b27113e74d85691016089ff0d89ae Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 6 Feb 2024 08:08:34 -0800 Subject: [PATCH 035/345] 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 5f89ad6cf8fdcc0a4a5522a35588a36c40670f70 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Thu, 8 Feb 2024 17:16:50 +0530 Subject: [PATCH 036/345] 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 037/345] 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 038/345] 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 039/345] 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 040/345] 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 041/345] 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 2c34dbf214c8511caa8357cfc186aacec0b0fef1 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Fri, 16 Feb 2024 14:39:23 +0530 Subject: [PATCH 042/345] 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 043/345] 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 b258238c5c7150ad34fef64b086d1f649be3266d Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 16 Feb 2024 17:34:00 -0700 Subject: [PATCH 044/345] 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 045/345] 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 046/345] 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 047/345] 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 24fb379d31f4fa6647a12795ff7308be4cdd8f47 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 21 Feb 2024 19:48:37 +0530 Subject: [PATCH 048/345] 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 708e818589c46508701cf0c6bfc0c93bbef437c8 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Wed, 21 Feb 2024 20:46:53 +0530 Subject: [PATCH 049/345] 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 ce8832837a03264d7a95085f06a247a03fdd0ff2 Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Wed, 21 Feb 2024 09:21:25 -0800 Subject: [PATCH 050/345] 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 579a227a7b8c369fdf376382d5e9066c0cd17745 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 26 Feb 2024 18:54:01 +0700 Subject: [PATCH 051/345] 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 58d5b933dbe07128cf33ee4d161b4c78318bc00b Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 27 Feb 2024 17:54:29 +0700 Subject: [PATCH 052/345] 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 f011e58cac8812b6fd09c2c03a198c5f6ba0b32c Mon Sep 17 00:00:00 2001 From: Toby Sullivan Date: Tue, 27 Feb 2024 08:12:02 -0800 Subject: [PATCH 053/345] 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 054/345] 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 055/345] 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 518153347c6760462a92370b066dbace32a56781 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 28 Feb 2024 18:01:29 +0700 Subject: [PATCH 056/345] 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 057/345] 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 058/345] 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 059/345] 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 060/345] 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 f9dd242735e6e64019807b4c5c981b0c80c89add Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 29 Feb 2024 10:51:11 +0100 Subject: [PATCH 061/345] 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 062/345] 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 063/345] 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 064/345] 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 065/345] 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 066/345] 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 067/345] 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 068/345] 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 069/345] 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 070/345] 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 071/345] 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 39d33deebea4e1a27bf6a83cf58767755576aaf8 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Mar 2024 14:14:34 -0300 Subject: [PATCH 072/345] 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 3053b96a9432b9f5161bcfd3a09699e73f8fc86a Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Mar 2024 14:25:28 -0300 Subject: [PATCH 073/345] 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 074/345] 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 075/345] 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 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 076/345] 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 15:11:32 +0530 Subject: [PATCH 077/345] 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 078/345] 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 8215b5377db03124eed0e166545c5cd2f9d16605 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 4 Mar 2024 12:57:26 +0100 Subject: [PATCH 079/345] 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 0f1fb25ffd036dc02b3e7aba1730a2ee46baeb44 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 4 Mar 2024 20:17:26 +0530 Subject: [PATCH 080/345] 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 e0813e48574bc22e8a14844d8fae4afcd7c86f20 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 4 Mar 2024 14:09:18 +0100 Subject: [PATCH 081/345] 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 082/345] 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 e9140e1aafc1e0e9cb836c34294a7fdbcfa9b14d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 5 Mar 2024 22:02:32 +0530 Subject: [PATCH 083/345] 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 084/345] 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 085/345] 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 086/345] 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 087/345] 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 088/345] 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 7b3c4134c5f03cca3b8f17c5e140d77ea5db1a83 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 6 Mar 2024 11:20:48 +0100 Subject: [PATCH 089/345] 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 fe7c953e9fd0206dd5dd308a5cd5e2d8c4309613 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Wed, 6 Mar 2024 11:43:30 +0100 Subject: [PATCH 090/345] 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 091/345] 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 092/345] 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 093/345] 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 094/345] 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 095/345] 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 096/345] 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 097/345] 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 098/345] 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 17377dae19c912f0932ed6fed7e42fad63c747aa Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:28:47 +0100 Subject: [PATCH 099/345] add type PolicyFeatureName --- src/types/onyx/Policy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index b005a9d2756f..c378c0eb3983 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -82,6 +82,8 @@ type Connection = { type AutoReportingOffset = number | ValueOf; +type PolicyFeatureName = 'areCategoriesEnabled' | 'areTagsEnabled' | 'areDistanceRatesEnabled' | 'areWorkflowsEnabled' | 'areReportFieldsEnabled' | 'areConnectionsEnabled' | 'tax'; + type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The ID of the policy */ @@ -250,4 +252,4 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< export default Policy; -export type {Unit, CustomUnit, Attributes, Rate, TaxRate, TaxRates, TaxRatesWithDefault}; +export type {Unit, CustomUnit, Attributes, Rate, TaxRate, TaxRates, TaxRatesWithDefault, PolicyFeatureName}; From 7ed9fa60385e8802367147dcda6eee703c6a395d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:28:56 +0100 Subject: [PATCH 100/345] add a helper isPolicyFeatureEnabled --- src/libs/PolicyUtils.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index b510edd7dcf4..3dfbc8688297 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -5,6 +5,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; +import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Navigation from './Navigation/Navigation'; @@ -268,6 +269,10 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +function isPolicyFeatureEnabled(policy: OnyxEntry | EmptyObject, featureName: PolicyFeatureName): boolean { + return Boolean(policy?.[featureName]); +} + export { getActivePolicies, hasAccountingConnections, @@ -299,6 +304,7 @@ export { getPathWithoutPolicyID, getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, + isPolicyFeatureEnabled, }; export type {MemberEmailsToAccountIDs}; From c8a73121879bfdf872ba305f5bfe685a86678c93 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:29:06 +0100 Subject: [PATCH 101/345] implement FeatureEnabledAccessOrNotFoundComponent --- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx new file mode 100644 index 000000000000..8f9ff546b98e --- /dev/null +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -0,0 +1,70 @@ +/* eslint-disable rulesdir/no-negated-variables */ +import React, {useEffect} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; +import * as Policy from '@userActions/Policy'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {PolicyFeatureName} from '@src/types/onyx/Policy'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type FeatureEnabledAccessOrNotFoundOnyxProps = { + /** The report currently being looked at */ + policy: OnyxEntry; + + /** Indicated whether the report data is loading */ + isLoadingReportData: OnyxEntry; +}; + +type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFoundOnyxProps & { + /** The children to render */ + children: ((props: FeatureEnabledAccessOrNotFoundOnyxProps) => React.ReactNode) | React.ReactNode; + + /** The report currently being looked at */ + policyID: string; + + /** The current feature name that the user tries to get access */ + featureName: PolicyFeatureName; +}; + +function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNotFoundComponentProps) { + const isPolicyIDInRoute = !!props.policyID?.length; + + useEffect(() => { + if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) { + // If the workspace is not required or is already loaded, we don't need to call the API + return; + } + + Policy.openWorkspace(props.policyID, []); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPolicyIDInRoute, props.policyID]); + + const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); + + const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); + + if (shouldShowFullScreenLoadingIndicator) { + return ; + } + + if (shouldShowNotFoundPage) { + return Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />; + } + + return typeof props.children === 'function' ? props.children(props) : props.children; +} + +export default withOnyx({ + policy: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`, + }, + isLoadingReportData: { + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + }, +})(FeatureEnabledAccessOrNotFoundComponent); From cf310c3cddc0d9de1574f166cee92490dff039d8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:29:41 +0100 Subject: [PATCH 102/345] integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceCategoriesPage --- .../categories/WorkspaceCategoriesPage.tsx | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 52d18d8de276..f2987bf624a1 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -23,6 +23,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -152,51 +153,56 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat return ( - - - {!isSmallScreenWidth && headerButtons} - - {isSmallScreenWidth && {headerButtons}} - - {translate('workspace.categories.subtitle')} - - {isLoading && ( - - )} - {categoryList.length === 0 && !isLoading && ( - - )} - {categoryList.length > 0 && ( - - )} - + + {!isSmallScreenWidth && headerButtons} + + {isSmallScreenWidth && {headerButtons}} + + {translate('workspace.categories.subtitle')} + + {isLoading && ( + + )} + {categoryList.length === 0 && !isLoading && ( + + )} + {categoryList.length > 0 && ( + + )} + + ); From 941e7fa6390f7b81ac645bdf171f5780eb7be018 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:41:47 +0100 Subject: [PATCH 103/345] integrate more features consts --- src/CONST.ts | 9 +++++++++ .../workspace/categories/WorkspaceCategoriesPage.tsx | 2 +- src/types/onyx/Policy.ts | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6861fe174ffe..b0470046f9b6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1412,6 +1412,15 @@ const CONST = { MAKE_MEMBER: 'makeMember', MAKE_ADMIN: 'makeAdmin', }, + MORE_FEATURES: { + ARE_CATEGORIES_ENABLED: 'areCategoriesEnabled', + ARE_TAGS_ENABLED: 'areTagsEnabled', + ARE_DISTANCE_RATES_ENABLED: 'areDistanceRatesEnabled', + ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled', + ARE_REPORTFIELDS_ENABLED: 'areReportFieldsEnabled', + ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled', + ARE_TAXES_ENABLED: 'tax', + }, }, CUSTOM_UNITS: { diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index f2987bf624a1..e3362d26f014 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -155,7 +155,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat ; -type PolicyFeatureName = 'areCategoriesEnabled' | 'areTagsEnabled' | 'areDistanceRatesEnabled' | 'areWorkflowsEnabled' | 'areReportFieldsEnabled' | 'areConnectionsEnabled' | 'tax'; +type PolicyFeatureName = ValueOf; type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< { From 699f38a993dbbea3d80c8cce7780e9ed8be70996 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:48:11 +0100 Subject: [PATCH 104/345] integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceTagsPage --- .../workspace/tags/WorkspaceTagsPage.tsx | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index c82740eff361..125695856e4c 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -19,7 +19,9 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -96,40 +98,45 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { return ( - - - - {translate('workspace.tags.subtitle')} - - {tagList.length ? ( - {}} - onSelectAll={toggleAllTags} - showScrollIndicator - ListItem={TableListItem} - customListHeader={getCustomListHeader()} - listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} + + - ) : ( - - )} - + + {translate('workspace.tags.subtitle')} + + {tagList.length ? ( + {}} + onSelectAll={toggleAllTags} + showScrollIndicator + ListItem={TableListItem} + customListHeader={getCustomListHeader()} + listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} + /> + ) : ( + + )} + + ); From daacbfebcf1322ffedb65b430d6ea8e7c5ec4db9 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:48:18 +0100 Subject: [PATCH 105/345] integrate FeatureEnabledAccessOrNotFoundComponent to WorkspaceCategoriesSettingsPage --- .../WorkspaceCategoriesSettingsPage.tsx | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx index a2ce06270c33..02ae87ce05d0 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx @@ -11,7 +11,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {setWorkspaceRequiresCategory} from '@libs/actions/Policy'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; type WorkspaceCategoriesSettingsPageProps = StackScreenProps; @@ -27,33 +29,38 @@ function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPag return ( - {({policy}) => ( - - - - - - - {translate('workspace.categories.requiresCategory')} - + + {({policy}) => ( + + + + + + + {translate('workspace.categories.requiresCategory')} + + - - - - - )} + + + + )} + ); From 7fb586ef9a7d7b255493c8d514a82024eac349fb Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:48:26 +0100 Subject: [PATCH 106/345] integrate FeatureEnabledAccessOrNotFoundComponent to CreateCategoryPage --- .../categories/CreateCategoryPage.tsx | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/pages/workspace/categories/CreateCategoryPage.tsx b/src/pages/workspace/categories/CreateCategoryPage.tsx index cfe28ba292b0..332371c866b6 100644 --- a/src/pages/workspace/categories/CreateCategoryPage.tsx +++ b/src/pages/workspace/categories/CreateCategoryPage.tsx @@ -16,6 +16,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -68,34 +69,39 @@ function CreateCategoryPage({route, policyCategories}: CreateCategoryPageProps) return ( - - - - - - + + + + + ); From 4bd3e8ac55a6140e96163deabeac37fc6bcfc0d5 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 7 Mar 2024 16:48:33 +0100 Subject: [PATCH 107/345] integrate FeatureEnabledAccessOrNotFoundComponent to CategorySettingsPage --- .../categories/CategorySettingsPage.tsx | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 16f128e5ea1f..97e71abb7a2e 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -16,8 +16,10 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -46,36 +48,41 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro return ( - - - - Policy.clearCategoryErrors(route.params.policyID, route.params.categoryName)} - > - - - {translate('workspace.categories.enableCategory')} - + + + + Policy.clearCategoryErrors(route.params.policyID, route.params.categoryName)} + > + + + {translate('workspace.categories.enableCategory')} + + - - - - - + + + + + ); From 7ccda640391a63472ec31bc2903a56348645bad7 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 8 Mar 2024 12:43:46 -0500 Subject: [PATCH 108/345] Include credentials on fetch --- package-lock.json | 6 +++--- package.json | 2 +- src/libs/HttpUtils.ts | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c416a788e7c7..88ec40988483 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#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", + "expensify-common": "github:Expensify/expensify-common#eb11f2543f0ca9fb55723a479ef12aabf904f0a8", "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#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", - "integrity": "sha512-hsP+sd3jc752y402gAoJbmk8eTkAIbWBhV2nXuAsRl86jt79qQvgkfDheG0TWmcYrO+7RApqkok0OfZJuyI5nA==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#eb11f2543f0ca9fb55723a479ef12aabf904f0a8", + "integrity": "sha512-VVRY+LLC4/olVkOgKtU1fcdPJwPa5qSTZxXr49bBlBw+OWKg4DmDFxocHHyWWp8nfRmKkqPGwma7GF3D/Ofebg==", "license": "MIT", "dependencies": { "classnames": "2.5.0", diff --git a/package.json b/package.json index c028c884e243..7dfffe141130 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#8dd750aee6a5403365007861cc6b6e6e1fddb8ff", + "expensify-common": "github:Expensify/expensify-common#eb11f2543f0ca9fb55723a479ef12aabf904f0a8", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index f52fefe02386..9c350ab05d81 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -39,7 +39,7 @@ const addSkewList: string[] = [SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, SIDE_EF /** * Regex to get API command from the command */ -const APICommandRegex = /[?&]command=([^&]+)/; +const APICommandRegex = /\/api\/([^&?]+)\??.*/; /** * Send an HTTP request, and attempt to resolve the json response. @@ -52,6 +52,8 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form signal: canCancel ? cancellationController.signal : undefined, method, body, + // We want to include the cookie accountID that is returned form the API + credentials: 'include', }) .then((response) => { // We are calculating the skew to minimize the delay when posting the messages From 87f28e2a6a43b26c58c656b04de4873943bbc394 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 8 Mar 2024 12:48:29 -0500 Subject: [PATCH 109/345] Update packagE --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7dfffe141130..e437f5c5526d 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": "github:Expensify/expensify-common#eb11f2543f0ca9fb55723a479ef12aabf904f0a8", + "expensify-common": "github:Expensify/expensify-common#7ce83cd75c7893dbea4b1517ff04b9589144eafe", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", From 904316b9036ad3c7b87ae371d59354094af4a39a Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 9 Mar 2024 12:55:58 +0530 Subject: [PATCH 110/345] fix: Room - Side panel does not close when clicking outside of panel. Signed-off-by: Krishna Gupta --- src/components/Modal/BaseModal.tsx | 4 ++++ src/components/Modal/types.ts | 3 +++ src/components/ValuePicker/ValueSelectorModal.tsx | 3 ++- src/components/ValuePicker/index.tsx | 2 ++ src/components/ValuePicker/types.ts | 3 +++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 2b8d25d5d639..514bcc421e09 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -41,6 +41,7 @@ function BaseModal( avoidKeyboard = false, children, shouldUseCustomBackdrop = false, + onBackdropPress, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -117,6 +118,9 @@ function BaseModal( return; } + if (onBackdropPress) { + onBackdropPress(); + } onClose(); }; diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index a0cdb737d448..cfa4a28c4d87 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -23,6 +23,9 @@ type BaseModalProps = Partial & { /** Callback method fired when the user requests to close the modal */ onClose: () => void; + /** Function to call when the user presses on the modal backdrop */ + onBackdropPress?: () => void; + /** State that determines whether to display the modal or not */ isVisible: boolean; diff --git a/src/components/ValuePicker/ValueSelectorModal.tsx b/src/components/ValuePicker/ValueSelectorModal.tsx index fad59d4e48e4..2891a452a0ea 100644 --- a/src/components/ValuePicker/ValueSelectorModal.tsx +++ b/src/components/ValuePicker/ValueSelectorModal.tsx @@ -8,7 +8,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type {ValueSelectorModalProps} from './types'; -function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips = true}: ValueSelectorModalProps) { +function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, onClose, onItemSelected, shouldShowTooltips = true, onBackdropPress}: ValueSelectorModalProps) { const styles = useThemeStyles(); const sections = useMemo( @@ -24,6 +24,7 @@ function ValueSelectorModal({items = [], selectedItem, label = '', isVisible, on onModalHide={onClose} hideModalContentWhileAnimating useNativeDriver + onBackdropPress={onBackdropPress} > ); diff --git a/src/components/ValuePicker/types.ts b/src/components/ValuePicker/types.ts index c7df88bc7417..8e42f80097ac 100644 --- a/src/components/ValuePicker/types.ts +++ b/src/components/ValuePicker/types.ts @@ -30,6 +30,9 @@ type ValueSelectorModalProps = { /** Function to call when the user closes the modal */ onClose?: () => void; + /** Function to call when the user presses on the modal backdrop */ + onBackdropPress?: () => void; + /** Whether to show the toolip text */ shouldShowTooltips?: boolean; }; From f692d394f14d5ff3766e2d61a8693785a2835d7d Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 11 Mar 2024 04:02:10 +0200 Subject: [PATCH 111/345] Fix: Unable to save manually entered address after selecting address from list --- src/pages/iou/request/step/IOURequestStepWaypoint.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx index 5a68c85546e6..c8232b9cdb7b 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -125,7 +125,7 @@ function IOURequestStepWaypoint({ if (isOffline && waypointValue) { const waypoint = { address: waypointValue, - name: values.name, + name: values.name ?? '', }; saveWaypoint(waypoint); } From 7ec68d59821269b3d4957de50f13f51dbe6c017e Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Mon, 11 Mar 2024 21:39:08 +0530 Subject: [PATCH 112/345] fixed restricted-imports for ScrollView --- src/pages/EnablePayments/OnfidoPrivacy.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/EnablePayments/OnfidoPrivacy.tsx b/src/pages/EnablePayments/OnfidoPrivacy.tsx index 99117e3f1e99..cf6e6837df16 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.tsx +++ b/src/pages/EnablePayments/OnfidoPrivacy.tsx @@ -1,5 +1,6 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports import type {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; From b69454519c8c89ae718a71493d63f172f6d6e44e Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 17:15:14 +0100 Subject: [PATCH 113/345] feat: policy distance rates settings --- src/CONST.ts | 4 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + src/languages/en.ts | 8 ++ src/languages/es.ts | 8 ++ ...olicyDistanceRatesDefaultCategoryParams.ts | 6 + .../SetPolicyDistanceRatesUnitParams.ts | 6 + src/libs/API/parameters/index.ts | 2 + src/libs/API/types.ts | 4 + .../AppNavigator/ModalStackNavigators.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/libs/actions/Policy.ts | 122 ++++++++++++++++++ .../CategorySelectorModal.tsx | 82 ++++++++++++ .../distanceRates/CategorySelector/index.tsx | 73 +++++++++++ .../distanceRates/CategorySelector/types.ts | 8 ++ .../distanceRates/PolicyDistanceRatesPage.tsx | 2 +- .../PolicyDistanceRatesSettingsPage.tsx | 80 ++++++++++++ .../UnitSelector/UnitSelectorModal.tsx | 80 ++++++++++++ .../distanceRates/UnitSelector/index.tsx | 69 ++++++++++ .../distanceRates/UnitSelector/types.ts | 13 ++ 22 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyDistanceRatesDefaultCategoryParams.ts create mode 100644 src/libs/API/parameters/SetPolicyDistanceRatesUnitParams.ts create mode 100644 src/pages/workspace/distanceRates/CategorySelector/CategorySelectorModal.tsx create mode 100644 src/pages/workspace/distanceRates/CategorySelector/index.tsx create mode 100644 src/pages/workspace/distanceRates/CategorySelector/types.ts create mode 100644 src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx create mode 100644 src/pages/workspace/distanceRates/UnitSelector/UnitSelectorModal.tsx create mode 100644 src/pages/workspace/distanceRates/UnitSelector/index.tsx create mode 100644 src/pages/workspace/distanceRates/UnitSelector/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index ce2029c78713..0b1dcd7ec250 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1416,6 +1416,10 @@ const CONST = { DISABLE: 'disable', ENABLE: 'enable', }, + UNITS: { + MI: 'Miles', + KM: 'Kilometers', + }, }, CUSTOM_UNITS: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 87be6247c27c..5ffb2f13d27e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -581,6 +581,10 @@ const ROUTES = { route: 'workspace/:policyID/distance-rates/new', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/new` as const, }, + WORKSPACE_DISTANCE_RATES_SETTINGS: { + route: 'workspace/:policyID/distance-rates/settings', + getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/settings` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 387d874efcf8..b3860b7a0e89 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -231,6 +231,7 @@ const SCREENS = { MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection', DISTANCE_RATES: 'Distance_Rates', CREATE_DISTANCE_RATE: 'Create_Distance_Rate', + DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', }, EDIT_REQUEST: { diff --git a/src/languages/en.ts b/src/languages/en.ts index be2d0dc569fd..722e3d708024 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1927,8 +1927,16 @@ export default { status: 'Status', enabled: 'Enabled', disabled: 'Disabled', + unit: 'Unit', + defaultCategory: 'Default category', errors: { createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + updateRateUnitGenericFailureMessage: 'An error occurred while updating the distance rate unit, please try again.', + updateRateDefaultCategoryGenericFailureMessage: 'An error occurred while updating the distance rate default category, please try again.', + }, + units: { + MI: 'Miles', + KM: 'Kilometers', }, }, editor: { diff --git a/src/languages/es.ts b/src/languages/es.ts index b85071197298..be71730f1cc4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1952,8 +1952,16 @@ export default { status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', + unit: 'Unit', + defaultCategory: 'Default category', errors: { createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + updateRateUnitGenericFailureMessage: 'An error occurred while updating the distance rate unit, please try again.', + updateRateDefaultCategoryGenericFailureMessage: 'An error occurred while updating the distance rate default category, please try again.', + }, + units: { + MI: 'Millas', + KM: 'Kilómetros', }, }, editor: { diff --git a/src/libs/API/parameters/SetPolicyDistanceRatesDefaultCategoryParams.ts b/src/libs/API/parameters/SetPolicyDistanceRatesDefaultCategoryParams.ts new file mode 100644 index 000000000000..d2d11993a172 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyDistanceRatesDefaultCategoryParams.ts @@ -0,0 +1,6 @@ +type SetPolicyDistanceRatesDefaultCategoryParams = { + policyID: string; + customUnit: string; +}; + +export default SetPolicyDistanceRatesDefaultCategoryParams; diff --git a/src/libs/API/parameters/SetPolicyDistanceRatesUnitParams.ts b/src/libs/API/parameters/SetPolicyDistanceRatesUnitParams.ts new file mode 100644 index 000000000000..c841f480d1bf --- /dev/null +++ b/src/libs/API/parameters/SetPolicyDistanceRatesUnitParams.ts @@ -0,0 +1,6 @@ +type SetPolicyDistanceRatesUnitParams = { + policyID: string; + customUnit: string; +}; + +export default SetPolicyDistanceRatesUnitParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 85f3d9d87f57..717d33eee8ee 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -161,3 +161,5 @@ export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; +export type {default as SetPolicyDistanceRatesUnitParams} from './SetPolicyDistanceRatesUnitParams'; +export type {default as SetPolicyDistanceRatesDefaultCategoryParams} from './SetPolicyDistanceRatesDefaultCategoryParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 51cf2721878b..be2d8b450425 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -161,6 +161,8 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', + SET_POLICY_DISTANCE_RATES_UNIT: 'SetPolicyDistanceRatesUnit', + SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY: 'SetPolicyDistanceRatesDefaultCategory', } as const; type WriteCommand = ValueOf; @@ -320,6 +322,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; + [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_UNIT]: Parameters.SetPolicyDistanceRatesUnitParams; + [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 7786e16e00ef..da51fa4d3433 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -255,6 +255,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').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 8c5a32182e99..954b2b5b7cef 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -7,7 +7,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION], [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_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index cc337c3b603f..7ada76628c82 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -297,6 +297,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { path: ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.route, }, + [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: { + path: ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.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 fc7ebc8f1b72..5ad59483bf38 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -224,6 +224,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { policyID: string; }; + [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e6fcbe9e1ba9..1311e2867a21 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -23,6 +23,8 @@ import type { OpenWorkspaceMembersPageParams, OpenWorkspaceParams, OpenWorkspaceReimburseViewParams, + SetPolicyDistanceRatesDefaultCategoryParams, + SetPolicyDistanceRatesUnitParams, SetWorkspaceApprovalModeParams, SetWorkspaceAutoReportingFrequencyParams, SetWorkspaceAutoReportingMonthlyOffsetParams, @@ -2858,6 +2860,124 @@ function clearCreateDistanceRateError(policyID: string, currentRates: Record; + + /** Whether the modal is visible */ + isVisible: boolean; + + /** Selected category */ + currentCategory: string; + + /** Function to call when the user selects a category */ + onCategorySelected: (value: CategoryItemType) => void; + + /** Function to call when the user closes the category selector modal */ + onClose: () => void; + + /** Label to display on field */ + label: string; +}; + +function CategorySelectorModal({policyCategories, isVisible, currentCategory, onCategorySelected, onClose, label}: CategorySelectorModalProps) { + const styles = useThemeStyles(); + + const categories = useMemo( + () => + Object.values(policyCategories ?? {}).map((value) => ({ + value: value.name, + text: value.name, + keyForList: value.name, + isSelected: value.name === currentCategory, + })), + [currentCategory, policyCategories], + ); + + return ( + + + + + + + ); +} + +CategorySelectorModal.displayName = 'CategorySelectorModal'; + +export default CategorySelectorModal; diff --git a/src/pages/workspace/distanceRates/CategorySelector/index.tsx b/src/pages/workspace/distanceRates/CategorySelector/index.tsx new file mode 100644 index 000000000000..f739e163a47f --- /dev/null +++ b/src/pages/workspace/distanceRates/CategorySelector/index.tsx @@ -0,0 +1,73 @@ +import React, {useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type * as OnyxTypes from '@src/types/onyx'; +import CategorySelectorModal from './CategorySelectorModal'; +import type CategoryItemType from './types'; + +type CategorySelectorProps = { + /** Collection of categories attached to a policy */ + policyCategories: OnyxEntry; + + /** Function to call when the user selects a category */ + setNewCategory: (value: CategoryItemType) => void; + + /** Currently selected category */ + defaultValue?: string; + + /** Label to display on field */ + label: string; + + /** Any additional styles to apply */ + wrapperStyle: StyleProp; +}; + +function CategorySelector({policyCategories, defaultValue = '', wrapperStyle, label, setNewCategory}: CategorySelectorProps) { + const styles = useThemeStyles(); + + const [isPickerVisible, setIsPickerVisible] = useState(false); + + const showPickerModal = () => { + setIsPickerVisible(true); + }; + + const hidePickerModal = () => { + setIsPickerVisible(false); + }; + + const updateCategoryInput = (categoryItem: CategoryItemType) => { + setNewCategory(categoryItem); + hidePickerModal(); + }; + + const title = defaultValue; + const descStyle = title.length === 0 ? styles.textNormal : null; + + return ( + + + + + ); +} + +CategorySelector.displayName = 'CategorySelector'; + +export default CategorySelector; diff --git a/src/pages/workspace/distanceRates/CategorySelector/types.ts b/src/pages/workspace/distanceRates/CategorySelector/types.ts new file mode 100644 index 000000000000..78e78afb4547 --- /dev/null +++ b/src/pages/workspace/distanceRates/CategorySelector/types.ts @@ -0,0 +1,8 @@ +type CategoryItemType = { + value: string; + text: string; + keyForList: string; + isSelected: boolean; +}; + +export default CategoryItemType; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index b9e1ca47b8a8..8f70fa9c13db 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -113,7 +113,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) }; const openSettings = () => { - // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(policyID)); + Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(policyID)); }; const editRate = () => { diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx new file mode 100644 index 000000000000..47df30dfd271 --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -0,0 +1,80 @@ +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 HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import * as Policy from '@userActions/Policy'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type * as OnyxTypes from '@src/types/onyx'; +import CategorySelector from './CategorySelector'; +import type CategoryItemType from './CategorySelector/types'; +import UnitSelector from './UnitSelector'; +import type {UnitItemType} from './UnitSelector/types'; + +type PolicyDistanceRatesSettingsPageOnyxProps = { + /** Policy details */ + policy: OnyxEntry; + + /** Collection of categories attached to a policy */ + policyCategories: OnyxEntry; +}; + +type PolicyDistanceRatesSettingsPageProps = PolicyDistanceRatesSettingsPageOnyxProps & StackScreenProps; + +function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: PolicyDistanceRatesSettingsPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyID = route.params.policyID; + const customUnits = policy?.customUnits ?? {}; + const customUnit = customUnits[Object.keys(customUnits)[0]]; + const customUnitID = customUnit.customUnitID; + + const defaultCategory = customUnits[customUnitID].defaultCategory; + const defaultUnit = customUnits[customUnitID].attributes.unit.toUpperCase(); + + const setNewUnit = (unit: UnitItemType) => { + Policy.setPolicyDistanceRatesUnit(policyID, customUnit, {...customUnit, attributes: {unit: unit.value}}); + }; + + const setNewCategory = (category: CategoryItemType) => { + Policy.setPolicyDistanceRatesDefaultCategory(policyID, customUnit, {...customUnit, defaultCategory: category.value}); + }; + + return ( + + + + {policy?.areCategoriesEnabled && ( + + )} + + ); +} + +PolicyDistanceRatesSettingsPage.displayName = 'PolicyDistanceRatesSettingsPage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, + policyCategories: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`, + }, +})(PolicyDistanceRatesSettingsPage); diff --git a/src/pages/workspace/distanceRates/UnitSelector/UnitSelectorModal.tsx b/src/pages/workspace/distanceRates/UnitSelector/UnitSelectorModal.tsx new file mode 100644 index 000000000000..cd4a7b926c4c --- /dev/null +++ b/src/pages/workspace/distanceRates/UnitSelector/UnitSelectorModal.tsx @@ -0,0 +1,80 @@ +import React, {useMemo} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Modal from '@components/Modal'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import type {Unit} from '@src/types/onyx/Policy'; +import type {UnitItemType, UnitType} from './types'; + +type UnitSelectorModalProps = { + /** Whether the modal is visible */ + isVisible: boolean; + + /** Selected unit */ + currentUnit: string; + + /** Function to call when the user selects a unit */ + onUnitSelected: (value: UnitItemType) => void; + + /** Function to call when the user closes the unit selector modal */ + onClose: () => void; + + /** Label to display on field */ + label: string; +}; + +function UnitSelectorModal({isVisible, currentUnit, onUnitSelected, onClose, label}: UnitSelectorModalProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const units = useMemo( + () => + Object.keys(CONST.POLICY.UNITS).map((key) => ({ + value: key.toLowerCase() as Unit, + text: translate(`workspace.distanceRates.units.${key as UnitType}`), + keyForList: key, + isSelected: key === currentUnit, + })), + [currentUnit, translate], + ); + + return ( + + + + + + + ); +} + +UnitSelectorModal.displayName = 'UnitSelectorModal'; + +export default UnitSelectorModal; diff --git a/src/pages/workspace/distanceRates/UnitSelector/index.tsx b/src/pages/workspace/distanceRates/UnitSelector/index.tsx new file mode 100644 index 000000000000..8615f101fdda --- /dev/null +++ b/src/pages/workspace/distanceRates/UnitSelector/index.tsx @@ -0,0 +1,69 @@ +import React, {useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {UnitItemType, UnitType} from './types'; +import UnitSelectorModal from './UnitSelectorModal'; + +type UnitSelectorProps = { + /** Function to call when the user selects a unit */ + setNewUnit: (value: UnitItemType) => void; + + /** Currently selected unit */ + defaultValue: string; + + /** Label to display on field */ + label: string; + + /** Any additional styles to apply */ + wrapperStyle: StyleProp; +}; + +function UnitSelector({defaultValue = '', wrapperStyle, label, setNewUnit}: UnitSelectorProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [isPickerVisible, setIsPickerVisible] = useState(false); + + const showPickerModal = () => { + setIsPickerVisible(true); + }; + + const hidePickerModal = () => { + setIsPickerVisible(false); + }; + + const updateUnitInput = (UnitItem: UnitItemType) => { + setNewUnit(UnitItem); + hidePickerModal(); + }; + + const title = defaultValue ? translate(`workspace.distanceRates.units.${defaultValue as UnitType}`) : ''; + const descStyle = title.length === 0 ? styles.textNormal : null; + + return ( + + + + + ); +} + +UnitSelector.displayName = 'UnitSelector'; + +export default UnitSelector; diff --git a/src/pages/workspace/distanceRates/UnitSelector/types.ts b/src/pages/workspace/distanceRates/UnitSelector/types.ts new file mode 100644 index 000000000000..98f2af3f48cf --- /dev/null +++ b/src/pages/workspace/distanceRates/UnitSelector/types.ts @@ -0,0 +1,13 @@ +import type CONST from '@src/CONST'; +import type {Unit} from '@src/types/onyx/Policy'; + +type UnitType = keyof typeof CONST.POLICY.UNITS; + +type UnitItemType = { + value: Unit; + text: string; + keyForList: string; + isSelected: boolean; +}; + +export type {UnitType, UnitItemType}; From 09203e49dbdf1bdf9ffc0155b4bc501255526796 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 11 Mar 2024 18:44:28 +0100 Subject: [PATCH 114/345] confirm navigation after onyx updates --- src/libs/actions/Policy.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8bfa2a4a11fd..6c5df9f94c95 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2662,7 +2662,11 @@ function navigateWhenEnableFeature(policyID: string, featureRoute: Route) { return; } - Navigation.navigate(featureRoute); + new Promise((resolve) => { + resolve(); + }).then(() => { + Navigation.navigate(featureRoute); + }); } function enablePolicyCategories(policyID: string, enabled: boolean) { From 2040059e6f5efb16660d30e182dd2a7c78b7c057 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 11 Mar 2024 18:44:48 +0100 Subject: [PATCH 115/345] redirect instead of not found --- ...FeatureEnabledAccessOrRedirectWrapper.tsx} | 30 ++++++++++--------- .../categories/CategorySettingsPage.tsx | 6 ++-- .../categories/CreateCategoryPage.tsx | 6 ++-- .../categories/WorkspaceCategoriesPage.tsx | 6 ++-- .../WorkspaceCategoriesSettingsPage.tsx | 6 ++-- .../workspace/tags/WorkspaceTagsPage.tsx | 6 ++-- 6 files changed, 31 insertions(+), 29 deletions(-) rename src/pages/workspace/{FeatureEnabledAccessOrNotFoundWrapper.tsx => FeatureEnabledAccessOrRedirectWrapper.tsx} (68%) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx similarity index 68% rename from src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx rename to src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx index 8f9ff546b98e..d5799e617226 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx @@ -5,7 +5,6 @@ import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Policy from '@userActions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -13,7 +12,7 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type FeatureEnabledAccessOrNotFoundOnyxProps = { +type FeatureEnabledAccessOrRedirectOnyxProps = { /** The report currently being looked at */ policy: OnyxEntry; @@ -21,9 +20,9 @@ type FeatureEnabledAccessOrNotFoundOnyxProps = { isLoadingReportData: OnyxEntry; }; -type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFoundOnyxProps & { +type FeatureEnabledAccessOrRedirectComponentProps = FeatureEnabledAccessOrRedirectOnyxProps & { /** The children to render */ - children: ((props: FeatureEnabledAccessOrNotFoundOnyxProps) => React.ReactNode) | React.ReactNode; + children: ((props: FeatureEnabledAccessOrRedirectOnyxProps) => React.ReactNode) | React.ReactNode; /** The report currently being looked at */ policyID: string; @@ -32,8 +31,10 @@ type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFou featureName: PolicyFeatureName; }; -function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNotFoundComponentProps) { +function FeatureEnabledAccessOrRedirectComponent(props: FeatureEnabledAccessOrRedirectComponentProps) { const isPolicyIDInRoute = !!props.policyID?.length; + const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); + const shouldRedirect = !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); useEffect(() => { if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) { @@ -45,26 +46,27 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPolicyIDInRoute, props.policyID]); - const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); + useEffect(() => { + if (!shouldRedirect) { + return; + } - const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); + Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(props.policyID)); + }, [props.policyID, shouldRedirect]); - if (shouldShowFullScreenLoadingIndicator) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (shouldShowFullScreenLoadingIndicator || shouldRedirect) { return ; } - if (shouldShowNotFoundPage) { - return Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />; - } - return typeof props.children === 'function' ? props.children(props) : props.children; } -export default withOnyx({ +export default withOnyx({ policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`, }, isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, -})(FeatureEnabledAccessOrNotFoundComponent); +})(FeatureEnabledAccessOrRedirectComponent); diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 97e71abb7a2e..cfb55ead8d6b 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -16,7 +16,7 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; -import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -48,7 +48,7 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro return ( - @@ -82,7 +82,7 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro /> - + ); diff --git a/src/pages/workspace/categories/CreateCategoryPage.tsx b/src/pages/workspace/categories/CreateCategoryPage.tsx index 332371c866b6..f36cbac03ae3 100644 --- a/src/pages/workspace/categories/CreateCategoryPage.tsx +++ b/src/pages/workspace/categories/CreateCategoryPage.tsx @@ -16,7 +16,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; -import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -69,7 +69,7 @@ function CreateCategoryPage({route, policyCategories}: CreateCategoryPageProps) return ( - @@ -101,7 +101,7 @@ function CreateCategoryPage({route, policyCategories}: CreateCategoryPageProps) /> - + ); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index ce1d891d631e..397216686eea 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -23,7 +23,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; -import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -154,7 +154,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat return ( - @@ -203,7 +203,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat /> )} - + ); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx index 02ae87ce05d0..b0882573d51c 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx @@ -11,7 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {setWorkspaceRequiresCategory} from '@libs/actions/Policy'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; -import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; @@ -29,7 +29,7 @@ function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPag return ( - @@ -60,7 +60,7 @@ function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPag )} - + ); diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index efb7ff1296c8..b68b0991f167 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -19,7 +19,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; -import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -98,7 +98,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { return ( - @@ -136,7 +136,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { /> )} - + ); From dd2b727c2008b1fca537686aede278ed3b9607e8 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 11 Mar 2024 15:30:05 -0700 Subject: [PATCH 116/345] Improve InitialURLContext --- src/App.tsx | 8 ++--- src/components/InitialURLContextProvider.tsx | 33 ++++++++++++++++++++ src/libs/InitialUrlContext/index.ts | 7 ----- src/libs/Navigation/AppNavigator/index.tsx | 4 +-- src/pages/LogOutPreviousUserPage.tsx | 5 ++- 5 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 src/components/InitialURLContextProvider.tsx delete mode 100644 src/libs/InitialUrlContext/index.ts diff --git a/src/App.tsx b/src/App.tsx index 0e247d5faa53..a2d353a026af 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackgro import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; +import InitialURLContextProvider from './components/InitialURLContextProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; @@ -30,12 +31,11 @@ import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; -import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; type AppProps = { - /** If we have an authToken this is true */ + /** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ url?: Route; }; @@ -52,7 +52,7 @@ function App({url}: AppProps) { useDefaultDragAndDrop(); OnyxUpdateManager(); return ( - + - + ); } diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx new file mode 100644 index 000000000000..a3df93844ca9 --- /dev/null +++ b/src/components/InitialURLContextProvider.tsx @@ -0,0 +1,33 @@ +import React, {createContext, useEffect, useState} from 'react'; +import type {ReactNode} from 'react'; +import {Linking} from 'react-native'; +import type {Route} from '@src/ROUTES'; + +/** Initial url that will be opened when NewDot is embedded into Hybrid App. */ +const InitialURLContext = createContext(undefined); + +type InitialURLContextProviderProps = { + /** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ + url?: Route; + + /** Children passed to the context provider */ + children: ReactNode; +}; + +function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) { + const [initialURL, setInitialURL] = useState(url); + useEffect(() => { + if (initialURL) { + return; + } + Linking.getInitialURL().then((initURL) => { + setInitialURL(initURL as Route); + }); + }, [initialURL]); + return {children}; +} + +InitialURLContextProvider.displayName = 'InitialURLContextProvider'; + +export default InitialURLContextProvider; +export {InitialURLContext}; diff --git a/src/libs/InitialUrlContext/index.ts b/src/libs/InitialUrlContext/index.ts deleted file mode 100644 index a87417fe4cc6..000000000000 --- a/src/libs/InitialUrlContext/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {createContext} from 'react'; -import type {Route} from '@src/ROUTES'; - -/** Initial url that will be opened when NewDot is embedded into Hybrid App. */ -const InitialUrlContext = createContext(undefined); - -export default InitialUrlContext; diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index 24f0f42c5d64..9729a2f812ce 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -1,6 +1,6 @@ import React, {useContext, useEffect} from 'react'; import {NativeModules} from 'react-native'; -import InitialUrlContext from '@libs/InitialUrlContext'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; type AppNavigatorProps = { @@ -9,7 +9,7 @@ type AppNavigatorProps = { }; function AppNavigator({authenticated}: AppNavigatorProps) { - const initUrl = useContext(InitialUrlContext); + const initUrl = useContext(InitialURLContext); useEffect(() => { if (!NativeModules.HybridAppModule || !initUrl) { diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 8679c1bcf4c3..08432a5fb964 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -4,7 +4,7 @@ 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 {InitialURLContext} from '@components/InitialURLContextProvider'; import * as SessionUtils from '@libs/SessionUtils'; import Navigation from '@navigation/Navigation'; import type {AuthScreensParamList} from '@navigation/types'; @@ -29,7 +29,7 @@ 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 initUrlFromOldApp = useContext(InitialUrlContext); + const initUrlFromOldApp = useContext(InitialURLContext); useEffect(() => { Linking.getInitialURL().then((url) => { const sessionEmail = session?.email; @@ -75,7 +75,6 @@ function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPag } }); - // 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 }, []); From d485dd85f0374401e996807c025fb5560d3ab6c0 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 11 Mar 2024 15:33:21 -0700 Subject: [PATCH 117/345] fix TS types --- src/pages/LogOutPreviousUserPage.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 08432a5fb964..52f95b7c23a0 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -14,12 +14,14 @@ 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 type {Session} from '@src/types/onyx'; 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; + + /** Is the account loading? */ + isAccountLoading: boolean; }; type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreenProps; @@ -28,12 +30,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, account}: LogOutPreviousUserPageProps) { +function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPreviousUserPageProps) { 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}${initUrlFromOldApp}` : url; const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; @@ -65,7 +67,7 @@ function LogOutPreviousUserPage({session, route, account}: LogOutPreviousUserPag // 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) { + if (exitTo && exitTo !== ROUTES.WORKSPACE_NEW && !isAccountLoading && !isLoggingInAsNewUser) { Navigation.isNavigationReady().then(() => { // remove this screen and navigate to exit route const exitUrl = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; @@ -87,7 +89,7 @@ LogOutPreviousUserPage.displayName = 'LogOutPreviousUserPage'; export default withOnyx({ isAccountLoading: { key: ONYXKEYS.ACCOUNT, - selector: (account) => account?.isLoading, + selector: (account) => account?.isLoading ?? false, }, session: { key: ONYXKEYS.SESSION, From 52f3877edf2b3aeeb36d048e323883363a68d94a Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 11 Mar 2024 15:53:18 -0700 Subject: [PATCH 118/345] Remove call to Linking.getInitialURL --- src/pages/LogOutPreviousUserPage.tsx | 84 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 52f95b7c23a0..e622424a6832 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useContext, useEffect} from 'react'; -import {Linking, NativeModules} from 'react-native'; +import {NativeModules} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -31,55 +31,53 @@ type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreen // // This component should not do any other navigation as that handled in App.setUpPoliciesAndNavigate function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPreviousUserPageProps) { - const initUrlFromOldApp = useContext(InitialURLContext); + const initialURL = useContext(InitialURLContext); useEffect(() => { - Linking.getInitialURL().then((url) => { - const sessionEmail = session?.email; - const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initUrlFromOldApp}` : url; - const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); - const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; + const sessionEmail = session?.email; + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL}` : initialURL; + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); + const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; - if (isLoggingInAsNewUser) { - SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); - } + if (isLoggingInAsNewUser) { + SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); + } - if (isSupportalLogin) { - SessionActions.signInWithSupportAuthToken(route.params.shortLivedAuthToken ?? ''); - Navigation.isNavigationReady().then(() => { - // We must call goBack() to remove the /transition route from history - Navigation.goBack(); - Navigation.navigate(ROUTES.HOME); - }); - return; - } + if (isSupportalLogin) { + SessionActions.signInWithSupportAuthToken(route.params.shortLivedAuthToken ?? ''); + Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + }); + return; + } - // We need to signin and fetch a new authToken, if a user was already authenticated in NewDot, and was redirected to OldDot - // and their authToken stored in Onyx becomes invalid. - // This workflow is triggered while setting up VBBA. User is redirected from NewDot to OldDot to set up 2FA, and then redirected back to NewDot - // On Enabling 2FA, authToken stored in Onyx becomes expired and hence we need to fetch new authToken - const shouldForceLogin = route.params.shouldForceLogin === 'true'; - if (shouldForceLogin) { - const email = route.params.email ?? ''; - 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 && !isAccountLoading && !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 need to signin and fetch a new authToken, if a user was already authenticated in NewDot, and was redirected to OldDot + // and their authToken stored in Onyx becomes invalid. + // This workflow is triggered while setting up VBBA. User is redirected from NewDot to OldDot to set up 2FA, and then redirected back to NewDot + // On Enabling 2FA, authToken stored in Onyx becomes expired and hence we need to fetch new authToken + const shouldForceLogin = route.params.shouldForceLogin === 'true'; + if (shouldForceLogin) { + const email = route.params.email ?? ''; + 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 && !isAccountLoading && !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 - }, []); + }, [initialURL]); return ; } From c5eab765a3179740daa1bf3f7e220f19fbdb6478 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 11 Mar 2024 15:54:41 -0700 Subject: [PATCH 119/345] Add missing return --- src/pages/LogOutPreviousUserPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index e622424a6832..f55b6014deef 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -40,6 +40,7 @@ function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPrevio if (isLoggingInAsNewUser) { SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); + return; } if (isSupportalLogin) { From bf9331918b12b25743dafb868550b2dbfffc1372 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 11 Mar 2024 15:55:52 -0700 Subject: [PATCH 120/345] Default initialURL to empty string --- 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 f55b6014deef..d1d51033c147 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -34,7 +34,7 @@ function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPrevio const initialURL = useContext(InitialURLContext); useEffect(() => { const sessionEmail = session?.email; - const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL}` : initialURL; + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL ?? ''}` : initialURL; const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; From 2f7e85e47156c9da8d3e7a459bcfa56dc1c3d64d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 12 Mar 2024 12:50:46 +0530 Subject: [PATCH 121/345] revert package-lock.json file --- package-lock.json | 149 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 5cf96414d5f1..112ae3fa8b94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8045,6 +8045,153 @@ "integrity": "sha512-C9Br1BQqm6io6lvYHptlLcOHbzlaqxp9tS35P8Qj3pdiiYRTzU3KPvZ61rQ+ZnZ4FOQ6MwPsKsmB8+6WHkAY6Q==", "license": "MIT" }, + "node_modules/@onfido/active-video-capture": { + "version": "0.28.6", + "resolved": "https://registry.npmjs.org/@onfido/active-video-capture/-/active-video-capture-0.28.6.tgz", + "integrity": "sha512-RFUeKaOSjj/amPp6VzhVkq/7kIkutEnnttT9n5KDeD3Vx8a09KD3a/xvxdQppveHlDAYsdBP6LrJwSSpjXiprg==", + "dependencies": { + "@mediapipe/face_detection": "^0.4.1646425229", + "@mediapipe/face_mesh": "^0.4.1633559619", + "@onfido/castor": "^2.2.2", + "@onfido/castor-icons": "^2.12.0", + "@tensorflow-models/face-detection": "^1.0.1", + "@tensorflow-models/face-landmarks-detection": "^1.0.2", + "@tensorflow/tfjs-backend-wasm": "3.20.0", + "@tensorflow/tfjs-backend-webgl": "3.20.0", + "@tensorflow/tfjs-converter": "3.20.0", + "@tensorflow/tfjs-core": "3.20.0", + "preact": "10.11.3", + "react-webcam": "^7.2.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow-models/face-landmarks-detection": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tensorflow-models/face-landmarks-detection/-/face-landmarks-detection-1.0.5.tgz", + "integrity": "sha512-54XJPi8g29/MknJ33ZBrLsEzr9kw/dJtrJMMD3xrCrnRlfFQPIKQ5PI2Wml55Fz2p4U2hemzBB0/H+S94JddIQ==", + "dependencies": { + "rimraf": "^3.0.2" + }, + "peerDependencies": { + "@mediapipe/face_mesh": "~0.4.0", + "@tensorflow-models/face-detection": "~1.0.0", + "@tensorflow/tfjs-backend-webgl": "^3.12.0", + "@tensorflow/tfjs-converter": "^3.12.0", + "@tensorflow/tfjs-core": "^3.12.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.20.0.tgz", + "integrity": "sha512-gf075YaBLwSAAiUwa0D4GvYyUBhbJ1BVSivUNQmUfGKvIr2lIhF0qstBr033YTc3lhkbFSHEEPAHh/EfpqyjXQ==", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-wasm": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-wasm/-/tfjs-backend-wasm-3.20.0.tgz", + "integrity": "sha512-k+sDcrcPtGToLjKRffgtSqlcN4MC6g4hXWRarZfgvvyvFqpxVfVqrGYHGTirXdN47sKYhmcTSMvbM2quGaaQnA==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.20.0", + "@types/emscripten": "~0.0.34" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.20.0.tgz", + "integrity": "sha512-SucbyQ08re3HvRgVfarRtKFIjNM4JvIAzcXmw4vaE/HrCtPEePkGO1VrmfQoN470EdUmGiwgqAjoyBvM2VOlVg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.20.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@types/webgl2": "0.0.6", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-converter": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.20.0.tgz", + "integrity": "sha512-8EIYqtQwvSYw9GFNW2OFU8Qnl/FQF/kKAsQJoORYaZ419WJo+FIZWbAWDtCpJSAgkgoHH1jYWgV9H313cVmqxg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-core": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.20.0.tgz", + "integrity": "sha512-L16JyVA4a8jFJXFgB9/oYZxcGq/GfLypt5dMVTyedznARZZ9SiY/UMMbo3IKl9ZylG1dOVVTpjzV3EvBYfeJXw==", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@webgpu/types": "0.1.16", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@webgpu/types": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", + "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==" + }, + "node_modules/@onfido/castor": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@onfido/castor/-/castor-2.3.0.tgz", + "integrity": "sha512-FkydkjedS6b2g3SqgZMYnVRZvUs/MkaEuXXJWG9+LNc7DMFT1K8smOnNuHzkiM3cJhXL6yAADdKE0mg+ZIrucQ==", + "dependencies": { + "@onfido/castor-tokens": "^1.0.0-beta.6", + "csstype": "^3.1.1" + }, + "peerDependencies": { + "@onfido/castor-icons": ">=1.0.0" + } + }, + "node_modules/@onfido/castor-icons": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@onfido/castor-icons/-/castor-icons-2.22.0.tgz", + "integrity": "sha512-7OnCvu5xqVWcBLqovZyb99NP0oHw7sjkVYXZhi438i0U6Pgecrhu/14Gc/IN/kvgDxWj9qmiYdd0qdjNaVckrQ==", + "peerDependencies": { + "react": ">=17 || ^16.14 || ^15.7 || ^0.14.10" + } + }, + "node_modules/@onfido/castor-tokens": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@onfido/castor-tokens/-/castor-tokens-1.0.0-beta.6.tgz", + "integrity": "sha512-MfwuSlNdM0Ay0cI3LLyqZGsHW0e1Y1R/0IdQKVU575PdWQx1Q/538aOZMo/a3/oSW0pMEgfOm+mNqPx057cvWA==" + }, + "node_modules/@onfido/opencv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@onfido/opencv/-/opencv-2.1.1.tgz", + "integrity": "sha512-Bwo0YsZrrdm+p5hpNFZ7yrqNVWJxOUbQW9aWDEUtkDWUL+nX2RHIR6F4lBGVmbqnG24anadS/+nEvy80SwD3tQ==", + "dependencies": { + "mirada": "^0.0.15" + } + }, "node_modules/@onfido/react-native-sdk": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-10.6.0.tgz", @@ -52751,4 +52898,4 @@ } } } -} +} \ No newline at end of file From 990a4401198bea614446f2e2221c5228f340c76d Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 12 Mar 2024 12:53:12 +0530 Subject: [PATCH 122/345] revert package-lock.json file --- package-lock.json | 149 +--------------------------------------------- 1 file changed, 1 insertion(+), 148 deletions(-) diff --git a/package-lock.json b/package-lock.json index 112ae3fa8b94..5cf96414d5f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8045,153 +8045,6 @@ "integrity": "sha512-C9Br1BQqm6io6lvYHptlLcOHbzlaqxp9tS35P8Qj3pdiiYRTzU3KPvZ61rQ+ZnZ4FOQ6MwPsKsmB8+6WHkAY6Q==", "license": "MIT" }, - "node_modules/@onfido/active-video-capture": { - "version": "0.28.6", - "resolved": "https://registry.npmjs.org/@onfido/active-video-capture/-/active-video-capture-0.28.6.tgz", - "integrity": "sha512-RFUeKaOSjj/amPp6VzhVkq/7kIkutEnnttT9n5KDeD3Vx8a09KD3a/xvxdQppveHlDAYsdBP6LrJwSSpjXiprg==", - "dependencies": { - "@mediapipe/face_detection": "^0.4.1646425229", - "@mediapipe/face_mesh": "^0.4.1633559619", - "@onfido/castor": "^2.2.2", - "@onfido/castor-icons": "^2.12.0", - "@tensorflow-models/face-detection": "^1.0.1", - "@tensorflow-models/face-landmarks-detection": "^1.0.2", - "@tensorflow/tfjs-backend-wasm": "3.20.0", - "@tensorflow/tfjs-backend-webgl": "3.20.0", - "@tensorflow/tfjs-converter": "3.20.0", - "@tensorflow/tfjs-core": "3.20.0", - "preact": "10.11.3", - "react-webcam": "^7.2.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow-models/face-landmarks-detection": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@tensorflow-models/face-landmarks-detection/-/face-landmarks-detection-1.0.5.tgz", - "integrity": "sha512-54XJPi8g29/MknJ33ZBrLsEzr9kw/dJtrJMMD3xrCrnRlfFQPIKQ5PI2Wml55Fz2p4U2hemzBB0/H+S94JddIQ==", - "dependencies": { - "rimraf": "^3.0.2" - }, - "peerDependencies": { - "@mediapipe/face_mesh": "~0.4.0", - "@tensorflow-models/face-detection": "~1.0.0", - "@tensorflow/tfjs-backend-webgl": "^3.12.0", - "@tensorflow/tfjs-converter": "^3.12.0", - "@tensorflow/tfjs-core": "^3.12.0" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.20.0.tgz", - "integrity": "sha512-gf075YaBLwSAAiUwa0D4GvYyUBhbJ1BVSivUNQmUfGKvIr2lIhF0qstBr033YTc3lhkbFSHEEPAHh/EfpqyjXQ==", - "dependencies": { - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" - }, - "engines": { - "yarn": ">= 1.3.2" - }, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.20.0" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-wasm": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-wasm/-/tfjs-backend-wasm-3.20.0.tgz", - "integrity": "sha512-k+sDcrcPtGToLjKRffgtSqlcN4MC6g4hXWRarZfgvvyvFqpxVfVqrGYHGTirXdN47sKYhmcTSMvbM2quGaaQnA==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "3.20.0", - "@types/emscripten": "~0.0.34" - }, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.20.0" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.20.0.tgz", - "integrity": "sha512-SucbyQ08re3HvRgVfarRtKFIjNM4JvIAzcXmw4vaE/HrCtPEePkGO1VrmfQoN470EdUmGiwgqAjoyBvM2VOlVg==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "3.20.0", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "@types/webgl-ext": "0.0.30", - "@types/webgl2": "0.0.6", - "seedrandom": "^3.0.5" - }, - "engines": { - "yarn": ">= 1.3.2" - }, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.20.0" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-converter": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.20.0.tgz", - "integrity": "sha512-8EIYqtQwvSYw9GFNW2OFU8Qnl/FQF/kKAsQJoORYaZ419WJo+FIZWbAWDtCpJSAgkgoHH1jYWgV9H313cVmqxg==", - "peerDependencies": { - "@tensorflow/tfjs-core": "3.20.0" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-core": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.20.0.tgz", - "integrity": "sha512-L16JyVA4a8jFJXFgB9/oYZxcGq/GfLypt5dMVTyedznARZZ9SiY/UMMbo3IKl9ZylG1dOVVTpjzV3EvBYfeJXw==", - "dependencies": { - "@types/long": "^4.0.1", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "@types/webgl-ext": "0.0.30", - "@webgpu/types": "0.1.16", - "long": "4.0.0", - "node-fetch": "~2.6.1", - "seedrandom": "^3.0.5" - }, - "engines": { - "yarn": ">= 1.3.2" - } - }, - "node_modules/@onfido/active-video-capture/node_modules/@webgpu/types": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", - "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==" - }, - "node_modules/@onfido/castor": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@onfido/castor/-/castor-2.3.0.tgz", - "integrity": "sha512-FkydkjedS6b2g3SqgZMYnVRZvUs/MkaEuXXJWG9+LNc7DMFT1K8smOnNuHzkiM3cJhXL6yAADdKE0mg+ZIrucQ==", - "dependencies": { - "@onfido/castor-tokens": "^1.0.0-beta.6", - "csstype": "^3.1.1" - }, - "peerDependencies": { - "@onfido/castor-icons": ">=1.0.0" - } - }, - "node_modules/@onfido/castor-icons": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@onfido/castor-icons/-/castor-icons-2.22.0.tgz", - "integrity": "sha512-7OnCvu5xqVWcBLqovZyb99NP0oHw7sjkVYXZhi438i0U6Pgecrhu/14Gc/IN/kvgDxWj9qmiYdd0qdjNaVckrQ==", - "peerDependencies": { - "react": ">=17 || ^16.14 || ^15.7 || ^0.14.10" - } - }, - "node_modules/@onfido/castor-tokens": { - "version": "1.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@onfido/castor-tokens/-/castor-tokens-1.0.0-beta.6.tgz", - "integrity": "sha512-MfwuSlNdM0Ay0cI3LLyqZGsHW0e1Y1R/0IdQKVU575PdWQx1Q/538aOZMo/a3/oSW0pMEgfOm+mNqPx057cvWA==" - }, - "node_modules/@onfido/opencv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@onfido/opencv/-/opencv-2.1.1.tgz", - "integrity": "sha512-Bwo0YsZrrdm+p5hpNFZ7yrqNVWJxOUbQW9aWDEUtkDWUL+nX2RHIR6F4lBGVmbqnG24anadS/+nEvy80SwD3tQ==", - "dependencies": { - "mirada": "^0.0.15" - } - }, "node_modules/@onfido/react-native-sdk": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-10.6.0.tgz", @@ -52898,4 +52751,4 @@ } } } -} \ No newline at end of file +} From 4dd5b43dcd7fccd1ee4ee5e3e49327030af03c04 Mon Sep 17 00:00:00 2001 From: Jayesh Mangwani Date: Tue, 12 Mar 2024 12:57:33 +0530 Subject: [PATCH 123/345] revert package-lock.json file --- package-lock.json | 147 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5cf96414d5f1..bc373abcd9b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8045,6 +8045,153 @@ "integrity": "sha512-C9Br1BQqm6io6lvYHptlLcOHbzlaqxp9tS35P8Qj3pdiiYRTzU3KPvZ61rQ+ZnZ4FOQ6MwPsKsmB8+6WHkAY6Q==", "license": "MIT" }, + "node_modules/@onfido/active-video-capture": { + "version": "0.28.6", + "resolved": "https://registry.npmjs.org/@onfido/active-video-capture/-/active-video-capture-0.28.6.tgz", + "integrity": "sha512-RFUeKaOSjj/amPp6VzhVkq/7kIkutEnnttT9n5KDeD3Vx8a09KD3a/xvxdQppveHlDAYsdBP6LrJwSSpjXiprg==", + "dependencies": { + "@mediapipe/face_detection": "^0.4.1646425229", + "@mediapipe/face_mesh": "^0.4.1633559619", + "@onfido/castor": "^2.2.2", + "@onfido/castor-icons": "^2.12.0", + "@tensorflow-models/face-detection": "^1.0.1", + "@tensorflow-models/face-landmarks-detection": "^1.0.2", + "@tensorflow/tfjs-backend-wasm": "3.20.0", + "@tensorflow/tfjs-backend-webgl": "3.20.0", + "@tensorflow/tfjs-converter": "3.20.0", + "@tensorflow/tfjs-core": "3.20.0", + "preact": "10.11.3", + "react-webcam": "^7.2.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow-models/face-landmarks-detection": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tensorflow-models/face-landmarks-detection/-/face-landmarks-detection-1.0.5.tgz", + "integrity": "sha512-54XJPi8g29/MknJ33ZBrLsEzr9kw/dJtrJMMD3xrCrnRlfFQPIKQ5PI2Wml55Fz2p4U2hemzBB0/H+S94JddIQ==", + "dependencies": { + "rimraf": "^3.0.2" + }, + "peerDependencies": { + "@mediapipe/face_mesh": "~0.4.0", + "@tensorflow-models/face-detection": "~1.0.0", + "@tensorflow/tfjs-backend-webgl": "^3.12.0", + "@tensorflow/tfjs-converter": "^3.12.0", + "@tensorflow/tfjs-core": "^3.12.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.20.0.tgz", + "integrity": "sha512-gf075YaBLwSAAiUwa0D4GvYyUBhbJ1BVSivUNQmUfGKvIr2lIhF0qstBr033YTc3lhkbFSHEEPAHh/EfpqyjXQ==", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-wasm": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-wasm/-/tfjs-backend-wasm-3.20.0.tgz", + "integrity": "sha512-k+sDcrcPtGToLjKRffgtSqlcN4MC6g4hXWRarZfgvvyvFqpxVfVqrGYHGTirXdN47sKYhmcTSMvbM2quGaaQnA==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.20.0", + "@types/emscripten": "~0.0.34" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.20.0.tgz", + "integrity": "sha512-SucbyQ08re3HvRgVfarRtKFIjNM4JvIAzcXmw4vaE/HrCtPEePkGO1VrmfQoN470EdUmGiwgqAjoyBvM2VOlVg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.20.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@types/webgl2": "0.0.6", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-converter": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.20.0.tgz", + "integrity": "sha512-8EIYqtQwvSYw9GFNW2OFU8Qnl/FQF/kKAsQJoORYaZ419WJo+FIZWbAWDtCpJSAgkgoHH1jYWgV9H313cVmqxg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "3.20.0" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@tensorflow/tfjs-core": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.20.0.tgz", + "integrity": "sha512-L16JyVA4a8jFJXFgB9/oYZxcGq/GfLypt5dMVTyedznARZZ9SiY/UMMbo3IKl9ZylG1dOVVTpjzV3EvBYfeJXw==", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@webgpu/types": "0.1.16", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@onfido/active-video-capture/node_modules/@webgpu/types": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", + "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==" + }, + "node_modules/@onfido/castor": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@onfido/castor/-/castor-2.3.0.tgz", + "integrity": "sha512-FkydkjedS6b2g3SqgZMYnVRZvUs/MkaEuXXJWG9+LNc7DMFT1K8smOnNuHzkiM3cJhXL6yAADdKE0mg+ZIrucQ==", + "dependencies": { + "@onfido/castor-tokens": "^1.0.0-beta.6", + "csstype": "^3.1.1" + }, + "peerDependencies": { + "@onfido/castor-icons": ">=1.0.0" + } + }, + "node_modules/@onfido/castor-icons": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@onfido/castor-icons/-/castor-icons-2.22.0.tgz", + "integrity": "sha512-7OnCvu5xqVWcBLqovZyb99NP0oHw7sjkVYXZhi438i0U6Pgecrhu/14Gc/IN/kvgDxWj9qmiYdd0qdjNaVckrQ==", + "peerDependencies": { + "react": ">=17 || ^16.14 || ^15.7 || ^0.14.10" + } + }, + "node_modules/@onfido/castor-tokens": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@onfido/castor-tokens/-/castor-tokens-1.0.0-beta.6.tgz", + "integrity": "sha512-MfwuSlNdM0Ay0cI3LLyqZGsHW0e1Y1R/0IdQKVU575PdWQx1Q/538aOZMo/a3/oSW0pMEgfOm+mNqPx057cvWA==" + }, + "node_modules/@onfido/opencv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@onfido/opencv/-/opencv-2.1.1.tgz", + "integrity": "sha512-Bwo0YsZrrdm+p5hpNFZ7yrqNVWJxOUbQW9aWDEUtkDWUL+nX2RHIR6F4lBGVmbqnG24anadS/+nEvy80SwD3tQ==", + "dependencies": { + "mirada": "^0.0.15" + } + }, "node_modules/@onfido/react-native-sdk": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-10.6.0.tgz", From 68e55e554e1ce92c0b8c38d240ab38c16e832fed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 12 Mar 2024 12:24:29 +0100 Subject: [PATCH 124/345] integrate FeatureEnabledAccessOrRedirectWrapper to PolicyDistanceRatesPage --- .../distanceRates/PolicyDistanceRatesPage.tsx | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index fd6466da1758..0134771b9ec5 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -22,6 +22,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import ButtonWithDropdownMenu from '@src/components/ButtonWithDropdownMenu'; @@ -232,52 +233,57 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) return ( - - - {!isSmallScreenWidth && headerButtons} - - {isSmallScreenWidth && {headerButtons}} - - {translate('workspace.distanceRates.centrallyManage')} - - {isLoading && ( - - )} - {Object.values(customUnitRates).length > 0 && ( - + {!isSmallScreenWidth && headerButtons} + + {isSmallScreenWidth && {headerButtons}} + + {translate('workspace.distanceRates.centrallyManage')} + + {isLoading && ( + + )} + {Object.values(customUnitRates).length > 0 && ( + + )} + setIsWarningModalVisible(false)} + isVisible={isWarningModalVisible} + title={translate('workspace.distanceRates.oopsNotSoFast')} + prompt={translate('workspace.distanceRates.workspaceNeeds')} + confirmText={translate('common.buttonConfirm')} + shouldShowCancelButton={false} /> - )} - setIsWarningModalVisible(false)} - isVisible={isWarningModalVisible} - title={translate('workspace.distanceRates.oopsNotSoFast')} - prompt={translate('workspace.distanceRates.workspaceNeeds')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> - + + ); From 1c1d7fd71312612ca94d2cbc84934701680fc92c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 12 Mar 2024 12:24:37 +0100 Subject: [PATCH 125/345] integrate FeatureEnabledAccessOrRedirectWrapper to tags pages --- .../workspace/tags/WorkspaceEditTagsPage.tsx | 56 +++++++------ .../tags/WorkspaceTagsSettingsPage.tsx | 81 ++++++++++--------- 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx index 98ae6f726d73..b011051999ae 100644 --- a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx @@ -16,6 +16,7 @@ import * as Policy from '@libs/actions/Policy'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -52,33 +53,38 @@ function WorkspaceEditTagsPage({route, policyTags}: WorkspaceEditTagsPageProps) ); return ( - - - - - - - - + + + + + + + + ); } diff --git a/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx index deac804980ea..7d675eb7fec3 100644 --- a/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx @@ -16,7 +16,9 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -43,44 +45,49 @@ function WorkspaceTagsSettingsPage({route, policyTags}: WorkspaceTagsSettingsPag return ( - {({policy}) => ( - - - - - - - {translate('workspace.tags.requiresTag')} - + + {({policy}) => ( + + + + + + + {translate('workspace.tags.requiresTag')} + + - - - - Navigation.navigate(ROUTES.WORKSPACE_EDIT_TAGS.getRoute(route.params.policyID))} - /> - - - - )} + + + Navigation.navigate(ROUTES.WORKSPACE_EDIT_TAGS.getRoute(route.params.policyID))} + /> + + + + )} + ); From 45d502c521b3ab7303d6ec3cd0cb7fc0f4e943d6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 12 Mar 2024 12:24:44 +0100 Subject: [PATCH 126/345] integrate FeatureEnabledAccessOrRedirectWrapper to workflows pages --- .../WorkspaceAutoReportingFrequencyPage.tsx | 53 ++++++++------- ...orkspaceAutoReportingMonthlyOffsetPage.tsx | 63 ++++++++++-------- .../WorkspaceWorkflowsApproverPage.tsx | 65 +++++++++++-------- .../workflows/WorkspaceWorkflowsPage.tsx | 54 ++++++++------- 4 files changed, 135 insertions(+), 100 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index cf66af726a72..5a7e7a2fc3a9 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -1,3 +1,4 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useState} from 'react'; import {FlatList} from 'react-native-gesture-handler'; import type {ValueOf} from 'type-fest'; @@ -11,18 +12,21 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AutoReportingFrequencyKey = Exclude, 'instant'>; type Locale = ValueOf; -type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps; +type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps & StackScreenProps; type WorkspaceAutoReportingFrequencyPageItem = { text: string; @@ -41,7 +45,7 @@ const getAutoReportingFrequencyDisplayNames = (locale: Locale): AutoReportingFre [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: Localize.translate(locale, 'workflowsPage.frequencies.manually'), }); -function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFrequencyPageProps) { +function WorkspaceAutoReportingFrequencyPage({policy, route}: WorkspaceAutoReportingFrequencyPageProps) { const {translate, preferredLocale, toLocaleOrdinal} = useLocalize(); const styles = useThemeStyles(); const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); @@ -105,28 +109,33 @@ function WorkspaceAutoReportingFrequencyPage({policy}: WorkspaceAutoReportingFre ); return ( - - - - - item.text} - /> - - + + + + item.text} + /> + + + ); } diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 84d70e799c42..3aad5fb9f0a4 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -1,3 +1,4 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useState} from 'react'; import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -7,16 +8,19 @@ import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; const DAYS_OF_MONTH = 28; -type WorkspaceAutoReportingMonthlyOffsetProps = WithPolicyOnyxProps; +type WorkspaceAutoReportingMonthlyOffsetProps = WithPolicyOnyxProps & StackScreenProps; type AutoReportingOffsetKeys = ValueOf; @@ -27,7 +31,7 @@ type WorkspaceAutoReportingMonthlyOffsetPageItem = { isNumber?: boolean; }; -function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportingMonthlyOffsetProps) { +function WorkspaceAutoReportingMonthlyOffsetPage({policy, route}: WorkspaceAutoReportingMonthlyOffsetProps) { const {translate, toLocaleOrdinal} = useLocalize(); const offset = policy?.autoReportingOffset ?? 0; const [searchText, setSearchText] = useState(''); @@ -67,34 +71,39 @@ function WorkspaceAutoReportingMonthlyOffsetPage({policy}: WorkspaceAutoReportin }; return ( - - - + + - - - + + + + ); } diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 52406a8033d2..34c0f8989888 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -1,3 +1,4 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -17,15 +18,18 @@ import compose from '@libs/compose'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as UserUtils from '@libs/UserUtils'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList, PolicyMember} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -34,11 +38,13 @@ type WorkspaceWorkflowsApproverPageOnyxProps = { personalDetails: OnyxEntry; }; -type WorkspaceWorkflowsApproverPageProps = WorkspaceWorkflowsApproverPageOnyxProps & WithPolicyAndFullscreenLoadingProps; +type WorkspaceWorkflowsApproverPageProps = WorkspaceWorkflowsApproverPageOnyxProps & + WithPolicyAndFullscreenLoadingProps & + StackScreenProps; type MemberOption = Omit & {accountID: number}; type MembersSection = SectionListData>; -function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, isLoadingReportData = true}: WorkspaceWorkflowsApproverPageProps) { +function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, isLoadingReportData = true, route}: WorkspaceWorkflowsApproverPageProps) { const {translate} = useLocalize(); const policyName = policy?.name ?? ''; const [searchTerm, setSearchTerm] = useState(''); @@ -161,33 +167,38 @@ function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, }; return ( - - - - - - + + + + + + ); } diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index ee3934cacc06..706b947b2704 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -17,6 +17,7 @@ import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; @@ -167,31 +168,36 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); return ( - - -
- - {translate('workflowsPage.workflowDescription')} - item.title} - /> - -
-
-
+ + +
+ + {translate('workflowsPage.workflowDescription')} + item.title} + /> + +
+
+
+ ); } From 6aa7212618fe0844871c94f080e4e621c8ad862c Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Tue, 12 Mar 2024 14:50:42 +0200 Subject: [PATCH 127/345] Desktop - Login - Unable to enter the 2FA code or exit the screen --- src/libs/Navigation/Navigation.ts | 9 +++++++++ src/libs/desktopLoginRedirect/index.desktop.ts | 16 ++++++++++++++++ src/libs/desktopLoginRedirect/index.ts | 5 +++++ src/pages/ValidateLoginPage/index.website.tsx | 6 ++++++ 4 files changed, 36 insertions(+) create mode 100644 src/libs/desktopLoginRedirect/index.desktop.ts create mode 100644 src/libs/desktopLoginRedirect/index.ts diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 4cd6a141bd3b..e05084e18690 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -347,6 +347,14 @@ function navigateWithSwitchPolicyID(params: SwitchPolicyIDParams) { return switchPolicyID(navigationRef.current, params); } +/** + * The `popToTop` action takes you back to the first screen in the stack, dismissing all the others. + * @note we used to call `Navigation.navigate()` before the new navigation was introduced. + */ +function popToTop() { + navigationRef.current?.dispatch(StackActions.popToTop()); +} + export default { setShouldPopAllStateOnUP, navigate, @@ -366,6 +374,7 @@ export default { parseHybridAppUrl, closeFullScreen, navigateWithSwitchPolicyID, + popToTop, }; export {navigationRef}; diff --git a/src/libs/desktopLoginRedirect/index.desktop.ts b/src/libs/desktopLoginRedirect/index.desktop.ts new file mode 100644 index 000000000000..e751fa1ffd78 --- /dev/null +++ b/src/libs/desktopLoginRedirect/index.desktop.ts @@ -0,0 +1,16 @@ +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import type {AutoAuthState} from '@src/types/onyx/Session'; + +function desktopLoginRedirect(autoAuthState: AutoAuthState, isSignedIn: boolean) { + // NOT_STARTED - covers edge case of autoAuthState not being initialized yet (after logout) + // JUST_SIGNED_IN - confirms passing the magic code step -> we're either logged-in or shown 2FA screen + // !isSignedIn - confirms we're not signed-in yet as there's possible one last step (2FA validation) + const shouldPopToTop = (autoAuthState === CONST.AUTO_AUTH_STATE.NOT_STARTED || autoAuthState === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN) && !isSignedIn; + + if (shouldPopToTop) { + Navigation.isNavigationReady().then(() => Navigation.popToTop()); + } +} + +export default desktopLoginRedirect; diff --git a/src/libs/desktopLoginRedirect/index.ts b/src/libs/desktopLoginRedirect/index.ts new file mode 100644 index 000000000000..14f5750c3de9 --- /dev/null +++ b/src/libs/desktopLoginRedirect/index.ts @@ -0,0 +1,5 @@ +import type {AutoAuthState} from '@src/types/onyx/Session'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function desktopLoginRedirect(autoAuthState: AutoAuthState, isSignedIn: boolean) {} +export default desktopLoginRedirect; diff --git a/src/pages/ValidateLoginPage/index.website.tsx b/src/pages/ValidateLoginPage/index.website.tsx index 2acad7815754..b8e8709215e8 100644 --- a/src/pages/ValidateLoginPage/index.website.tsx +++ b/src/pages/ValidateLoginPage/index.website.tsx @@ -4,6 +4,7 @@ import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ExpiredValidateCodeModal from '@components/ValidateCode/ExpiredValidateCodeModal'; import JustSignedInModal from '@components/ValidateCode/JustSignedInModal'; import ValidateCodeModal from '@components/ValidateCode/ValidateCodeModal'; +import desktopLoginRedirect from '@libs/desktopLoginRedirect'; import Navigation from '@libs/Navigation/Navigation'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; @@ -43,6 +44,11 @@ function ValidateLoginPage({ // The user has initiated the sign in process on the same browser, in another tab. Session.signInWithValidateCode(Number(accountID), validateCode); + + // Since on Desktop we don't have multi-tab functionality to handle the login flow, + // we need to `popToTop` the stack after `signInWithValidateCode` in order to + // perform login for both 2FA and non-2FA accounts. + desktopLoginRedirect(autoAuthState, isSignedIn); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 5460b54f694ab2f1383d867e1200bb6e43230e82 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 12 Mar 2024 14:51:18 +0100 Subject: [PATCH 128/345] migrate workflow_tests/utils files --- tsconfig.json | 2 +- workflow_tests/utils/ExtendedAct.js | 44 ------ workflow_tests/utils/ExtendedAct.ts | 50 +++++++ .../utils/{JobMocker.js => JobMocker.ts} | 67 ++++++--- ...{preGenerateTest.js => preGenerateTest.ts} | 130 ++++++++++-------- workflow_tests/utils/{utils.js => utils.ts} | 82 +++++++---- 6 files changed, 230 insertions(+), 145 deletions(-) delete mode 100644 workflow_tests/utils/ExtendedAct.js create mode 100644 workflow_tests/utils/ExtendedAct.ts rename workflow_tests/utils/{JobMocker.js => JobMocker.ts} (58%) rename workflow_tests/utils/{preGenerateTest.js => preGenerateTest.ts} (70%) rename workflow_tests/utils/{utils.js => utils.ts} (66%) diff --git a/tsconfig.json b/tsconfig.json index 30708f63d12b..79413fdd2ca7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -48,6 +48,6 @@ } }, "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*"], - "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*"], + "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*", "workflow_tests"], "extends": "expo/tsconfig.base" } diff --git a/workflow_tests/utils/ExtendedAct.js b/workflow_tests/utils/ExtendedAct.js deleted file mode 100644 index 9b4ab1bebda2..000000000000 --- a/workflow_tests/utils/ExtendedAct.js +++ /dev/null @@ -1,44 +0,0 @@ -const kieActJs = require('@kie/act-js'); -const path = require('path'); -const _ = require('underscore'); -const JobMocker = require('./JobMocker'); - -class ExtendedAct extends kieActJs.Act { - async parseRunOpts(opts) { - const {cwd, actArguments, proxy} = await super.parseRunOpts(opts); - if (opts && opts.actor) { - actArguments.push('--actor', opts.actor); - } - return {cwd, actArguments, proxy}; - } - - async runEvent(event, opts) { - const {mockJobs, ...vanillaOpts} = opts; - if (mockJobs) { - await this.handleJobMocking((workflow) => workflow.events.includes(event), {mockJobs, workflowFile: opts.workflowFile, cwd: opts.cwd}); - } - return super.runEvent(event, vanillaOpts); - } - - async handleJobMocking(filter, opts) { - let workflowFiles; - if (opts.workflowFile) { - workflowFiles = [path.basename(opts.workflowFile)]; - } else if (this.workflowFile !== this.cwd) { - workflowFiles = [path.basename(this.workflowFile)]; - } else { - workflowFiles = _(_(await this.list(undefined, opts.cwd, opts.workflowFile)).filter(filter)).map((l) => l.workflowFile); - } - return Promise.all( - _(workflowFiles).map((workflowFile) => { - // eslint-disable-next-line es/no-nullish-coalescing-operators - const jobMocker = new JobMocker.JobMocker(workflowFile, opts.cwd ?? this.cwd); - return jobMocker.mock(opts.mockJobs); - }), - ); - } -} - -module.exports = { - ExtendedAct, -}; diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts new file mode 100644 index 000000000000..f471292b1509 --- /dev/null +++ b/workflow_tests/utils/ExtendedAct.ts @@ -0,0 +1,50 @@ +/* eslint-disable @typescript-eslint/dot-notation */ +// This eslint-disable comment is here to allow accessing private properties in the Act class +import type {RunOpts, Workflow} from '@kie/act-js'; +import kieActJs from '@kie/act-js'; +import path from 'path'; +import {JobMocker} from './JobMocker'; +import type {MockJob} from './JobMocker'; + +type ExtendedActOpts = RunOpts & {actor?: string; workflowFile?: string; mockJobs?: Record}; + +// @ts-expect-error Override shouldn't be done on private methods - wait until the issue is resolved +class ExtendedAct extends kieActJs.Act { + async parseRunOpts(opts?: ExtendedActOpts) { + const {cwd, actArguments, proxy} = await super['parseRunOpts'](opts); + if (opts?.actor) { + actArguments.push('--actor', opts.actor); + } + return {cwd, actArguments, proxy}; + } + + async runEvent(event: string, opts?: ExtendedActOpts) { + const {mockJobs, ...vanillaOpts} = opts ?? {}; + + if (mockJobs) { + await this.handleJobMocking((workflow) => workflow.events.includes(event), {mockJobs, workflowFile: opts?.workflowFile, cwd: opts?.cwd}); + } + return super.runEvent(event, vanillaOpts); + } + + async handleJobMocking(filter: (workflow: Workflow) => boolean, opts?: ExtendedActOpts) { + let workflowFiles: string[]; + if (opts?.workflowFile) { + workflowFiles = [path.basename(opts.workflowFile)]; + } else if (this['workflowFile'] !== this['cwd']) { + workflowFiles = [path.basename(this['workflowFile'])]; + } else { + const availableWorkflows = await this.list(undefined, opts?.cwd, opts?.workflowFile); + workflowFiles = availableWorkflows.filter(filter).map((workflow: Workflow) => workflow.workflowFile); + } + return Promise.all( + workflowFiles.map((workflowFile) => { + const jobMocker = new JobMocker(workflowFile, opts?.cwd ?? this['cwd']); + return jobMocker.mock(opts?.mockJobs); + }), + ); + } +} + +// eslint-disable-next-line import/prefer-default-export +export {ExtendedAct}; diff --git a/workflow_tests/utils/JobMocker.js b/workflow_tests/utils/JobMocker.ts similarity index 58% rename from workflow_tests/utils/JobMocker.js rename to workflow_tests/utils/JobMocker.ts index a2682a657380..66e1e19e542b 100644 --- a/workflow_tests/utils/JobMocker.js +++ b/workflow_tests/utils/JobMocker.ts @@ -1,17 +1,48 @@ -const fs = require('fs'); -const path = require('path'); -const yaml = require('yaml'); -const _ = require('underscore'); +import type {PathOrFileDescriptor} from 'fs'; +import fs from 'fs'; +import path from 'path'; +import yaml from 'yaml'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +type YamlMockJob = Omit & {'runs-on'?: string}; + +type YamlWorkflow = { + jobs: Record; +}; + +type MockJob = { + steps: MockJobStep[]; + uses?: string; // ? + secrets?: string[]; // ? + with?: string; // ? + outputs?: string[]; // ? + runsOn: string; // ? +}; + +type MockJobStep = { + id?: string; // ? + name: string; + run?: string; // ? + mockWith?: string; + with?: string; // ? + envs?: string[]; // ? + inputs?: string[]; +}; class JobMocker { - constructor(workflowFile, cwd) { + workflowFile: string; + + cwd: string; + + constructor(workflowFile: string, cwd: string) { this.workflowFile = workflowFile; this.cwd = cwd; } - async mock(mockJobs) { + mock(mockJobs: Record = {}) { const filePath = this.getWorkflowPath(); - const workflow = await this.readWorkflowFile(filePath); + const workflow = this.readWorkflowFile(filePath); + Object.entries(mockJobs).forEach(([jobId, mockJob]) => { const job = this.locateJob(workflow, jobId); if (job) { @@ -21,13 +52,13 @@ class JobMocker { if (job.secrets) { delete job.secrets; } - let jobWith; + let jobWith: string | undefined; if (job.with) { jobWith = job.with; delete job.with; } - job.steps = _(mockJob.steps).map((step) => { - const mockStep = { + job.steps = mockJob.steps.map((step) => { + const mockStep: MockJobStep = { name: step.name, run: step.mockWith, }; @@ -52,7 +83,7 @@ class JobMocker { return this.writeWorkflowFile(filePath, workflow); } - locateJob(workflow, jobId) { + locateJob(workflow: YamlWorkflow, jobId: string) { return workflow.jobs[jobId]; } @@ -69,15 +100,17 @@ class JobMocker { throw new Error(`Could not locate ${this.workflowFile}`); } - async readWorkflowFile(location) { - return yaml.parse(fs.readFileSync(location, 'utf8')); + readWorkflowFile(location: PathOrFileDescriptor) { + const test: YamlWorkflow = yaml.parse(fs.readFileSync(location, 'utf8')); + + return test; } - async writeWorkflowFile(location, data) { + writeWorkflowFile(location: PathOrFileDescriptor, data: YamlWorkflow) { return fs.writeFileSync(location, yaml.stringify(data), 'utf8'); } } -module.exports = { - JobMocker, -}; +// eslint-disable-next-line import/prefer-default-export +export {JobMocker}; +export type {MockJob, YamlWorkflow, YamlMockJob, MockJobStep}; diff --git a/workflow_tests/utils/preGenerateTest.js b/workflow_tests/utils/preGenerateTest.ts similarity index 70% rename from workflow_tests/utils/preGenerateTest.js rename to workflow_tests/utils/preGenerateTest.ts index df4a12f68dc6..485b7b30bb35 100644 --- a/workflow_tests/utils/preGenerateTest.js +++ b/workflow_tests/utils/preGenerateTest.ts @@ -1,9 +1,10 @@ /* eslint no-console: ["error", { allow: ["warn", "log"] }] */ -const path = require('path'); -const {exit} = require('process'); -const fs = require('fs'); -const yaml = require('yaml'); -const _ = require('underscore'); +import type {PathLike} from 'fs'; +import fs from 'fs'; +import path from 'path'; +import {exit} from 'process'; +import yaml from 'yaml'; +import type {MockJobStep, YamlMockJob, YamlWorkflow} from './JobMocker'; const workflowsDirectory = path.resolve(__dirname, '..', '..', '.github', 'workflows'); const workflowTestsDirectory = path.resolve(__dirname, '..'); @@ -12,16 +13,16 @@ const workflowTestAssertionsDirectory = path.join(workflowTestsDirectory, 'asser const workflowFilePattern = '\\w+\\.yml'; const workflowFileRegex = new RegExp(workflowFilePattern, 'g'); -const capitalize = (s) => (s && s.charAt(0).toUpperCase() + s.slice(1)) || ''; -const mockFileTemplate = (mockSteps, exports) => `const utils = require('../utils/utils'); +const capitalize = (s: string): string => (s && s.charAt(0).toUpperCase() + s.slice(1)) || ''; +const mockFileTemplate = (mockSteps: string, exports: string) => `const utils = require('../utils/utils'); ${mockSteps} ${exports} `; -const assertionFileTemplate = (jobAssertions, exports) => `const utils = require('../utils/utils'); +const assertionFileTemplate = (jobAssertions: string, exports: string) => `const utils = require('../utils/utils'); ${jobAssertions} ${exports} `; -const testFileTemplate = (workflowName) => `const path = require('path'); +const testFileTemplate = (workflowName: string) => `const path = require('path'); const kieMockGithub = require('@kie/mock-github'); const utils = require('./utils/utils'); const assertions = require('./assertions/${workflowName}Assertions'); @@ -95,35 +96,42 @@ describe('test workflow ${workflowName}', () => { }); }); `; -const mockStepTemplate = (stepMockName, step, jobId) => ` + +const mockStepTemplate = (stepMockName: string, step: MockJobStep, jobId: string | undefined) => ` const ${stepMockName} = utils.createMockStep( - '${step.name || ''}', - '${step.name || ''}', + '${step.name ?? ''}', + '${step.name ?? ''}', ${jobId ? `'${jobId.toUpperCase()}'` : 'null'}, ${step.inputs ? JSON.stringify(step.inputs).replaceAll('"', "'") : 'null'}, ${step.envs ? JSON.stringify(step.envs).replaceAll('"', "'") : 'null'}, // add outputs if needed );`; -const stepAssertionTemplate = (step_name, job_id, step_message, inputs, envs) => ` +const stepAssertionTemplate = (stepName: string, jobId: string, stepMessage: string, inputs: string[] = [], envs: string[] = []) => { + const inputsString = inputs.map((input) => `{key: '${input}', value: '[FILL_IN]'}`).join(','); + const envsString = envs.map((env) => `{key: '${env}', value: '[FILL_IN]'}`).join(','); + + return ` utils.createStepAssertion( - '${step_name}', + '${stepName}', true, null, - '${job_id}', - '${step_message}', - [${_(inputs).map((input) => `{key: '${input}', value: '[FILL_IN]'}`)}], - [${_(envs).map((env) => `{key: '${env}', value: '[FILL_IN]'}`)}], + '${jobId}', + '${stepMessage}', + [${inputsString}], + [${envsString}], ),`; -const jobMocksTemplate = (jobMocksName, stepMocks) => ` -const ${jobMocksName} = [${_(stepMocks).map( - (stepMock) => ` - ${stepMock}`, -)} -];`; -const jobAssertionTemplate = (jobAssertionName, stepAssertions) => ` +}; + +const jobMocksTemplate = (jobMocksName: string, stepMocks: string[]) => { + const stepMocksString = stepMocks.map((stepMock) => `${stepMock}`).join(','); + + return `const ${jobMocksName} = [${stepMocksString} + ];`; +}; + +const jobAssertionTemplate = (jobAssertionName: string, stepAssertionsContent: string) => ` const ${jobAssertionName} = (workflowResult, didExecute = true) => { - const steps = [${stepAssertions} - ]; + const steps = [\n${stepAssertionsContent}\n]; for (const expectedStep of steps) { if (didExecute) { @@ -133,80 +141,84 @@ const ${jobAssertionName} = (workflowResult, didExecute = true) => { } } };`; -const mocksExportsTemplate = (jobMocks) => ` -module.exports = { - ${_(jobMocks).map((jobMock) => `${jobMock}`)} -};`; -const assertionsExportsTemplate = (jobAssertions) => ` -module.exports = { - ${_(jobAssertions).map((jobAssertion) => `${jobAssertion}`)} -};`; -const checkArguments = (args) => { +const mocksExportsTemplate = (jobMocks: string[]) => { + const jobMocksString = jobMocks.map((jobMock) => ` ${jobMock}: ${jobMock}`).join(',\n'); + + return `module.exports = {\n${jobMocksString}\n};\n`; +}; + +const assertionsExportsTemplate = (jobAssertions: string[]) => { + const assertionsString = jobAssertions.map((assertion) => ` ${assertion}: ${assertion}`).join(',\n'); + + return `module.exports = {\n${assertionsString}\n};\n`; +}; + +const checkArguments = (args: string[]) => { if (args.length > 0 && args[0]) { return; } console.warn('Please provide workflow file name'); exit(1); }; -const checkWorkflowFileName = (fileName) => { +const checkWorkflowFileName = (fileName: string) => { if (workflowFileRegex.test(fileName)) { return; } console.warn(`Please provide a valid workflow file name ([workflow].yml) instead of ${fileName}`); exit(1); }; -const checkWorkflowFilePath = (filePath) => { +const checkWorkflowFilePath = (filePath: PathLike) => { if (fs.existsSync(filePath)) { return; } - console.warn(`Provided workflow file does not exist: ${filePath}`); + console.warn(`Provided workflow file does not exist: ${filePath.toString()}`); exit(1); }; -const checkIfTestFileExists = (testsDirectory, testFileName) => { +const checkIfTestFileExists = (testsDirectory: string, testFileName: string) => { if (!fs.existsSync(path.join(testsDirectory, testFileName))) { return; } console.warn(`The test file ${testFileName} already exists, exiting`); exit(1); }; -const checkIfMocksFileExists = (mocksDirectory, mocksFileName) => { +const checkIfMocksFileExists = (mocksDirectory: string, mocksFileName: string) => { if (!fs.existsSync(path.join(mocksDirectory, mocksFileName))) { return; } console.warn(`The mocks file ${mocksFileName} already exists, exiting`); exit(1); }; -const checkIfAssertionsFileExists = (assertionsDirectory, assertionsFileName) => { +const checkIfAssertionsFileExists = (assertionsDirectory: string, assertionsFileName: string) => { if (!fs.existsSync(path.join(assertionsDirectory, assertionsFileName))) { return; } console.warn(`The assertions file ${assertionsFileName} already exists, exiting`); exit(1); }; -const parseWorkflowFile = (workflow) => { - const workflowJobs = {}; +const parseWorkflowFile = (workflow: YamlWorkflow) => { + const workflowJobs: Record = {}; Object.entries(workflow.jobs).forEach(([jobId, job]) => { workflowJobs[jobId] = { steps: [], }; job.steps.forEach((step) => { const workflowStep = { - name: step.name || '', - inputs: _.keys(step.with || {}) || [], - envs: _.keys(step.env || {}) || [], + name: step.name || '', // ? + inputs: Object.keys(step.with ?? {}), + envs: step.envs ?? [], }; workflowJobs[jobId].steps.push(workflowStep); }); }); return workflowJobs; }; -const getMockFileContent = (workflowName, jobs) => { +const getMockFileContent = (workflowName: string, jobs: Record) => { let content = ''; - const jobMocks = []; + const jobMocks: string[] = []; Object.entries(jobs).forEach(([jobId, job]) => { let mockStepsContent = `\n// ${jobId.toLowerCase()}`; - const stepMocks = []; + const stepMocks: string[] = []; job.steps.forEach((step) => { const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${step.name .replaceAll(' ', '_') @@ -215,7 +227,7 @@ const getMockFileContent = (workflowName, jobs) => { .replaceAll('#', '') .toUpperCase()}__STEP_MOCK`; stepMocks.push(stepMockName); - mockStepsContent += mockStepTemplate(stepMockName, step, jobId); + mockStepsContent += mockStepTemplate(stepMockName, step, jobId); // ? }); const jobMocksName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__STEP_MOCKS`; jobMocks.push(jobMocksName); @@ -224,9 +236,9 @@ const getMockFileContent = (workflowName, jobs) => { }); return mockFileTemplate(content, mocksExportsTemplate(jobMocks)); }; -const getAssertionsFileContent = (workflowName, jobs) => { +const getAssertionsFileContent = (jobs: Record) => { let content = ''; - const jobAssertions = []; + const jobAssertions: string[] = []; Object.entries(jobs).forEach(([jobId, job]) => { let stepAssertionsContent = ''; job.steps.forEach((step) => { @@ -238,12 +250,12 @@ const getAssertionsFileContent = (workflowName, jobs) => { }); return assertionFileTemplate(content, assertionsExportsTemplate(jobAssertions)); }; -const getTestFileContent = (workflowName) => testFileTemplate(workflowName); +const getTestFileContent = (workflowName: string) => testFileTemplate(workflowName); -const call_args = process.argv.slice(2); -checkArguments(call_args); +const callArgs = process.argv.slice(2); +checkArguments(callArgs); -const workflowFileName = call_args[0]; +const workflowFileName = callArgs[0]; checkWorkflowFileName(workflowFileName); const workflowName = workflowFileName.slice(0, -4); @@ -268,13 +280,13 @@ console.log(`Creating mock file ${mockFilePath}`); fs.writeFileSync(mockFilePath, mockFileContent); console.log(`Mock file ${mockFilePath} created`); -const assertionsFileContent = getAssertionsFileContent(workflowName, workflowJobs); +const assertionsFileContent = getAssertionsFileContent(workflowJobs); const assertionsFilePath = path.join(workflowTestAssertionsDirectory, workflowTestAssertionsFileName); console.log(`Creating assertions file ${assertionsFilePath}`); fs.writeFileSync(assertionsFilePath, assertionsFileContent); console.log(`Assertions file ${assertionsFilePath} created`); -const testFileContent = getTestFileContent(workflowName, workflowJobs); +const testFileContent = getTestFileContent(workflowName); const testFilePath = path.join(workflowTestsDirectory, workflowTestFileName); console.log(`Creating test file ${testFilePath}`); fs.writeFileSync(testFilePath, testFileContent); diff --git a/workflow_tests/utils/utils.js b/workflow_tests/utils/utils.ts similarity index 66% rename from workflow_tests/utils/utils.js rename to workflow_tests/utils/utils.ts index 32e106cfb1de..36e6cccd87fa 100644 --- a/workflow_tests/utils/utils.js +++ b/workflow_tests/utils/utils.ts @@ -1,9 +1,23 @@ -const yaml = require('yaml'); -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; +import yaml from 'yaml'; +import type {ExtendedAct} from './ExtendedAct'; +import type {MockJobStep} from './JobMocker'; + +type EventOptions = { + action: string; +}; -function setUpActParams(act, event = null, eventOptions = null, secrets = null, githubToken = null, envVars = null, inputs = null) { - let updated_act = act; +function setUpActParams( + act: ExtendedAct, + event = null, + eventOptions: EventOptions | null = null, + secrets: Record | null = null, + githubToken: string | null = null, + envVars: Record | null = null, + inputs: Record | null = null, +) { + let updatedAct = act; if (event && eventOptions) { // according to `Act` docs event data should be under the key with the event name (`[event]: eventOptions`), but @@ -13,39 +27,49 @@ function setUpActParams(act, event = null, eventOptions = null, secrets = null, [event]: eventOptions, ...eventOptions, }; - updated_act = updated_act.setEvent(eventData); + updatedAct = updatedAct.setEvent(eventData); } if (secrets) { Object.entries(secrets).forEach(([key, value]) => { - updated_act = updated_act.setSecret(key, value); + updatedAct = updatedAct.setSecret(key, value); }); } if (githubToken) { - updated_act = updated_act.setGithubToken(githubToken); + updatedAct = updatedAct.setGithubToken(githubToken); } if (envVars) { Object.entries(envVars).forEach(([key, value]) => { - updated_act = updated_act.setEnv(key, value); + updatedAct = updatedAct.setEnv(key, value); }); } if (inputs) { Object.entries(inputs).forEach(([key, value]) => { - updated_act = updated_act.setInput(key, value); + updatedAct = updatedAct.setInput(key, value); }); } - return updated_act; + return updatedAct; } -function createMockStep(name, message, job_id = null, inputs = null, in_envs = null, outputs = null, out_envs = null, isSuccessful = true, id = null) { +function createMockStep( + name: string, + message: string, + jobId: string | null = null, + inputs: string[] | null = null, + inEnvs: string[] | null = null, + outputs: Record | null = null, + outEnvs: Record | null = null, + isSuccessful = true, + id = null, +) { const mockStepName = name; let mockWithCommand = 'echo [MOCK]'; - if (job_id) { - mockWithCommand += ` [${job_id}]`; + if (jobId) { + mockWithCommand += ` [${jobId}]`; } mockWithCommand += ` ${message}`; if (inputs) { @@ -53,8 +77,8 @@ function createMockStep(name, message, job_id = null, inputs = null, in_envs = n mockWithCommand += `, ${input}="\${{ inputs.${input} && inputs.${input} || github.event.inputs.${input} }}"`; }); } - if (in_envs) { - in_envs.forEach((env) => { + if (inEnvs) { + inEnvs.forEach((env) => { mockWithCommand += `, ${env}="\${{ env.${env} }}"`; }); } @@ -63,15 +87,15 @@ function createMockStep(name, message, job_id = null, inputs = null, in_envs = n mockWithCommand += `\necho "${key}=${value}" >> "$GITHUB_OUTPUT"`; }); } - if (out_envs) { - Object.entries(out_envs).forEach(([key, value]) => { + if (outEnvs) { + Object.entries(outEnvs).forEach(([key, value]) => { mockWithCommand += `\necho "${key}=${value}" >> "$GITHUB_ENV"`; }); } if (!isSuccessful) { mockWithCommand += '\nexit 1'; } - const mockStep = { + const mockStep: MockJobStep = { name: mockStepName, mockWith: mockWithCommand, }; @@ -81,10 +105,19 @@ function createMockStep(name, message, job_id = null, inputs = null, in_envs = n return mockStep; } -function createStepAssertion(name, isSuccessful = true, expectedOutput = null, jobId = null, message = null, inputs = null, envs = null) { +function createStepAssertion( + name: string, + isSuccessful = true, + expectedOutput = null, + jobId: string | null = null, + message: string | null = null, + // Replace arrays with records + inputs: Array<{key: string; value: string}> | null = null, + envs: Array<{key: string; value: string}> | null = null, +) { const stepName = `Main ${name}`; const stepStatus = isSuccessful ? 0 : 1; - let stepOutput; + let stepOutput: string; if (expectedOutput !== undefined && expectedOutput !== null) { stepOutput = expectedOutput; } else { @@ -113,7 +146,7 @@ function createStepAssertion(name, isSuccessful = true, expectedOutput = null, j }; } -function setJobRunners(act, jobs, workflowPath) { +function setJobRunners(act: ExtendedAct, jobs: Record, workflowPath: string) { if (!act || !jobs || !workflowPath) { return act; } @@ -127,11 +160,12 @@ function setJobRunners(act, jobs, workflowPath) { return act; } -function deepCopy(originalObject) { +function deepCopy(originalObject: T): T { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return JSON.parse(JSON.stringify(originalObject)); } -function getLogFilePath(workflowName, testName) { +function getLogFilePath(workflowName: string, testName: string) { const logsDir = path.resolve(__dirname, '..', 'logs'); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir); From 8de01d0f2c7a81dfa8a542c071b93aa6e04714f5 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 12 Mar 2024 16:10:52 +0100 Subject: [PATCH 129/345] address comments --- src/types/onyx/ReportAction.ts | 6 +- tests/unit/MigrationTest.ts | 192 ++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 93 deletions(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 4512f04964b8..f6c34fe742a4 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -2,8 +2,6 @@ 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'; @@ -229,7 +227,5 @@ type ReportAction = ReportActionBase & OriginalMessage; type ReportActions = Record; -type ReportActionCollectionDataSet = CollectionDataSet; - export default ReportAction; -export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage, ReportActionCollectionDataSet}; +export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage}; diff --git a/tests/unit/MigrationTest.ts b/tests/unit/MigrationTest.ts index d60761cd1d89..147588559e13 100644 --- a/tests/unit/MigrationTest.ts +++ b/tests/unit/MigrationTest.ts @@ -5,10 +5,11 @@ 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 { toCollectionDataSet } from '@src/types/utils/CollectionDataSet'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + jest.mock('@src/libs/getPlatform'); let LogSpy: jest.SpyInstance>; @@ -34,22 +35,23 @@ describe('Migrations', () => { )); 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, - }, - }; + const reportActionsCollectionDataSet = toCollectionDataSet( + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + [ + { + 1: { + reportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '1', + }, + 2: {reportActionID: '2', created: '', actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, reportID: '1'}, + }, + ], + (item) => item[1].reportID ?? '', + ); - return Onyx.multiSet(setQueries) + return Onyx.multiSet(reportActionsCollectionDataSet) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith( @@ -68,24 +70,30 @@ describe('Migrations', () => { }); 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, - }, - }; + const reportActionsCollectionDataSet = toCollectionDataSet( + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + [ + { + 1: { + reportActionID: '1', + previousReportActionID: '0', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '1', + }, + 2: { + reportActionID: '2', + previousReportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '1', + }, + }, + ], + (item) => item[1].reportID ?? '', + ); - return Onyx.multiSet(setQueries) + return Onyx.multiSet(reportActionsCollectionDataSet) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); @@ -111,29 +119,33 @@ describe('Migrations', () => { }); 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) + const reportActionsCollectionDataSet = toCollectionDataSet( + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + [ + { + 1: { + reportActionID: '1', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '4', + }, + 2: { + reportActionID: '2', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '4', + }, + }, + ], + (item) => item[1].reportID ?? '', + ); + + return Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + ...reportActionsCollectionDataSet, + }) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith( @@ -155,30 +167,35 @@ describe('Migrations', () => { }); 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, - }, - }; - - return Onyx.multiSet(setQueries) + const reportActionsCollectionDataSet = toCollectionDataSet( + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + [ + { + 1: { + reportActionID: '1', + previousReportActionID: '10', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '4', + }, + 2: { + reportActionID: '2', + previousReportActionID: '23', + created: '', + actionName: CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + reportID: '4', + }, + }, + ], + (item) => item[1].reportID ?? '', + ); + + return Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + ...reportActionsCollectionDataSet, + }) .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); @@ -208,15 +225,14 @@ describe('Migrations', () => { }); 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; + const setQueries = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: null, + }; + // @ts-expect-error preset null values return Onyx.multiSet(setQueries) .then(CheckForPreviousReportActionID) .then(() => { From a8317715003ac1c12dec272cdc7818a5c7e6cc69 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 12 Mar 2024 19:08:55 +0200 Subject: [PATCH 130/345] adding fallback values for the lat and lng --- .../iou/request/step/IOURequestStepWaypoint.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx index c8232b9cdb7b..96d355489f82 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -1,10 +1,10 @@ import {useNavigation} from '@react-navigation/native'; import React, {useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; import type {TextInput} from 'react-native'; +import {View} from 'react-native'; import type {Place} from 'react-native-google-places-autocomplete'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AddressSearch from '@components/AddressSearch'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -124,8 +124,10 @@ function IOURequestStepWaypoint({ // Therefore, we're going to save the waypoint as just the address, and the lat/long will be filled in on the backend if (isOffline && waypointValue) { const waypoint = { - address: waypointValue, + address: waypointValue ?? '', name: values.name ?? '', + lat: values.lat ?? 0, + lng: values.lng ?? 0, }; saveWaypoint(waypoint); } @@ -142,10 +144,10 @@ function IOURequestStepWaypoint({ const selectWaypoint = (values: Waypoint) => { const waypoint = { - lat: values.lat, - lng: values.lng, - address: values.address, - name: values.name, + lat: values.lat ?? 0, + lng: values.lng ?? 0, + address: values.address ?? '', + name: values.name ?? '', }; Transaction.saveWaypoint(transactionID, pageIndex, waypoint, action === CONST.IOU.ACTION.CREATE); From a669de520285b8c7da12f755d6037470be9202ba Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 12 Mar 2024 14:47:23 -0400 Subject: [PATCH 131/345] Fix Ping endpoint --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index f5c391aad09c..1a51391e8684 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -81,7 +81,7 @@ function subscribeToNetInfo(): void { // By default, NetInfo uses `/` for `reachabilityUrl` // When App is served locally (or from Electron) this address is always reachable - even offline // Using the API url ensures reachability is tested over internet - reachabilityUrl: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, + reachabilityUrl: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, reachabilityMethod: 'GET', reachabilityTest: (response) => { if (!response.ok) { From 9c196a4468f8517d3432a944e5defdf09ba93f65 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 13 Mar 2024 08:44:42 +0100 Subject: [PATCH 132/345] cleanup --- package.json | 2 +- workflow_tests/utils/JobMocker.ts | 18 +++++++++--------- workflow_tests/utils/preGenerateTest.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7dab1aeaa386..d8a35d7753b9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "test:e2e:dev": "ts-node tests/e2e/testRunner.js --config ./config.dev.js", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", - "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js", + "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.ts", "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1", "e2e-test-runner-build": "ncc build tests/e2e/testRunner.js -o tests/e2e/dist/" }, diff --git a/workflow_tests/utils/JobMocker.ts b/workflow_tests/utils/JobMocker.ts index 66e1e19e542b..3bbdaa118e0c 100644 --- a/workflow_tests/utils/JobMocker.ts +++ b/workflow_tests/utils/JobMocker.ts @@ -12,20 +12,20 @@ type YamlWorkflow = { type MockJob = { steps: MockJobStep[]; - uses?: string; // ? - secrets?: string[]; // ? - with?: string; // ? - outputs?: string[]; // ? - runsOn: string; // ? + uses?: string; + secrets?: string[]; + with?: string; + outputs?: string[]; + runsOn: string; }; type MockJobStep = { - id?: string; // ? + id?: string; name: string; - run?: string; // ? + run?: string; mockWith?: string; - with?: string; // ? - envs?: string[]; // ? + with?: string; + envs?: string[]; inputs?: string[]; }; diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index 485b7b30bb35..1a8bdfd85ed5 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -204,7 +204,7 @@ const parseWorkflowFile = (workflow: YamlWorkflow) => { }; job.steps.forEach((step) => { const workflowStep = { - name: step.name || '', // ? + name: step.name, inputs: Object.keys(step.with ?? {}), envs: step.envs ?? [], }; @@ -227,7 +227,7 @@ const getMockFileContent = (workflowName: string, jobs: Record Date: Wed, 13 Mar 2024 14:58:32 +0700 Subject: [PATCH 133/345] fix: stop camera usage --- 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 09eb85348ef7..7b1a5936a4ef 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -91,6 +91,7 @@ function IOURequestStepScan({ } navigator.mediaDevices.getUserMedia({video: {facingMode: {exact: 'environment'}, zoom: {ideal: 1}}}).then((stream) => { + _.forEach(stream.getTracks(), (track) => track.stop()); // Only Safari 17+ supports zoom constraint if (Browser.isMobileSafari() && stream.getTracks().length > 0) { const deviceId = _.chain(stream.getTracks()) @@ -103,7 +104,6 @@ function IOURequestStepScan({ return; } } - _.forEach(stream.getTracks(), (track) => track.stop()); if (!navigator.mediaDevices.enumerateDevices) { setVideoConstraints({facingMode: {exact: 'environment'}}); return; From dff681dfb13a67bc6e8618936ea2288cc8340d1c Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:29:56 +0100 Subject: [PATCH 134/345] add edit tax modal --- src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 4 + src/libs/PolicyUtils.ts | 8 +- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 97 +++++++++++++++++++ .../workspace/taxes/WorkspaceTaxesPage.tsx | 2 +- 8 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fd30bb0a6ac9..5a9c0cc7ad2a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -601,6 +601,10 @@ const ROUTES = { route: 'workspace/:policyID/taxes/new', getRoute: (policyID: string) => `workspace/${policyID}/taxes/new` as const, }, + WORKSPACE_TAXES_EDIT: { + route: 'workspace/:policyID/tax/:taxID', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5e3126dfe7f5..11c2d38f4361 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -221,6 +221,7 @@ const SCREENS = { TAGS_EDIT: 'Tags_Edit', TAXES: 'Workspace_Taxes', TAXES_NEW: 'Workspace_Taxes_New', + TAXES_EDIT: 'Workspace_Taxes_Edit', TAG_CREATE: 'Tag_Create', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2a6a1a0dbb03..164dccbc10ad 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -277,6 +277,7 @@ const SettingsModalStackNavigator = createModalStackNavigator 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, + [SCREENS.WORKSPACE.TAXES_EDIT]: () => require('../../../pages/workspace/taxes/WorkspaceEditTaxPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index b759ff9e977e..d9fd1fc98c9c 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -318,6 +318,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.TAXES_NEW]: { path: ROUTES.WORKSPACE_TAXES_NEW.route, }, + [SCREENS.WORKSPACE.TAXES_EDIT]: { + path: ROUTES.WORKSPACE_TAXES_EDIT.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c8ea81b2b5a7..dafe451262d2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -212,6 +212,10 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.TAXES_NEW]: { policyID: string; }; + [SCREENS.WORKSPACE.TAXES_EDIT]: { + policyID: string; + taxID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index d42ad0d56d77..fe6fec83730b 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,7 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Navigation from './Navigation/Navigation'; @@ -272,6 +272,11 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +/** Get a tax with given ID from policy */ +function getTaxByID(policy: OnyxEntry, taxID: string): TaxRate | undefined { + return policy?.taxRates?.taxes?.[taxID ?? '']; +} + export { getActivePolicies, hasAccountingConnections, @@ -303,6 +308,7 @@ export { getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, hasTaxRateError, + getTaxByID, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx new file mode 100644 index 000000000000..94524ae16cd7 --- /dev/null +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -0,0 +1,97 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import type SCREENS from '@src/SCREENS'; + +type WorkspaceEditTaxPageBaseProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +function WorkspaceEditTaxPage({ + route: { + params: {taxID}, + }, + policy, +}: WorkspaceEditTaxPageBaseProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const {windowWidth} = useWindowDimensions(); + + const toggle = () => {}; + + const threeDotsMenuItems = useMemo(() => { + const menuItems = [ + { + icon: Expensicons.Trashcan, + text: translate('common.delete'), + onSelected: () => {}, + }, + ]; + return menuItems; + }, [translate]); + + return ( + + + + + {taxID ? ( + // TODO: Extract it to a separate component or use a common one + + + Enable rate + + + + + + ) : null} + {}} + /> + {}} + /> + + + + ); +} + +WorkspaceEditTaxPage.displayName = 'WorkspaceEditTaxPage'; + +export default withPolicyAndFullscreenLoading(WorkspaceEditTaxPage); diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index b0436f20a522..cede2bd31e7d 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -182,7 +182,7 @@ function WorkspaceTaxesPage({policy, route}: WorkspaceTaxesPageProps) { canSelectMultiple sections={[{data: taxesList, indexOffset: 0, isDisabled: false}]} onCheckboxPress={toggleTax} - onSelectRow={() => {}} + onSelectRow={(tax: ListItem) => tax.keyForList && Navigation.navigate(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policy?.id ?? '', tax.keyForList))} onSelectAll={toggleAllTaxes} showScrollIndicator ListItem={TableListItem} From ff731cbbad482073e39f4638d4ca020a353ae75c Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:45:08 +0100 Subject: [PATCH 135/345] add enabling/disabling taxes --- .../parameters/SetPolicyTaxesEnabledParams.ts | 10 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 2 +- src/libs/actions/TaxRate.ts | 75 ++++++++++++++++++- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 10 ++- 6 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts diff --git a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts new file mode 100644 index 000000000000..0bc8550cd01b --- /dev/null +++ b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts @@ -0,0 +1,10 @@ +type SetPolicyTaxesEnabledParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array<{taxCode: string, enabled: bool}> + */ + taxFields: string; +}; + +export default SetPolicyTaxesEnabledParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 643657e86614..bfe08dbab50f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -176,3 +176,4 @@ export type {default as OpenPolicyWorkflowsPageParams} from './OpenPolicyWorkflo export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams'; export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; +export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 12c7f3c3bd5a..271aec0ec9be 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -173,6 +173,7 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_TAX: 'CreatePolicyTax', + SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', } as const; type WriteCommand = ValueOf; @@ -344,6 +345,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; + [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 2693f443d659..a7d3fad55788 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -13,7 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW], + [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 770417e56fe2..3c1f777f315e 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,14 +1,22 @@ +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {TaxRate} from '@src/types/onyx'; +import type {Policy, TaxRate} from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; +let allPolicies: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (value) => (allPolicies = value), +}); + /** * Get tax value with percentage */ @@ -111,4 +119,65 @@ function clearTaxRateError(policyID: string, taxID: string, pendingAction?: Pend }); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage}; +type TaxRateEnabledMap = Record>; + +function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isEnabled: boolean) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxes = {...policy?.taxRates?.taxes}; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !isEnabled, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !isEnabled, pendingAction: null}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !!originalTaxes[taxID].isDisabled, pendingAction: null}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + }; + + const parameters = { + policyID, + taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), + } satisfies SetPolicyTaxesEnabledParams; + + console.log({parameters}); + + API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 94524ae16cd7..22706f22faf1 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -10,6 +10,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -29,7 +30,14 @@ function WorkspaceEditTaxPage({ const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const {windowWidth} = useWindowDimensions(); - const toggle = () => {}; + const toggle = () => { + // TODO: Backend call doesn't exist yet + return; + if (!policy?.id || !currentTaxRate) { + return; + } + setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); + }; const threeDotsMenuItems = useMemo(() => { const menuItems = [ From ea97afb6673f59c3112b9a85658538f6fb051c47 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:39:36 +0100 Subject: [PATCH 136/345] add deleting tax rates --- src/languages/en.ts | 2 + .../API/parameters/DeletePolicyTaxesParams.ts | 11 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/TaxRate.ts | 83 +++++++++++++++++-- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 28 ++++++- 6 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 src/libs/API/parameters/DeletePolicyTaxesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 4ca7b1e059ab..1c5a6931e10b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1856,6 +1856,8 @@ export default { valuePercentageRange: 'Please enter a valid percentage between 0 and 100', genericFailureMessage: 'An error occurred while updating the tax rate, please try again.', }, + deleteTax: 'Delete tax', + deleteTaxConfirmation: 'Are you sure you want to delete this tax?', }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/libs/API/parameters/DeletePolicyTaxesParams.ts b/src/libs/API/parameters/DeletePolicyTaxesParams.ts new file mode 100644 index 000000000000..fe03d388a129 --- /dev/null +++ b/src/libs/API/parameters/DeletePolicyTaxesParams.ts @@ -0,0 +1,11 @@ +type DeletePolicyTaxesParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + * Each element is a tax name + */ + taxCodes: string; +}; + +export default DeletePolicyTaxesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index bfe08dbab50f..6567a3e22ad0 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -177,3 +177,4 @@ export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDis export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams'; export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; +export type {default as DeletePolicyTaxesParams} from './DeletePolicyTaxesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 271aec0ec9be..98e5d820363a 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -174,6 +174,7 @@ const WRITE_COMMANDS = { DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_TAX: 'CreatePolicyTax', SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', + DELETE_POLICY_TAXES: 'DeletePolicyTaxes', } as const; type WriteCommand = ValueOf; @@ -346,6 +347,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; + [WRITE_COMMANDS.DELETE_POLICY_TAXES]: Parameters.DeletePolicyTaxesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 3c1f777f315e..1bc1f3460af1 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,13 +1,13 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, TaxRate} from '@src/types/onyx'; -import type {PendingAction} from '@src/types/onyx/OnyxCommon'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; let allPolicies: OnyxCollection; @@ -99,7 +99,7 @@ function createWorkspaceTax(policyID: string, taxRate: TaxRate) { API.write(WRITE_COMMANDS.CREATE_POLICY_TAX, parameters, onyxData); } -function clearTaxRateError(policyID: string, taxID: string, pendingAction?: PendingAction) { +function clearTaxRateError(policyID: string, taxID: string, pendingAction?: OnyxCommon.PendingAction) { if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { taxRates: { @@ -175,9 +175,80 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; - console.log({parameters}); - API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled}; +type TaxRateDeleteMap = Record< + string, + | (Pick & { + errors: OnyxCommon.Errors | null; + }) + | null +>; + +/** + * API call to delete policy taxes + * @param taxesToDelete A tax IDs array to delete + */ +function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const policyTaxRates = policy?.taxRates?.taxes; + + if (!policyTaxRates) { + throw new Error('Policy or tax rates not found'); + } + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, errors: null}; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = null; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + }; + + const parameters = { + policyID, + taxCodes: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), + } as DeletePolicyTaxesParams; + + API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 22706f22faf1..26e36e7f9b38 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -10,7 +11,8 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; +import {deletePolicyTaxes, setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -29,6 +31,7 @@ function WorkspaceEditTaxPage({ const {translate} = useLocalize(); const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const {windowWidth} = useWindowDimensions(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const toggle = () => { // TODO: Backend call doesn't exist yet @@ -39,12 +42,21 @@ function WorkspaceEditTaxPage({ setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); }; + const deleteTax = () => { + if (!policy?.id) { + return; + } + deletePolicyTaxes(policy?.id, [taxID]); + setIsDeleteModalVisible(false); + Navigation.goBack(); + }; + const threeDotsMenuItems = useMemo(() => { const menuItems = [ { icon: Expensicons.Trashcan, text: translate('common.delete'), - onSelected: () => {}, + onSelected: () => setIsDeleteModalVisible(true), }, ]; return menuItems; @@ -96,6 +108,16 @@ function WorkspaceEditTaxPage({ /> + setIsDeleteModalVisible(false)} + prompt={translate('workspace.taxes.deleteTaxConfirmation')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> ); } From 8a489d3a70907d5c24f7021e8a47def7047dcc79 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 13 Mar 2024 12:54:38 +0100 Subject: [PATCH 137/345] fix: after conflicts resolve --- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 2 +- .../linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts | 2 +- src/libs/actions/Policy.ts | 8 -------- .../distanceRates/PolicyDistanceRatesSettingsPage.tsx | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 293dd15440b9..42932a78bd0a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -262,7 +262,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, - [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index da56b98519d4..905c62e5f812 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -13,7 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index cb03857c0507..22370983e4bb 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3536,10 +3536,6 @@ function clearCreateDistanceRateError(policyID: string, currentRates: Record Date: Wed, 13 Mar 2024 13:15:43 +0100 Subject: [PATCH 138/345] config jest ts --- package-lock.json | 1460 ++++----------------------- package.json | 1 + workflow_tests/jest.config.js | 1 + workflow_tests/utils/ExtendedAct.ts | 2 +- workflow_tests/utils/utils.ts | 11 +- 5 files changed, 201 insertions(+), 1274 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81686286bf1c..2986136f8a98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -234,6 +234,7 @@ "shellcheck": "^1.1.0", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", + "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", "typescript": "^5.3.2", @@ -5960,30 +5961,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6099,30 +6076,6 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6204,94 +6157,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/create-cache-key-function/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/@jest/create-cache-key-function/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/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==" - }, - "node_modules/@jest/create-cache-key-function/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -6306,94 +6171,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/@jest/environment/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/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==" - }, - "node_modules/@jest/environment/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/expect": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", @@ -6433,94 +6210,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/@jest/fake-timers/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/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==" - }, - "node_modules/@jest/fake-timers/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/globals": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", @@ -6535,94 +6224,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/@jest/globals/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/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==" - }, - "node_modules/@jest/globals/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/reporters": { "version": "29.4.1", "license": "MIT", @@ -6664,30 +6265,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6823,100 +6400,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/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==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "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/@jest/test-result/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==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/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==", - "license": "MIT" - }, - "node_modules/@jest/test-result/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/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==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/test-sequencer": { "version": "29.4.1", "license": "MIT", @@ -6955,30 +6438,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/transform/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7061,19 +6520,19 @@ } }, "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types/node_modules/ansi-styles": { @@ -9440,6 +8899,29 @@ "ws": "^7.5.1" } }, + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, "node_modules/@react-native-community/cli-server-api/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -9462,6 +8944,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/@react-native-community/cli-server-api/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -9478,6 +8975,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/@react-native-community/cli-server-api/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==", + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -9497,6 +9002,17 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/@react-native-community/cli-server-api/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", @@ -21227,18 +20743,17 @@ } }, "node_modules/@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "license": "MIT" + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@types/yauzl": { "version": "2.10.0", @@ -25046,6 +24561,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -35690,30 +35217,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -35818,30 +35321,6 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -36004,30 +35483,6 @@ } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -36206,30 +35661,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-each/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -36325,30 +35756,6 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-environment-jsdom/node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -36360,55 +35767,6 @@ "node": ">=0.4.0" } }, - "node_modules/jest-environment-jsdom/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==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "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/jest-environment-jsdom/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==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/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==", - "license": "MIT" - }, "node_modules/jest-environment-jsdom/node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", @@ -36462,15 +35820,6 @@ "node": ">= 6" } }, - "node_modules/jest-environment-jsdom/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -36537,18 +35886,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/jest-environment-jsdom/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==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom/node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -36626,94 +35963,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/jest-environment-node/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/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==" - }, - "node_modules/jest-environment-node/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-expo": { "version": "50.0.1", "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-50.0.1.tgz", @@ -36786,75 +36035,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/jest-haste-map/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/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==" - }, "node_modules/jest-haste-map/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -36891,17 +36071,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest-haste-map/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-leak-detector": { "version": "29.4.1", "license": "MIT", @@ -37016,30 +36185,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -37117,94 +36262,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/jest-mock/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/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==" - }, - "node_modules/jest-mock/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -37359,30 +36416,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runner/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -37524,30 +36557,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-runtime/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -37648,30 +36657,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -37762,30 +36747,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -37866,30 +36827,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-validate/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -38210,30 +37147,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-watcher/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -38351,100 +37264,6 @@ "node": ">=8" } }, - "node_modules/jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest/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==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "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/jest/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==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/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==", - "license": "MIT" - }, - "node_modules/jest/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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/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==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jimp-compact": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz", @@ -39226,6 +38045,12 @@ "dev": true, "peer": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -45137,6 +43962,29 @@ "node": ">=8" } }, + "node_modules/react-native/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/react-native/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, "node_modules/react-native/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -45159,6 +44007,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/react-native/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/react-native/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -45201,6 +44064,14 @@ "node": ">=18" } }, + "node_modules/react-native/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==", + "engines": { + "node": ">=8" + } + }, "node_modules/react-native/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -45255,6 +44126,17 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-native/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/react-native/node_modules/ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -50317,6 +49199,58 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/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/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/package.json b/package.json index d8a35d7753b9..487e345e28dd 100644 --- a/package.json +++ b/package.json @@ -283,6 +283,7 @@ "shellcheck": "^1.1.0", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", + "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", "typescript": "^5.3.2", diff --git a/workflow_tests/jest.config.js b/workflow_tests/jest.config.js index c8a4534764e3..cecdf8589d7f 100644 --- a/workflow_tests/jest.config.js +++ b/workflow_tests/jest.config.js @@ -2,6 +2,7 @@ module.exports = { verbose: true, transform: { '^.+\\.jsx?$': 'babel-jest', + '^.+\\.tsx?$': 'ts-jest', }, clearMocks: true, resetMocks: true, diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index f471292b1509..addd59be43c7 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/dot-notation */ // This eslint-disable comment is here to allow accessing private properties in the Act class import type {RunOpts, Workflow} from '@kie/act-js'; -import kieActJs from '@kie/act-js'; +import * as kieActJs from '@kie/act-js'; import path from 'path'; import {JobMocker} from './JobMocker'; import type {MockJob} from './JobMocker'; diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index 36e6cccd87fa..d6db1ff8a92b 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -200,13 +200,4 @@ const FILES_TO_COPY_INTO_TEST_REPO = [ }, ]; -module.exports = { - setUpActParams, - createMockStep, - createStepAssertion, - setJobRunners, - deepCopy, - getLogFilePath, - FILES_TO_COPY_INTO_TEST_REPO, - removeMockRepoDir, -}; +export {setUpActParams, createMockStep, createStepAssertion, setJobRunners, deepCopy, getLogFilePath, FILES_TO_COPY_INTO_TEST_REPO, removeMockRepoDir}; From 3cf6a2a98fdb6ca38ac9dee063cda4a4d10d69f9 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 13 Mar 2024 13:16:03 +0100 Subject: [PATCH 139/345] fix: update error message --- src/libs/actions/Policy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 22370983e4bb..641dd06a97e8 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3574,7 +3574,7 @@ function setPolicyDistanceRatesUnit(policyID: string, currentCustomUnit: CustomU customUnits: { [currentCustomUnit.customUnitID]: { ...currentCustomUnit, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.updateRateUnitGenericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), pendingAction: null, }, }, @@ -3629,7 +3629,7 @@ function setPolicyDistanceRatesDefaultCategory(policyID: string, currentCustomUn customUnits: { [currentCustomUnit.customUnitID]: { ...currentCustomUnit, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.updateRateDefaultCategoryGenericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), pendingAction: null, }, }, From 3e5fe1dcb929e909f93d72ed9edc2ff07619c17b Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 13 Mar 2024 14:53:35 +0100 Subject: [PATCH 140/345] review fixes --- workflow_tests/utils/ExtendedAct.ts | 2 +- workflow_tests/utils/preGenerateTest.ts | 3 +-- workflow_tests/utils/utils.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index addd59be43c7..99b8551337b8 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -8,7 +8,7 @@ import type {MockJob} from './JobMocker'; type ExtendedActOpts = RunOpts & {actor?: string; workflowFile?: string; mockJobs?: Record}; -// @ts-expect-error Override shouldn't be done on private methods - wait until the issue is resolved +// @ts-expect-error Override shouldn't be done on private methods wait until https://github.com/kiegroup/act-js/issues/77 is resolved or try to create a params workaround class ExtendedAct extends kieActJs.Act { async parseRunOpts(opts?: ExtendedActOpts) { const {cwd, actArguments, proxy} = await super['parseRunOpts'](opts); diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index 1a8bdfd85ed5..1bc5a01975e7 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -125,8 +125,7 @@ const stepAssertionTemplate = (stepName: string, jobId: string, stepMessage: str const jobMocksTemplate = (jobMocksName: string, stepMocks: string[]) => { const stepMocksString = stepMocks.map((stepMock) => `${stepMock}`).join(','); - return `const ${jobMocksName} = [${stepMocksString} - ];`; + return `const ${jobMocksName} = [${stepMocksString}\n];`; }; const jobAssertionTemplate = (jobAssertionName: string, stepAssertionsContent: string) => ` diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index d6db1ff8a92b..7e477c6ab63d 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -160,7 +160,7 @@ function setJobRunners(act: ExtendedAct, jobs: Record, workflowP return act; } -function deepCopy(originalObject: T): T { +function deepCopy>(originalObject: TObject): TObject { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return JSON.parse(JSON.stringify(originalObject)); } From f0719b2776b3e51ad089b758a7186f83130ef9bb Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 13 Mar 2024 16:07:31 +0100 Subject: [PATCH 141/345] revert deepCopy signature --- workflow_tests/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index 7e477c6ab63d..9583ec233e60 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -160,7 +160,7 @@ function setJobRunners(act: ExtendedAct, jobs: Record, workflowP return act; } -function deepCopy>(originalObject: TObject): TObject { +function deepCopy(originalObject: TObject): TObject { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return JSON.parse(JSON.stringify(originalObject)); } From 9a9d55d09ff9b283ed9abb9947efcdd84164a62d Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Wed, 13 Mar 2024 17:21:44 +0100 Subject: [PATCH 142/345] initial edit page --- src/ONYXKEYS.ts | 6 +- src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../API/parameters/RenamePolicyTagsParams.ts | 10 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 6 + src/libs/Navigation/types.ts | 4 + src/libs/actions/Policy.ts | 71 +++++++++++ .../workspace/tags/WorkspaceCreateTagPage.tsx | 2 +- .../workspace/tags/WorkspaceEditTagPage.tsx | 114 ++++++++++++++++++ ...ceTagCreateForm.ts => WorkspaceTagForm.ts} | 4 +- src/types/form/index.ts | 2 +- 16 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 src/libs/API/parameters/RenamePolicyTagsParams.ts create mode 100644 src/pages/workspace/tags/WorkspaceEditTagPage.tsx rename src/types/form/{WorkspaceTagCreateForm.ts => WorkspaceTagForm.ts} (78%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8c48cbad561f..5393519f693d 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -332,8 +332,8 @@ const ONYXKEYS = { WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm', WORKSPACE_CATEGORY_CREATE_FORM: 'workspaceCategoryCreate', WORKSPACE_CATEGORY_CREATE_FORM_DRAFT: 'workspaceCategoryCreateDraft', - WORKSPACE_TAG_CREATE_FORM: 'workspaceTagCreate', - WORKSPACE_TAG_CREATE_FORM_DRAFT: 'workspaceTagCreateDraft', + WORKSPACE_TAG_FORM: 'workspaceTagForm', + WORKSPACE_TAG_FORM_DRAFT: 'workspaceTagFormDraft', WORKSPACE_SETTINGS_FORM_DRAFT: 'workspaceSettingsFormDraft', WORKSPACE_DESCRIPTION_FORM: 'workspaceDescriptionForm', WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft', @@ -418,7 +418,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: FormTypes.AddDebitCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm; [ONYXKEYS.FORMS.WORKSPACE_CATEGORY_CREATE_FORM]: FormTypes.WorkspaceCategoryCreateForm; - [ONYXKEYS.FORMS.WORKSPACE_TAG_CREATE_FORM]: FormTypes.WorkspaceTagCreateForm; + [ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm; [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: FormTypes.WorkspaceRateAndUnitForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ad2d9c10700b..9f014a47b4c0 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -585,6 +585,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tags/edit', getRoute: (policyID: string) => `settings/workspaces/${policyID}/tags/edit` as const, }, + WORKSPACE_TAG_EDIT: { + route: 'workspace/:policyID/tags/:tagName/edit', + getRoute: (policyID: string, tagName: string) => `workspace/${policyID}/tags/${encodeURI(tagName)}/edit` as const, + }, WORKSPACE_TAXES: { route: 'settings/workspaces/:policyID/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/taxes` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6c742f08bfb7..db62371d4591 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -219,6 +219,7 @@ const SCREENS = { TAGS: 'Workspace_Tags', TAGS_SETTINGS: 'Tags_Settings', TAGS_EDIT: 'Tags_Edit', + TAG_EDIT: 'Tag_Edit', TAXES: 'Workspace_Taxes', TAG_CREATE: 'Tag_Create', CURRENCY: 'Workspace_Profile_Currency', diff --git a/src/languages/en.ts b/src/languages/en.ts index 6ec5983583fc..0054fd8326a4 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1840,6 +1840,7 @@ export default { customTagName: 'Custom tag name', enableTag: 'Enable tag', addTag: 'Add tag', + editTag: 'Edit tag', subtitle: 'Tags add more detailed ways to classify costs.', emptyTags: { title: "You haven't created any tags", diff --git a/src/languages/es.ts b/src/languages/es.ts index c2eb6374affa..b7de31f792ed 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1864,6 +1864,7 @@ export default { customTagName: 'Nombre de etiqueta personalizada', enableTag: 'Habilitar etiqueta', addTag: 'Añadir etiqueta', + editTag: 'Editar etiqueta', subtitle: 'Las etiquetas añaden formas más detalladas de clasificar los costos.', emptyTags: { title: 'No has creado ninguna etiqueta', diff --git a/src/libs/API/parameters/RenamePolicyTagsParams.ts b/src/libs/API/parameters/RenamePolicyTagsParams.ts new file mode 100644 index 000000000000..51686ade1b9e --- /dev/null +++ b/src/libs/API/parameters/RenamePolicyTagsParams.ts @@ -0,0 +1,10 @@ +type RenamePolicyTagsParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * {[oldName: string]: string;} where value is new tag name + */ + tags: string; +}; + +export default RenamePolicyTagsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 25c336753203..09f69fe1f535 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -177,3 +177,4 @@ export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDis export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams'; export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMoreFeaturesPageParams'; export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; +export type {default as RenamePolicyTagsParams} from './RenamePolicyTagsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 07f1ca09d7c5..f8f128c6d4e8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -119,6 +119,7 @@ const WRITE_COMMANDS = { SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled', CREATE_WORKSPACE_CATEGORIES: 'CreateWorkspaceCategories', CREATE_POLICY_TAG: 'CreatePolicyTag', + RENAME_POLICY_TAG: 'RenamePolicyTag', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', DELETE_WORKSPACE_CATEGORIES: 'DeleteWorkspaceCategories', SET_POLICY_REQUIRES_TAG: 'SetPolicyRequiresTag', @@ -287,6 +288,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG]: Parameters.SetPolicyRequiresTag; [WRITE_COMMANDS.RENAME_POLICY_TAG_LIST]: Parameters.RenamePolicyTaglist; [WRITE_COMMANDS.CREATE_POLICY_TAG]: Parameters.CreatePolicyTagsParams; + [WRITE_COMMANDS.RENAME_POLICY_TAG]: Parameters.RenamePolicyTagsParams; [WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams; [WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams; [WRITE_COMMANDS.EDIT_TASK_ASSIGNEE]: Parameters.EditTaskAssigneeParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index a4d7593cf750..f8ea306d1cef 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -266,6 +266,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../pages/workspace/tags/WorkspaceCreateTagPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAG_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagPage').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/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 04bc53e7b542..aacc759c4bfd 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -292,6 +292,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.TAG_CREATE]: { path: ROUTES.WORKSPACE_TAG_CREATE.route, }, + [SCREENS.WORKSPACE.TAG_EDIT]: { + path: ROUTES.WORKSPACE_TAG_EDIT.route, + parse: { + tagName: (tagName: string) => decodeURI(tagName), + }, + }, [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 da418625ff55..f58d45b76af8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -185,6 +185,10 @@ type SettingsNavigatorParamList = { policyID: string; tagName: string; }; + [SCREENS.WORKSPACE.TAG_EDIT]: { + policyID: string; + tagName: string; + }; [SCREENS.WORKSPACE.MEMBER_DETAILS]: { policyID: string; accountID: string; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 2adcfd29e00d..a4aba7e1ad5b 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2826,6 +2826,76 @@ function createPolicyTag(policyID: string, tagName: string) { API.write(WRITE_COMMANDS.CREATE_POLICY_TAG, parameters, onyxData); } +function renamePolicyTag(policyID: string, policyTag: {oldName: string; newName: string}) { + const tagListName = Object.keys(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})[0]; + const oldTag = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]?.[policyTag.oldName] ?? {}; + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [tagListName]: { + tags: { + [policyTag.oldName]: null, + [policyTag.newName]: { + ...oldTag, + name: policyTag.newName, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [tagListName]: { + tags: { + [policyTag.oldName]: null, + [policyTag.newName]: { + ...oldTag, + name: policyTag.newName, + errors: null, + pendingAction: null, + }, + }, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [tagListName]: { + tags: { + [policyTag.newName]: null, + [policyTag.oldName]: { + ...oldTag, + name: policyTag.oldName, + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + pendingAction: null, + }, + }, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + tags: JSON.stringify({[policyTag.oldName]: policyTag.newName}), + }; + + API.write(WRITE_COMMANDS.RENAME_POLICY_TAG, parameters, onyxData); +} + function setWorkspaceRequiresCategory(policyID: string, requiresCategory: boolean) { const onyxData: OnyxData = { optimisticData: [ @@ -3589,6 +3659,7 @@ export { openPolicyDistanceRatesPage, openPolicyMoreFeaturesPage, createPolicyTag, + renamePolicyTag, clearWorkspaceReimbursementErrors, deleteWorkspaceCategories, }; diff --git a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx index 2df7621c17d3..dc2c17427651 100644 --- a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx +++ b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx @@ -23,7 +23,7 @@ import * as Policy 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/WorkspaceTagCreateForm'; +import INPUT_IDS from '@src/types/form/WorkspaceTagForm'; import type {PolicyTagList} from '@src/types/onyx'; type WorkspaceCreateTagPageOnyxProps = { diff --git a/src/pages/workspace/tags/WorkspaceEditTagPage.tsx b/src/pages/workspace/tags/WorkspaceEditTagPage.tsx new file mode 100644 index 000000000000..1ba3f88a3c68 --- /dev/null +++ b/src/pages/workspace/tags/WorkspaceEditTagPage.tsx @@ -0,0 +1,114 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {Keyboard} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTagForm'; +import type {PolicyTagList} from '@src/types/onyx'; + +type WorkspaceEditTagPageOnyxProps = { + /** All policy tags */ + policyTags: OnyxEntry; +}; + +type EditTagPageProps = WorkspaceEditTagPageOnyxProps & StackScreenProps; + +function EditTagPage({route, policyTags}: EditTagPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + + const validate = useCallback( + (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + const tagName = values.tagName.trim(); + const {tags} = PolicyUtils.getTagList(policyTags, 0); + + if (!ValidationUtils.isRequiredFulfilled(tagName)) { + errors.tagName = 'workspace.tags.tagRequiredError'; + } else if (tags?.[tagName]) { + errors.tagName = 'workspace.tags.existingTagError'; + } else if ([...tagName].length > CONST.TAG_NAME_LIMIT) { + // Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16 code units. + ErrorUtils.addErrorMessage(errors, 'tagName', ['common.error.characterLimitExceedCounter', {length: [...tagName].length, limit: CONST.TAG_NAME_LIMIT}]); + } + + return errors; + }, + [policyTags], + ); + + const editTag = useCallback( + (values: FormOnyxValues) => { + Policy.renamePolicyTag(route.params.policyID, {oldName: route.params.tagName, newName: values.tagName.trim()}); + Keyboard.dismiss(); + Navigation.goBack(ROUTES.WORKSPACE_TAGS.getRoute(route.params.policyID)); + }, + [route.params.policyID, route.params.tagName], + ); + + return ( + + + + + + + + + + + ); +} + +EditTagPage.displayName = 'EditTagPage'; + +export default withOnyx({ + policyTags: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${route?.params?.policyID}`, + }, +})(EditTagPage); diff --git a/src/types/form/WorkspaceTagCreateForm.ts b/src/types/form/WorkspaceTagForm.ts similarity index 78% rename from src/types/form/WorkspaceTagCreateForm.ts rename to src/types/form/WorkspaceTagForm.ts index 9a9670d84ae8..a6cc4c6c37cd 100644 --- a/src/types/form/WorkspaceTagCreateForm.ts +++ b/src/types/form/WorkspaceTagForm.ts @@ -7,12 +7,12 @@ const INPUT_IDS = { type InputID = ValueOf; -type WorkspaceTagCreateForm = Form< +type WorkspaceTagForm = Form< InputID, { [INPUT_IDS.TAG_NAME]: string; } >; -export type {WorkspaceTagCreateForm}; +export type {WorkspaceTagForm}; export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 5a574de3db54..01c682e54e14 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -39,5 +39,5 @@ export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; export type {PolicyTagNameForm} from './PolicyTagNameForm'; -export type {WorkspaceTagCreateForm} from './WorkspaceTagCreateForm'; +export type {WorkspaceTagForm} from './WorkspaceTagForm'; export type {default as Form} from './Form'; From 7155c3fda8730d8c23883b6a9d7fde8ac86988c6 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Wed, 13 Mar 2024 12:59:32 -0400 Subject: [PATCH 143/345] Update tagSelection and categorySelection copies --- src/languages/en.ts | 4 ++-- src/pages/EditRequestTagPage.js | 2 +- src/pages/iou/request/step/IOURequestStepTag.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 6ec5983583fc..976bd0269b04 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -657,8 +657,8 @@ export default { `changed the distance to ${newDistanceToDisplay} (previously ${oldDistanceToDisplay}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`, threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'request'}`, threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, - tagSelection: ({tagName}: TagSelectionParams) => `Select a ${tagName} to add additional organization to your money.`, - categorySelection: 'Select a category to add additional organization to your money.', + tagSelection: 'Select a tag to better organize your spend.', + categorySelection: 'Select a category to better organize your spend.', error: { invalidCategoryLength: 'The length of the category chosen exceeds the maximum allowed (255). Please choose a different or shorten the category name first.', invalidAmount: 'Please enter a valid amount before continuing.', diff --git a/src/pages/EditRequestTagPage.js b/src/pages/EditRequestTagPage.js index 74643afa347f..b64cb925a213 100644 --- a/src/pages/EditRequestTagPage.js +++ b/src/pages/EditRequestTagPage.js @@ -49,7 +49,7 @@ function EditRequestTagPage({defaultTag, policyID, tagName, tagIndex, onSubmit}) title={tagName || translate('common.tag')} onBackButtonPress={Navigation.goBack} /> - {translate('iou.tagSelection', {tagName: tagName || translate('common.tag')})} + {translate('iou.tagSelection')} {({insets}) => ( <> - {translate('iou.tagSelection', {tagName: policyTagListName})} + {translate('iou.tagSelection')} Date: Wed, 13 Mar 2024 13:07:20 -0400 Subject: [PATCH 144/345] fix spanish too --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index c2eb6374affa..3dea318760cd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -652,7 +652,7 @@ export default { `cambió la distancia a ${newDistanceToDisplay} (previamente ${oldDistanceToDisplay}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`, threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Solicitud de ${formattedAmount}`}`, threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, - tagSelection: ({tagName}: TagSelectionParams) => `Seleccione una ${tagName} para organizar mejor tu dinero.`, + tagSelection: 'Selecciona un tag para organizar mejor tu dinero.', categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.', error: { invalidCategoryLength: 'El largo de la categoría escogida excede el máximo permitido (255). Por favor, escoge otra categoría o acorta la categoría primero.', From 7f3a461a5dc1b7e3e54e675d6df5363eb1e57065 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Wed, 13 Mar 2024 13:08:10 -0400 Subject: [PATCH 145/345] remove unused TagSelectionParams --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 976bd0269b04..dabf6a61da2b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -68,7 +68,6 @@ import type { SizeExceededParams, SplitAmountParams, StepCounterParams, - TagSelectionParams, TaskCreatedActionParams, TermsParams, ThreadRequestReportNameParams, diff --git a/src/languages/es.ts b/src/languages/es.ts index 3dea318760cd..58d3a203da10 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -67,7 +67,6 @@ import type { SizeExceededParams, SplitAmountParams, StepCounterParams, - TagSelectionParams, TaskCreatedActionParams, TermsParams, ThreadRequestReportNameParams, From 2cd0925db14e84dc4bd048d0e2d25b70592e617e Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Wed, 13 Mar 2024 13:08:59 -0400 Subject: [PATCH 146/345] Better spanish --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 58d3a203da10..7f4c8c4e051f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -651,7 +651,7 @@ export default { `cambió la distancia a ${newDistanceToDisplay} (previamente ${oldDistanceToDisplay}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`, threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Solicitud de ${formattedAmount}`}`, threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, - tagSelection: 'Selecciona un tag para organizar mejor tu dinero.', + tagSelection: 'Selecciona una etiqueta para organizar mejor tu dinero.', categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.', error: { invalidCategoryLength: 'El largo de la categoría escogida excede el máximo permitido (255). Por favor, escoge otra categoría o acorta la categoría primero.', From cb71f33e529eac374b76478c9e6a2d9147beebf5 Mon Sep 17 00:00:00 2001 From: Gandalf Date: Wed, 13 Mar 2024 22:48:24 +0530 Subject: [PATCH 147/345] Update NextStepUtils.ts --- src/libs/NextStepUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 0a5cfad2d146..5a19e68afe72 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -258,7 +258,7 @@ function buildNextStep( text: 'Waiting for ', }, { - text: managerDisplayName, + text: 'you', type: 'strong', }, { @@ -281,7 +281,7 @@ function buildNextStep( text: 'Waiting for ', }, { - text: 'you', + text: managerDisplayName, type: 'strong', }, { From 3316b0a545072f93fa53350398d3e78178321b19 Mon Sep 17 00:00:00 2001 From: Florent De Neve Date: Wed, 13 Mar 2024 13:54:05 -0400 Subject: [PATCH 148/345] remove unused type --- src/languages/types.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/languages/types.ts b/src/languages/types.ts index 2d04bdc156c9..6c2460f76168 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -211,8 +211,6 @@ type UpdatedTheDistanceParams = {newDistanceToDisplay: string; oldDistanceToDisp type FormattedMaxLengthParams = {formattedMaxLength: string}; -type TagSelectionParams = {tagName: string}; - type WalletProgramParams = {walletProgram: string}; type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; @@ -362,7 +360,6 @@ export type { SizeExceededParams, SplitAmountParams, StepCounterParams, - TagSelectionParams, TaskCreatedActionParams, TermsParams, ThreadRequestReportNameParams, From 588e599bb4e3d48675985ad5817d5fb639fbe044 Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 13 Mar 2024 14:08:19 -0400 Subject: [PATCH 149/345] No more credentials include --- src/libs/HttpUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index 9c350ab05d81..842776d0c8dd 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -52,8 +52,6 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form signal: canCancel ? cancellationController.signal : undefined, method, body, - // We want to include the cookie accountID that is returned form the API - credentials: 'include', }) .then((response) => { // We are calculating the skew to minimize the delay when posting the messages From 7431057cdc96d6509f91d47bb8cc99670f63a79c Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:06:32 +0100 Subject: [PATCH 150/345] fix https://github.com/Expensify/App/pull/37521/#issuecomment-1987648277 --- 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 99c10550b552..308124e14e73 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -452,7 +452,7 @@ function BaseSelectionList( disabled={flattenedSections.allOptions.length === flattenedSections.disabledOptionsIndexes.length} /> {!customListHeader ? ( - + {translate('workspace.people.selectAll')} ) : null} From 669099756dc09345a4415495f168604f7df9d2bb Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:53:45 +0100 Subject: [PATCH 151/345] fix https://github.com/Expensify/App/pull/37521/#issuecomment-1987640167 --- .../SelectionList/BaseSelectionList.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 76afb85e588e..fafbcf9b4f80 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -512,28 +512,28 @@ function BaseSelectionList( <> {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - e.preventDefault() : undefined} - > + - {!customListHeader ? ( - + {!customListHeader && ( + e.preventDefault() : undefined} + > {translate('workspace.people.selectAll')} - - ) : null} - + + )} + {customListHeader} )} From 3776262761d4d458c3702f4c658398b56a0839fc Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 13 Mar 2024 21:20:10 +0100 Subject: [PATCH 152/345] fix StepIdentifier type --- workflow_tests/utils/utils.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index 9583ec233e60..76ab284f5bb9 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -1,16 +1,16 @@ +import type {StepIdentifier} from '@kie/act-js/build/src/step-mocker/step-mocker.types'; import fs from 'fs'; import path from 'path'; import yaml from 'yaml'; import type {ExtendedAct} from './ExtendedAct'; -import type {MockJobStep} from './JobMocker'; type EventOptions = { - action: string; + action?: string; }; function setUpActParams( act: ExtendedAct, - event = null, + event: string | null = null, eventOptions: EventOptions | null = null, secrets: Record | null = null, githubToken: string | null = null, @@ -65,7 +65,7 @@ function createMockStep( outEnvs: Record | null = null, isSuccessful = true, id = null, -) { +): StepIdentifier { const mockStepName = name; let mockWithCommand = 'echo [MOCK]'; if (jobId) { @@ -95,14 +95,16 @@ function createMockStep( if (!isSuccessful) { mockWithCommand += '\nexit 1'; } - const mockStep: MockJobStep = { + if (id) { + return { + id, + mockWith: mockWithCommand, + }; + } + return { name: mockStepName, mockWith: mockWithCommand, }; - if (id) { - mockStep.id = id; - } - return mockStep; } function createStepAssertion( @@ -111,7 +113,6 @@ function createStepAssertion( expectedOutput = null, jobId: string | null = null, message: string | null = null, - // Replace arrays with records inputs: Array<{key: string; value: string}> | null = null, envs: Array<{key: string; value: string}> | null = null, ) { @@ -165,7 +166,11 @@ function deepCopy(originalObject: TObject): TObject { return JSON.parse(JSON.stringify(originalObject)); } -function getLogFilePath(workflowName: string, testName: string) { +function getLogFilePath(workflowName: string, testName: string | undefined) { + if (!testName) { + throw new Error(); + } + const logsDir = path.resolve(__dirname, '..', 'logs'); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir); From 161c43d428f0784f430fb976daebb46b3af2e91f Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 13 Mar 2024 22:04:05 +0100 Subject: [PATCH 153/345] adjust createMockStep --- workflow_tests/utils/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index 76ab284f5bb9..7fc57eed75ad 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -95,13 +95,15 @@ function createMockStep( if (!isSuccessful) { mockWithCommand += '\nexit 1'; } - if (id) { + if (!id) { return { - id, + name: mockStepName, mockWith: mockWithCommand, }; } + return { + id, name: mockStepName, mockWith: mockWithCommand, }; From f512cf09249c49f58410c90342df6ffa861cb9a9 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:25:43 +0100 Subject: [PATCH 154/345] update enable tax api call --- src/libs/actions/TaxRate.ts | 2 +- src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 1bc1f3460af1..665ceb1da22c 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -172,7 +172,7 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE const parameters = { policyID, - taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), + taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 26e36e7f9b38..98d19ecc2b6b 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -34,12 +34,10 @@ function WorkspaceEditTaxPage({ const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const toggle = () => { - // TODO: Backend call doesn't exist yet - return; if (!policy?.id || !currentTaxRate) { return; } - setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); + setPolicyTaxesEnabled(policy.id, [taxID], !!currentTaxRate?.isDisabled); }; const deleteTax = () => { From 2a3f4c33e8262d0e05dc731410e041f24e7d5261 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:39:42 +0100 Subject: [PATCH 155/345] add NamePage and ValuePage --- src/ONYXKEYS.ts | 4 + src/ROUTES.ts | 8 ++ src/SCREENS.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 2 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 6 ++ src/libs/Navigation/types.ts | 8 ++ src/pages/workspace/taxes/NamePage.tsx | 85 ++++++++++++++++++ src/pages/workspace/taxes/ValuePage.tsx | 90 +++++++++++++++++++ .../workspace/taxes/WorkspaceEditTaxPage.tsx | 5 +- src/types/form/WorkspaceTaxNameForm.ts | 18 ++++ src/types/form/WorkspaceTaxValueForm.ts | 18 ++++ src/types/form/index.ts | 2 + 13 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/pages/workspace/taxes/NamePage.tsx create mode 100644 src/pages/workspace/taxes/ValuePage.tsx create mode 100644 src/types/form/WorkspaceTaxNameForm.ts create mode 100644 src/types/form/WorkspaceTaxValueForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4de6c6ef258..900bb9804d29 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -411,6 +411,8 @@ const ONYXKEYS = { POLICY_TAG_NAME_FORM_DRAFT: 'policyTagNameFormDraft', WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', WORKSPACE_NEW_TAX_FORM_DRAFT: 'workspaceNewTaxFormDraft', + WORKSPACE_TAX_NAME_FORM: 'workspaceTaxNameForm', + WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm', }, } as const; @@ -459,6 +461,8 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; + [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; + [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5a9c0cc7ad2a..2487102bc504 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -605,6 +605,14 @@ const ROUTES = { route: 'workspace/:policyID/tax/:taxID', getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}` as const, }, + WORKSPACE_TAXES_NAME: { + route: 'workspace/:policyID/tax/:taxID/name', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}/name` as const, + }, + WORKSPACE_TAXES_VALUE: { + route: 'workspace/:policyID/tax/:taxID/value', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}/value` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 11c2d38f4361..d8eb643cefde 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -222,6 +222,8 @@ const SCREENS = { TAXES: 'Workspace_Taxes', TAXES_NEW: 'Workspace_Taxes_New', TAXES_EDIT: 'Workspace_Taxes_Edit', + TAXES_NAME: 'Workspace_Taxes_Name', + TAXES_VALUE: 'Workspace_Taxes_Value', TAG_CREATE: 'Tag_Create', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 164dccbc10ad..41fe1b298576 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -278,6 +278,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAXES_NEW]: () => require('../../../pages/workspace/taxes/WorkspaceNewTaxPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAXES_EDIT]: () => require('../../../pages/workspace/taxes/WorkspaceEditTaxPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_NAME]: () => require('../../../pages/workspace/taxes/NamePage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_VALUE]: () => require('../../../pages/workspace/taxes/ValuePage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index a7d3fad55788..9eb35121413c 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -13,7 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT], + [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT, SCREENS.WORKSPACE.TAXES_NAME, SCREENS.WORKSPACE.TAXES_VALUE], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index d9fd1fc98c9c..cfef83bf9c1c 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -321,6 +321,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.TAXES_EDIT]: { path: ROUTES.WORKSPACE_TAXES_EDIT.route, }, + [SCREENS.WORKSPACE.TAXES_NAME]: { + path: ROUTES.WORKSPACE_TAXES_NAME.route, + }, + [SCREENS.WORKSPACE.TAXES_VALUE]: { + path: ROUTES.WORKSPACE_TAXES_VALUE.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index dafe451262d2..f93b52657acf 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -216,6 +216,14 @@ type SettingsNavigatorParamList = { policyID: string; taxID: string; }; + [SCREENS.WORKSPACE.TAXES_NAME]: { + policyID: string; + taxID: string; + }; + [SCREENS.WORKSPACE.TAXES_VALUE]: { + policyID: string; + taxID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/taxes/NamePage.tsx b/src/pages/workspace/taxes/NamePage.tsx new file mode 100644 index 000000000000..d7626f5dca5c --- /dev/null +++ b/src/pages/workspace/taxes/NamePage.tsx @@ -0,0 +1,85 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import React, {useState} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {renamePolicyTax} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTaxNameForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type NamePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +const parser = new ExpensiMark(); + +function NamePage({ + route: { + params: {policyID, taxID}, + }, + policy, +}: NamePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + + const [name, setName] = useState(() => parser.htmlToMarkdown(currentTaxRate?.name ?? '')); + + const submit = () => { + renamePolicyTax(policyID, taxID, name); + Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + }; + + return ( + + + + + + + + + + ); +} + +NamePage.displayName = 'NamePage'; + +export default withPolicyAndFullscreenLoading(NamePage); diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx new file mode 100644 index 000000000000..5a390f27dacf --- /dev/null +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -0,0 +1,90 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useState} from 'react'; +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 Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {renamePolicyTax, updatePolicyTaxValue} from '@libs/actions/TaxRate'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTaxValueForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type ValuePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +function ValuePage({ + route: { + params: {policyID, taxID}, + }, + policy, +}: ValuePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const [value, setValue] = useState(currentTaxRate?.value?.replace('%', '')); + + // TODO: Extract it to a separate file, and use it also when creating a new tax + const validate = useCallback((values: FormOnyxValues) => { + const errors = {}; + + if (Number(values.value) < 0 || Number(values.value) >= 100) { + ErrorUtils.addErrorMessage(errors, 'value', 'Percentage must be between 0 and 100'); + } + + return errors; + }, []); + + const submit = useCallback( + (values: FormOnyxValues) => { + updatePolicyTaxValue(policyID, taxID, `${values.value}%`); + Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + }, + [policyID, taxID], + ); + + return ( + + + + + %} + /> + + + ); +} + +ValuePage.displayName = 'ValuePage'; + +export default withPolicyAndFullscreenLoading(ValuePage); diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 98d19ecc2b6b..2617b710f55c 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -17,6 +17,7 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type WorkspaceEditTaxPageBaseProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -94,7 +95,7 @@ function WorkspaceEditTaxPage({ description={translate('common.name')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => {}} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TAXES_NAME.getRoute(`${policy?.id}`, taxID))} /> {}} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TAXES_VALUE.getRoute(`${policy?.id}`, taxID))} /> diff --git a/src/types/form/WorkspaceTaxNameForm.ts b/src/types/form/WorkspaceTaxNameForm.ts new file mode 100644 index 000000000000..dfe01ab55fae --- /dev/null +++ b/src/types/form/WorkspaceTaxNameForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + NAME: 'name', +} as const; + +type InputID = ValueOf; + +type WorkspaceTaxNameForm = Form< + InputID, + { + [INPUT_IDS.NAME]: string; + } +>; + +export type {WorkspaceTaxNameForm}; +export default INPUT_IDS; diff --git a/src/types/form/WorkspaceTaxValueForm.ts b/src/types/form/WorkspaceTaxValueForm.ts new file mode 100644 index 000000000000..e53c6cd46cc2 --- /dev/null +++ b/src/types/form/WorkspaceTaxValueForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + VALUE: 'value', +} as const; + +type InputID = ValueOf; + +type WorkspaceTaxValueForm = Form< + InputID, + { + [INPUT_IDS.VALUE]: string; + } +>; + +export type {WorkspaceTaxValueForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 8beada7ad6a8..7df684ccbd3e 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -40,5 +40,7 @@ export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; export type {PolicyTagNameForm} from './PolicyTagNameForm'; export type {WorkspaceNewTaxForm} from './WorkspaceNewTaxForm'; +export type {WorkspaceTaxNameForm} from './WorkspaceTaxNameForm'; +export type {WorkspaceTaxValueForm} from './WorkspaceTaxValueForm'; export type {WorkspaceTagCreateForm} from './WorkspaceTagCreateForm'; export type {default as Form} from './Form'; From a25926f7f41b07d74eddf4173fd90c145cd39109 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:23:02 +0100 Subject: [PATCH 156/345] renaming tax names --- .../parameters/UpdatePolicyTaxValueParams.ts | 7 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 3 + src/libs/actions/TaxRate.ts | 135 +++++++++++++++++- src/pages/workspace/taxes/ValuePage.tsx | 2 +- 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/libs/API/parameters/UpdatePolicyTaxValueParams.ts diff --git a/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts b/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts new file mode 100644 index 000000000000..1124755ea9ef --- /dev/null +++ b/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts @@ -0,0 +1,7 @@ +type UpdatePolicyTaxValueParams = { + policyID: string; + taxCode: string; + taxAmount: number; +}; + +export default UpdatePolicyTaxValueParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index f67ea4690e7d..6ab1eed97d16 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -180,3 +180,4 @@ export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMore export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; export type {default as DeletePolicyTaxesParams} from './DeletePolicyTaxesParams'; +export type {default as UpdatePolicyTaxValueParams} from './UpdatePolicyTaxValueParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 5aa0f6a18599..ab2d17ebd4b9 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -176,6 +176,8 @@ const WRITE_COMMANDS = { CREATE_POLICY_TAX: 'CreatePolicyTax', SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', DELETE_POLICY_TAXES: 'DeletePolicyTaxes', + UPDATE_POLICY_TAX_VALUE: 'UpdatePolicyTaxValue', + RENAME_POLICY_TAX: 'RenamePolicyTax', } as const; type WriteCommand = ValueOf; @@ -350,6 +352,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; [WRITE_COMMANDS.DELETE_POLICY_TAXES]: Parameters.DeletePolicyTaxesParams; + [WRITE_COMMANDS.UPDATE_POLICY_TAX_VALUE]: Parameters.UpdatePolicyTaxValueParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 665ceb1da22c..41c7800be5c6 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,7 +1,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams, UpdatePolicyTaxValueParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; @@ -251,4 +251,135 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes}; +/** + * Rename policy tax + */ +function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; + const stringTaxValue = `${taxValue}%`; + + console.log({policy, originalTaxRate, stringTaxValue, taxValue, taxID}); + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: { + value: stringTaxValue, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + errors: null, + }, + }, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {value: stringTaxValue, pendingAction: null, errors: null}, + }, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {value: originalTaxRate.value, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}, + }, + }, + }, + }, + ], + }; + + if (!originalTaxRate.name) { + throw new Error('Tax rate name not found'); + } + + const parameters = { + policyID, + taxCode: originalTaxRate.name, + taxAmount: Number(taxValue), + } as UpdatePolicyTaxValueParams; + + API.write(WRITE_COMMANDS.UPDATE_POLICY_TAX_VALUE, parameters, onyxData); +} + +function renamePolicyTax(policyID: string, taxID: string, newName: string) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: { + name: newName, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + errors: null, + }, + }, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {name: newName, pendingAction: null, errors: null}, + }, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {name: originalTaxRate.name, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}, + }, + }, + }, + }, + ], + }; + + if (!originalTaxRate.name) { + throw new Error('Tax rate name not found'); + } + + const parameters = { + policyID, + taxCode: taxID, + newName, + }; + + API.write(WRITE_COMMANDS.RENAME_POLICY_TAX, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes, updatePolicyTaxValue, renamePolicyTax}; diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx index 5a390f27dacf..6c733968aa5e 100644 --- a/src/pages/workspace/taxes/ValuePage.tsx +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -48,7 +48,7 @@ function ValuePage({ const submit = useCallback( (values: FormOnyxValues) => { - updatePolicyTaxValue(policyID, taxID, `${values.value}%`); + updatePolicyTaxValue(policyID, taxID, Number(values.value)); Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); }, [policyID, taxID], From ac59d758b8c8bb69978fbfca5326c974acc8350a Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:45:03 +0100 Subject: [PATCH 157/345] update backend queries --- src/libs/actions/TaxRate.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 41c7800be5c6..50a8ed0a1bdc 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -86,14 +86,12 @@ function createWorkspaceTax(policyID: string, taxRate: TaxRate) { const parameters = { policyID, - taxFields: JSON.stringify([ - { - name: taxRate.name, - value: taxRate.value, - enabled: true, - taxCode: taxRate.code, - }, - ]), + taxFields: JSON.stringify({ + name: taxRate.name, + value: taxRate.value, + enabled: true, + taxCode: taxRate.code, + }), } satisfies CreatePolicyTaxParams; API.write(WRITE_COMMANDS.CREATE_POLICY_TAX, parameters, onyxData); @@ -313,7 +311,7 @@ function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) const parameters = { policyID, - taxCode: originalTaxRate.name, + taxCode: taxID, taxAmount: Number(taxValue), } as UpdatePolicyTaxValueParams; From 139d14bd20e1c53372e6ebac77af079637a13d9c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Mar 2024 11:59:40 +0100 Subject: [PATCH 158/345] remove lint disable --- src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx index d5799e617226..05424c9975c5 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrRedirectWrapper.tsx @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/no-negated-variables */ import React, {useEffect} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; From de14e6669e44b818dd7001c11556936c9106dcd7 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 14 Mar 2024 12:07:36 +0100 Subject: [PATCH 159/345] fix: remove redundant error clearing --- src/libs/actions/Policy.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9279874b1fc5..1d08ccf0c1d6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3620,7 +3620,6 @@ function setPolicyDistanceRatesUnit(policyID: string, currentCustomUnit: CustomU value: { customUnits: { [newCustomUnit.customUnitID]: { - errors: null, pendingAction: null, }, }, @@ -3675,7 +3674,6 @@ function setPolicyDistanceRatesDefaultCategory(policyID: string, currentCustomUn value: { customUnits: { [newCustomUnit.customUnitID]: { - errors: null, pendingAction: null, }, }, From 7d9e274c061dbf660de98238f0379ac0a290db0a Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Thu, 14 Mar 2024 13:11:07 +0200 Subject: [PATCH 160/345] solved conflict --- src/libs/desktopLoginRedirect/index.desktop.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/desktopLoginRedirect/index.desktop.ts b/src/libs/desktopLoginRedirect/index.desktop.ts index e751fa1ffd78..ccc442346dc1 100644 --- a/src/libs/desktopLoginRedirect/index.desktop.ts +++ b/src/libs/desktopLoginRedirect/index.desktop.ts @@ -9,7 +9,7 @@ function desktopLoginRedirect(autoAuthState: AutoAuthState, isSignedIn: boolean) const shouldPopToTop = (autoAuthState === CONST.AUTO_AUTH_STATE.NOT_STARTED || autoAuthState === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN) && !isSignedIn; if (shouldPopToTop) { - Navigation.isNavigationReady().then(() => Navigation.popToTop()); + Navigation.isNavigationReady().then(() => Navigation.resetToHome()); } } From 476445981b2b41762a3230e14c3ee43415f9f4b3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Mar 2024 12:15:00 +0100 Subject: [PATCH 161/345] fix nav type --- .../workflows/WorkspaceAutoReportingFrequencyPage.tsx | 4 ++-- .../workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx | 5 +++-- .../workspace/workflows/WorkspaceWorkflowsApproverPage.tsx | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 5a7e7a2fc3a9..d9997a1aefca 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -12,7 +12,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import withPolicy from '@pages/workspace/withPolicy'; @@ -26,7 +26,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AutoReportingFrequencyKey = Exclude, 'instant'>; type Locale = ValueOf; -type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps & StackScreenProps; +type WorkspaceAutoReportingFrequencyPageProps = WithPolicyOnyxProps & StackScreenProps; type WorkspaceAutoReportingFrequencyPageItem = { text: string; diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx index 3aad5fb9f0a4..834765cb331b 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx @@ -8,7 +8,7 @@ import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import FeatureEnabledAccessOrRedirectWrapper from '@pages/workspace/FeatureEnabledAccessOrRedirectWrapper'; import withPolicy from '@pages/workspace/withPolicy'; @@ -20,7 +20,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; const DAYS_OF_MONTH = 28; -type WorkspaceAutoReportingMonthlyOffsetProps = WithPolicyOnyxProps & StackScreenProps; +type WorkspaceAutoReportingMonthlyOffsetProps = WithPolicyOnyxProps & + StackScreenProps; type AutoReportingOffsetKeys = ValueOf; diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 34c0f8989888..7475d0f195aa 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -18,7 +18,7 @@ import compose from '@libs/compose'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -40,7 +40,7 @@ type WorkspaceWorkflowsApproverPageOnyxProps = { type WorkspaceWorkflowsApproverPageProps = WorkspaceWorkflowsApproverPageOnyxProps & WithPolicyAndFullscreenLoadingProps & - StackScreenProps; + StackScreenProps; type MemberOption = Omit & {accountID: number}; type MembersSection = SectionListData>; From 1b2779543d9aaa6e06bb91235b2774d8f00d8c86 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 14 Mar 2024 12:37:44 +0100 Subject: [PATCH 162/345] fix prettier --- tests/unit/MigrationTest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/MigrationTest.ts b/tests/unit/MigrationTest.ts index 147588559e13..c6513671776b 100644 --- a/tests/unit/MigrationTest.ts +++ b/tests/unit/MigrationTest.ts @@ -6,10 +6,9 @@ import CheckForPreviousReportActionID from '@src/libs/migrations/CheckForPreviou import KeyReportActionsDraftByReportActionID from '@src/libs/migrations/KeyReportActionsDraftByReportActionID'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportActionsDraftCollectionDataSet} from '@src/types/onyx/ReportActionsDrafts'; -import { toCollectionDataSet } from '@src/types/utils/CollectionDataSet'; +import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; - jest.mock('@src/libs/getPlatform'); let LogSpy: jest.SpyInstance>; From 76d56f8f74d33f8fa09184a470b8b993804aae10 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:40:08 +0100 Subject: [PATCH 163/345] refactor --- src/pages/workspace/taxes/NamePage.tsx | 22 +++++++++++++--------- src/pages/workspace/taxes/ValuePage.tsx | 14 +++++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/taxes/NamePage.tsx b/src/pages/workspace/taxes/NamePage.tsx index d7626f5dca5c..c6c4f04177f1 100644 --- a/src/pages/workspace/taxes/NamePage.tsx +++ b/src/pages/workspace/taxes/NamePage.tsx @@ -1,12 +1,13 @@ import type {StackScreenProps} from '@react-navigation/stack'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import React, {useState} from 'react'; +import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {renamePolicyTax} from '@libs/actions/TaxRate'; @@ -20,7 +21,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/WorkspaceTaxNameForm'; -import type * as OnyxTypes from '@src/types/onyx'; type NamePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -35,12 +35,15 @@ function NamePage({ const styles = useThemeStyles(); const {translate} = useLocalize(); const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const {inputCallbackRef} = useAutoFocusInput(); const [name, setName] = useState(() => parser.htmlToMarkdown(currentTaxRate?.name ?? '')); + const goBack = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)), [policyID, taxID]); + const submit = () => { renamePolicyTax(policyID, taxID, name); - Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + goBack(); }; return ( @@ -49,7 +52,10 @@ function NamePage({ shouldEnableMaxHeight testID={NamePage.displayName} > - + diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx index 6c733968aa5e..3aa990db13c3 100644 --- a/src/pages/workspace/taxes/ValuePage.tsx +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -9,7 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {renamePolicyTax, updatePolicyTaxValue} from '@libs/actions/TaxRate'; +import {updatePolicyTaxValue} from '@libs/actions/TaxRate'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -20,7 +20,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/WorkspaceTaxValueForm'; -import type * as OnyxTypes from '@src/types/onyx'; type ValuePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -35,6 +34,8 @@ function ValuePage({ const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const [value, setValue] = useState(currentTaxRate?.value?.replace('%', '')); + const goBack = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)), [policyID, taxID]); + // TODO: Extract it to a separate file, and use it also when creating a new tax const validate = useCallback((values: FormOnyxValues) => { const errors = {}; @@ -49,9 +50,9 @@ function ValuePage({ const submit = useCallback( (values: FormOnyxValues) => { updatePolicyTaxValue(policyID, taxID, Number(values.value)); - Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + goBack(); }, - [policyID, taxID], + [goBack, policyID, taxID], ); return ( @@ -60,7 +61,10 @@ function ValuePage({ shouldEnableMaxHeight testID={ValuePage.displayName} > - + Date: Thu, 14 Mar 2024 12:45:10 +0100 Subject: [PATCH 164/345] update to new backend --- src/libs/API/parameters/DeletePolicyTaxesParams.ts | 2 +- src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts | 2 +- src/libs/actions/TaxRate.ts | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/API/parameters/DeletePolicyTaxesParams.ts b/src/libs/API/parameters/DeletePolicyTaxesParams.ts index fe03d388a129..9e0963cdcb28 100644 --- a/src/libs/API/parameters/DeletePolicyTaxesParams.ts +++ b/src/libs/API/parameters/DeletePolicyTaxesParams.ts @@ -5,7 +5,7 @@ type DeletePolicyTaxesParams = { * Array * Each element is a tax name */ - taxCodes: string; + taxNames: string; }; export default DeletePolicyTaxesParams; diff --git a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts index 0bc8550cd01b..4ed0a05cfdec 100644 --- a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts +++ b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts @@ -4,7 +4,7 @@ type SetPolicyTaxesEnabledParams = { * Stringified JSON object with type of following structure: * Array<{taxCode: string, enabled: bool}> */ - taxFields: string; + taxFieldsArray: string; }; export default SetPolicyTaxesEnabledParams; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 50a8ed0a1bdc..ced92e12e4b8 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -170,7 +170,7 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE const parameters = { policyID, - taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), + taxFieldsArray: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); @@ -243,7 +243,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const parameters = { policyID, - taxCodes: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), + taxNames: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), } as DeletePolicyTaxesParams; API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); @@ -257,8 +257,6 @@ function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; const stringTaxValue = `${taxValue}%`; - console.log({policy, originalTaxRate, stringTaxValue, taxValue, taxID}); - const onyxData: OnyxData = { optimisticData: [ { From 3b590c672aa27e32ef4f76c9c272e9126a9834e5 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:07:26 +0100 Subject: [PATCH 165/345] add bulk actions --- src/CONST.ts | 5 ++ .../ButtonWithDropdownMenu/types.ts | 4 +- src/languages/en.ts | 6 +- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 2 +- .../workspace/taxes/WorkspaceTaxesPage.tsx | 73 +++++++++++++++---- 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index cf0d6ac57a08..bb04b27dc1a2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1422,6 +1422,11 @@ const CONST = { DISABLE: 'disable', ENABLE: 'enable', }, + TAX_RATES_BULK_ACTION_TYPES: { + DELETE: 'delete', + DISABLE: 'disable', + ENABLE: 'enable', + }, }, CUSTOM_UNITS: { diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 798369292958..83100788761f 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -12,6 +12,8 @@ type WorkspaceMemberBulkActionType = DeepValueOf; +type WorkspaceTaxRatesBulkActionType = DeepValueOf; + type DropdownOption = { value: TValueType; text: string; @@ -73,4 +75,4 @@ type ButtonWithDropdownMenuProps = { wrapperStyle?: StyleProp; }; -export type {PaymentType, WorkspaceMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps}; +export type {PaymentType, WorkspaceMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps, WorkspaceTaxRatesBulkActionType}; diff --git a/src/languages/en.ts b/src/languages/en.ts index d60861a838be..876399e9a864 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1860,8 +1860,12 @@ export default { valuePercentageRange: 'Please enter a valid percentage between 0 and 100', genericFailureMessage: 'An error occurred while updating the tax rate, please try again.', }, - deleteTax: 'Delete tax', deleteTaxConfirmation: 'Are you sure you want to delete this tax?', + actions: { + delete: 'Delete rate', + disable: 'Disable rate', + enable: 'Enable rate', + }, }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 2617b710f55c..e785790d64e4 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -108,7 +108,7 @@ function WorkspaceEditTaxPage({ setIsDeleteModalVisible(false)} diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index cede2bd31e7d..04529014b2ac 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -2,6 +2,8 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import Button from '@components/Button'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; +import type {DropdownOption, WorkspaceTaxRatesBulkActionType} from '@components/ButtonWithDropdownMenu/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -129,23 +131,64 @@ function WorkspaceTaxesPage({policy, route}: WorkspaceTaxesPageProps) { ); + const dropdownMenuOptions = useMemo(() => { + const options: Array> = [ + { + icon: Expensicons.Trashcan, + text: translate('workspace.taxes.actions.delete'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.DELETE, + onSelected: () => {}, + }, + ]; + + // `Disable rates` when at least one enabled rate is selected. + if (selectedTaxesIDs.some((taxID) => !policy?.taxRates?.taxes[taxID]?.isDisabled)) { + options.push({ + icon: Expensicons.Document, + text: translate('workspace.taxes.actions.disable'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.DISABLE, + }); + } + + // `Enable rates` when at least one disabled rate is selected. + if (selectedTaxesIDs.some((taxID) => policy?.taxRates?.taxes[taxID]?.isDisabled)) { + options.push({ + icon: Expensicons.Document, + text: translate('workspace.taxes.actions.enable'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.ENABLE, + }); + } + return options; + }, [policy?.taxRates?.taxes, selectedTaxesIDs, translate]); + const headerButtons = ( -