From 6b6afd8326c14044681838a44c5e4b929ef77e26 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 15 Nov 2023 15:06:15 +0100 Subject: [PATCH 001/298] [TS migration] Migrate 'Onfido' component --- .../{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} | 50 ++++++++++--------- src/components/Onfido/index.desktop.js | 11 ---- .../{index.native.js => index.native.tsx} | 16 +++--- .../Onfido/{index.website.js => index.tsx} | 11 ++-- src/components/Onfido/onfidoPropTypes.js | 15 ------ src/components/Onfido/types.ts | 20 ++++++++ 6 files changed, 58 insertions(+), 65 deletions(-) rename src/components/Onfido/{BaseOnfidoWeb.js => BaseOnfidoWeb.tsx} (81%) delete mode 100644 src/components/Onfido/index.desktop.js rename src/components/Onfido/{index.native.js => index.native.tsx} (79%) rename src/components/Onfido/{index.website.js => index.tsx} (64%) delete mode 100644 src/components/Onfido/onfidoPropTypes.js create mode 100644 src/components/Onfido/types.ts diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.tsx similarity index 81% rename from src/components/Onfido/BaseOnfidoWeb.js rename to src/components/Onfido/BaseOnfidoWeb.tsx index 5c0f83902e55..79842823a975 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,7 +1,6 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; -import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import React, {ForwardedRef, forwardRef, useEffect} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import fontFamily from '@styles/fontFamily'; @@ -10,9 +9,15 @@ import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}) { +type LocaleProps = Pick; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate}: OnfidoProps & LocaleProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -22,7 +27,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${fontFamily.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: fontWeightBold, + fontWeightTitle: Number(fontWeightBold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: themeColors.text, @@ -47,7 +52,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: themeColors.link, colorBackgroundLinkHover: themeColors.link, colorBackgroundLinkActive: themeColors.link, - authAccentColor: themeColors.link, colorBackgroundInfoPill: themeColors.link, colorBackgroundSelector: themeColors.appBG, colorBackgroundDocTypeButton: themeColors.success, @@ -59,11 +63,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -78,17 +81,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (!Object.keys(data).length) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -101,32 +102,33 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, }); @@ -143,8 +145,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.desktop.js b/src/components/Onfido/index.desktop.js deleted file mode 100644 index e455eaf78d32..000000000000 --- a/src/components/Onfido/index.desktop.js +++ /dev/null @@ -1,11 +0,0 @@ -import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; - -// On desktop, we do not want to teardown onfido, because it causes a crash. -// See https://github.com/Expensify/App/issues/6082 -const Onfido = BaseOnfidoWeb; - -Onfido.propTypes = onfidoPropTypes; -Onfido.displayName = 'Onfido'; - -export default Onfido; diff --git a/src/components/Onfido/index.native.js b/src/components/Onfido/index.native.tsx similarity index 79% rename from src/components/Onfido/index.native.js rename to src/components/Onfido/index.native.tsx index ed0578187d3c..e09eeec4f322 100644 --- a/src/components/Onfido/index.native.js +++ b/src/components/Onfido/index.native.tsx @@ -1,15 +1,13 @@ import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; -import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import Log from '@libs/Log'; import CONST from '@src/CONST'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoProps} from './types'; -function Onfido({sdkToken, onUserExit, onSuccess, onError}) { +function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { const {translate} = useLocalize(); useEffect(() => { @@ -28,19 +26,20 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { }) .then(onSuccess) .catch((error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; + Log.hmmm('Onfido error on native', {errorType, errorMessage}); // If the user cancels the Onfido flow we won't log this error as it's normal. In the React Native SDK the user exiting the flow will trigger this error which we can use as // our "user exited the flow" callback. On web, this event has it's own callback passed as a config so we don't need to bother with this there. - if (_.contains([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CANCELLED, CONST.ONFIDO.ERROR.USER_TAPPED_BACK, CONST.ONFIDO.ERROR.USER_EXITED].includes(errorMessage)) { onUserExit(); return; } // Handle user camera permission on iOS and Android - if (_.contains([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED], errorMessage)) { + if ([CONST.ONFIDO.ERROR.USER_CAMERA_PERMISSION, CONST.ONFIDO.ERROR.USER_CAMERA_DENINED, CONST.ONFIDO.ERROR.USER_CAMERA_CONSENT_DENIED].includes(errorMessage)) { Alert.alert( translate('onfidoStep.cameraPermissionsNotGranted'), translate('onfidoStep.cameraRequestMessage'), @@ -71,7 +70,6 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}) { return ; } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/index.website.js b/src/components/Onfido/index.tsx similarity index 64% rename from src/components/Onfido/index.website.js rename to src/components/Onfido/index.tsx index 12ad1edd8fb9..139dc3cec405 100644 --- a/src/components/Onfido/index.website.js +++ b/src/components/Onfido/index.tsx @@ -1,14 +1,14 @@ -import lodashGet from 'lodash/get'; import React, {useEffect, useRef} from 'react'; import BaseOnfidoWeb from './BaseOnfidoWeb'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function Onfido({sdkToken, onSuccess, onError, onUserExit}) { - const baseOnfidoRef = useRef(null); +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps) { + const baseOnfidoRef = useRef(null); useEffect( () => () => { - const onfidoOut = lodashGet(baseOnfidoRef.current, 'onfidoOut'); + const onfidoOut = baseOnfidoRef.current?.onfidoOut; + if (!onfidoOut) { return; } @@ -29,7 +29,6 @@ function Onfido({sdkToken, onSuccess, onError, onUserExit}) { ); } -Onfido.propTypes = onfidoPropTypes; Onfido.displayName = 'Onfido'; export default Onfido; diff --git a/src/components/Onfido/onfidoPropTypes.js b/src/components/Onfido/onfidoPropTypes.js deleted file mode 100644 index ff0023c70058..000000000000 --- a/src/components/Onfido/onfidoPropTypes.js +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - /** Token used to initialize the Onfido SDK */ - sdkToken: PropTypes.string.isRequired, - - /** Called when the user intentionally exits the flow without completing it */ - onUserExit: PropTypes.func.isRequired, - - /** Called when the user is totally done with Onfido */ - onSuccess: PropTypes.func.isRequired, - - /** Called when Onfido throws an error */ - onError: PropTypes.func.isRequired, -}; diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts new file mode 100644 index 000000000000..a4fe3d93f05e --- /dev/null +++ b/src/components/Onfido/types.ts @@ -0,0 +1,20 @@ +import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; +import * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; + +type OnfidoProps = { + /** Token used to initialize the Onfido SDK */ + sdkToken: string; + + /** Called when the user intentionally exits the flow without completing it */ + onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; + + /** Called when the user is totally done with Onfido */ + onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + + /** Called when Onfido throws an error */ + onError: (error?: string) => void; +}; + +export type {OnfidoProps, OnfidoElement}; From dae08c96c8d7d3008858edf666fa452c6269d310 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 31 Jan 2024 12:56:40 +0530 Subject: [PATCH 002/298] Remove MoneyRequestSelectorPage.js and copy any changes since Nov 27 into IOURequestStartPage.js. Signed-off-by: Krishna Gupta --- ...oraryForRefactorRequestConfirmationList.js | 4 +-- .../AttachmentPickerWithMenuItems.js | 3 +- .../FloatingActionButtonAndPopover.js | 11 ++++-- src/pages/iou/request/IOURequestStartPage.js | 36 ++++++++++--------- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepConfirmation.js | 17 ++++++--- .../step/IOURequestStepParticipants.js | 12 +++++-- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 2aff0444a59e..f48820654768 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -266,8 +266,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // Do not hide fields in case of send money request const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; - const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; - const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + const shouldShowDate = (shouldShowSmartScanFields || isDistanceRequest) && !isTypeSend; + const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest && !isTypeSend; // Fetches the first tag list of the policy const policyTag = PolicyUtils.getTag(policyTags); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 444dd939142b..4091016baeeb 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -20,7 +20,6 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; @@ -154,7 +153,7 @@ function AttachmentPickerWithMenuItems({ [CONST.IOU.TYPE.SEND]: { icon: Expensicons.Send, text: translate('iou.sendMoney'), - onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND, report.reportID), + onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)), }, }; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index bbcdc5cebef4..4ab4e5fbb081 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -15,7 +15,6 @@ import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; -import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -182,7 +181,15 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: props.translate('iou.sendMoney'), - onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), + onSelected: () => + interceptAnonymousUser(() => + Navigation.navigate( + // When starting to create a send money request from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SEND, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()), + ), + ), }, ...[ { diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 3d80ab89347d..c790b30c5f7d 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -156,22 +156,26 @@ function IOURequestStartPage({ title={tabTitles[iouType]} onBackButtonPress={navigateBack} /> - ( - - )} - > - {() => } - {() => } - {shouldDisplayDistanceRequest && {() => }} - + {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( + ( + + )} + > + {() => } + {() => } + {shouldDisplayDistanceRequest && {() => }} + + ) : ( + + )} diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 15f98205839e..5d84b0d81b68 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -256,7 +256,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; - const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE; + const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 6028a735d132..bdee619dba9f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -92,7 +92,15 @@ function IOURequestStepConfirmation({ const transactionTaxCode = transaction.taxRate && transaction.taxRate.keyForList; const transactionTaxAmount = transaction.taxAmount; const requestType = TransactionUtils.getRequestType(transaction); - const headerTitle = iouType === CONST.IOU.TYPE.SPLIT ? translate('iou.split') : translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + const headerTitle = () => { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const participants = useMemo( () => _.map(transaction.participants, (participant) => { @@ -287,7 +295,7 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = transaction.comment.trim(); + const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -299,8 +307,9 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction.amount, transaction.comment, participants, transaction.currency, currentUserPersonalDetails.accountID, report], + [transaction, participants, currentUserPersonalDetails.accountID, report], ); + const addNewParticipant = (option) => { const newParticipants = _.map(transaction.participants, (participant) => { if (participant.accountID === option.accountID) { @@ -327,7 +336,7 @@ function IOURequestStepConfirmation({ {({safeAreaPaddingBottomStyle}) => ( { + if (iouType === CONST.IOU.TYPE.SPLIT) { + return translate('iou.split'); + } + if (iouType === CONST.IOU.TYPE.SEND) { + return translate('common.send'); + } + translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -81,7 +89,7 @@ function IOURequestStepParticipants({ return ( Date: Sat, 3 Feb 2024 16:27:01 +0530 Subject: [PATCH 003/298] remove redundant code and old component. Signed-off-by: Krishna Gupta --- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 18 -- src/libs/Navigation/types.ts | 1 - .../FloatingActionButtonAndPopover.js | 2 +- src/pages/iou/MoneyRequestSelectorPage.js | 169 ------------------ ...yForRefactorRequestParticipantsSelector.js | 1 - 6 files changed, 1 insertion(+), 191 deletions(-) delete mode 100644 src/pages/iou/MoneyRequestSelectorPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 4606f867c3fc..60b87fd85f3d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -93,7 +93,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_TAG]: () => require('../../../pages/iou/request/step/IOURequestStepTag').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..72c2c9d305f3 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -379,24 +379,6 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SCAN]: ROUTES.MONEY_REQUEST_STEP_SCAN.route, [SCREENS.MONEY_REQUEST.STEP_TAG]: ROUTES.MONEY_REQUEST_STEP_TAG.route, [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: ROUTES.MONEY_REQUEST_STEP_WAYPOINT.route, - [SCREENS.MONEY_REQUEST.ROOT]: { - path: ROUTES.MONEY_REQUEST.route, - exact: true, - screens: { - [SCREENS.MONEY_REQUEST.MANUAL_TAB]: { - path: ROUTES.MONEY_REQUEST_MANUAL_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.SCAN_TAB]: { - path: ROUTES.MONEY_REQUEST_SCAN_TAB, - exact: true, - }, - [SCREENS.MONEY_REQUEST.DISTANCE_TAB]: { - path: ROUTES.MONEY_REQUEST_DISTANCE_TAB.route, - exact: true, - }, - }, - }, [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route, [SCREENS.MONEY_REQUEST.STEP_TAX_RATE]: ROUTES.MONEY_REQUEST_STEP_TAX_RATE.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3c4cf17853f1..13eeb972305a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -193,7 +193,6 @@ type RoomInviteNavigatorParamList = { }; type MoneyRequestNavigatorParamList = { - [SCREENS.MONEY_REQUEST.ROOT]: undefined; [SCREENS.MONEY_REQUEST.AMOUNT]: undefined; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 7a3390ab5478..b2844374b6ae 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -174,7 +174,7 @@ function FloatingActionButtonAndPopover(props) { }, { icon: Expensicons.Send, - text: props.translate('iou.sendMoney'), + text: translate('iou.sendMoney'), // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js deleted file mode 100644 index 0a0efc38313a..000000000000 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ /dev/null @@ -1,169 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import DragAndDropProvider from '@components/DragAndDrop/Provider'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import TabSelector from '@components/TabSelector/TabSelector'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as IOUUtils from '@libs/IOUUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; -import * as ReportUtils from '@libs/ReportUtils'; -import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import NewDistanceRequestPage from './NewDistanceRequestPage'; -import IOURequestStepScan from './request/step/IOURequestStepScan'; -import NewRequestAmountPage from './steps/NewRequestAmountPage'; - -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, - - /** Report on which the money request is being created */ - report: reportPropTypes, - - /** The policy tied to the report */ - policy: PropTypes.shape({ - /** Type of the policy */ - type: PropTypes.string, - }), - - /** Which tab has been selected */ - selectedTab: PropTypes.string, -}; - -const defaultProps = { - selectedTab: CONST.TAB_REQUEST.SCAN, - report: {}, - policy: {}, -}; - -function MoneyRequestSelectorPage(props) { - const styles = useThemeStyles(); - const [isDraggingOver, setIsDraggingOver] = useState(false); - - const iouType = lodashGet(props.route, 'params.iouType', ''); - const reportID = lodashGet(props.route, 'params.reportID', ''); - const {translate} = useLocalize(); - - const title = { - [CONST.IOU.TYPE.REQUEST]: translate('iou.requestMoney'), - [CONST.IOU.TYPE.SEND]: translate('iou.sendMoney'), - [CONST.IOU.TYPE.SPLIT]: translate('iou.splitBill'), - }; - const isFromGlobalCreate = !reportID; - const isExpenseChat = ReportUtils.isPolicyExpenseChat(props.report); - const isExpenseReport = ReportUtils.isExpenseReport(props.report); - const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; - - const resetMoneyRequestInfo = () => { - const moneyRequestID = `${iouType}${reportID}`; - IOU.resetMoneyRequestInfo(moneyRequestID); - }; - - // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(props.report.reportID) || ReportUtils.canCreateRequest(props.report, props.policy, iouType); - const prevSelectedTab = usePrevious(props.selectedTab); - - useEffect(() => { - if (prevSelectedTab === props.selectedTab) { - return; - } - - resetMoneyRequestInfo(); - // resetMoneyRequestInfo function is not added as dependencies since they don't change between renders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.selectedTab, prevSelectedTab]); - - return ( - - {({safeAreaPaddingBottomStyle}) => ( - - - - - {iouType === CONST.IOU.TYPE.REQUEST || iouType === CONST.IOU.TYPE.SPLIT ? ( - ( - - )} - > - - {() => } - {shouldDisplayDistanceRequest && ( - - )} - - ) : ( - - )} - - - - )} - - ); -} - -MoneyRequestSelectorPage.propTypes = propTypes; -MoneyRequestSelectorPage.defaultProps = defaultProps; -MoneyRequestSelectorPage.displayName = 'MoneyRequestSelectorPage'; - -export default compose( - withReportOrNotFound(false), - withOnyx({ - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, - }, - }), -)(MoneyRequestSelectorPage); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 6e9de5b62a7e..a4807572bf56 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -258,7 +258,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE && iouType !== CONST.IOU.TYPE.SEND; - const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST; const handleConfirmSelection = useCallback(() => { if (shouldShowSplitBillErrorMessage) { From b915c4ef809709a29734f0a67a51d423b1eb8d2b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 3 Feb 2024 16:53:52 +0530 Subject: [PATCH 004/298] remove redundant code and comment Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.js | 11 ----------- .../SidebarScreen/FloatingActionButtonAndPopover.js | 1 - 2 files changed, 12 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4db89a1e926b..053d48b7ed0f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3572,16 +3572,6 @@ function setMoneyRequestParticipantsFromReport(transactionID, report) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** - * Initialize money request info and navigate to the MoneyRequest page - * @param {String} iouType - * @param {String} reportID - */ -function startMoneyRequest(iouType, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - /** * @param {String} id */ @@ -3799,7 +3789,6 @@ export { submitReport, payMoneyRequest, sendMoneyWithWallet, - startMoneyRequest, startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b2844374b6ae..52251da7a67a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -175,7 +175,6 @@ function FloatingActionButtonAndPopover(props) { { icon: Expensicons.Send, text: translate('iou.sendMoney'), - // onSelected: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND)), onSelected: () => interceptAnonymousUser(() => Navigation.navigate( From 0824fb95fa3df6b9f06e96d4d42266deb000c50d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:08:40 +0530 Subject: [PATCH 005/298] update startMoneyRequest_temporaryForRefactor to startMoneyRequest. Signed-off-by: Krishna Gupta --- src/libs/actions/IOU.ts | 9 +-------- src/pages/iou/request/IOURequestStartPage.js | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 569b6b26b728..f20e0b0519c7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -225,7 +225,7 @@ Onyx.connect({ * @param iouRequestType one of manual/scan/distance */ // eslint-disable-next-line @typescript-eslint/naming-convention -function startMoneyRequest_temporaryForRefactor(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { +function startMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) { // Generate a brand new transactionID const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID; // Disabling this line since currentDate can be an empty string @@ -3530,12 +3530,6 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); } -/** Initialize money request info and navigate to the MoneyRequest page */ -function startMoneyRequest(iouType: string, reportID = '') { - resetMoneyRequestInfo(`${iouType}${reportID}`); - Navigation.navigate(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID)); -} - function setMoneyRequestId(id: string) { Onyx.merge(ONYXKEYS.IOU, {id}); } @@ -3686,7 +3680,6 @@ export { payMoneyRequest, sendMoneyWithWallet, startMoneyRequest, - startMoneyRequest_temporaryForRefactor, resetMoneyRequestCategory, resetMoneyRequestCategory_temporaryForRefactor, resetMoneyRequestInfo, diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index c790b30c5f7d..dd6420bf2c1a 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -106,7 +106,7 @@ function IOURequestStartPage({ if (transaction.reportID === reportID) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, transactionRequestType.current); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current); }, [transaction, reportID, iouType, isFromGlobalCreate]); const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); @@ -125,7 +125,7 @@ function IOURequestStartPage({ if (newIouType === previousIOURequestType) { return; } - IOU.startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, newIouType); + IOU.startMoneyRequest(reportID, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, [previousIOURequestType, reportID, isFromGlobalCreate], From 4e69672e311a7509f05b5e2946814c131c5284b2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 11:35:40 +0530 Subject: [PATCH 006/298] remove: unused routes from ROUTES.ts Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 7 ------- src/pages/iou/request/step/IOURequestStepWaypoint.js | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 016e4267803b..aabed6fdb0a1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -301,13 +301,6 @@ const ROUTES = { route: ':iouType/new/address/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const, }, - MONEY_REQUEST_DISTANCE_TAB: { - route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` as const, - }, - MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', - MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', - MONEY_REQUEST_CREATE: { route: 'create/:iouType/start/:transactionID/:reportID', getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.js b/src/pages/iou/request/step/IOURequestStepWaypoint.js index 4c35951bc297..89e2b2cc297d 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.js +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.js @@ -158,13 +158,13 @@ function IOURequestStepWaypoint({ } // Other flows will be handled by selecting a waypoint with selectWaypoint as this is mainly for the offline flow - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; const deleteStopAndHideModal = () => { Transaction.removeWaypoint(transaction, pageIndex, true); setIsDeleteStopModalOpen(false); - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }; /** @@ -200,7 +200,7 @@ function IOURequestStepWaypoint({ title={translate(waypointDescriptionKey)} shouldShowBackButton onBackButtonPress={() => { - Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); + Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID)); }} shouldShowThreeDotsButton={shouldShowThreeDotsButton} shouldSetModalVisibility={false} From 013376d144397e0335f1eedb794be9fd81f12f88 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 13:59:30 +0530 Subject: [PATCH 007/298] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index daae4bcf0831..a11877739cb5 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -46,7 +46,7 @@ function IOURequestStepParticipants({ if (iouType === CONST.IOU.TYPE.SEND) { return translate('common.send'); } - translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); }; const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); From 423af4e488e11f3df71ce02f6b295e7292b978bc Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 6 Feb 2024 14:14:51 +0530 Subject: [PATCH 008/298] updated sendMoney callback dependencies. Signed-off-by: Krishna Gupta --- .../iou/request/step/IOURequestStepConfirmation.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 70d5e1fceac7..c66dc91f4f64 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -308,7 +308,13 @@ function IOURequestStepConfirmation({ const sendMoney = useCallback( (paymentMethodType) => { const currency = transaction.currency; - const trimmedComment = lodashGet(transaction, 'comment.comment', '').trim(); + + let trimmedComment = ''; + + if (transaction.comment && transaction.comment.comment) { + trimmedComment = transaction.comment.comment.trim(); + } + const participant = participants[0]; if (paymentMethodType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { @@ -320,7 +326,7 @@ function IOURequestStepConfirmation({ IOU.sendMoneyWithWallet(report, transaction.amount, currency, trimmedComment, currentUserPersonalDetails.accountID, participant); } }, - [transaction, participants, currentUserPersonalDetails.accountID, report], + [transaction.amount, transaction.comment, transaction.currency, participants, currentUserPersonalDetails.accountID, report], ); const addNewParticipant = (option) => { From 21e3ec0ad48fc8a99f30c75c27fc809be6a5f1bf Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 6 Feb 2024 23:51:15 +0700 Subject: [PATCH 009/298] fix: app crashes when changing from auditor to employee --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index bfe27910c943..2e2d7251de6f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -418,7 +418,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && _.isEmpty(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && _.isEmpty(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { @@ -643,7 +643,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID) { + if (!parentReportActionID || !parentReportActions) { return {}; } return lodashGet(parentReportActions, parentReportActionID); From 92789b565db5233a5f6dfdc243f266c92a406aa7 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 13 Feb 2024 20:55:10 +0530 Subject: [PATCH 010/298] Remove redundant constants for the old screen names. Signed-off-by: Krishna Gupta --- src/SCREENS.ts | 4 ---- src/pages/iou/request/step/IOURequestStepConfirmation.js | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 1626fdbd1898..6da12b453ef8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -122,9 +122,6 @@ const SCREENS = { SAML_SIGN_IN: 'SAMLSignIn', MONEY_REQUEST: { - MANUAL_TAB: 'manual', - SCAN_TAB: 'scan', - DISTANCE_TAB: 'distance', CREATE: 'Money_Request_Create', STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', START: 'Money_Request_Start', @@ -141,7 +138,6 @@ const SCREENS = { STEP_WAYPOINT: 'Money_Request_Step_Waypoint', STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', - ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', CONFIRMATION: 'Money_Request_Confirmation', diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 038841027fae..6536541c289b 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -360,11 +360,7 @@ function IOURequestStepConfirmation({ (paymentMethodType) => { const currency = transaction.currency; - let trimmedComment = ''; - - if (transaction.comment && transaction.comment.comment) { - trimmedComment = transaction.comment.comment.trim(); - } + const trimmedComment = transaction.comment && transaction.comment.comment ? transaction.comment.comment.trim() : ''; const participant = participants[0]; From 40cd54db77e7a89c46163a4ba0074fe5c7c114dc Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:01 +0100 Subject: [PATCH 011/298] Migrate ReimbursementAccount to ts --- .../RestartBankAccountSetupParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../deleteFromBankAccountList.ts | 16 ++++ .../actions/ReimbursementAccount/errors.ts | 44 ++++++++++ .../actions/ReimbursementAccount/index.ts | 63 ++++++++++++++ .../ReimbursementAccount/navigation.ts | 24 ++++++ .../resetFreePlanBankAccount.ts | 84 +++++++++++++++++++ .../actions/ReimbursementAccount/store.ts | 69 +++++++++++++++ 9 files changed, 309 insertions(+) create mode 100644 src/libs/API/parameters/RestartBankAccountSetupParams.ts create mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts create mode 100644 src/libs/actions/ReimbursementAccount/errors.ts create mode 100644 src/libs/actions/ReimbursementAccount/index.ts create mode 100644 src/libs/actions/ReimbursementAccount/navigation.ts create mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts create mode 100644 src/libs/actions/ReimbursementAccount/store.ts diff --git a/src/libs/API/parameters/RestartBankAccountSetupParams.ts b/src/libs/API/parameters/RestartBankAccountSetupParams.ts new file mode 100644 index 000000000000..b338eac0dea1 --- /dev/null +++ b/src/libs/API/parameters/RestartBankAccountSetupParams.ts @@ -0,0 +1,6 @@ +type RestartBankAccountSetupParams = { + bankAccountID: number; + ownerEmail: string; +}; + +export default RestartBankAccountSetupParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 482c5e0336c4..90b27d825580 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -2,6 +2,7 @@ export type {default as ActivatePhysicalExpensifyCardParams} from './ActivatePhy export type {default as AddNewContactMethodParams} from './AddNewContactMethodParams'; export type {default as AddPaymentCardParams} from './AddPaymentCardParams'; export type {default as AddPersonalBankAccountParams} from './AddPersonalBankAccountParams'; +export type {default as RestartBankAccountSetupParams} from './RestartBankAccountSetupParams'; export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams'; export type {default as AuthenticatePusherParams} from './AuthenticatePusherParams'; export type {default as BankAccountHandlePlaidErrorParams} from './BankAccountHandlePlaidErrorParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index f5d99d8cf40e..4f811b85e709 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -76,6 +76,7 @@ const WRITE_COMMANDS = { ADD_ATTACHMENT: 'AddAttachment', CONNECT_BANK_ACCOUNT_WITH_PLAID: 'ConnectBankAccountWithPlaid', ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount', + RESTART_BANK_ACCOUNT_SETUP: 'RestartBankAccountSetup', OPT_IN_TO_PUSH_NOTIFICATIONS: 'OptInToPushNotifications', OPT_OUT_OF_PUSH_NOTIFICATIONS: 'OptOutOfPushNotifications', RECONNECT_TO_REPORT: 'ReconnectToReport', @@ -213,6 +214,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ADD_ATTACHMENT]: Parameters.AddCommentOrAttachementParams; [WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID]: Parameters.ConnectBankAccountWithPlaidParams; [WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT]: Parameters.AddPersonalBankAccountParams; + [WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP]: Parameters.RestartBankAccountSetupParams; [WRITE_COMMANDS.OPT_IN_TO_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.OPT_OUT_OF_PUSH_NOTIFICATIONS]: Parameters.OptInOutToPushNotificationsParams; [WRITE_COMMANDS.RECONNECT_TO_REPORT]: Parameters.ReconnectToReportParams; diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts new file mode 100644 index 000000000000..d9a2dd130d62 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts @@ -0,0 +1,16 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as store from './store'; + +/** + * Deletes a bank account from bankAccountList + */ +function deleteFromBankAccountList(bankAccountID: number) { + // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx + const bankAccountList = store.getBankAccountList(); + delete bankAccountList?.[bankAccountID]; + + Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); +} + +export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts new file mode 100644 index 000000000000..c65da17690bb --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -0,0 +1,44 @@ +import Onyx from 'react-native-onyx'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; + +/** + * Set the current fields with errors. + */ +function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { + // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); +} + +/** + * Set the current fields with errors. + + */ +function setBankAccountFormValidationErrors(errors: Errors) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +} + +/** + * Clear validation messages from reimbursement account + */ +function resetReimbursementAccount() { + setBankAccountFormValidationErrors({}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: null, + pendingAction: null, + }); +} + +/** + * Set the current error message. + */ +function showBankAccountFormValidationError(error: string | null) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { + errors: ErrorUtils.getMicroSecondOnyxError(error), + }); +} + +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts new file mode 100644 index 000000000000..5c9bf1c822d1 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -0,0 +1,63 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReimbursementAccountForm} from '@src/types/form'; +import type {BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; +import deleteFromBankAccountList from './deleteFromBankAccountList'; +import resetFreePlanBankAccount from './resetFreePlanBankAccount'; + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; +export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; + +/** + * Set the current sub step in first step of adding withdrawal bank account: + * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually + * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber + * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid + * + * @param subStep + * @returns + */ +function setBankAccountSubStep(subStep: BankAccountSubStep) { + return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); +} + +function hideBankAccountErrors() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); +} + +function setWorkspaceIDForReimbursementAccount(workspaceID: string) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); +} + +/** + * @param bankAccountData + */ +function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { + Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); +} + +/** + * Triggers a modal to open allowing the user to reset their bank account + */ +function requestResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); +} + +/** + * Hides modal allowing the user to reset their bank account + */ +function cancelResetFreePlanBankAccount() { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); +} + +export { + resetFreePlanBankAccount, + setBankAccountSubStep, + hideBankAccountErrors, + setWorkspaceIDForReimbursementAccount, + updateReimbursementAccountDraft, + requestResetFreePlanBankAccount, + cancelResetFreePlanBankAccount, + deleteFromBankAccountList, +}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.ts b/src/libs/actions/ReimbursementAccount/navigation.ts new file mode 100644 index 000000000000..2c3eb7cf0384 --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/navigation.ts @@ -0,0 +1,24 @@ +import Onyx from 'react-native-onyx'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {BankAccountStep} from '@src/types/onyx/ReimbursementAccount'; + +/** + * Navigate to a specific step in the VBA flow + */ +function goToWithdrawalAccountSetupStep(stepID: BankAccountStep) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); +} + +/** + * Navigate to the correct bank account route based on the bank account state and type + * + * @param policyID - The policy ID associated with the bank account. + * @param [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + */ +function navigateToBankAccountRoute(policyID: string, backTo?: string) { + Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); +} + +export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts new file mode 100644 index 000000000000..3cc34db0846f --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -0,0 +1,84 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import * as API from '@libs/API'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; +import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +/** + * Reset user's reimbursement account. This will delete the bank account. + */ +function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry) { + if (!bankAccountID) { + throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); + } + if (!session?.email) { + throw new Error('Missing credentials when attempting to reset free plan bank account'); + } + + API.write( + WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP, + { + bankAccountID, + ownerEmail: session.email, + }, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + shouldShowResetModal: false, + isLoading: true, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + achData: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.ONFIDO_APPLICANT_ID, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_DATA, + value: PlaidDataProps.plaidDataDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.PLAID_LINK_TOKEN, + value: '', + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: ReimbursementAccountProps.reimbursementAccountDefaultProps, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, + value: {}, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: {isLoading: false, pendingAction: null}, + }, + ], + }, + ); +} + +export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts new file mode 100644 index 000000000000..bdceb4e2ad5d --- /dev/null +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -0,0 +1,69 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import BankAccount from '@libs/models/BankAccount'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {ACHData} from '@src/types/onyx/ReimbursementAccount'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; + +/** Reimbursement account actively being set up */ +let reimbursementAccountInSetup: ACHData | EmptyObject = {}; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + callback: (val) => { + reimbursementAccountInSetup = val?.achData ?? {}; + }, +}); + +let reimbursementAccountWorkspaceID: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, + callback: (val) => { + reimbursementAccountWorkspaceID = val; + }, +}); + +let bankAccountList: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.BANK_ACCOUNT_LIST, + callback: (val) => { + bankAccountList = val; + }, +}); + +let credentials: OnyxEntry = null; +Onyx.connect({ + key: ONYXKEYS.CREDENTIALS, + callback: (val) => { + credentials = val; + }, +}); + +function getReimbursementAccountInSetup() { + return reimbursementAccountInSetup; +} + +function getBankAccountList() { + return bankAccountList; +} + +function hasCreditBankAccount() { + if (!bankAccountList) { + return false; + } + + Object.entries(bankAccountList).some(([, bankAccountJSON]) => { + const bankAccount = new BankAccount(bankAccountJSON); + return bankAccount.isDefaultCredit(); + }); +} + +function getCredentials() { + return credentials; +} + +function getReimbursementAccountWorkspaceID() { + return reimbursementAccountWorkspaceID; +} + +export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 6a886fefb7d026fb767ec41172b226e1dfad4842 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:51 +0100 Subject: [PATCH 012/298] Remove old js implementations --- .../deleteFromBankAccountList.js | 18 ---- .../actions/ReimbursementAccount/errors.js | 47 ----------- .../actions/ReimbursementAccount/index.js | 61 -------------- .../ReimbursementAccount/navigation.js | 25 ------ .../resetFreePlanBankAccount.js | 83 ------------------- .../actions/ReimbursementAccount/store.js | 63 -------------- 6 files changed, 297 deletions(-) delete mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js delete mode 100644 src/libs/actions/ReimbursementAccount/errors.js delete mode 100644 src/libs/actions/ReimbursementAccount/index.js delete mode 100644 src/libs/actions/ReimbursementAccount/navigation.js delete mode 100644 src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js delete mode 100644 src/libs/actions/ReimbursementAccount/store.js diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js deleted file mode 100644 index 6161066c1c69..000000000000 --- a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.js +++ /dev/null @@ -1,18 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as store from './store'; - -/** - * Deletes a bank account from bankAccountList - * - * @param {Number} bankAccountID - */ -function deleteFromBankAccountList(bankAccountID) { - // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx - const bankAccountList = store.getBankAccountList(); - delete bankAccountList[bankAccountID]; - - Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); -} - -export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.js b/src/libs/actions/ReimbursementAccount/errors.js deleted file mode 100644 index fd2eaf852bce..000000000000 --- a/src/libs/actions/ReimbursementAccount/errors.js +++ /dev/null @@ -1,47 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Set the current fields with errors. - * @param {Object} errorFields - */ -function setPersonalBankAccountFormValidationErrorFields(errorFields) { - // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); -} - -/** - * Set the current fields with errors. - * - * @param {Object} errorFields - */ -function setBankAccountFormValidationErrors(errorFields) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); -} - -/** - * Clear validation messages from reimbursement account - */ -function resetReimbursementAccount() { - setBankAccountFormValidationErrors({}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: null, - pendingAction: null, - }); -} - -/** - * Set the current error message. - * - * @param {String} error - */ -function showBankAccountFormValidationError(error) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: ErrorUtils.getMicroSecondOnyxError(error), - }); -} - -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js deleted file mode 100644 index 12b5b940a0f2..000000000000 --- a/src/libs/actions/ReimbursementAccount/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import deleteFromBankAccountList from './deleteFromBankAccountList'; -import resetFreePlanBankAccount from './resetFreePlanBankAccount'; - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; - -/** - * Set the current sub step in first step of adding withdrawal bank account: - * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually - * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber - * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param {String | null} subStep - * @returns {Promise} - */ -function setBankAccountSubStep(subStep) { - return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); -} - -function hideBankAccountErrors() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); -} - -function setWorkspaceIDForReimbursementAccount(workspaceID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); -} - -/** - * @param {Object} bankAccountData - */ -function updateReimbursementAccountDraft(bankAccountData) { - Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); -} - -/** - * Triggers a modal to open allowing the user to reset their bank account - */ -function requestResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: true}); -} - -/** - * Hides modal allowing the user to reset their bank account - */ -function cancelResetFreePlanBankAccount() { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); -} - -export { - resetFreePlanBankAccount, - setBankAccountSubStep, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, - deleteFromBankAccountList, -}; diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js deleted file mode 100644 index 6c82561c16ee..000000000000 --- a/src/libs/actions/ReimbursementAccount/navigation.js +++ /dev/null @@ -1,25 +0,0 @@ -import Onyx from 'react-native-onyx'; -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -/** - * Navigate to a specific step in the VBA flow - * - * @param {String} stepID - */ -function goToWithdrawalAccountSetupStep(stepID) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {currentStep: stepID}}); -} - -/** - * Navigate to the correct bank account route based on the bank account state and type - * - * @param {string} policyID - The policy ID associated with the bank account. - * @param {string} [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. - */ -function navigateToBankAccountRoute(policyID, backTo) { - Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); -} - -export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute}; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js deleted file mode 100644 index 962800fb2e55..000000000000 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js +++ /dev/null @@ -1,83 +0,0 @@ -import Onyx from 'react-native-onyx'; -import * as API from '@libs/API'; -import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** - * Reset user's reimbursement account. This will delete the bank account. - * @param {Number} bankAccountID - * @param {Object} session - */ -function resetFreePlanBankAccount(bankAccountID, session) { - if (!bankAccountID) { - throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); - } - if (!session.email) { - throw new Error('Missing credentials when attempting to reset free plan bank account'); - } - - API.write( - 'RestartBankAccountSetup', - { - bankAccountID, - ownerEmail: session.email, - }, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - shouldShowResetModal: false, - isLoading: true, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - achData: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.ONFIDO_APPLICANT_ID, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_DATA, - value: PlaidDataProps.plaidDataDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, - value: '', - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: ReimbursementAccountProps.reimbursementAccountDefaultProps, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, - value: {}, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: {isLoading: false, pendingAction: null}, - }, - ], - }, - ); -} - -export default resetFreePlanBankAccount; diff --git a/src/libs/actions/ReimbursementAccount/store.js b/src/libs/actions/ReimbursementAccount/store.js deleted file mode 100644 index 4b8549b60b2e..000000000000 --- a/src/libs/actions/ReimbursementAccount/store.js +++ /dev/null @@ -1,63 +0,0 @@ -import lodashGet from 'lodash/get'; -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import BankAccount from '@libs/models/BankAccount'; -import ONYXKEYS from '@src/ONYXKEYS'; - -/** Reimbursement account actively being set up */ -let reimbursementAccountInSetup = {}; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - callback: (val) => { - reimbursementAccountInSetup = lodashGet(val, 'achData', {}); - }, -}); - -let reimbursementAccountWorkspaceID = null; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, - callback: (val) => { - reimbursementAccountWorkspaceID = val; - }, -}); - -let bankAccountList = null; -Onyx.connect({ - key: ONYXKEYS.BANK_ACCOUNT_LIST, - callback: (val) => { - bankAccountList = val; - }, -}); - -let credentials; -Onyx.connect({ - key: ONYXKEYS.CREDENTIALS, - callback: (val) => { - credentials = val || {}; - }, -}); - -function getReimbursementAccountInSetup() { - return reimbursementAccountInSetup; -} - -function getBankAccountList() { - return bankAccountList; -} - -function hasCreditBankAccount() { - return _.some(bankAccountList, (bankAccountJSON) => { - const bankAccount = new BankAccount(bankAccountJSON); - return bankAccount.isDefaultCredit(); - }); -} - -function getCredentials() { - return credentials; -} - -function getReimbursementAccountWorkspaceID() { - return reimbursementAccountWorkspaceID; -} - -export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; From 8b6fb5e1e3639f3532c1183e953f416417c6151c Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:58:46 +0100 Subject: [PATCH 013/298] Add ErrorFields to PersonalBankAccount type --- src/libs/actions/ReimbursementAccount/errors.ts | 14 +++++++------- src/types/onyx/PersonalBankAccount.ts | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index c65da17690bb..f85426f8d4fe 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -1,24 +1,24 @@ import Onyx from 'react-native-onyx'; import * as ErrorUtils from '@libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; /** * Set the current fields with errors. */ -function setPersonalBankAccountFormValidationErrorFields(errors: Errors) { +function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errors}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } /** * Set the current fields with errors. */ -function setBankAccountFormValidationErrors(errors: Errors) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null}); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors}); +function setBankAccountFormValidationErrors(errorFields: ErrorFields) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields}); } /** diff --git a/src/types/onyx/PersonalBankAccount.ts b/src/types/onyx/PersonalBankAccount.ts index 3714cc9f314b..3e52a3cf59f3 100644 --- a/src/types/onyx/PersonalBankAccount.ts +++ b/src/types/onyx/PersonalBankAccount.ts @@ -5,6 +5,9 @@ type PersonalBankAccount = { /** An error message to display to the user */ errors?: OnyxCommon.Errors; + /** Error objects keyed by field name containing errors keyed by microtime */ + errorFields?: OnyxCommon.ErrorFields; + /** Whether we should show the view that the bank account was successfully added */ shouldShowSuccess?: boolean; From 53fb383b51d8382c2cfc3e8fc9068adbddd82c07 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 16:03:55 +0100 Subject: [PATCH 014/298] Adjust types --- src/libs/actions/ReimbursementAccount/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 5c9bf1c822d1..416c5e956189 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -13,11 +13,8 @@ export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidation * - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid - * - * @param subStep - * @returns */ -function setBankAccountSubStep(subStep: BankAccountSubStep) { +function setBankAccountSubStep(subStep: BankAccountSubStep | null) { return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); } @@ -25,13 +22,10 @@ function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); } -function setWorkspaceIDForReimbursementAccount(workspaceID: string) { +function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); } -/** - * @param bankAccountData - */ function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); From dfd6b8bedea3616571ba853945abef2913504d35 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 15 Feb 2024 01:59:35 +0530 Subject: [PATCH 015/298] removed MoneyRequest route. Signed-off-by: Krishna Gupta --- src/ROUTES.ts | 6 ------ src/pages/iou/IOUCurrencySelection.js | 4 +--- src/pages/iou/request/step/IOURequestStepParticipants.js | 9 +++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 615d7c708d1d..691ff5c78551 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -260,12 +260,6 @@ const ROUTES = { route: 'r/:reportID/invite', getRoute: (reportID: string) => `r/${reportID}/invite` as const, }, - - // To see the available iouType, please refer to CONST.IOU.TYPE - MONEY_REQUEST: { - route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` as const, - }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` as const, diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 2a48897bfc85..50833534cb0e 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -71,8 +71,6 @@ function IOUCurrencySelection(props) { const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); - const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); - const reportID = lodashGet(props.route, 'params.reportID', ''); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); // Decides whether to allow or disallow editing a money request @@ -161,7 +159,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))} + onBackButtonPress={() => Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} /> { + const headerTitle = useMemo(() => { if (iouType === CONST.IOU.TYPE.SPLIT) { return translate('iou.split'); } @@ -47,7 +47,8 @@ function IOURequestStepParticipants({ return translate('common.send'); } return translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); - }; + }, [iouType, transaction, translate]); + const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); @@ -89,7 +90,7 @@ function IOURequestStepParticipants({ return ( Date: Thu, 15 Feb 2024 02:04:51 +0530 Subject: [PATCH 016/298] minor fix. Signed-off-by: Krishna Gupta --- src/pages/iou/IOUCurrencySelection.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 50833534cb0e..125b95046d67 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -17,7 +17,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import {iouDefaultProps, iouPropTypes} from './propTypes'; /** @@ -72,6 +71,7 @@ function IOUCurrencySelection(props) { const optionsSelectorRef = useRef(); const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); + const backTo = lodashGet(props.route, 'params.backTo', ''); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -96,7 +96,6 @@ function IOUCurrencySelection(props) { const confirmCurrencySelection = useCallback( (option) => { - const backTo = lodashGet(props.route, 'params.backTo', ''); Keyboard.dismiss(); // When we refresh the web, the money request route gets cleared from the navigation stack. @@ -108,7 +107,7 @@ function IOUCurrencySelection(props) { Navigation.navigate(`${props.route.params.backTo}?currency=${option.currencyCode}`); } }, - [props.route, props.navigation], + [props.route, props.navigation, backTo], ); const {translate, currencyList} = props; @@ -159,7 +158,7 @@ function IOUCurrencySelection(props) { <> Navigation.goBack(ROUTES.EDIT_REQUEST.getRoute(threadReportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} + onBackButtonPress={() => Navigation.goBack(backTo)} /> Date: Wed, 21 Feb 2024 12:12:14 +0100 Subject: [PATCH 017/298] TS updates after merging main --- src/components/Onfido/BaseOnfidoWeb.tsx | 54 +++++++++++-------- src/components/Onfido/index.native.tsx | 13 ++--- src/components/Onfido/types.ts | 10 ++-- .../VerifyIdentity/VerifyIdentity.tsx | 6 +-- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/components/Onfido/BaseOnfidoWeb.tsx b/src/components/Onfido/BaseOnfidoWeb.tsx index ee206b15fc24..2722b9e9be79 100644 --- a/src/components/Onfido/BaseOnfidoWeb.tsx +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -1,17 +1,28 @@ -import lodashGet from 'lodash/get'; import * as OnfidoSDK from 'onfido-sdk-ui'; import React, {forwardRef, useEffect} from 'react'; -import _ from 'underscore'; +import type {ForwardedRef} from 'react'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import Log from '@libs/Log'; +import type {ThemeColors} from '@styles/theme/types'; import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import './index.css'; -import onfidoPropTypes from './onfidoPropTypes'; +import type {OnfidoElement, OnfidoProps} from './types'; -function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}) { +type InitializeOnfidoProps = OnfidoProps & + Pick & { + theme: ThemeColors; + }; + +type OnfidoEvent = Event & { + detail?: Record; +}; + +function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLocale, translate, theme}: InitializeOnfidoProps) { OnfidoSDK.init({ token: sdkToken, containerId: CONST.ONFIDO.CONTAINER_ID, @@ -21,7 +32,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo fontFamilySubtitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontFamilyBody: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`, fontSizeTitle: `${variables.fontSizeLarge}px`, - fontWeightTitle: FontUtils.fontWeight.bold, + fontWeightTitle: Number(FontUtils.fontWeight.bold), fontWeightSubtitle: 400, fontSizeSubtitle: `${variables.fontSizeNormal}px`, colorContentTitle: theme.text, @@ -46,7 +57,6 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo colorBorderLinkUnderline: theme.link, colorBackgroundLinkHover: theme.link, colorBackgroundLinkActive: theme.link, - authAccentColor: theme.link, colorBackgroundInfoPill: theme.link, colorBackgroundSelector: theme.appBG, colorBackgroundDocTypeButton: theme.success, @@ -58,11 +68,10 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo { type: CONST.ONFIDO.TYPE.DOCUMENT, options: { - useLiveDocumentCapture: true, forceCrossDevice: true, hideCountrySelection: true, - country: 'USA', documentTypes: { + // eslint-disable-next-line @typescript-eslint/naming-convention driving_licence: { country: 'USA', }, @@ -77,17 +86,15 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, }, ], - smsNumberCountryCode: CONST.ONFIDO.SMS_NUMBER_COUNTRY_CODE.US, - showCountrySelection: false, onComplete: (data) => { - if (_.isEmpty(data)) { + if (isEmptyObject(data)) { Log.warn('Onfido completed with no data'); } onSuccess(data); }, onError: (error) => { - const errorMessage = lodashGet(error, 'message', CONST.ERROR.UNKNOWN_ERROR); - const errorType = lodashGet(error, 'type'); + const errorMessage = error.message ?? CONST.ERROR.UNKNOWN_ERROR; + const errorType = error.type; Log.hmmm('Onfido error', {errorType, errorMessage}); onError(errorMessage); }, @@ -100,33 +107,34 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo }, language: { // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido - locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale as OnfidoSDK.SupportedLanguages), // Provide a custom phrase for the back button so that the first letter is capitalized, // and translate the phrase while we're at it. See the issue and documentation for more context. // https://github.com/Expensify/App/issues/17244 // https://documentation.onfido.com/sdk/web/#custom-languages phrases: { + // eslint-disable-next-line @typescript-eslint/naming-convention 'generic.back': translate('common.back'), }, }, }); } -function logOnFidoEvent(event) { +function logOnFidoEvent(event: OnfidoEvent) { Log.hmmm('Receiving Onfido analytic event', event.detail); } -const Onfido = forwardRef((props, ref) => { +function Onfido({sdkToken, onSuccess, onError, onUserExit}: OnfidoProps, ref: ForwardedRef) { const {preferredLocale, translate} = useLocalize(); const theme = useTheme(); useEffect(() => { initializeOnfido({ - sdkToken: props.sdkToken, - onSuccess: props.onSuccess, - onError: props.onError, - onUserExit: props.onUserExit, + sdkToken, + onSuccess, + onError, + onUserExit, preferredLocale, translate, theme, @@ -144,8 +152,8 @@ const Onfido = forwardRef((props, ref) => { ref={ref} /> ); -}); +} Onfido.displayName = 'Onfido'; -Onfido.propTypes = onfidoPropTypes; -export default Onfido; + +export default forwardRef(Onfido); diff --git a/src/components/Onfido/index.native.tsx b/src/components/Onfido/index.native.tsx index eae01d14869e..a7e7a277fff9 100644 --- a/src/components/Onfido/index.native.tsx +++ b/src/components/Onfido/index.native.tsx @@ -1,13 +1,13 @@ -import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK} from '@onfido/react-native-sdk'; +import {OnfidoCaptureType, OnfidoCountryCode, OnfidoDocumentType, Onfido as OnfidoSDK, OnfidoTheme} from '@onfido/react-native-sdk'; import React, {useEffect} from 'react'; import {Alert, Linking} from 'react-native'; import {checkMultiple, PERMISSIONS, RESULTS} from 'react-native-permissions'; -import _ from 'underscore'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useLocalize from '@hooks/useLocalize'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import type {OnfidoProps} from './types'; function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { @@ -16,6 +16,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { useEffect(() => { OnfidoSDK.start({ sdkToken, + theme: OnfidoTheme.AUTOMATIC, flowSteps: { welcome: true, captureFace: { @@ -41,13 +42,13 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { return; } - if (!_.isEmpty(errorMessage) && getPlatform() === CONST.PLATFORM.IOS) { + if (!!errorMessage && getPlatform() === CONST.PLATFORM.IOS) { checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA]) .then((statuses) => { const isMicAllowed = statuses[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED; const isCameraAllowed = statuses[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED; - let alertTitle = ''; - let alertMessage = ''; + let alertTitle: TranslationPaths | '' = ''; + let alertMessage: TranslationPaths | '' = ''; if (!isCameraAllowed) { alertTitle = 'onfidoStep.cameraPermissionsNotGranted'; alertMessage = 'onfidoStep.cameraRequestMessage'; @@ -56,7 +57,7 @@ function Onfido({sdkToken, onUserExit, onSuccess, onError}: OnfidoProps) { alertMessage = 'onfidoStep.microphoneRequestMessage'; } - if (!_.isEmpty(alertTitle) && !_.isEmpty(alertMessage)) { + if (!!alertTitle && !!alertMessage) { Alert.alert( translate(alertTitle), translate(alertMessage), diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index a4fe3d93f05e..e341dfd64960 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,5 +1,7 @@ -import {OnfidoError, OnfidoResult} from '@onfido/react-native-sdk'; -import * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnfidoResult} from '@onfido/react-native-sdk'; +import type * as OnfidoSDK from 'onfido-sdk-ui'; + +type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; @@ -11,10 +13,10 @@ type OnfidoProps = { onUserExit: (userExitCode?: OnfidoSDK.UserExitCode) => void; /** Called when the user is totally done with Onfido */ - onSuccess: (data: OnfidoSDK.SdkResponse | OnfidoResult | OnfidoError) => void; + onSuccess: (data: OnfidoData) => void; /** Called when Onfido throws an error */ onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement}; +export type {OnfidoProps, OnfidoElement, OnfidoData}; diff --git a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx index d17166365a39..f7c4df6fd915 100644 --- a/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/ReimbursementAccount/VerifyIdentity/VerifyIdentity.tsx @@ -5,8 +5,8 @@ import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; -// @ts-expect-error TODO: Remove this once Onfido (https://github.com/Expensify/App/issues/25136) is migrated to TypeScript. import Onfido from '@components/Onfido'; +import type {OnfidoData} from '@components/Onfido/types'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -40,7 +40,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican const policyID = reimbursementAccount?.achData?.policyID ?? ''; const handleOnfidoSuccess = useCallback( - (onfidoData: Record) => { + (onfidoData: OnfidoData) => { BankAccounts.verifyIdentityForBankAccount(Number(reimbursementAccount?.achData?.bankAccountID ?? '0'), {...onfidoData, applicantID: onfidoApplicantID}, policyID); BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); }, @@ -74,7 +74,7 @@ function VerifyIdentity({reimbursementAccount, onBackButtonPress, onfidoApplican Date: Wed, 21 Feb 2024 12:29:27 +0100 Subject: [PATCH 018/298] Add OnfidoDataWithApplicantID type --- src/components/Onfido/types.ts | 7 ++++++- src/libs/actions/BankAccounts.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index e341dfd64960..90403f1ab179 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,8 +1,13 @@ import type {OnfidoResult} from '@onfido/react-native-sdk'; import type * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnyxEntry} from 'react-native-onyx'; type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; +type OnfidoDataWithApplicantID = OnfidoData & { + applicantID: OnyxEntry; +}; + type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; type OnfidoProps = { @@ -19,4 +24,4 @@ type OnfidoProps = { onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement, OnfidoData}; +export type {OnfidoProps, OnfidoElement, OnfidoData, OnfidoDataWithApplicantID}; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 30dd03b6e780..90e4a6c4aaed 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnfidoDataWithApplicantID} from '@components/Onfido/types'; import * as API from '@libs/API'; import type { AddPersonalBankAccountParams, @@ -436,7 +437,7 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan /** * Verify the user's identity via Onfido */ -function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: Record, policyID: string) { +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID: string) { const parameters: VerifyIdentityForBankAccountParams = { bankAccountID, onfidoData: JSON.stringify(onfidoData), From a8097cd6a6be5b86a7d045304d1d8cf6e668c00c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 21 Feb 2024 16:33:00 +0200 Subject: [PATCH 019/298] Initial historical action to show --- src/CONST.ts | 1 + src/pages/home/report/ReportActionItem.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6a57738d06ec..dade8443bfce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -653,6 +653,7 @@ const CONST = { LEAVE_ROOM: 'LEAVEROOM', UPDATE_ROOM_DESCRIPTION: 'UPDATEROOMDESCRIPTION', }, + UNAPPROVED: 'UNAPPROVED', UNHOLD: 'UNHOLD', }, THREAD_DISABLED: ['CREATED'], diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 66394190fde6..dac0353623a5 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -464,7 +464,11 @@ function ReportActionItem(props) { children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; - } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) { + } else if ([ + CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED + ].includes(props.action.actionName)) { + // This handles all historical actions from OldDot that we just want to display the message text children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; From 8b2a982a0d31ca5348a4b29a38accd1b2fc90519 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 22 Feb 2024 19:16:54 +0530 Subject: [PATCH 020/298] minor fic. Signed-off-by: Krishna Gupta --- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 3 +++ .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 1 + 2 files changed, 4 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 3828a43cd54d..af8f31e8c6b4 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -18,11 +18,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** The report currently being looked at */ diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index b514c374e34c..5b9dce43ddcc 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -16,6 +16,7 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; +import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; From 840685854cf85f535be41e9df7792673d3572504 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 23 Feb 2024 16:15:20 +0100 Subject: [PATCH 021/298] Fix typescript errors --- .../RestartBankAccountSetupParams.ts | 1 + .../actions/ReimbursementAccount/index.ts | 2 +- .../resetFreePlanBankAccount.ts | 42 ++++++++++++++++++- .../BankInfo/BankInfo.tsx | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libs/API/parameters/RestartBankAccountSetupParams.ts b/src/libs/API/parameters/RestartBankAccountSetupParams.ts index b338eac0dea1..5cb4fa132d10 100644 --- a/src/libs/API/parameters/RestartBankAccountSetupParams.ts +++ b/src/libs/API/parameters/RestartBankAccountSetupParams.ts @@ -1,6 +1,7 @@ type RestartBankAccountSetupParams = { bankAccountID: number; ownerEmail: string; + policyID: string; }; export default RestartBankAccountSetupParams; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 416c5e956189..dd1c784d2218 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -26,7 +26,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); } -function updateReimbursementAccountDraft(bankAccountData: ReimbursementAccountForm) { +function updateReimbursementAccountDraft(bankAccountData: Partial) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); } diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts index 9eea9504d604..b3effa220e59 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -6,6 +6,7 @@ import * as PlaidDataProps from '@pages/ReimbursementAccount/plaidDataPropTypes' import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import type * as OnyxTypes from '@src/types/onyx'; /** @@ -60,6 +61,7 @@ function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry Date: Fri, 23 Feb 2024 16:21:05 +0100 Subject: [PATCH 022/298] Fix comment typo --- src/libs/actions/ReimbursementAccount/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index f85426f8d4fe..05c375364329 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -7,7 +7,7 @@ import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; * Set the current fields with errors. */ function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { - // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } From a78c3f8f66403c475c9468cdeafc460352f174ad Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 13:51:28 +0700 Subject: [PATCH 023/298] remove redundant change --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 7f923b20864e..fe5ed9bb1150 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -648,7 +648,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID || !parentReportActions) { + if (!parentReportActionID) { return {}; } return lodashGet(parentReportActions, parentReportActionID, {}); From 229e1561643f0df80486d414a8f9136cf5584007 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 14:44:40 +0700 Subject: [PATCH 024/298] fix: infinite loading in policy expense chat when changing from auditor to employee --- src/pages/home/ReportScreen.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index fe5ed9bb1150..997989c97aa4 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -137,6 +137,17 @@ function getReportID(route) { return String(lodashGet(route, 'params.reportID') || 0); } +/** + * Check is the report is deleted. + * We currently use useMemo to memorize every properties of the report + * so we can't check using isEmpty. + * + * @param {Object} report + */ +function isDeletedReport(report) { + return _.every(report, _.isUndefined); +} + function ReportScreen({ betas, route, @@ -421,7 +432,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && _.isEmpty(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isDeletedReport(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { From feeceae1bcbd1d11ef2d8d0f94c1815f8fbcc810 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 24 Feb 2024 15:38:10 +0700 Subject: [PATCH 025/298] fix lint --- src/pages/home/ReportScreen.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 997989c97aa4..9c3d31fda9e7 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -143,6 +143,7 @@ function getReportID(route) { * so we can't check using isEmpty. * * @param {Object} report + * @returns {Boolean} */ function isDeletedReport(report) { return _.every(report, _.isUndefined); From d2b3674759cd5be4572b09228d5a87d9801f4661 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 11:58:43 +0100 Subject: [PATCH 026/298] Change Object.entries to Object.values --- src/libs/actions/ReimbursementAccount/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts index bdceb4e2ad5d..30005b6fdc04 100644 --- a/src/libs/actions/ReimbursementAccount/store.ts +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -52,7 +52,7 @@ function hasCreditBankAccount() { return false; } - Object.entries(bankAccountList).some(([, bankAccountJSON]) => { + Object.values(bankAccountList).some((bankAccountJSON) => { const bankAccount = new BankAccount(bankAccountJSON); return bankAccount.isDefaultCredit(); }); From 60ea1a16422b9452a62464600f73d89da28b8beb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 26 Feb 2024 16:03:36 +0500 Subject: [PATCH 027/298] perf: remove redundant translations --- src/CONST.ts | 1 + src/libs/PersonalDetailsUtils.ts | 9 +++++++-- src/libs/ReportUtils.ts | 23 +++++++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8abd4c087b16..bb167bb5d8d8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -41,6 +41,7 @@ const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { + MERGED_ACCOUNT_PREFIX: 'MERGED_', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 55aee10e611a..7cd6ac5bdedd 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -24,9 +24,14 @@ Onyx.connect({ }, }); +const hiddenText = Localize.translateLocal('common.hidden'); +const substringStartIndex = 8; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { - const displayName = passedPersonalDetails?.displayName ? passedPersonalDetails.displayName.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '') : ''; - const fallbackValue = shouldFallbackToHidden ? Localize.translateLocal('common.hidden') : ''; + let displayName = passedPersonalDetails?.displayName ?? ''; + if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { + displayName = displayName.substring(substringStartIndex); + } + const fallbackValue = shouldFallbackToHidden ? hiddenText : ''; return displayName || defaultValue || fallbackValue; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8813501e2b3f..adfefec932e5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -575,17 +575,18 @@ function getPolicyType(report: OnyxEntry, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable'); + const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableWorkspaceText; if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return Localize.translateLocal('workspace.common.unavailable'); + return unavailableWorkspaceText; } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -773,11 +774,12 @@ function isAnnounceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE; } +const chatTypes = [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL]; /** * Whether the provided report is a default room */ function isDefaultRoom(report: OnyxEntry): boolean { - return [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL].some((type) => type === getChatType(report)); + return chatTypes.some((type) => type === getChatType(report)); } /** @@ -1625,6 +1627,7 @@ function getPersonalDetailsForAccountID(accountID: number): Partial, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return Localize.translateLocal('parentReportAction.deletedMessage'); + return deletedMessageText; } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${Localize.translateLocal('common.attachment')}]`; + return `[${attachmentText}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2529,7 +2536,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return Localize.translateLocal('parentReportAction.deletedTask'); + return deletedTaskText; } if (isChatRoom(report) || isTaskReport(report)) { @@ -2545,7 +2552,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${Localize.translateLocal('common.archived')})`; + formattedName += ` (${archivedText})`; } if (formattedName) { From 22f0ffa46f75a5828e6edbf6774c0b53e688fbda Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 26 Feb 2024 16:29:41 +0500 Subject: [PATCH 028/298] perf: only parse the number if it matches the pattern --- src/CONST.ts | 1 + src/libs/LocalePhoneNumber.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index bb167bb5d8d8..fce096a9957a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -42,6 +42,7 @@ const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', + SMS_DOMAIN_PATTERN: 'expensify.sms', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 933aa7937560..4fc13322b0cf 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {parsePhoneNumber} from './PhoneNumber'; @@ -18,6 +19,10 @@ function formatPhoneNumber(number: string): string { return ''; } + // do not parse the string, if it's not a phone number + if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1) { + return number; + } const numberWithoutSMSDomain = Str.removeSMSDomain(number); const parsedPhoneNumber = parsePhoneNumber(numberWithoutSMSDomain); From c34bee5fd6a110df9a4a34ea4e7d28f7084aed28 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 12:44:47 +0100 Subject: [PATCH 029/298] Add missing isOnfidoSetupComplete --- .../actions/ReimbursementAccount/resetFreePlanBankAccount.ts | 1 + src/types/form/ReimbursementAccountForm.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts index b3effa220e59..3d529ce54cd6 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.ts @@ -96,6 +96,7 @@ function resetFreePlanBankAccount(bankAccountID: number, session: OnyxEntry Date: Mon, 26 Feb 2024 19:01:09 +0700 Subject: [PATCH 030/298] change function name --- src/pages/home/ReportScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 9c3d31fda9e7..157be9118c6c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -145,7 +145,7 @@ function getReportID(route) { * @param {Object} report * @returns {Boolean} */ -function isDeletedReport(report) { +function isEmpty(report) { return _.every(report, _.isUndefined); } @@ -433,7 +433,7 @@ function ReportScreen({ !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || - ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isDeletedReport(report)) + ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport)) && isEmpty(report)) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { From 643c7e150cd9e7f42f559c5df0e384182c7a0c0d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 12:44:01 +0500 Subject: [PATCH 031/298] perf: return early if displayName exists --- src/libs/PersonalDetailsUtils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 7cd6ac5bdedd..912e9aba36aa 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -25,14 +25,22 @@ Onyx.connect({ }); const hiddenText = Localize.translateLocal('common.hidden'); -const substringStartIndex = 8; +const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } + + /** + * If displayName exists, return it early so we don't have to allocate + * memory for the fallback string. + */ + if (displayName) { + return displayName; + } const fallbackValue = shouldFallbackToHidden ? hiddenText : ''; - return displayName || defaultValue || fallbackValue; + return defaultValue || fallbackValue; } /** From c683db7392b2ad06320785a78661bf56c5c2bc4e Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 13:10:35 +0500 Subject: [PATCH 032/298] test: fix failing test for formatPhoneNumber --- src/libs/LocalePhoneNumber.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 4fc13322b0cf..ec704ec4d44b 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -10,6 +10,28 @@ Onyx.connect({ callback: (val) => (countryCodeByIP = val ?? 1), }); +/** + * Checks whether the given string contains any numbers. + * It uses indexOf instead of regex and includes for performance reasons. + * + * @param text + * @returns boolean + */ +function containsNumbers(text: string) { + return ( + text.indexOf('0') !== -1 || + text.indexOf('1') !== -1 || + text.indexOf('2') !== -1 || + text.indexOf('3') !== -1 || + text.indexOf('4') !== -1 || + text.indexOf('5') !== -1 || + text.indexOf('6') !== -1 || + text.indexOf('7') !== -1 || + text.indexOf('8') !== -1 || + text.indexOf('9') !== -1 + ); +} + /** * Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions @@ -20,7 +42,7 @@ function formatPhoneNumber(number: string): string { } // do not parse the string, if it's not a phone number - if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1) { + if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1 && !containsNumbers(number)) { return number; } const numberWithoutSMSDomain = Str.removeSMSDomain(number); From 454a9b258fee05e310d2378b5bfd2cdaf915b251 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 15:24:10 +0500 Subject: [PATCH 033/298] refactor: move translations to utils --- src/libs/CommonTranslationUtils.ts | 49 ++++++++++++++++++++++++++++++ src/libs/PersonalDetailsUtils.ts | 11 +++++-- src/libs/ReportUtils.ts | 21 +++++-------- 3 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 src/libs/CommonTranslationUtils.ts diff --git a/src/libs/CommonTranslationUtils.ts b/src/libs/CommonTranslationUtils.ts new file mode 100644 index 000000000000..80a06e51ce9f --- /dev/null +++ b/src/libs/CommonTranslationUtils.ts @@ -0,0 +1,49 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import * as Localize from './Localize'; + +/** + * This file contains common translations that are used in multiple places in the app. + * This is done to avoid duplicate translations and to keep the translations consistent. + * This also allows us to not repeatedly translate the same string which may happen due + * to translations being done for eg, in a loop. + * + * This was identified as part of a performance audit. + * details: https://github.com/Expensify/App/issues/35234#issuecomment-1926911643 + */ + +let deletedTaskText = ''; +let deletedMessageText = ''; +let attachmentText = ''; +let archivedText = ''; +let hiddenText = ''; +let unavailableWorkspaceText = ''; + +function isTranslationAvailable() { + return deletedTaskText && deletedMessageText && attachmentText && archivedText && hiddenText && unavailableWorkspaceText; +} + +Onyx.connect({ + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + callback: (val) => { + if (!val && isTranslationAvailable()) { + return; + } + + deletedTaskText = Localize.translateLocal('parentReportAction.deletedTask'); + deletedMessageText = Localize.translateLocal('parentReportAction.deletedMessage'); + attachmentText = Localize.translateLocal('common.attachment'); + archivedText = Localize.translateLocal('common.archived'); + hiddenText = Localize.translateLocal('common.hidden'); + unavailableWorkspaceText = Localize.translateLocal('workspace.common.unavailable'); + }, +}); + +export default { + deletedTaskText: () => deletedTaskText, + deletedMessageText: () => deletedMessageText, + attachmentText: () => attachmentText, + archivedText: () => archivedText, + hiddenText: () => hiddenText, + unavailableWorkspaceText: () => unavailableWorkspaceText, +}; diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 912e9aba36aa..7a9b3fe96598 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -5,6 +5,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; +import CommonTranslationUtils from './CommonTranslationUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as UserUtils from './UserUtils'; @@ -24,10 +25,16 @@ Onyx.connect({ }, }); -const hiddenText = Localize.translateLocal('common.hidden'); +/** + * Index for the substring method to remove the merged account prefix. + */ const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; + function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; + /** + * If the displayName starts with the merged account prefix, remove it. + */ if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } @@ -39,7 +46,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableWorkspaceText; + const noPolicyFound = returnEmptyIfNotFound ? '' : CommonTranslationUtils.unavailableWorkspaceText(); if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return unavailableWorkspaceText; + return CommonTranslationUtils.unavailableWorkspaceText(); } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -1627,7 +1627,6 @@ function getPersonalDetailsForAccountID(accountID: number): Partial, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return deletedMessageText; + return CommonTranslationUtils.deletedMessageText(); } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${attachmentText}]`; + return `[${CommonTranslationUtils.attachmentText()}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2536,7 +2531,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return deletedTaskText; + return CommonTranslationUtils.deletedTaskText(); } if (isChatRoom(report) || isTaskReport(report)) { @@ -2552,7 +2547,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${archivedText})`; + formattedName += ` (${CommonTranslationUtils.archivedText()})`; } if (formattedName) { From eb8f26ccb2fabb54fbb7f6c054e13dd53127c9ee Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 27 Feb 2024 15:24:28 +0500 Subject: [PATCH 034/298] fix: linting --- src/libs/LocalePhoneNumber.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index ec704ec4d44b..798e0a50c43e 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -13,7 +13,7 @@ Onyx.connect({ /** * Checks whether the given string contains any numbers. * It uses indexOf instead of regex and includes for performance reasons. - * + * * @param text * @returns boolean */ From 34cd2e8402fe6fab0135e503114b7d514360a76b Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 28 Feb 2024 13:03:14 +0500 Subject: [PATCH 035/298] refactor: use regex and dedicated const for sms domain --- src/CONST.ts | 1 - src/libs/LocalePhoneNumber.ts | 26 ++------------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fce096a9957a..bb167bb5d8d8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -42,7 +42,6 @@ const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', - SMS_DOMAIN_PATTERN: 'expensify.sms', ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 798e0a50c43e..460d5fc0fe9f 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -10,28 +10,6 @@ Onyx.connect({ callback: (val) => (countryCodeByIP = val ?? 1), }); -/** - * Checks whether the given string contains any numbers. - * It uses indexOf instead of regex and includes for performance reasons. - * - * @param text - * @returns boolean - */ -function containsNumbers(text: string) { - return ( - text.indexOf('0') !== -1 || - text.indexOf('1') !== -1 || - text.indexOf('2') !== -1 || - text.indexOf('3') !== -1 || - text.indexOf('4') !== -1 || - text.indexOf('5') !== -1 || - text.indexOf('6') !== -1 || - text.indexOf('7') !== -1 || - text.indexOf('8') !== -1 || - text.indexOf('9') !== -1 - ); -} - /** * Returns a locally converted phone number for numbers from the same region * and an internationally converted phone number with the country code for numbers from other regions @@ -41,8 +19,8 @@ function formatPhoneNumber(number: string): string { return ''; } - // do not parse the string, if it's not a phone number - if (number.indexOf(CONST.SMS_DOMAIN_PATTERN) === -1 && !containsNumbers(number)) { + // do not parse the string, if it doesn't contain the SMS domain and it's not a phone number + if (number.indexOf(CONST.SMS.DOMAIN) === -1 && !CONST.REGEX.DIGITS_AND_PLUS.test(number)) { return number; } const numberWithoutSMSDomain = Str.removeSMSDomain(number); From 707f56c7f913dcf6e7e764933c7e3b7f78021294 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 28 Feb 2024 13:03:42 +0500 Subject: [PATCH 036/298] refactor: remove allocating a variable and return the evaluated expresssion --- src/libs/PersonalDetailsUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 7a9b3fe96598..d5946feb2f74 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -46,8 +46,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial Date: Thu, 29 Feb 2024 13:00:44 +0500 Subject: [PATCH 037/298] refactor: use single-line comments --- src/libs/PersonalDetailsUtils.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index d5946feb2f74..97e2dc91492b 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -25,24 +25,19 @@ Onyx.connect({ }, }); -/** - * Index for the substring method to remove the merged account prefix. - */ +// Index for the substring method to remove the merged account prefix. const substringStartIndex = CONST.MERGED_ACCOUNT_PREFIX.length; function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string { let displayName = passedPersonalDetails?.displayName ?? ''; - /** - * If the displayName starts with the merged account prefix, remove it. - */ + + // If the displayName starts with the merged account prefix, remove it. if (displayName.startsWith(CONST.MERGED_ACCOUNT_PREFIX)) { displayName = displayName.substring(substringStartIndex); } - /** - * If displayName exists, return it early so we don't have to allocate - * memory for the fallback string. - */ + // If displayName exists, return it early so we don't have to allocate + // memory for the fallback string. if (displayName) { return displayName; } From b99c2cf228e15d8e138a23d7b3dae277ed89d8e3 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 1 Mar 2024 17:25:20 +0100 Subject: [PATCH 038/298] [TS migration] Migrate 'Desktop' files --- config/webpack/webpack.desktop.js | 4 +- ...{ELECTRON_EVENTS.js => ELECTRON_EVENTS.ts} | 4 +- desktop/README.md | 4 +- desktop/contextBridge.js | 98 ---------- desktop/contextBridge.ts | 82 ++++++++ desktop/{main.js => main.ts} | 178 ++++++++++-------- desktop/{start.js => start.ts} | 9 +- package.json | 2 +- src/types/modules/electron.d.ts | 9 +- 9 files changed, 195 insertions(+), 195 deletions(-) rename desktop/{ELECTRON_EVENTS.js => ELECTRON_EVENTS.ts} (90%) delete mode 100644 desktop/contextBridge.js create mode 100644 desktop/contextBridge.ts rename desktop/{main.js => main.ts} (82%) rename desktop/{start.js => start.ts} (88%) diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.js index 2612e2b190fa..20ee4a4025df 100644 --- a/config/webpack/webpack.desktop.js +++ b/config/webpack/webpack.desktop.js @@ -28,8 +28,8 @@ module.exports = (env) => { name: 'desktop-main', target: 'electron-main', entry: { - main: './desktop/main.js', - contextBridge: './desktop/contextBridge.js', + main: './desktop/main.ts', + contextBridge: './desktop/contextBridge.ts', }, output: { filename: '[name].js', diff --git a/desktop/ELECTRON_EVENTS.js b/desktop/ELECTRON_EVENTS.ts similarity index 90% rename from desktop/ELECTRON_EVENTS.js rename to desktop/ELECTRON_EVENTS.ts index ee8c0521892e..de0bd655e12c 100644 --- a/desktop/ELECTRON_EVENTS.js +++ b/desktop/ELECTRON_EVENTS.ts @@ -9,6 +9,6 @@ const ELECTRON_EVENTS = { KEYBOARD_SHORTCUTS_PAGE: 'keyboard-shortcuts-page', START_UPDATE: 'start-update', UPDATE_DOWNLOADED: 'update-downloaded', -}; +} as const; -module.exports = ELECTRON_EVENTS; +export default ELECTRON_EVENTS; diff --git a/desktop/README.md b/desktop/README.md index 77abff97a898..4493196b5ed4 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -31,7 +31,7 @@ The New Expensify desktop app is built using [Electron.js](https://www.electronj The desktop app is organized in three pieces: 1. The Electron main process - - Implemented in https://github.com/Expensify/App/blob/main/desktop/main.js. + - Implemented in https://github.com/Expensify/App/blob/main/desktop/main.ts. - This file has access to the full set of Electron and Node.JS APIs. 2. The Electron renderer process - This is the webpack-bundled version of our react-native-web app (except using `index.desktop.js` files instead of `index.website.js`, where applicable) @@ -131,7 +131,7 @@ The root [package.json](../package.json) serves for `devDependencies` and shared The [desktop/package.json](./package.json) serves for desktop (electron-main) specific dependencies We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.js) to bundle our js code Half of the config takes care of packaging root package dependencies - everything related to rendering App in the Electron window. Packaged under `dist/www` -The other half is about bundling the `main.js` script which initializes Electron and renders `www` +The other half is about bundling the `main.ts` script which initializes Electron and renders `www` ## See what is getting packaged in the app If you suspect unnecessary items might be getting packaged you can inspect the package content in `desktop-build/` diff --git a/desktop/contextBridge.js b/desktop/contextBridge.js deleted file mode 100644 index a8b89cdc0b64..000000000000 --- a/desktop/contextBridge.js +++ /dev/null @@ -1,98 +0,0 @@ -const _ = require('underscore'); -const {contextBridge, ipcRenderer} = require('electron'); -const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); - -const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ - ELECTRON_EVENTS.REQUEST_DEVICE_ID, - ELECTRON_EVENTS.REQUEST_FOCUS_APP, - ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, - ELECTRON_EVENTS.REQUEST_VISIBILITY, - ELECTRON_EVENTS.START_UPDATE, - ELECTRON_EVENTS.LOCALE_UPDATED, -]; - -const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; - -const getErrorMessage = (channel) => `Electron context bridge cannot be used with channel '${channel}'`; - -/** - * The following methods will be available in the renderer process under `window.electron`. - */ -contextBridge.exposeInMainWorld('electron', { - /** - * Send data asynchronously from renderer process to main process. - * Note that this is a one-way channel – main will not respond. In order to get a response from main, either: - * - * - Use `sendSync` - * - Or implement `invoke` if you want to maintain asynchronous communication: https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args - * - * @param {String} channel - * @param {*} data - */ - send: (channel, data) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - ipcRenderer.send(channel, data); - }, - - /** - * Send data synchronously from renderer process to main process. Main process may return a result. - * - * @param {String} channel - * @param {*} data - * @returns {*} - */ - sendSync: (channel, data) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - return ipcRenderer.sendSync(channel, data); - }, - - /** - * Execute a function in the main process and return a promise that resolves with its response. - * - * @param {String} channel - * @param {*} args - * @returns {Promise} - */ - invoke: (channel, ...args) => { - if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { - throw new Error(getErrorMessage(channel)); - } - - return ipcRenderer.invoke(channel, ...args); - }, - - /** - * Set up a listener for events emitted from the main process and sent to the renderer process. - * - * @param {String} channel - * @param {Function} func - */ - on: (channel, func) => { - if (!_.contains(WHITELIST_CHANNELS_MAIN_TO_RENDERER, channel)) { - throw new Error(getErrorMessage(channel)); - } - - // Deliberately strip event as it includes `sender` - ipcRenderer.on(channel, (event, ...args) => func(...args)); - }, - - /** - * Remove listeners for a single channel from the main process and sent to the renderer process. - * - * @param {String} channel - * @param {Function} func - */ - removeAllListeners: (channel) => { - if (!_.contains(WHITELIST_CHANNELS_MAIN_TO_RENDERER, channel)) { - throw new Error(getErrorMessage(channel)); - } - - ipcRenderer.removeAllListeners(channel); - }, -}); diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts new file mode 100644 index 000000000000..f2693259e51a --- /dev/null +++ b/desktop/contextBridge.ts @@ -0,0 +1,82 @@ +import {contextBridge, ipcRenderer} from 'electron'; +import ELECTRON_EVENTS from './ELECTRON_EVENTS'; + +type ContextBridgeApi = { + send: (channel: string, data?: unknown) => void; + sendSync: (channel: string, data?: unknown) => unknown; + invoke: (channel: string, ...args: unknown[]) => Promise; + on: (channel: string, func: (...args: unknown[]) => void) => void; + removeAllListeners: (channel: string) => void; +}; + +const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ + ELECTRON_EVENTS.REQUEST_DEVICE_ID, + ELECTRON_EVENTS.REQUEST_FOCUS_APP, + ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, + ELECTRON_EVENTS.REQUEST_VISIBILITY, + ELECTRON_EVENTS.START_UPDATE, + ELECTRON_EVENTS.LOCALE_UPDATED, +]; + +const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; + +const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`; + +/** + * The following methods will be available in the renderer process under `window.electron`. + */ +contextBridge.exposeInMainWorld('electron', { + /** + * Send data asynchronously from renderer process to main process. + * Note that this is a one-way channel – main will not respond. In order to get a response from main, either: + * + * - Use `sendSync` + * - Or implement `invoke` if you want to maintain asynchronous communication: https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args + */ + send: (channel: string, data: unknown) => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + ipcRenderer.send(channel, data); + }, + + /** Send data synchronously from renderer process to main process. Main process may return a result. */ + sendSync: (channel: string, data: unknown): unknown => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + return ipcRenderer.sendSync(channel, data); + }, + + /** Execute a function in the main process and return a promise that resolves with its response. */ + invoke: (channel: string, ...args: unknown[]): Promise => { + if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + return ipcRenderer.invoke(channel, ...args); + }, + + /** Set up a listener for events emitted from the main process and sent to the renderer process. */ + on: (channel: string, func: (...args: unknown[]) => void) => { + if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + // Deliberately strip event as it includes `sender` + ipcRenderer.on(channel, (event, ...args) => func(...args)); + }, + + /** Remove listeners for a single channel from the main process and sent to the renderer process. */ + removeAllListeners: (channel: string) => { + if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) { + throw new Error(getErrorMessage(channel)); + } + + ipcRenderer.removeAllListeners(channel); + }, +}); + +export default ContextBridgeApi; diff --git a/desktop/main.js b/desktop/main.ts similarity index 82% rename from desktop/main.js rename to desktop/main.ts index 4b38c5d36ab3..071ddb7c7a41 100644 --- a/desktop/main.js +++ b/desktop/main.ts @@ -1,17 +1,21 @@ -const {app, dialog, clipboard, BrowserWindow, Menu, MenuItem, shell, ipcMain} = require('electron'); -const _ = require('underscore'); -const serve = require('electron-serve'); -const contextMenu = require('electron-context-menu'); -const {autoUpdater} = require('electron-updater'); -const log = require('electron-log'); -const {machineId} = require('node-machine-id'); -const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); -const checkForUpdates = require('../src/libs/checkForUpdates'); -const CONFIG = require('../src/CONFIG').default; -const CONST = require('../src/CONST').default; -const Localize = require('../src/libs/Localize'); - -const port = process.env.PORT || 8082; +import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItem, shell} from 'electron'; +import type {BrowserView, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; +import contextMenu from 'electron-context-menu'; +import log from 'electron-log'; +import type {ElectronLog} from 'electron-log'; +import serve from 'electron-serve'; +import {autoUpdater} from 'electron-updater'; +import {machineId} from 'node-machine-id'; +import checkForUpdates from '@libs/checkForUpdates'; +import * as Localize from '@libs/Localize'; +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; +import type {Locale} from '@src/types/onyx'; +import ELECTRON_EVENTS from './ELECTRON_EVENTS'; + +const port = process.env.PORT ?? 8082; const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST; // Setup google api key in process environment, we are setting it this way intentionally. It is required by the @@ -34,43 +38,46 @@ app.commandLine.appendSwitch('enable-network-information-downlink-max'); /** * Inserts the plain text from the clipboard into the provided browser window's web contents. * - * @param {BrowserWindow} browserWindow - The Electron BrowserWindow instance where the text should be inserted. + * @param browserWindow - The Electron BrowserWindow instance where the text should be inserted. */ -function pasteAsPlainText(browserWindow) { +function pasteAsPlainText(browserWindow: BrowserWindow | BrowserView | WebviewTag | WebContents) { const text = clipboard.readText(); - browserWindow.webContents.insertText(text); + + if ('webContents' in browserWindow) { + browserWindow.webContents.insertText(text); + } } /** * Initialize the right-click menu * See https://github.com/sindresorhus/electron-context-menu * - * @param {String} preferredLocale - The current user language to be used for translating menu labels. - * @returns {Function} A dispose function to clean up the created context menu. + * @param preferredLocale - The current user language to be used for translating menu labels. + * @returns A dispose function to clean up the created context menu. */ - -function createContextMenu(preferredLocale = LOCALES.DEFAULT) { +function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => void { return contextMenu({ labels: { cut: Localize.translate(preferredLocale, 'desktopApplicationMenu.cut'), paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), }, - append: (defaultActions, parameters, browserWindow) => [ - new MenuItem({ - // Only enable the menu item for Editable context which supports paste - visible: parameters.isEditable && parameters.editFlags.canPaste, - role: 'pasteAndMatchStyle', - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), - }), - new MenuItem({ - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), - visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, - click: () => pasteAsPlainText(browserWindow), - }), - ], + append: (defaultActions, parameters, browserWindow) => + [ + new MenuItem({ + // Only enable the menu item for Editable context which supports paste + visible: parameters.isEditable && parameters.editFlags.canPaste, + role: 'pasteAndMatchStyle', + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + }), + new MenuItem({ + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, + click: () => pasteAsPlainText(browserWindow), + }), + ] as unknown as MenuItemConstructorOptions[], }); } @@ -79,11 +86,11 @@ let disposeContextMenu = createContextMenu(); // Send all autoUpdater logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log // See https://www.npmjs.com/package/electron-log autoUpdater.logger = log; -autoUpdater.logger.transports.file.level = 'info'; +(autoUpdater.logger as ElectronLog).transports.file.level = 'info'; // Send all Console logs to a log file: ~/Library/Logs/new.expensify.desktop/main.log // See https://www.npmjs.com/package/electron-log -_.assign(console, log.functions); +Object.assign(console, log.functions); // This sets up the command line arguments used to manage the update. When // the --expected-update-version flag is set, the app will open pre-hidden @@ -92,23 +99,24 @@ _.assign(console, log.functions); const EXPECTED_UPDATE_VERSION_FLAG = '--expected-update-version'; const APP_DOMAIN = __DEV__ ? `https://dev.new.expensify.com:${port}` : 'app://-'; -let expectedUpdateVersion; -for (let i = 0; i < process.argv.length; i++) { - const arg = process.argv[i]; - if (arg.startsWith(`${EXPECTED_UPDATE_VERSION_FLAG}=`)) { - expectedUpdateVersion = arg.substr(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); +let expectedUpdateVersion: string; +process.argv.forEach((arg) => { + if (!arg.startsWith(`${EXPECTED_UPDATE_VERSION_FLAG}=`)) { + return; } -} + + expectedUpdateVersion = arg.substr(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); +}); // Add the listeners and variables required to ensure that auto-updating // happens correctly. let hasUpdate = false; -let downloadedVersion; +let downloadedVersion: string; // Note that we have to subscribe to this separately and cannot use Localize.translateLocal, // because the only way code can be shared between the main and renderer processes at runtime is via the context bridge // So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED -const preferredLocale = CONST.LOCALES.DEFAULT; +const preferredLocale: Locale = CONST.LOCALES.DEFAULT; const appProtocol = CONST.DEEPLINK_BASE_URL.replace('://', ''); @@ -120,12 +128,8 @@ const quitAndInstallWithUpdate = () => { autoUpdater.quitAndInstall(); }; -/** - * Menu Item callback to triggers an update check - * @param {MenuItem} menuItem - * @param {BrowserWindow} browserWindow - */ -const manuallyCheckForUpdates = (menuItem, browserWindow) => { +/** Menu Item callback to triggers an update check */ +const manuallyCheckForUpdates = (menuItem: MenuItem, browserWindow?: BrowserWindow) => { // Disable item until the check (and download) is complete // eslint: menu item flags like enabled or visible can be dynamically toggled by mutating the object // eslint-disable-next-line no-param-reassign @@ -135,7 +139,11 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { .checkForUpdates() .catch((error) => ({error})) .then((result) => { - const downloadPromise = result && result.downloadPromise; + const downloadPromise = result && 'downloadPromise' in result ? result.downloadPromise : undefined; + + if (!browserWindow) { + return; + } if (downloadPromise) { dialog.showMessageBox(browserWindow, { @@ -144,7 +152,7 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message'), buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], }); - } else if (result && result.error) { + } else if (result && 'error' in result && result.error) { dialog.showMessageBox(browserWindow, { type: 'error', message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'), @@ -170,25 +178,30 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => { }); }; -/** - * Trigger event to show keyboard shortcuts - * @param {BrowserWindow} browserWindow - */ -const showKeyboardShortcutsPage = (browserWindow) => { +/** Trigger event to show keyboard shortcuts */ +const showKeyboardShortcutsPage = (browserWindow: BrowserWindow) => { if (!browserWindow.isVisible()) { return; } browserWindow.webContents.send(ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE); }; -// Actual auto-update listeners -const electronUpdater = (browserWindow) => ({ +/** Actual auto-update listeners */ +const electronUpdater = (browserWindow: BrowserWindow): PlatformSpecificUpdater => ({ init: () => { autoUpdater.on(ELECTRON_EVENTS.UPDATE_DOWNLOADED, (info) => { const systemMenu = Menu.getApplicationMenu(); + const updateMenuItem = systemMenu?.getMenuItemById(`update`); + const checkForUpdatesMenuItem = systemMenu?.getMenuItemById(`checkForUpdates`); + downloadedVersion = info.version; - systemMenu.getMenuItemById(`update`).visible = true; - systemMenu.getMenuItemById(`checkForUpdates`).visible = false; + + if (updateMenuItem) { + updateMenuItem.visible = true; + } + if (checkForUpdatesMenuItem) { + checkForUpdatesMenuItem.visible = false; + } if (browserWindow.isVisible()) { browserWindow.webContents.send(ELECTRON_EVENTS.UPDATE_DOWNLOADED, info.version); } else { @@ -204,29 +217,26 @@ const electronUpdater = (browserWindow) => ({ }, }); -/* - * @param {Menu} systemMenu - */ -const localizeMenuItems = (submenu, updatedLocale) => - _.map(submenu, (menu) => { - const newMenu = _.clone(menu); +const localizeMenuItems = (submenu: MenuItemConstructorOptions[], updatedLocale: Locale): MenuItemConstructorOptions[] => + submenu.map((menu) => { + const newMenu: MenuItemConstructorOptions = {...menu}; if (menu.id) { - const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}`); + const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); if (labelTranslation) { newMenu.label = labelTranslation; } } if (menu.submenu) { - newMenu.submenu = localizeMenuItems(menu.submenu, updatedLocale); + newMenu.submenu = localizeMenuItems(menu.submenu as MenuItemConstructorOptions[], updatedLocale); } return newMenu; }); -const mainWindow = () => { - let deeplinkUrl; - let browserWindow; +const mainWindow = (): Promise => { + let deeplinkUrl: string; + let browserWindow: BrowserWindow; - const loadURL = __DEV__ ? (win) => win.loadURL(`https://dev.new.expensify.com:${port}`) : serve({directory: `${__dirname}/www`}); + const loadURL = __DEV__ ? (win: BrowserWindow): Promise => win.loadURL(`https://dev.new.expensify.com:${port}`) : serve({directory: `${__dirname}/www`}); // Prod and staging set the icon in the electron-builder config, so only update it here for dev if (__DEV__) { @@ -296,7 +306,9 @@ const mainWindow = () => { if (!__DEV__) { // Modify the origin and referer for requests sent to our API webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { + // @ts-expect-error need to confirm if it's used details.requestHeaders.origin = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; + // @ts-expect-error need to confirm if it's used details.requestHeaders.referer = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; callback({requestHeaders: details.requestHeaders}); }); @@ -304,9 +316,11 @@ const mainWindow = () => { // Modify access-control-allow-origin header and CSP for the response webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { - details.responseHeaders['access-control-allow-origin'] = [APP_DOMAIN]; - if (details.responseHeaders['content-security-policy']) { - details.responseHeaders['content-security-policy'] = _.map(details.responseHeaders['content-security-policy'], (value) => + if (details.responseHeaders) { + details.responseHeaders['access-control-allow-origin'] = [APP_DOMAIN]; + } + if (details.responseHeaders?.['content-security-policy']) { + details.responseHeaders['content-security-policy'] = details.responseHeaders['content-security-policy'].map((value) => value.startsWith('frame-ancestors') ? `${value} ${APP_DOMAIN}` : value, ); } @@ -319,7 +333,7 @@ const mainWindow = () => { browserWindow.setTitle('New Expensify'); } - const initialMenuTemplate = [ + const initialMenuTemplate: MenuItemConstructorOptions[] = [ { id: 'mainMenu', label: Localize.translate(preferredLocale, `desktopApplicationMenu.mainMenu`), @@ -404,6 +418,7 @@ const mainWindow = () => { submenu: [ { id: 'back', + // @ts-expect-error role doesn't exist but removing cause problems role: 'back', accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[', click: () => { @@ -411,6 +426,7 @@ const mainWindow = () => { }, }, { + // @ts-expect-error role doesn't exist but removing cause problems role: 'back', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Left' : 'Shift+Left', @@ -420,6 +436,7 @@ const mainWindow = () => { }, { id: 'forward', + // @ts-expect-error role doesn't exist but removing cause problems role: 'forward', accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]', click: () => { @@ -427,6 +444,7 @@ const mainWindow = () => { }, }, { + // @ts-expect-error role doesn't exist but removing cause problems role: 'forward', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Right' : 'Shift+Right', @@ -485,7 +503,7 @@ const mainWindow = () => { // When the user clicks a link that has target="_blank" (which is all external links) // open the default browser instead of a new electron window browserWindow.webContents.setWindowOpenHandler(({url}) => { - const denial = {action: 'deny'}; + const denial = {action: 'deny'} as const; // Make sure local urls stay in electron perimeter if (url.substr(0, 'file://'.length).toLowerCase() === 'file://') { diff --git a/desktop/start.js b/desktop/start.ts similarity index 88% rename from desktop/start.js rename to desktop/start.ts index 05a1b031350d..9efc7e04c9be 100644 --- a/desktop/start.js +++ b/desktop/start.ts @@ -1,7 +1,10 @@ #!/usr/bin/env node -const portfinder = require('portfinder'); +import {config} from 'dotenv'; +import portfinder from 'portfinder'; + const concurrently = require('concurrently'); -require('dotenv').config(); + +config(); const basePort = 8082; @@ -39,6 +42,8 @@ portfinder }, ]; + // concurrently lib problem + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return concurrently(processes, { inputStream: process.stdin, prefix: 'name', diff --git a/package.json b/package.json index e3c23d4538d3..20a9418eb74d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "build": "webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "build-staging": "webpack --config config/webpack/webpack.common.js --env envFile=.env.staging", "build-adhoc": "webpack --config config/webpack/webpack.common.js --env envFile=.env.adhoc", - "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.js", + "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.js", diff --git a/src/types/modules/electron.d.ts b/src/types/modules/electron.d.ts index 09e33f29ba38..7b2ecaa3866d 100644 --- a/src/types/modules/electron.d.ts +++ b/src/types/modules/electron.d.ts @@ -1,11 +1,4 @@ -// TODO: Move this type to desktop/contextBridge.js once it is converted to TS -type ContextBridgeApi = { - send: (channel: string, data?: unknown) => void; - sendSync: (channel: string, data?: unknown) => unknown; - invoke: (channel: string, ...args: unknown) => Promise; - on: (channel: string, func: () => void) => void; - removeAllListeners: (channel: string) => void; -}; +import type ContextBridgeApi from '@desktop/contextBridge'; declare global { // eslint-disable-next-line @typescript-eslint/consistent-type-definitions From 75cd434c4c72e8cb77ed5d8872ae136750be894b Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:01 +0000 Subject: [PATCH 039/298] Add relevant oldDot messages we're missing in newDot to const --- src/CONST.ts | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d9e00f0a207e..c02d4e4bd4f5 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -561,27 +561,55 @@ const CONST = { SPLIT_REPORTID: '-2', ACTIONS: { LIMIT: 50, + // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMarkedReimbursedMessage in ReportActionItem.js TYPE: { + ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', ADDCOMMENT: 'ADDCOMMENT', - APPROVED: 'APPROVED', + APPROVED: 'APPROVED', // OldDot Action + CHANGEFIELD: 'CHANGEFIELD', // OldDot Action + CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action + CHANGETYPE: 'CHANGETYPE', // OldDot Action CHRONOSOOOLIST: 'CHRONOSOOOLIST', CLOSED: 'CLOSED', CREATED: 'CREATED', + DELEGATESUBMIT: 'DELEGATESUBMIT', // OldDot Action + DELETEDACCOUNT: 'DELETEDACCOUNT', // OldDot Action + DONATION: 'DONATION', // OldDot Action + EXPORTEDTOINTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action + EXPORTEDTOQUICKBOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action + FORWARDED: 'FORWARDED', // OldDot Action HOLD: 'HOLD', IOU: 'IOU', - MARKEDREIMBURSED: 'MARKEDREIMBURSED', + INTEGRATIONSMESSAGE: 'INTEGRATIONSMESSAGE', // OldDot Action + MANAGERATTACHRECEIPT: 'MANAGERATTACHRECEIPT', // OldDot Action + MANAGERDETACHRECEIPT: 'MANAGERDETACHRECEIPT', // OldDot Action + MARKEDREIMBURSED: 'MARKEDREIMBURSED', // OldDot Action + MARKREIMBURSEDFROMINTEGRATION: 'MARKREIMBURSEDFROMINTEGRATION', // OldDot Action MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', + OUTDATEDBANKACCOUNT: 'OUTDATEDBANKACCOUNT', // OldDot Action + REIMBURSEMENTACHBOUNCE: 'REIMBURSEMENTACHBOUNCE', // OldDot Action + REIMBURSEMENTACHCANCELLED: 'REIMBURSEMENTACHCANCELLED', // OldDot Action + REIMBURSEMENTACCOUNTCHANGED: 'REIMBURSEMENTACCOUNTCHANGED', // OldDot Action + REIMBURSEMENTDELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', + REIMBURSEMENTREQUESTED: 'REIMBURSEMENTREQUESTED', // OldDot Action + REIMBURSEMENTSETUP: 'REIMBURSEMENTSETUP', // OldDot Action RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', + SELECTEDFORRANDOMAUDIT: 'SELECTEDFORRANDOMAUDIT', // OldDot Action + SHARE: 'SHARE', // OldDot Action + STRIPEPAID: 'STRIPEPAID', // OldDot Action SUBMITTED: 'SUBMITTED', + TAKECONTROL: 'TAKECONTROL', // OldDot Action TASKCANCELLED: 'TASKCANCELLED', TASKCOMPLETED: 'TASKCOMPLETED', TASKEDITED: 'TASKEDITED', TASKREOPENED: 'TASKREOPENED', - ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', + UNAPPROVED: 'UNAPPROVED', // OldDot Action + UNHOLD: 'UNHOLD', + UNSHARE: 'UNSHARE', // OldDot Action POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', @@ -655,8 +683,6 @@ const CONST = { LEAVE_ROOM: 'LEAVEROOM', UPDATE_ROOM_DESCRIPTION: 'UPDATEROOMDESCRIPTION', }, - UNAPPROVED: 'UNAPPROVED', - UNHOLD: 'UNHOLD', }, THREAD_DISABLED: ['CREATED'], }, From 9c8833f407324b061f0b079a799bb25225997319 Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:18 +0000 Subject: [PATCH 040/298] Render oldDot messages from const --- src/pages/home/report/ReportActionItem.js | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b07421a66479..8859d0a7c060 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -465,8 +465,33 @@ function ReportActionItem(props) { } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else if ([ + CONST.REPORT.ACTIONS.TYPE.CHANGEFIELD, + CONST.REPORT.ACTIONS.TYPE.CHANGEPOLICY, + CONST.REPORT.ACTIONS.TYPE.CHANGETYPE, + CONST.REPORT.ACTIONS.TYPE.DELEGATESUBMIT, + CONST.REPORT.ACTIONS.TYPE.DELETEDACCOUNT, + CONST.REPORT.ACTIONS.TYPE.DONATION, + CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOINTEGRATION, + CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOQUICKBOOKS, + CONST.REPORT.ACTIONS.TYPE.FORWARDED, + CONST.REPORT.ACTIONS.TYPE.INTEGRATIONSMESSAGE, + CONST.REPORT.ACTIONS.TYPE.MANAGERATTACHRECEIPT, + CONST.REPORT.ACTIONS.TYPE.MANAGERDETACHRECEIPT, CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED, - CONST.REPORT.ACTIONS.TYPE.UNAPPROVED + CONST.REPORT.ACTIONS.TYPE.MARKREIMBURSEDFROMINTEGRATION, + CONST.REPORT.ACTIONS.TYPE.OUTDATEDBANKACCOUNT, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACHBOUNCE, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACHCANCELLED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTACCOUNTCHANGED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDELAYED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTREQUESTED, + CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTSETUP, + CONST.REPORT.ACTIONS.TYPE.SELECTEDFORRANDOMAUDIT, + CONST.REPORT.ACTIONS.TYPE.SHARE, + CONST.REPORT.ACTIONS.TYPE.STRIPEPAID, + CONST.REPORT.ACTIONS.TYPE.TAKECONTROL, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, + CONST.REPORT.ACTIONS.TYPE.UNSHARE ].includes(props.action.actionName)) { // This handles all historical actions from OldDot that we just want to display the message text children = ; From 53f4289ce51a6bd375e841b098043fd166fafd78 Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:59 +0000 Subject: [PATCH 041/298] Update CONST.ts --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index c02d4e4bd4f5..9e6cf4990188 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -565,7 +565,7 @@ const CONST = { TYPE: { ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', ADDCOMMENT: 'ADDCOMMENT', - APPROVED: 'APPROVED', // OldDot Action + APPROVED: 'APPROVED', CHANGEFIELD: 'CHANGEFIELD', // OldDot Action CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action CHANGETYPE: 'CHANGETYPE', // OldDot Action From a3115ba8ba75d23895af7eb1d4e320d3976641ae Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 3 Mar 2024 17:23:19 +0530 Subject: [PATCH 042/298] fix failing typechecks. Signed-off-by: Krishna Gupta --- src/libs/actions/Session/index.ts | 2 +- src/pages/LogInWithShortLivedAuthTokenPage.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 76f335a3bec0..5abe235ca029 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -855,7 +855,7 @@ function handleExitToNavigation(exitTo: Routes | HybridAppRoute) { InteractionManager.runAfterInteractions(() => { waitForUserSignIn().then(() => { Navigation.waitForProtectedRoutes().then(() => { - const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Routes); Navigation.navigate(url); }); }); diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 4e7372f10dc6..17cc971a075f 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -17,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PublicScreensParamList} from '@libs/Navigation/types'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Account} from '@src/types/onyx'; @@ -51,7 +52,7 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA if (exitTo) { Navigation.isNavigationReady().then(() => { - const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Routes); Navigation.navigate(url); }); } From 3ad7a4f59cd23558b7d70422ec5ba15f1f3a2a36 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 4 Mar 2024 07:42:44 +0530 Subject: [PATCH 043/298] fix route iou type for send. Signed-off-by: Krishna Gupta --- src/pages/iou/request/step/IOURequestStepParticipants.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 954983808c80..38af1af8302d 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -118,7 +118,14 @@ function IOURequestStepParticipants({ const goToNextStep = useCallback( (selectedIouType) => { const isSplit = selectedIouType === CONST.IOU.TYPE.SPLIT; - const nextStepIOUType = !isSplit && iouType !== CONST.IOU.TYPE.REQUEST ? CONST.IOU.TYPE.REQUEST : iouType; + let nextStepIOUType; + + if (isSplit && iouType !== CONST.IOU.TYPE.REQUEST) { + nextStepIOUType = CONST.IOU.TYPE.SPLIT; + } else { + nextStepIOUType = iouType === CONST.IOU.TYPE.SEND ? CONST.IOU.TYPE.SEND : CONST.IOU.TYPE.REQUEST; + } + IOU.setMoneyRequestTag(transactionID, ''); IOU.setMoneyRequestCategory(transactionID, ''); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID)); From e02b55a5f95fe10e4f22a28b16f20aaf75977c5f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 13:02:21 +0500 Subject: [PATCH 044/298] refactor: use default policy room chat types from CONST --- src/CONST.ts | 17 ++++++++++------- src/libs/ReportUtils.ts | 3 +-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bb167bb5d8d8..78c562ea2cde 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -37,11 +37,20 @@ const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInpu // describes if a shortcut key can cause navigation const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; +const chatTypes = { + POLICY_ANNOUNCE: 'policyAnnounce', + POLICY_ADMINS: 'policyAdmins', + DOMAIN_ALL: 'domainAll', + POLICY_ROOM: 'policyRoom', + POLICY_EXPENSE_CHAT: 'policyExpenseChat', +} as const; + // Explicit type annotation is required const cardActiveStates: number[] = [2, 3, 4, 7]; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', + DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, @@ -684,13 +693,7 @@ const CONST = { IOU: 'iou', TASK: 'task', }, - CHAT_TYPE: { - POLICY_ANNOUNCE: 'policyAnnounce', - POLICY_ADMINS: 'policyAdmins', - DOMAIN_ALL: 'domainAll', - POLICY_ROOM: 'policyRoom', - POLICY_EXPENSE_CHAT: 'policyExpenseChat', - }, + CHAT_TYPE: chatTypes, WORKSPACE_CHAT_ROOMS: { ANNOUNCE: '#announce', ADMINS: '#admins', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 37fee63780fb..93d2fd5da37d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -774,12 +774,11 @@ function isAnnounceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE; } -const chatTypes = [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL]; /** * Whether the provided report is a default room */ function isDefaultRoom(report: OnyxEntry): boolean { - return chatTypes.some((type) => type === getChatType(report)); + return CONST.DEFAULT_POLICY_ROOM_CHAT_TYPES.some((type) => type === getChatType(report)); } /** From 7cbca08a4bd4b93ec49f6277df41f476744a33e7 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 15:55:46 +0500 Subject: [PATCH 045/298] revert: bring back localize.translateLocal --- src/libs/PersonalDetailsUtils.ts | 2 +- src/libs/ReportUtils.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 97e2dc91492b..5a3de690d8f8 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -41,7 +41,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial, policies: OnyxCollection | undefined | EmptyObject, returnEmptyIfNotFound = false, policy: OnyxEntry | undefined = undefined): string { - const noPolicyFound = returnEmptyIfNotFound ? '' : CommonTranslationUtils.unavailableWorkspaceText(); + const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable'); if (isEmptyObject(report)) { return noPolicyFound; } if ((!allPolicies || Object.keys(allPolicies).length === 0) && !report?.policyName) { - return CommonTranslationUtils.unavailableWorkspaceText(); + return Localize.translateLocal('workspace.common.unavailable'); } const finalPolicy = policy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; @@ -1647,7 +1647,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f const longName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, formattedLogin, shouldFallbackToHidden); // If the user's personal details (first name) should be hidden, make sure we return "hidden" instead of the short name - if (shouldFallbackToHidden && longName === CommonTranslationUtils.hiddenText()) { + if (shouldFallbackToHidden && longName === Localize.translateLocal('common.hidden')) { return longName; } @@ -2508,13 +2508,13 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (parentReportAction?.message?.[0]?.isDeletedParentAction) { - return CommonTranslationUtils.deletedMessageText(); + return Localize.translateLocal('parentReportAction.deletedMessage'); } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); const parentReportActionMessage = (parentReportAction?.message?.[0]?.text ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { - return `[${CommonTranslationUtils.attachmentText()}]`; + return `[${Localize.translateLocal('common.attachment')}]`; } if ( parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || @@ -2530,7 +2530,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { - return CommonTranslationUtils.deletedTaskText(); + return Localize.translateLocal('parentReportAction.deletedTask'); } if (isChatRoom(report) || isTaskReport(report)) { @@ -2546,7 +2546,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } if (isArchivedRoom(report)) { - formattedName += ` (${CommonTranslationUtils.archivedText()})`; + formattedName += ` (${Localize.translateLocal('common.archived')})`; } if (formattedName) { From 4df1d3da5dd9f6cb7862c8c62b105688cbbe9274 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 16:51:30 +0500 Subject: [PATCH 046/298] feat: add cache for translated values in Localize --- src/libs/Localize/index.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 64d07897aa8a..9cadc352ca9a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -93,11 +93,42 @@ function translate(desiredLanguage: 'en' | 'es' | throw new Error(`${phraseKey} was not found in the default language`); } +/** + * Map to store translated values for each locale + * This is used to avoid translating the same phrase multiple times. + * + * The data is stored in the following format: + * + * { + * "name_en": "Name", + * "name_es": "Nombre", + * } + * + * Note: We are not storing any translated values for phrases with variables, + * as they have higher chance of being unique, so we'll end up wasting space + * in our cache. + */ +const translatedValues = new Map(); + /** * Uses the locale in this file updated by the Onyx subscriber. */ function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { - return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); + const preferredLocale = BaseLocaleListener.getPreferredLocale(); + const key = `${phrase}_${preferredLocale}`; + const isVariablesEmpty = variables.length === 0; + + // If the phrase is already translated and there are no variables, return the translated value + if (translatedValues.has(key) && isVariablesEmpty) { + return translatedValues.get(key) as string; + } + const translatedText = translate(preferredLocale, phrase, ...variables); + + // We don't want to store translated values for phrases with variables + if (isVariablesEmpty) { + translatedValues.set(key, translatedText); + } + return translatedText; } /** From 55eb5d2f765099561de1a8720e2cb1345ca013e3 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:13:32 +0500 Subject: [PATCH 047/298] refactor: delete common translation utils --- src/libs/CommonTranslationUtils.ts | 49 ------------------------------ 1 file changed, 49 deletions(-) delete mode 100644 src/libs/CommonTranslationUtils.ts diff --git a/src/libs/CommonTranslationUtils.ts b/src/libs/CommonTranslationUtils.ts deleted file mode 100644 index 80a06e51ce9f..000000000000 --- a/src/libs/CommonTranslationUtils.ts +++ /dev/null @@ -1,49 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as Localize from './Localize'; - -/** - * This file contains common translations that are used in multiple places in the app. - * This is done to avoid duplicate translations and to keep the translations consistent. - * This also allows us to not repeatedly translate the same string which may happen due - * to translations being done for eg, in a loop. - * - * This was identified as part of a performance audit. - * details: https://github.com/Expensify/App/issues/35234#issuecomment-1926911643 - */ - -let deletedTaskText = ''; -let deletedMessageText = ''; -let attachmentText = ''; -let archivedText = ''; -let hiddenText = ''; -let unavailableWorkspaceText = ''; - -function isTranslationAvailable() { - return deletedTaskText && deletedMessageText && attachmentText && archivedText && hiddenText && unavailableWorkspaceText; -} - -Onyx.connect({ - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - callback: (val) => { - if (!val && isTranslationAvailable()) { - return; - } - - deletedTaskText = Localize.translateLocal('parentReportAction.deletedTask'); - deletedMessageText = Localize.translateLocal('parentReportAction.deletedMessage'); - attachmentText = Localize.translateLocal('common.attachment'); - archivedText = Localize.translateLocal('common.archived'); - hiddenText = Localize.translateLocal('common.hidden'); - unavailableWorkspaceText = Localize.translateLocal('workspace.common.unavailable'); - }, -}); - -export default { - deletedTaskText: () => deletedTaskText, - deletedMessageText: () => deletedMessageText, - attachmentText: () => attachmentText, - archivedText: () => archivedText, - hiddenText: () => hiddenText, - unavailableWorkspaceText: () => unavailableWorkspaceText, -}; From e643960c7ef54a8e2451f052c926ab664be127a4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:43:24 +0500 Subject: [PATCH 048/298] refactor: avoid multiple cache lookups --- src/libs/Localize/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 9cadc352ca9a..ad5e93af24ee 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -118,9 +118,13 @@ function translateLocal(phrase: TKey, ...variable const key = `${phrase}_${preferredLocale}`; const isVariablesEmpty = variables.length === 0; - // If the phrase is already translated and there are no variables, return the translated value - if (translatedValues.has(key) && isVariablesEmpty) { - return translatedValues.get(key) as string; + // Directly access and assign the translated value from the cache, instead of + // going through map.has() and map.get() to avoid multiple lookups. + const valueFromCache = translatedValues.get(key); + + // If the phrase is already translated, return the translated value + if (valueFromCache) { + return valueFromCache; } const translatedText = translate(preferredLocale, phrase, ...variables); From f3d6f1f73255fe186eea218f6600365dbfe06e41 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 4 Mar 2024 17:43:39 +0500 Subject: [PATCH 049/298] fix: lint --- src/libs/PersonalDetailsUtils.ts | 1 - src/libs/ReportUtils.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index c5de7456629d..391cabef0790 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -5,7 +5,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; -import CommonTranslationUtils from './CommonTranslationUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as UserUtils from './UserUtils'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1c64deff881e..f7d49389e200 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -51,7 +51,6 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import * as CollectionUtils from './CollectionUtils'; -import CommonTranslationUtils from './CommonTranslationUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; From 46d1bf52143d4647dd74737e05cc819b044c7980 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 4 Mar 2024 14:25:29 +0100 Subject: [PATCH 050/298] Improve typing --- desktop/main.ts | 49 +++++++++++++++++++----------------------------- desktop/start.ts | 4 +--- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index 071ddb7c7a41..cbc12d9d2608 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,5 +1,5 @@ -import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItem, shell} from 'electron'; -import type {BrowserView, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; +import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell} from 'electron'; +import type {BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; import contextMenu from 'electron-context-menu'; import log from 'electron-log'; import type {ElectronLog} from 'electron-log'; @@ -62,22 +62,21 @@ function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => voi paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), }, - append: (defaultActions, parameters, browserWindow) => - [ - new MenuItem({ - // Only enable the menu item for Editable context which supports paste - visible: parameters.isEditable && parameters.editFlags.canPaste, - role: 'pasteAndMatchStyle', - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), - }), - new MenuItem({ - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), - visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, - accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, - click: () => pasteAsPlainText(browserWindow), - }), - ] as unknown as MenuItemConstructorOptions[], + append: (defaultActions, parameters, browserWindow) => [ + { + // Only enable the menu item for Editable context which supports paste + visible: parameters.isEditable && parameters.editFlags.canPaste, + role: 'pasteAndMatchStyle', + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + }, + { + label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, + accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, + click: () => pasteAsPlainText(browserWindow), + }, + ], }); } @@ -306,10 +305,6 @@ const mainWindow = (): Promise => { if (!__DEV__) { // Modify the origin and referer for requests sent to our API webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { - // @ts-expect-error need to confirm if it's used - details.requestHeaders.origin = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; - // @ts-expect-error need to confirm if it's used - details.requestHeaders.referer = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; callback({requestHeaders: details.requestHeaders}); }); } @@ -418,16 +413,13 @@ const mainWindow = (): Promise => { submenu: [ { id: 'back', - // @ts-expect-error role doesn't exist but removing cause problems - role: 'back', accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[', click: () => { browserWindow.webContents.goBack(); }, }, { - // @ts-expect-error role doesn't exist but removing cause problems - role: 'back', + label: 'backWithKeyShortcut', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Left' : 'Shift+Left', click: () => { @@ -436,16 +428,13 @@ const mainWindow = (): Promise => { }, { id: 'forward', - // @ts-expect-error role doesn't exist but removing cause problems - role: 'forward', accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]', click: () => { browserWindow.webContents.goForward(); }, }, { - // @ts-expect-error role doesn't exist but removing cause problems - role: 'forward', + label: 'forwardWithKeyShortcut', visible: false, accelerator: process.platform === 'darwin' ? 'Cmd+Right' : 'Shift+Right', click: () => { diff --git a/desktop/start.ts b/desktop/start.ts index 9efc7e04c9be..f92cd10dc5aa 100644 --- a/desktop/start.ts +++ b/desktop/start.ts @@ -42,8 +42,6 @@ portfinder }, ]; - // concurrently lib problem - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return concurrently(processes, { inputStream: process.stdin, prefix: 'name', @@ -53,6 +51,6 @@ portfinder }).then( () => process.exit(0), () => process.exit(1), - ); + ) as never; }) .catch(() => process.exit(1)); From 9f7258c4c6ec7a127be94b704de6d7b95cada235 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 4 Mar 2024 15:50:28 +0100 Subject: [PATCH 051/298] Re-run checks From ab1369805213034680390bd4d572b50047972612 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 5 Mar 2024 11:56:42 +0100 Subject: [PATCH 052/298] Update concurrently lib, minor code improvements --- desktop/contextBridge.ts | 4 +- desktop/start.ts | 17 +- package-lock.json | 621 +++++++++------------------------------ package.json | 2 +- 4 files changed, 145 insertions(+), 499 deletions(-) diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts index f2693259e51a..689c69de0cc8 100644 --- a/desktop/contextBridge.ts +++ b/desktop/contextBridge.ts @@ -16,9 +16,9 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ ELECTRON_EVENTS.REQUEST_VISIBILITY, ELECTRON_EVENTS.START_UPDATE, ELECTRON_EVENTS.LOCALE_UPDATED, -]; +] as const; -const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR]; +const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR] as const; const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`; diff --git a/desktop/start.ts b/desktop/start.ts index f92cd10dc5aa..17ec690507ed 100644 --- a/desktop/start.ts +++ b/desktop/start.ts @@ -1,10 +1,9 @@ #!/usr/bin/env node -import {config} from 'dotenv'; +import concurrently from 'concurrently'; +import {config as configDotenv} from 'dotenv'; import portfinder from 'portfinder'; -const concurrently = require('concurrently'); - -config(); +configDotenv(); const basePort = 8082; @@ -12,7 +11,7 @@ portfinder .getPortPromise({ port: basePort, }) - .then((port) => { + .then((port): Promise => { const devServer = `webpack-dev-server --config config/webpack/webpack.dev.js --port ${port} --env platform=desktop`; const buildMain = 'webpack watch --config config/webpack/webpack.desktop.js --config-name desktop-main --mode=development'; @@ -42,15 +41,17 @@ portfinder }, ]; - return concurrently(processes, { + const {result} = concurrently(processes, { inputStream: process.stdin, prefix: 'name', // Like Harry Potter and he-who-must-not-be-named, "neither can live while the other survives" killOthers: ['success', 'failure'], - }).then( + }); + + return result.then( () => process.exit(0), () => process.exit(1), - ) as never; + ); }) .catch(() => process.exit(1)); diff --git a/package-lock.json b/package-lock.json index 80d3d1c6e911..db601a4ac37b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -197,7 +197,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", - "concurrently": "^5.3.0", + "concurrently": "^8.2.2", "copy-webpack-plugin": "^6.4.1", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", @@ -2709,20 +2709,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@callstack/reassure-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@callstack/reassure-cli/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2762,42 +2748,6 @@ "node": ">=8" } }, - "node_modules/@callstack/reassure-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@callstack/reassure-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/@callstack/reassure-compare": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@callstack/reassure-compare/-/reassure-compare-0.6.0.tgz", @@ -26041,75 +25991,16 @@ } }, "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "license": "MIT", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/clone": { @@ -26457,40 +26348,115 @@ } }, "node_modules/concurrently": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz", - "integrity": "sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, - "license": "MIT", "dependencies": { - "chalk": "^2.4.2", - "date-fns": "^2.0.1", - "lodash": "^4.17.15", - "read-pkg": "^4.0.1", - "rxjs": "^6.5.2", - "spawn-command": "^0.0.2-1", - "supports-color": "^6.1.0", + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", "tree-kill": "^1.2.2", - "yargs": "^13.3.0" + "yargs": "^17.7.2" }, "bin": { - "concurrently": "bin/concurrently.js" + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=6.0.0" + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/concurrently/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/config-file-ts": { @@ -28722,20 +28688,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/electron-builder/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-builder/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -28794,42 +28746,6 @@ "node": ">=8" } }, - "node_modules/electron-builder/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/electron-builder/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-builder/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-publish": { "version": "24.5.0", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.5.0.tgz", @@ -35634,20 +35550,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-cli/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -35687,41 +35589,6 @@ "node": ">=8" } }, - "node_modules/jest-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "29.4.1", "license": "MIT", @@ -39736,16 +39603,6 @@ "tslib": "2" } }, - "node_modules/memfs/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -40462,19 +40319,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, - "node_modules/metro/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/metro/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -40600,39 +40444,6 @@ } } }, - "node_modules/metro/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/metro/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/microevent.ts": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", @@ -44871,19 +44682,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/react-native/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/react-native/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -44975,39 +44773,6 @@ "async-limiter": "~1.0.0" } }, - "node_modules/react-native/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/react-native/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/react-native/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/react-pdf": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.3.3.tgz", @@ -46791,25 +46556,14 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, "node_modules/safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -48008,11 +47762,10 @@ } }, "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true, - "license": "MIT" + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true }, "node_modules/spdx-correct": { "version": "3.1.1", @@ -52692,22 +52445,20 @@ } }, "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "license": "MIT", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -52720,126 +52471,20 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/yargs/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" } }, "node_modules/yauzl": { diff --git a/package.json b/package.json index 92afaafc85e7..7f5847d50608 100644 --- a/package.json +++ b/package.json @@ -246,7 +246,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^3.0.0", - "concurrently": "^5.3.0", + "concurrently": "^8.2.2", "copy-webpack-plugin": "^6.4.1", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", From bb932cbed6602f6c6c527e504d0a67e0a429e761 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 5 Mar 2024 12:08:19 +0100 Subject: [PATCH 053/298] Update files extension in the README --- desktop/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/README.md b/desktop/README.md index 4493196b5ed4..2fc074153d87 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -37,9 +37,9 @@ The desktop app is organized in three pieces: - This is the webpack-bundled version of our react-native-web app (except using `index.desktop.js` files instead of `index.website.js`, where applicable) - This is _very_ similar to our web app, and code in this process should assume it will be run in the context of a browser (no access to `require`, Electron, or Node.js APis) 3. The context bridge - - Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.js + - Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.ts - The context bridge enables communication between the main and renderer processes. For example, if the renderer process needs to make use of a Node.js or Electron API it must: - 1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.js + 1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.ts 2. Add that event to the whitelist defined in the context bridge 3. Set up a handler for the event in the main process that can respond to the renderer process back through the bridge, if necessary. From 018516241e2691db789e7e7ff67f171950c2dcb6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 5 Mar 2024 19:37:54 +0700 Subject: [PATCH 054/298] fix: image shrinks while loading in carousel --- src/components/Lightbox/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index a7ed6946fb28..1a69e1f8958b 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -137,7 +137,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { - return DEFAULT_IMAGE_DIMENSION; + return undefined; } const {minScale} = getCanvasFitScale({canvasSize, contentSize}); @@ -217,7 +217,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan > setFallbackImageLoaded(true)} From 175556071115a7732be109d64241adf732851cf7 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Mar 2024 14:20:49 +0500 Subject: [PATCH 055/298] refactor: use dedicated map for each locale --- src/libs/Localize/index.ts | 39 +++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ad5e93af24ee..f64d39d5e24a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -12,6 +12,7 @@ import type {Locale} from '@src/types/onyx'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import type BaseLocale from './LocaleListener/types'; // Current user mail is needed for handling missing translations let userEmail = ''; @@ -94,33 +95,55 @@ function translate(desiredLanguage: 'en' | 'es' | } /** - * Map to store translated values for each locale + * Map to store translated values for each locale. * This is used to avoid translating the same phrase multiple times. * * The data is stored in the following format: * * { - * "name_en": "Name", - * "name_es": "Nombre", + * "name": "Name", * } * * Note: We are not storing any translated values for phrases with variables, * as they have higher chance of being unique, so we'll end up wasting space * in our cache. */ -const translatedValues = new Map(); +const TRANSLATED_VALUES_EN = new Map(); +const TRANSLATED_VALUES_ES = new Map(); +const TRANSLATED_VALUES_ES_ES = new Map(); +const TRANSLATED_VALUES_ES_ONFIDO = new Map(); + +/** + * Returns the map for the given locale. + */ +function getTranslatedValuesMap(locale: BaseLocale) { + switch (locale) { + case CONST.LOCALES.ES_ES: + return TRANSLATED_VALUES_ES_ES; + case CONST.LOCALES.ES_ES_ONFIDO: + return TRANSLATED_VALUES_ES_ONFIDO; + case CONST.LOCALES.ES: + return TRANSLATED_VALUES_ES; + case CONST.LOCALES.DEFAULT: + default: + return TRANSLATED_VALUES_EN; + } +} /** * Uses the locale in this file updated by the Onyx subscriber. */ function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { const preferredLocale = BaseLocaleListener.getPreferredLocale(); - const key = `${phrase}_${preferredLocale}`; + const key = `${phrase}`; const isVariablesEmpty = variables.length === 0; + // Get the map for the preferred locale + const map = getTranslatedValuesMap(preferredLocale); + // Directly access and assign the translated value from the cache, instead of // going through map.has() and map.get() to avoid multiple lookups. - const valueFromCache = translatedValues.get(key); + const valueFromCache = map.get(key); // If the phrase is already translated, return the translated value if (valueFromCache) { @@ -130,7 +153,9 @@ function translateLocal(phrase: TKey, ...variable // We don't want to store translated values for phrases with variables if (isVariablesEmpty) { - translatedValues.set(key, translatedText); + // We set the translated value in the cache in the next iteration + // of the event loop to make this operation asynchronous. + setImmediate(() => map.set(key, translatedText)); } return translatedText; } From 99dd34cfdf973681a713100ea345ced47911e206 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 6 Mar 2024 14:32:23 +0500 Subject: [PATCH 056/298] refactor: remove setImmediate --- src/libs/Localize/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index f64d39d5e24a..72bb4dd89bb1 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -153,9 +153,8 @@ function translateLocal(phrase: TKey, ...variable // We don't want to store translated values for phrases with variables if (isVariablesEmpty) { - // We set the translated value in the cache in the next iteration - // of the event loop to make this operation asynchronous. - setImmediate(() => map.set(key, translatedText)); + // We set the translated value in the cache + map.set(key, translatedText); } return translatedText; } From f86fbdb470d32bfd32b95675d807661cbc914faa Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 6 Mar 2024 11:35:06 +0100 Subject: [PATCH 057/298] Add return type and change comment --- src/libs/actions/ReimbursementAccount/index.ts | 2 +- src/libs/actions/ReimbursementAccount/navigation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index dd1c784d2218..9edf1d9dbcd9 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -14,7 +14,7 @@ export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidation * - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber * - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid */ -function setBankAccountSubStep(subStep: BankAccountSubStep | null) { +function setBankAccountSubStep(subStep: BankAccountSubStep | null): Promise { return Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}}); } diff --git a/src/libs/actions/ReimbursementAccount/navigation.ts b/src/libs/actions/ReimbursementAccount/navigation.ts index 2c3eb7cf0384..49cf17fcc5bf 100644 --- a/src/libs/actions/ReimbursementAccount/navigation.ts +++ b/src/libs/actions/ReimbursementAccount/navigation.ts @@ -15,7 +15,7 @@ function goToWithdrawalAccountSetupStep(stepID: BankAccountStep) { * Navigate to the correct bank account route based on the bank account state and type * * @param policyID - The policy ID associated with the bank account. - * @param [backTo=''] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. + * @param [backTo] - An optional return path. If provided, it will be URL-encoded and appended to the resulting URL. */ function navigateToBankAccountRoute(policyID: string, backTo?: string) { Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID, backTo)); From 5770a2f2a4cfe66a91f5d2ec39e7d7a09099d053 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 7 Mar 2024 15:10:22 +0100 Subject: [PATCH 058/298] Remove unused functions --- src/libs/actions/BankAccounts.ts | 1 - .../deleteFromBankAccountList.ts | 16 -------- .../actions/ReimbursementAccount/errors.ts | 21 +--------- .../actions/ReimbursementAccount/index.ts | 18 +------- .../actions/ReimbursementAccount/store.ts | 41 +------------------ 5 files changed, 4 insertions(+), 93 deletions(-) delete mode 100644 src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 0f4e1aed36a7..878e1a443181 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -29,7 +29,6 @@ export { resetReimbursementAccount, resetFreePlanBankAccount, hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, setBankAccountSubStep, updateReimbursementAccountDraft, requestResetFreePlanBankAccount, diff --git a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts b/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts deleted file mode 100644 index d9a2dd130d62..000000000000 --- a/src/libs/actions/ReimbursementAccount/deleteFromBankAccountList.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import * as store from './store'; - -/** - * Deletes a bank account from bankAccountList - */ -function deleteFromBankAccountList(bankAccountID: number) { - // We should delete the bankAccountID key from the bankAccountList object before setting it in Onyx - const bankAccountList = store.getBankAccountList(); - delete bankAccountList?.[bankAccountID]; - - Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); -} - -export default deleteFromBankAccountList; diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index 05c375364329..2ab99c847291 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -1,17 +1,7 @@ import Onyx from 'react-native-onyx'; -import * as ErrorUtils from '@libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; -/** - * Set the current fields with errors. - */ -function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { - // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); - Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); -} - /** * Set the current fields with errors. @@ -32,13 +22,4 @@ function resetReimbursementAccount() { }); } -/** - * Set the current error message. - */ -function showBankAccountFormValidationError(error: string | null) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, { - errors: ErrorUtils.getMicroSecondOnyxError(error), - }); -} - -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, showBankAccountFormValidationError, resetReimbursementAccount}; +export {setBankAccountFormValidationErrors, resetReimbursementAccount}; diff --git a/src/libs/actions/ReimbursementAccount/index.ts b/src/libs/actions/ReimbursementAccount/index.ts index 9edf1d9dbcd9..57d785c5d1c9 100644 --- a/src/libs/actions/ReimbursementAccount/index.ts +++ b/src/libs/actions/ReimbursementAccount/index.ts @@ -2,11 +2,10 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form'; import type {BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; -import deleteFromBankAccountList from './deleteFromBankAccountList'; import resetFreePlanBankAccount from './resetFreePlanBankAccount'; export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation'; -export {setBankAccountFormValidationErrors, setPersonalBankAccountFormValidationErrorFields, resetReimbursementAccount, showBankAccountFormValidationError} from './errors'; +export {setBankAccountFormValidationErrors, resetReimbursementAccount} from './errors'; /** * Set the current sub step in first step of adding withdrawal bank account: @@ -22,10 +21,6 @@ function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', errors: null}); } -function setWorkspaceIDForReimbursementAccount(workspaceID: string | null) { - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, workspaceID); -} - function updateReimbursementAccountDraft(bankAccountData: Partial) { Onyx.merge(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, bankAccountData); Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {draftStep: undefined}); @@ -45,13 +40,4 @@ function cancelResetFreePlanBankAccount() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldShowResetModal: false}); } -export { - resetFreePlanBankAccount, - setBankAccountSubStep, - hideBankAccountErrors, - setWorkspaceIDForReimbursementAccount, - updateReimbursementAccountDraft, - requestResetFreePlanBankAccount, - cancelResetFreePlanBankAccount, - deleteFromBankAccountList, -}; +export {resetFreePlanBankAccount, setBankAccountSubStep, hideBankAccountErrors, updateReimbursementAccountDraft, requestResetFreePlanBankAccount, cancelResetFreePlanBankAccount}; diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts index 30005b6fdc04..6e14cf72a569 100644 --- a/src/libs/actions/ReimbursementAccount/store.ts +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -3,25 +3,6 @@ import Onyx from 'react-native-onyx'; import BankAccount from '@libs/models/BankAccount'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {ACHData} from '@src/types/onyx/ReimbursementAccount'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; - -/** Reimbursement account actively being set up */ -let reimbursementAccountInSetup: ACHData | EmptyObject = {}; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - callback: (val) => { - reimbursementAccountInSetup = val?.achData ?? {}; - }, -}); - -let reimbursementAccountWorkspaceID: OnyxEntry = null; -Onyx.connect({ - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID, - callback: (val) => { - reimbursementAccountWorkspaceID = val; - }, -}); let bankAccountList: OnyxEntry = null; Onyx.connect({ @@ -31,18 +12,6 @@ Onyx.connect({ }, }); -let credentials: OnyxEntry = null; -Onyx.connect({ - key: ONYXKEYS.CREDENTIALS, - callback: (val) => { - credentials = val; - }, -}); - -function getReimbursementAccountInSetup() { - return reimbursementAccountInSetup; -} - function getBankAccountList() { return bankAccountList; } @@ -58,12 +27,4 @@ function hasCreditBankAccount() { }); } -function getCredentials() { - return credentials; -} - -function getReimbursementAccountWorkspaceID() { - return reimbursementAccountWorkspaceID; -} - -export {getReimbursementAccountInSetup, getBankAccountList, getCredentials, getReimbursementAccountWorkspaceID, hasCreditBankAccount}; +export {getBankAccountList, hasCreditBankAccount}; From c14fbf85cbf91608e7b7f4ed7b2a6426a3bc57ed Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 7 Mar 2024 15:14:11 +0100 Subject: [PATCH 059/298] feat: PolicyNewDistanceRatePage --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../CreatePolicyDistanceRateParams.ts | 9 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/libs/actions/Policy.ts | 51 +++++++++++ .../distanceRates/PolicyDistanceRatesPage.tsx | 4 +- .../PolicyNewDistanceRatePage.tsx | 86 +++++++++++++++++++ .../form/PolicyCreateDistanceRateForm.ts | 18 ++++ src/types/form/index.ts | 1 + 15 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/libs/API/parameters/CreatePolicyDistanceRateParams.ts create mode 100644 src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx create mode 100644 src/types/form/PolicyCreateDistanceRateForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f6b5c635e4ae..ce2a2be262e9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -331,6 +331,8 @@ const ONYXKEYS = { WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft', WORKSPACE_RATE_AND_UNIT_FORM: 'workspaceRateAndUnitForm', WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft', + POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', + POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', CLOSE_ACCOUNT_FORM: 'closeAccount', CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft', PROFILE_SETTINGS_FORM: 'profileSettingsForm', @@ -443,6 +445,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fee69b9d785a..61dc44ac0432 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -554,6 +554,10 @@ const ROUTES = { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, }, + WORKSPACE_CREATE_DISTANCE_RATE: { + route: 'workspace/:policyID/distance-rates/new', + getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/new` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index caedab241349..4d572a65e24f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -226,6 +226,7 @@ const SCREENS = { CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', DISTANCE_RATES: 'Distance_Rates', + CREATE_DISTANCE_RATE: 'Create_Distance_Rate', }, EDIT_REQUEST: { diff --git a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts new file mode 100644 index 000000000000..ea06408d4402 --- /dev/null +++ b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts @@ -0,0 +1,9 @@ +import type {Rate} from '@src/types/onyx/Policy'; + +type CreatePolicyDistanceRateParams = { + policyID: string; + customUnitID: string; + customUnitRate: Rate; +}; + +export default CreatePolicyDistanceRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 7c4d592fe48d..34cc473b5d50 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -156,3 +156,4 @@ export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './Se export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; +export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4cec18ae646..dce2592eae08 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -156,6 +156,7 @@ const WRITE_COMMANDS = { CANCEL_PAYMENT: 'CancelPayment', ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount', SWITCH_TO_OLD_DOT: 'SwitchToOldDot', + CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', } as const; type WriteCommand = ValueOf; @@ -310,6 +311,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; + [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 545641957c9a..c6cf3d515336 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -251,6 +251,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 7959999ee813..1d2cad900ff1 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -7,6 +7,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 927b5b509277..a99d3e9cb695 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -283,6 +283,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route, }, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + path: ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.route, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f9b70b4b7ac8..73debd9d3897 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -207,6 +207,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b94ae68b04ac..2f9005633b48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -10,6 +10,7 @@ import type {ValueOf} from 'type-fest'; import * as API from '@libs/API'; import type { AddMembersToWorkspaceParams, + CreatePolicyDistanceRateParams, CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, @@ -2514,6 +2515,54 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } +function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + ...customUnitRate, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), + pendingAction: null, + }, + }, + }, + }, + }, + }, + ]; + + const params: CreatePolicyDistanceRateParams = { + policyID, + customUnitID, + customUnitRate, + }; + + API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, failureData}); +} + export { removeMembers, updateWorkspaceMembersRole, @@ -2565,4 +2614,6 @@ export { setWorkspaceRequiresCategory, clearCategoryErrors, openPolicyDistanceRatesPage, + generateCustomUnitID, + createPolicyDistanceRate, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 08cd3dffe709..8c81ff7765b5 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -19,6 +19,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; @@ -26,6 +27,7 @@ import {openPolicyDistanceRatesPage} from '@userActions/Policy'; import ButtonWithDropdownMenu from '@src/components/ButtonWithDropdownMenu'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type Policy from '@src/types/onyx/Policy'; import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; @@ -91,7 +93,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); const addRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); + Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); }; const openSettings = () => { diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx new file mode 100644 index 000000000000..8e794e51674d --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -0,0 +1,86 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import {createPolicyDistanceRate, generateCustomUnitID} from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm'; +import type {Rate} from '@src/types/onyx/Policy'; +import type Policy from '@src/types/onyx/Policy'; + +type PolicyNewDistanceRatePageOnyxProps = { + policy: OnyxEntry; +}; + +type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScreenProps; + +function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; + const customUnits = policy?.customUnits ?? {}; + const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; + const customUnitRateID = generateCustomUnitID(); + + const submit = (values: FormOnyxValues) => { + const newRate: Rate = { + currency, + name: CONST.CUSTOM_UNITS.DEFAULT_RATE, + rate: Number(values.rate), + customUnitRateID, + }; + + createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + Navigation.goBack(); + }; + + return ( + + + + + + + + + + + ); +} + +PolicyNewDistanceRatePage.displayName = 'CreateDistanceRatePage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyNewDistanceRatePage); diff --git a/src/types/form/PolicyCreateDistanceRateForm.ts b/src/types/form/PolicyCreateDistanceRateForm.ts new file mode 100644 index 000000000000..11bb9f4aa83c --- /dev/null +++ b/src/types/form/PolicyCreateDistanceRateForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + RATE: 'rate', +} as const; + +type InputID = ValueOf; + +type PolicyCreateDistanceRateForm = Form< + InputID, + { + [INPUT_IDS.RATE]: string; + } +>; + +export type {PolicyCreateDistanceRateForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 1ff8d0df2031..bf58c78d0b90 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -37,4 +37,5 @@ export type {WorkspaceRateAndUnitForm} from './WorkspaceRateAndUnitForm'; export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; +export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; export type {default as Form} from './Form'; From b8065489197501e2b65d40dd42ee882211dd4a57 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 7 Mar 2024 22:23:01 +0800 Subject: [PATCH 060/298] don't clear the optimistic personal detail if fail when inviting new user --- src/libs/actions/Policy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 38cc11756068..db62c68291a0 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -948,7 +948,6 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount // need to remove the members since that is handled by onClose of OfflineWithFeedback. value: failureMembersState, }, - ...newPersonalDetailsOnyxData.finallyData, ...membersChats.onyxFailureData, ...announceRoomMembers.onyxFailureData, ]; From b7cdc1b51d9ae04112c81c7c468de55dd29fe372 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:26:24 +0100 Subject: [PATCH 061/298] add initial new tax page --- src/CONST.ts | 4 + src/ONYXKEYS.ts | 2 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + src/languages/en.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + .../workspace/taxes/WorkspaceNewTaxPage.tsx | 96 +++++++++++++++++++ .../workspace/taxes/WorkspaceTaxesPage.tsx | 6 +- src/types/form/WorkspaceNewTaxForm.ts | 22 +++++ src/types/form/index.ts | 1 + src/types/onyx/Policy.ts | 2 +- 13 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx create mode 100644 src/types/form/WorkspaceNewTaxForm.ts diff --git a/src/CONST.ts b/src/CONST.ts index 6861fe174ffe..b981d10e5be3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4021,6 +4021,10 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', }, + + TAX_RATES: { + NAME_MAX_LENGTH: 50, + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 188ab5646d30..05b1e1bec1f9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -404,6 +404,7 @@ const ONYXKEYS = { EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', + WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', }, } as const; @@ -449,6 +450,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9704bf047315..f0f6fed658f9 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -558,6 +558,10 @@ const ROUTES = { route: 'workspace/:policyID/taxes', getRoute: (policyID: string) => `workspace/${policyID}/taxes` as const, }, + WORKSPACE_TAXES_NEW: { + route: 'workspace/:policyID/taxes/new', + getRoute: (policyID: string) => `workspace/${policyID}/taxes/new` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 27909319b724..d48cc9c19b83 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -216,6 +216,7 @@ const SCREENS = { CATEGORIES: 'Workspace_Categories', TAGS: 'Workspace_Tags', TAXES: 'Workspace_Taxes', + TAXES_NEW: 'Workspace_Taxes_New', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver', diff --git a/src/languages/en.ts b/src/languages/en.ts index 715640bbb200..56c983ace282 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1787,6 +1787,8 @@ export default { }, taxes: { subtitle: 'Add tax names, rates, and set defaults.', + value: 'Value', + name: 'Name', }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index cee9e31cedea..001891805bb2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -262,6 +262,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_NEW]: () => require('../../../pages/workspace/taxes/WorkspaceNewTaxPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 0cae5e34e958..76f605c648b8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -309,6 +309,9 @@ const config: LinkingOptions['config'] = { [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: { path: ROUTES.SETTINGS_EXIT_SURVEY_CONFIRM.route, }, + [SCREENS.WORKSPACE.TAXES_NEW]: { + path: ROUTES.WORKSPACE_TAXES_NEW.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 5c64dd62fd43..fcd69c49a135 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -225,6 +225,9 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: { backTo: Routes; }; + [SCREENS.WORKSPACE.TAXES_NEW]: { + policyID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx new file mode 100644 index 000000000000..a23b8d3061de --- /dev/null +++ b/src/pages/workspace/taxes/WorkspaceNewTaxPage.tsx @@ -0,0 +1,96 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import AmountPicker from '@components/AmountPicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextPicker from '@components/TextPicker'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {createWorkspaceTax} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceNewTaxForm'; +import type {TaxRate} from '@src/types/onyx'; + +type WorkspaceNewTaxPageProps = StackScreenProps; + +function WorkspaceNewTaxPage({ + route: { + params: {policyID}, + }, +}: WorkspaceNewTaxPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const validate = useCallback((values: FormOnyxValues): Partial> => { + const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.VALUE, INPUT_IDS.NAME]); + + const value = Number(values[INPUT_IDS.VALUE]); + if (value > 100 || value < 0) { + errors[INPUT_IDS.VALUE] = 'workspace.taxes.errors.value.percentageRange'; + } + + return errors; + }, []); + + const submitForm = useCallback( + (values: FormOnyxValues) => { + // TODO: Add proper code generation + const taxRate = { + ...values, + code: `tax_${Date.now()}`, + } satisfies TaxRate; + createWorkspaceTax({policyID, taxRate}); + Navigation.goBack(); + }, + [policyID], + ); + + return ( + + + + + + + + + + ); +} + +WorkspaceNewTaxPage.displayName = 'WorkspaceNewTaxPage'; + +export default WorkspaceNewTaxPage; diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index b19a69adebe2..0f4340b97eb8 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -15,12 +15,14 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import type SCREENS from '@src/SCREENS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; type WorkspaceTaxesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -83,7 +85,7 @@ function WorkspaceTaxesPage({policy}: WorkspaceTaxesPageProps) {