From 6b6afd8326c14044681838a44c5e4b929ef77e26 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 15 Nov 2023 15:06:15 +0100 Subject: [PATCH 001/146] [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 40cd54db77e7a89c46163a4ba0074fe5c7c114dc Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 14 Feb 2024 15:39:01 +0100 Subject: [PATCH 002/146] 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 003/146] 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 004/146] 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 005/146] 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 0c056bed123c0eff882e120b692b04428fa5cf40 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 12:59:10 +0100 Subject: [PATCH 006/146] ref: move SignInOrAvatarWithOptionalStatus to TS --- ...js => SignInOrAvatarWithOptionalStatus.tsx} | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) rename src/pages/home/sidebar/{SignInOrAvatarWithOptionalStatus.js => SignInOrAvatarWithOptionalStatus.tsx} (61%) diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx similarity index 61% rename from src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js rename to src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx index 0ea6195cd713..2a9356d78232 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx @@ -1,6 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import * as Session from '@userActions/Session'; @@ -8,18 +5,13 @@ import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, +type SignInOrAvatarWithOptionalStatusProps = { + isCreateMenuOpen?: boolean; }; -const defaultProps = { - isCreateMenuOpen: false, -}; - -function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { +function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen = false}: SignInOrAvatarWithOptionalStatusProps) { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const emojiStatus = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); + const emojiStatus = currentUserPersonalDetails.status?.emojiCode ?? ''; if (Session.isAnonymousUser()) { return ; @@ -35,7 +27,5 @@ function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { return ; } -SignInOrAvatarWithOptionalStatus.propTypes = propTypes; -SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps; SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus'; export default SignInOrAvatarWithOptionalStatus; From 6bd5633ec4631b4a1ef76b03c5a7e576d7d15ca8 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:04:18 +0100 Subject: [PATCH 007/146] ref: move SignInButton to TS --- src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} | 1 - 1 file changed, 1 deletion(-) rename src/pages/home/sidebar/{SignInButton.js => SignInButton.tsx} (95%) diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.tsx similarity index 95% rename from src/pages/home/sidebar/SignInButton.js rename to src/pages/home/sidebar/SignInButton.tsx index f89deb6f65b2..1dc65bfd5050 100644 --- a/src/pages/home/sidebar/SignInButton.js +++ b/src/pages/home/sidebar/SignInButton.tsx @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ import React from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; From 5ed0bc2c9fc59b181d3067e88b7c10c36e0ed497 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 13:05:47 +0100 Subject: [PATCH 008/146] ref: removed SidebarNavigationContext --- src/pages/home/sidebar/SidebarNavigationContext.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/pages/home/sidebar/SidebarNavigationContext.js diff --git a/src/pages/home/sidebar/SidebarNavigationContext.js b/src/pages/home/sidebar/SidebarNavigationContext.js deleted file mode 100644 index e69de29bb2d1..000000000000 From 5443274f38ae358c00e9013f8100f7195ebe77e5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:19:53 +0100 Subject: [PATCH 009/146] ref: move SidebarLinksData to TS --- src/libs/ReportUtils.ts | 2 +- src/libs/SidebarUtils.ts | 14 +- ...debarLinksData.js => SidebarLinksData.tsx} | 296 +++++++----------- 3 files changed, 127 insertions(+), 185 deletions(-) rename src/pages/home/sidebar/{SidebarLinksData.js => SidebarLinksData.tsx} (53%) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b5da21c0f67e..40581f46e76a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3824,7 +3824,7 @@ function shouldReportBeInOptionList({ report: OnyxEntry; currentReportId: string; isInGSDMode: boolean; - betas: Beta[]; + betas: OnyxEntry; policies: OnyxCollection; excludeEmptyChats: boolean; doesReportHaveViolations: boolean; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5fe646c5ad13..7b757d7fc5b8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -79,10 +79,10 @@ let hasInitialReportActions = false; */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, - betas: Beta[], - policies: Record, - priorityMode: ValueOf, + allReports: OnyxEntry>, + betas: OnyxEntry, + policies: OnyxEntry>, + priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', @@ -110,7 +110,7 @@ function getOrderedReportIDs( const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { @@ -118,7 +118,7 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -126,7 +126,7 @@ function getOrderedReportIDs( betas, policies, excludeEmptyChats: true, - doesReportHaveViolations, + doesReportHaveViolations: !!doesReportHaveViolations, }); }); diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.tsx similarity index 53% rename from src/pages/home/sidebar/SidebarLinksData.js rename to src/pages/home/sidebar/SidebarLinksData.tsx index 3bd538e8beab..b128fcaf33ec 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -1,154 +1,114 @@ +import {useIsFocused} from '@react-navigation/native'; import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; -import withCurrentReportID from '@components/withCurrentReportID'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withNavigationFocus from '@components/withNavigationFocus'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useCurrentReportID from '@hooks/useCurrentReportID'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import SidebarLinks, {basePropTypes} from './SidebarLinks'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; +import SidebarLinks from './SidebarLinks'; -const propTypes = { - ...basePropTypes, - - /* Onyx Props */ - /** List of reports */ - chatReports: PropTypes.objectOf(reportPropTypes), - - /** All report actions for all reports */ - - /** Object of report actions for this report */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), - - /** Whether the reports are loading. When false it means they are ready to be used. */ - isLoadingApp: PropTypes.bool, - - /** The chat priority mode */ - priorityMode: PropTypes.string, - - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - network: networkPropTypes.isRequired, - - /** The policies which the user has access to */ - // eslint-disable-next-line react/forbid-prop-types - policies: PropTypes.object, - - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), - /** All of the transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), +type SidebarLinksDataOnyxProps = { + chatReports: OnyxEntry< + Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' + > & {isUnreadWithMention: boolean} + >; + isLoadingApp: OnyxEntry; + priorityMode: OnyxEntry>; + betas: OnyxEntry; + allReportActions: OnyxEntry>>; + policies: OnyxEntry>; + policyMembers: OnyxCollection; + transactionViolations: OnyxCollection; }; -const defaultProps = { - chatReports: {}, - allReportActions: {}, - isLoadingApp: true, - priorityMode: CONST.PRIORITY_MODE.DEFAULT, - betas: [], - policies: {}, - policyMembers: {}, - session: { - accountID: '', - }, - transactionViolations: {}, +type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { + onLinkClick: (reportID: number) => void; + insets: EdgeInsets; }; function SidebarLinksData({ - isFocused, allReportActions, betas, chatReports, - currentReportID, insets, - isLoadingApp, + isLoadingApp = true, onLinkClick, policies, - priorityMode, - network, + priorityMode = CONST.PRIORITY_MODE.DEFAULT, policyMembers, - session: {accountID}, + // session: {accountID}, transactionViolations, -}) { +}: SidebarLinksDataProps) { + const {currentReportID} = useCurrentReportID() ?? {}; + const {accountID} = useCurrentUserPersonalDetails(); + const network = useNetwork(); + const isFocused = useIsFocused(); const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); - const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); + useEffect(() => Policy.openWorkspace(activeWorkspaceID ?? '', policyMemberAccountIDs), [activeWorkspaceID]); - const reportIDsRef = useRef(null); + const reportIDsRef = useRef(null); const isLoading = isLoadingApp; const optionListItems = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -161,7 +121,7 @@ function SidebarLinksData({ // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { + if (!isLoading || !reportIDsRef.current || !!network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; @@ -173,14 +133,14 @@ function SidebarLinksData({ // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportID && !_.contains(optionListItems, currentReportID)) { + if (currentReportID && !optionListItems?.includes(currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportID, - chatReports, + chatReports as OnyxEntry>, betas, - policies, + policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxEntry>, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -191,7 +151,7 @@ function SidebarLinksData({ const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; - const isActiveReport = useCallback((reportID) => currentReportIDRef.current === reportID, []); + const isActiveReport = useCallback((reportID: string) => currentReportIDRef.current === reportID, []); return ( +const chatReportSelector = (report: OnyxEntry) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, @@ -233,7 +190,7 @@ const chatReportSelector = (report) => isHidden: report.isHidden, notificationPreference: report.notificationPreference, errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, }, lastMessageText: report.lastMessageText, lastVisibleActionCreated: report.lastVisibleActionCreated, @@ -264,78 +221,63 @@ const chatReportSelector = (report) => isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }; -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => +const reportActionsSelector = (reportActions: OnyxEntry) => reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; return { reportActionID, - parentReportActionID, actionName, errors, message: [ { moderationDecision: {decision}, }, - ], + ] as Message[], }; }); -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => +const policySelector = (policy: OnyxEntry) => policy && { type: policy.type, name: policy.name, avatar: policy.avatar, }; -export default compose( - withCurrentReportID, - withCurrentUserPersonalDetails, - withNavigationFocus, - withNetwork(), - withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, - }), -)(SidebarLinksData); +export default withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, +})(SidebarLinksData); From bc5cd4df25c3dbd9e8a07e14a8abc44d61ec32b0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:46:36 +0100 Subject: [PATCH 010/146] ref: move SidebarLinks to TS --- src/components/LHNOptionsList/types.ts | 2 +- .../{SidebarLinks.js => SidebarLinks.tsx} | 64 +++++++++---------- src/pages/home/sidebar/SidebarLinksData.tsx | 4 +- 3 files changed, 32 insertions(+), 38 deletions(-) rename src/pages/home/sidebar/{SidebarLinks.js => SidebarLinks.tsx} (78%) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..f3d6bde9d41c 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -45,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[]; + data: string[] | null; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.tsx similarity index 78% rename from src/pages/home/sidebar/SidebarLinks.js rename to src/pages/home/sidebar/SidebarLinks.tsx index 9431bae68d8a..52165c148727 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -1,10 +1,9 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {EdgeInsets} from 'react-native-safe-area-context'; +import type {ValueOf} from 'type-fest'; import Breadcrumbs from '@components/Breadcrumbs'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -16,37 +15,33 @@ import KeyboardShortcut from '@libs/KeyboardShortcut'; import Navigation from '@libs/Navigation/Navigation'; import onyxSubscribe from '@libs/onyxSubscribe'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import safeAreaInsetPropTypes from '@pages/safeAreaInsetPropTypes'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Modal, Policy, Report} from '@src/types/onyx'; -const basePropTypes = { - /** Toggles the navigation menu open and closed */ - onLinkClick: PropTypes.func.isRequired, - - /** Safe area insets required for mobile devices margins */ - insets: safeAreaInsetPropTypes.isRequired, +type SidebarLinksOnyxProps = { + activePolicy: OnyxEntry; }; -const propTypes = { - ...basePropTypes, - - optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, - - isLoading: PropTypes.bool.isRequired, - - // eslint-disable-next-line react/require-default-props - priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)), - - isActiveReport: PropTypes.func.isRequired, +type SidebarLinksProps = SidebarLinksOnyxProps & { + onLinkClick: () => void; + insets: EdgeInsets; + optionListItems: string[] | null; + isLoading: OnyxEntry; + priorityMode?: OnyxEntry>; + isActiveReport: (reportID: string) => boolean; + isCreateMenuOpen?: boolean; + + // eslint-disable-next-line react/no-unused-prop-types -- its used in withOnyx + activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const modal = useRef({}); + const modal = useRef({}); const {translate, updateLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -67,7 +62,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { - if (_.isNull(modalArg) || typeof modalArg !== 'object') { + if (modalArg === null || typeof modalArg !== 'object') { return; } modal.current = modalArg; @@ -105,18 +100,19 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority /** * Show Report page with selected report id - * - * @param {Object} option - * @param {String} option.reportID */ const showReportPage = useCallback( - (option) => { + (option: Report) => { // Prevent opening Report page when clicking LHN row quickly after clicking FAB icon // or when clicking the active LHN row on large screens // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices const reportActionID = Navigation.getTopmostReportActionId(); - if (isCreateMenuOpen || (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID)) { + if ( + !!isCreateMenuOpen || + (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || + (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID) + ) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); @@ -137,7 +133,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority activePolicy ? { type: CONST.BREADCRUMB_TYPE.STRONG, - text: lodashGet(activePolicy, 'name', ''), + text: activePolicy.name ?? '', } : { type: CONST.BREADCRUMB_TYPE.ROOT, @@ -158,7 +154,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority optionMode={viewMode} onFirstItemRendered={App.setSidebarLoaded} /> - {isLoading && optionListItems.length === 0 && ( + {isLoading && optionListItems?.length === 0 && ( @@ -168,12 +164,10 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority ); } -SidebarLinks.propTypes = propTypes; SidebarLinks.displayName = 'SidebarLinks'; -export default withOnyx({ +export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, })(SidebarLinks); -export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index b128fcaf33ec..ddd232e99275 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -69,7 +69,7 @@ type SidebarLinksDataOnyxProps = { }; type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - onLinkClick: (reportID: number) => void; + onLinkClick: () => void; insets: EdgeInsets; }; @@ -101,7 +101,7 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const optionListItems = useMemo(() => { + const optionListItems: string[] | null = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, chatReports as OnyxEntry>, From 3fb3715847c5c82958147940cdcb68073456aedf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:54:54 +0100 Subject: [PATCH 011/146] ref: move PressableAvatarWithIndicator to TS --- ...or.js => PressableAvatarWithIndicator.tsx} | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) rename src/pages/home/sidebar/{PressableAvatarWithIndicator.js => PressableAvatarWithIndicator.tsx} (56%) diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx similarity index 56% rename from src/pages/home/sidebar/PressableAvatarWithIndicator.js rename to src/pages/home/sidebar/PressableAvatarWithIndicator.tsx index 63c5936e957b..e07b6e856823 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx @@ -1,44 +1,30 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, - - /** The personal details of the person who is logged in */ - currentUserPersonalDetails: personalDetailsPropType, - +type PressableAvatarWithIndicatorOnyxProps = { /** Indicates whether the app is loading initial data */ - isLoading: PropTypes.bool, + isLoading: OnyxEntry; }; -const defaultProps = { - isCreateMenuOpen: false, - currentUserPersonalDetails: { - pendingFields: {avatar: ''}, - accountID: '', - avatar: '', - }, - isLoading: true, +type PressableAvatarWithIndicatorProps = PressableAvatarWithIndicatorOnyxProps & { + /** Whether the create menu is open or not */ + isCreateMenuOpen: boolean; }; -function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDetails, isLoading}) { +function PressableAvatarWithIndicator({isCreateMenuOpen = false, isLoading = true}: PressableAvatarWithIndicatorProps) { const {translate} = useLocalize(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const showSettingsPage = useCallback(() => { if (isCreateMenuOpen) { @@ -55,26 +41,22 @@ function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDeta role={CONST.ROLE.BUTTON} onPress={showSettingsPage} > - + ); } -PressableAvatarWithIndicator.propTypes = propTypes; -PressableAvatarWithIndicator.defaultProps = defaultProps; PressableAvatarWithIndicator.displayName = 'PressableAvatarWithIndicator'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(PressableAvatarWithIndicator); + +export default withOnyx({ + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(PressableAvatarWithIndicator); From 5fa814c327d1e9767afa896e3304487885039547 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 16:57:26 +0100 Subject: [PATCH 012/146] ref: move AvatarWithOptionalStatus to TS --- ...alStatus.js => AvatarWithOptionalStatus.tsx} | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) rename src/pages/home/sidebar/{AvatarWithOptionalStatus.js => AvatarWithOptionalStatus.tsx} (81%) diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx similarity index 81% rename from src/pages/home/sidebar/AvatarWithOptionalStatus.js rename to src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index e1ff3982a0cc..5597d46c29bc 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -1,5 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -12,20 +10,15 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; -const propTypes = { +type AvatarWithOptionalStatusProps = { /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, + isCreateMenuOpen: boolean; /** Emoji status */ - emojiStatus: PropTypes.string, + emojiStatus: string; }; -const defaultProps = { - isCreateMenuOpen: false, - emojiStatus: '', -}; - -function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { +function AvatarWithOptionalStatus({emojiStatus = '', isCreateMenuOpen = false}: AvatarWithOptionalStatusProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -61,7 +54,5 @@ function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { ); } -AvatarWithOptionalStatus.propTypes = propTypes; -AvatarWithOptionalStatus.defaultProps = defaultProps; AvatarWithOptionalStatus.displayName = 'AvatarWithOptionalStatus'; export default AvatarWithOptionalStatus; From 6733581a39b700a147c1708f606cdc6fede9b1ee Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 16 Feb 2024 17:02:16 +0100 Subject: [PATCH 013/146] ref: move BaseSidebarScreen to TS --- src/pages/home/sidebar/SidebarLinks.tsx | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 2 +- .../{BaseSidebarScreen.js => BaseSidebarScreen.tsx} | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{BaseSidebarScreen.js => BaseSidebarScreen.tsx} (94%) diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index 52165c148727..f01ed07ea476 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -27,7 +27,7 @@ type SidebarLinksOnyxProps = { type SidebarLinksProps = SidebarLinksOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; optionListItems: string[] | null; isLoading: OnyxEntry; priorityMode?: OnyxEntry>; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index ddd232e99275..ab84f69e824b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -70,7 +70,7 @@ type SidebarLinksDataOnyxProps = { type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { onLinkClick: () => void; - insets: EdgeInsets; + insets: EdgeInsets | undefined; }; function SidebarLinksData({ diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx similarity index 94% rename from src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js rename to src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 9188a859d175..314b3921cc0b 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -17,7 +17,7 @@ const startTimer = () => { Performance.markStart(CONST.TIMING.SWITCH_REPORT); }; -function BaseSidebarScreen(props) { +function BaseSidebarScreen() { const styles = useThemeStyles(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); @@ -37,7 +37,6 @@ function BaseSidebarScreen(props) { )} From 2fad64eddb709a5bdde140f53ce65beb63b64578 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 10:02:39 +0100 Subject: [PATCH 014/146] ref: move FloatingActionButtonAndPopover to TS --- src/libs/actions/Policy.ts | 6 +- src/libs/actions/Task.ts | 2 +- ....js => FloatingActionButtonAndPopover.tsx} | 139 +++++++----------- 3 files changed, 58 insertions(+), 89 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{FloatingActionButtonAndPopover.js => FloatingActionButtonAndPopover.tsx} (70%) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 9e6745bbc291..911837cd3bf5 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record>; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; @@ -206,8 +206,8 @@ function updateLastAccessedWorkspace(policyID: OnyxEntry) { /** * Check if the user has any active free policies (aka workspaces) */ -function hasActiveFreePolicy(policies: Array> | PoliciesRecord): boolean { - const adminFreePolicies = Object.values(policies).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); +function hasActiveFreePolicy(policies: OnyxEntry): boolean { + const adminFreePolicies = Object.values(policies ?? {}).filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN); if (adminFreePolicies.length === 0) { return false; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 28cecf460a5f..b05e7cd6b2d9 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -645,7 +645,7 @@ function setParentReportID(parentReportID: string) { /** * Clears out the task info from the store and navigates to the NewTaskDetails page */ -function clearOutTaskInfoAndNavigate(reportID: string) { +function clearOutTaskInfoAndNavigate(reportID?: string) { clearOutTaskInfo(); if (reportID && reportID !== '0') { setParentReportID(reportID); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx similarity index 70% rename from src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js rename to src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 0df490fa4466..83cc719f2e90 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,17 +1,16 @@ -import PropTypes from 'prop-types'; +import {useIsFocused, useNavigation} from '@react-navigation/native'; +import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; -import withNavigation from '@components/withNavigation'; -import withNavigationFocus from '@components/withNavigationFocus'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -22,74 +21,53 @@ import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => - policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; - -const propTypes = { - ...windowDimensionsPropTypes, +type FloatingActionButtonAndPopoverOnyxProps = { + /** The list of policies the user has access to. */ + allPolicies: OnyxEntry>; + isLoading: OnyxEntry; +}; +type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: PropTypes.func, + onShowCreateMenu: () => void; /* Callback function before the menu is hidden */ - onHideCreateMenu: PropTypes.func, - - /** The list of policies the user has access to. */ - allPolicies: PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, - }), - - /** Indicated whether the report data is loading */ - isLoading: PropTypes.bool, - - /** Forwarded ref to FloatingActionButtonAndPopover */ - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + onHideCreateMenu: () => void; }; -const defaultProps = { - onHideCreateMenu: () => {}, - onShowCreateMenu: () => {}, - allPolicies: {}, - isLoading: false, - innerRef: null, + +type FloatingActionButtonAndPopoverRef = { + hideCreateMenu: () => void; }; /** * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. - * @param {Object} props - * @returns {JSX.Element} */ -function FloatingActionButtonAndPopover(props) { +function FloatingActionButtonAndPopover( + {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, + ref: ForwardedRef, +) { + console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); - const fabRef = useRef(null); + const fabRef = useRef(null); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const isFocused = useIsFocused(); - const prevIsFocused = usePrevious(props.isFocused); + const prevIsFocused = usePrevious(isFocused); /** * Check if LHN status changed from active to inactive. * Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web). - * - * @param {Object} prevProps - * @return {Boolean} */ const didScreenBecomeInactive = useCallback( () => // When any other page is opened over LHN - !props.isFocused && prevIsFocused, - [props.isFocused, prevIsFocused], + !isFocused && prevIsFocused, + [isFocused, prevIsFocused], ); /** @@ -97,14 +75,14 @@ function FloatingActionButtonAndPopover(props) { */ const showCreateMenu = useCallback( () => { - if (!props.isFocused && props.isSmallScreenWidth) { + if (!isFocused && isSmallScreenWidth) { return; } setIsCreateMenuActive(true); - props.onShowCreateMenu(); + onShowCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [props.isFocused, props.isSmallScreenWidth], + [isFocused, isSmallScreenWidth], ); /** @@ -118,7 +96,7 @@ function FloatingActionButtonAndPopover(props) { return; } setIsCreateMenuActive(false); - props.onHideCreateMenu(); + onHideCreateMenu(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isCreateMenuActive], @@ -133,7 +111,7 @@ function FloatingActionButtonAndPopover(props) { hideCreateMenu(); }, [didScreenBecomeInactive, hideCreateMenu]); - useImperativeHandle(props.innerRef, () => ({ + useImperativeHandle(ref, () => ({ hideCreateMenu() { hideCreateMenu(); }, @@ -151,10 +129,10 @@ function FloatingActionButtonAndPopover(props) { interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!props.isLoading && !Policy.hasActiveFreePolicy(props.allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) ? [ { displayInDefaultIconColor: true, - contentFit: 'contain', + contentFit: 'contain' as const, icon: Expensicons.NewWorkspace, iconWidth: 46, iconHeight: 40, @@ -219,31 +197,22 @@ function FloatingActionButtonAndPopover(props) { ); } -FloatingActionButtonAndPopover.propTypes = propTypes; -FloatingActionButtonAndPopover.defaultProps = defaultProps; FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; -const FloatingActionButtonAndPopoverWithRef = forwardRef((props, ref) => ( - -)); - -FloatingActionButtonAndPopoverWithRef.displayName = 'FloatingActionButtonAndPopoverWithRef'; - -export default compose( - withNavigation, - withNavigationFocus, - withWindowDimensions, - withOnyx({ - allPolicies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - }, - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(FloatingActionButtonAndPopoverWithRef); +const policySelector = (policy: OnyxEntry) => + policy && { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + }; + +export default withOnyx({ + allPolicies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector, + }, + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(forwardRef(FloatingActionButtonAndPopover)); From c9158cbd92eaed9d32315fb38fda611e4c182079 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 14:48:56 +0100 Subject: [PATCH 015/146] fix: wip --- src/libs/actions/Policy.ts | 2 +- .../sidebar/BottomTabBarFloatingActionButton/index.tsx | 1 - .../home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 2 -- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 9 ++++----- .../home/sidebar/SidebarScreen/{index.js => index.tsx} | 8 +++++--- src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js | 7 ------- 6 files changed, 10 insertions(+), 19 deletions(-) rename src/pages/home/sidebar/SidebarScreen/{index.js => index.tsx} (75%) delete mode 100644 src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e780001a7ecc..261e15af8d48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -81,7 +81,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record; +type PoliciesRecord = Record; type NewCustomUnit = { customUnitID: string; diff --git a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx index 788dd4ae5bc8..33b89be8fd17 100644 --- a/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx +++ b/src/pages/home/sidebar/BottomTabBarFloatingActionButton/index.tsx @@ -32,7 +32,6 @@ function BottomTabBarFloatingActionButton() { return ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 314b3921cc0b..b3901e1ae06f 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -7,7 +7,6 @@ import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; -import sidebarPropTypes from './sidebarPropTypes'; /** * Function called when a pinned chat is selected. @@ -44,7 +43,6 @@ function BaseSidebarScreen() { ); } -BaseSidebarScreen.propTypes = sidebarPropTypes; BaseSidebarScreen.displayName = 'BaseSidebarScreen'; export default BaseSidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 83cc719f2e90..01d5ddaff475 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,4 +1,4 @@ -import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -25,13 +25,13 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ - allPolicies: OnyxEntry>; + allPolicies: OnyxEntry>>; isLoading: OnyxEntry; }; type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxProps & { /* Callback function when the menu is shown */ - onShowCreateMenu: () => void; + onShowCreateMenu?: () => void; /* Callback function before the menu is hidden */ onHideCreateMenu: () => void; @@ -49,7 +49,6 @@ function FloatingActionButtonAndPopover( {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef, ) { - console.log('allPolices', allPolicies); const styles = useThemeStyles(); const {translate} = useLocalize(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); @@ -200,7 +199,7 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - policy && { + !!policy && { type: policy.type, role: policy.role, isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.tsx similarity index 75% rename from src/pages/home/sidebar/SidebarScreen/index.js rename to src/pages/home/sidebar/SidebarScreen/index.tsx index 7086e8a8561a..f017750c912c 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,10 +1,13 @@ import React from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -import sidebarPropTypes from './sidebarPropTypes'; -function SidebarScreen(props) { +type SidebarScreenProps = { + onLayout: (event: LayoutChangeEvent) => void; +}; +function SidebarScreen(props: SidebarScreenProps) { const {isSmallScreenWidth} = useWindowDimensions(); return ( @@ -17,7 +20,6 @@ function SidebarScreen(props) { ); } -SidebarScreen.propTypes = sidebarPropTypes; SidebarScreen.displayName = 'SidebarScreen'; export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js deleted file mode 100644 index 61a9194bb1e5..000000000000 --- a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js +++ /dev/null @@ -1,7 +0,0 @@ -import PropTypes from 'prop-types'; - -const sidebarPropTypes = { - /** Callback when onLayout of sidebar is called */ - onLayout: PropTypes.func, -}; -export default sidebarPropTypes; From dc58fff4022813cc70e994bc2f1f97f4e427cebf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 20 Feb 2024 15:42:06 +0100 Subject: [PATCH 016/146] fix: wip --- .../FloatingActionButtonAndPopover.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 01d5ddaff475..9291401d4569 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -26,6 +26,8 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ allPolicies: OnyxEntry>>; + + /** Wheater app is in loading state */ isLoading: OnyxEntry; }; @@ -199,12 +201,14 @@ function FloatingActionButtonAndPopover( FloatingActionButtonAndPopover.displayName = 'FloatingActionButtonAndPopover'; const policySelector = (policy: OnyxEntry) => - !!policy && { - type: policy.type, - role: policy.role, - isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, - pendingAction: policy.pendingAction, - }; + policy + ? { + type: policy.type, + role: policy.role, + isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, + pendingAction: policy.pendingAction, + } + : null; export default withOnyx({ allPolicies: { From cb7bfac8c0e73a6ef1814436e7fe35237dc3f5de Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 21 Feb 2024 12:12:14 +0100 Subject: [PATCH 017/146] 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/146] Add OnfidoDataWithApplicantID type --- src/components/Onfido/types.ts | 7 ++++++- src/libs/actions/BankAccounts.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index e341dfd64960..90403f1ab179 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,8 +1,13 @@ import type {OnfidoResult} from '@onfido/react-native-sdk'; import type * as OnfidoSDK from 'onfido-sdk-ui'; +import type {OnyxEntry} from 'react-native-onyx'; type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; +type OnfidoDataWithApplicantID = OnfidoData & { + applicantID: OnyxEntry; +}; + type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; type OnfidoProps = { @@ -19,4 +24,4 @@ type OnfidoProps = { onError: (error?: string) => void; }; -export type {OnfidoProps, OnfidoElement, OnfidoData}; +export type {OnfidoProps, OnfidoElement, OnfidoData, OnfidoDataWithApplicantID}; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 30dd03b6e780..90e4a6c4aaed 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnfidoDataWithApplicantID} from '@components/Onfido/types'; import * as API from '@libs/API'; import type { AddPersonalBankAccountParams, @@ -436,7 +437,7 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan /** * Verify the user's identity via Onfido */ -function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: Record, policyID: string) { +function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoDataWithApplicantID, policyID: string) { const parameters: VerifyIdentityForBankAccountParams = { bankAccountID, onfidoData: JSON.stringify(onfidoData), From 4bb8acb56b6f3995a58eebf0f96cb975d6a94baa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 12:42:45 +0100 Subject: [PATCH 019/146] fix: removed unused props spreading --- src/pages/home/sidebar/SidebarScreen/index.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/index.tsx b/src/pages/home/sidebar/SidebarScreen/index.tsx index f017750c912c..e448a9cad332 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.tsx +++ b/src/pages/home/sidebar/SidebarScreen/index.tsx @@ -1,21 +1,14 @@ import React from 'react'; -import type {LayoutChangeEvent} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; -type SidebarScreenProps = { - onLayout: (event: LayoutChangeEvent) => void; -}; -function SidebarScreen(props: SidebarScreenProps) { +function SidebarScreen() { const {isSmallScreenWidth} = useWindowDimensions(); return ( - + ); } From a8097cd6a6be5b86a7d045304d1d8cf6e668c00c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 21 Feb 2024 16:33:00 +0200 Subject: [PATCH 020/146] 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 7064cfe6e4487972ff995c5886994cd45f80e5ea Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:52:22 +0000 Subject: [PATCH 021/146] [TS migration] Migrate postTestBuildComment to typescript --- ...uildComment.js => postTestBuildComment.ts} | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) rename tests/unit/{postTestBuildComment.js => postTestBuildComment.ts} (83%) diff --git a/tests/unit/postTestBuildComment.js b/tests/unit/postTestBuildComment.ts similarity index 83% rename from tests/unit/postTestBuildComment.js rename to tests/unit/postTestBuildComment.ts index ff77ca190c08..df0a623aaced 100644 --- a/tests/unit/postTestBuildComment.js +++ b/tests/unit/postTestBuildComment.ts @@ -1,10 +1,11 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; +import type {Writable} from 'type-fest'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; const mockGetInput = jest.fn(); -const mockCreateComment = jest.fn(); +const createCommentMock = jest.spyOn(GithubUtils, 'createComment'); const mockListComments = jest.fn(); const mockGraphql = jest.fn(); jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ @@ -12,7 +13,11 @@ jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ listComments: mockListComments, }, }); -jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint, params) => endpoint(params).then(({data}) => data)); + +jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint: (params: Record) => Promise<{data: TData}>, params: Record) => + endpoint(params).then((response) => response.data), +); + jest.spyOn(GithubUtils, 'graphql', 'get').mockReturnValue(mockGraphql); jest.mock('@actions/github', () => ({ @@ -49,11 +54,12 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; +const asMutable = (value: T): Writable => value as Writable; + describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module - core.getInput = mockGetInput; - GithubUtils.createComment = mockCreateComment; + asMutable(core).getInput = mockGetInput; }); test('Test GH action', async () => { @@ -66,11 +72,12 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('IOS_LINK').mockReturnValue('https://expensify.app/IOS_LINK'); when(core.getInput).calledWith('WEB_LINK').mockReturnValue('https://expensify.app/WEB_LINK'); when(core.getInput).calledWith('DESKTOP_LINK').mockReturnValue('https://expensify.app/DESKTOP_LINK'); - GithubUtils.createComment.mockResolvedValue(true); + createCommentMock.mockResolvedValue(true); mockListComments.mockResolvedValue({ data: [ { body: ':test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing!', + // eslint-disable-next-line @typescript-eslint/naming-convention node_id: 'IC_abcd', }, ], @@ -86,7 +93,7 @@ describe('Post test build comments action tests', () => { } } `); - expect(GithubUtils.createComment).toBeCalledTimes(1); - expect(GithubUtils.createComment).toBeCalledWith('App', 12, message); + expect(createCommentMock).toBeCalledTimes(1); + expect(createCommentMock).toBeCalledWith('App', 12, message); }); }); From 0ead9f440a8ff6d5bd8167a27dc0bf2e3def6ca8 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 21 Feb 2024 15:58:26 +0000 Subject: [PATCH 022/146] [TS migration][postTestBuildComment] Added last ts issue fix --- tests/unit/postTestBuildComment.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index df0a623aaced..808e85d5c8a8 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -63,7 +63,9 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); + when(core.getInput) + .calledWith('PR_NUMBER', {required: true}) + .mockReturnValue(12 as unknown as string); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); From 09e448f7ddbe7992a943a63abc17f960e71cd647 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 21 Feb 2024 17:37:35 +0100 Subject: [PATCH 023/146] fix: added ugly fix for onyx selectors --- src/pages/home/sidebar/SidebarLinksData.tsx | 74 +++++++++---------- .../FloatingActionButtonAndPopover.tsx | 10 ++- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 29f58d679760..182f413272f0 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -23,42 +23,42 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; +type PickedReport = Pick< + OnyxTypes.Report, + | 'reportID' + | 'participantAccountIDs' + | 'hasDraft' + | 'isPinned' + | 'isHidden' + | 'notificationPreference' + | 'errorFields' + | 'lastMessageText' + | 'lastVisibleActionCreated' + | 'iouReportID' + | 'total' + | 'nonReimbursableTotal' + | 'hasOutstandingChildRequest' + | 'isWaitingOnBankAccount' + | 'statusNum' + | 'stateNum' + | 'chatType' + | 'type' + | 'policyID' + | 'visibility' + | 'lastReadTime' + | 'reportName' + | 'policyName' + | 'oldPolicyName' + | 'ownerAccountID' + | 'currency' + | 'managerID' + | 'parentReportActionID' + | 'parentReportID' + | 'isDeletedParentAction' +>; + type SidebarLinksDataOnyxProps = { - chatReports: OnyxCollection< - Pick< - OnyxTypes.Report, - | 'reportID' - | 'participantAccountIDs' - | 'hasDraft' - | 'isPinned' - | 'isHidden' - | 'notificationPreference' - | 'errorFields' - | 'lastMessageText' - | 'lastVisibleActionCreated' - | 'iouReportID' - | 'total' - | 'nonReimbursableTotal' - | 'hasOutstandingChildRequest' - | 'isWaitingOnBankAccount' - | 'statusNum' - | 'stateNum' - | 'chatType' - | 'type' - | 'policyID' - | 'visibility' - | 'lastReadTime' - | 'reportName' - | 'policyName' - | 'oldPolicyName' - | 'ownerAccountID' - | 'currency' - | 'managerID' - | 'parentReportActionID' - | 'parentReportID' - | 'isDeletedParentAction' - > & {isUnreadWithMention: boolean} - >; + chatReports: OnyxCollection; isLoadingApp: OnyxEntry; priorityMode: OnyxEntry>; betas: OnyxEntry; @@ -248,7 +248,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, isLoadingApp: { @@ -269,7 +269,7 @@ export default withOnyx({ }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, initialValue: {}, }, policyMembers: { diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 2a0682b776cb..208a9c0a9deb 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -170,7 +170,7 @@ function FloatingActionButtonAndPopover( text: translate('sidebarScreen.saveTheWorld'), onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as Record) ? [ { displayInDefaultIconColor: true, @@ -211,10 +211,12 @@ const policySelector = (policy: OnyxEntry) => } : null; -export default withOnyx({ +export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, + selector: policySelector as unknown as ( + policy: OnyxEntry, + ) => OnyxEntry>>, }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, From a5ea70b6898e063ef27b2e11884cfaf716d633e7 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:05:21 +0000 Subject: [PATCH 024/146] [TS migration][postTestBuildComment] Updated asMutable to the util --- src/utils/asMutable.ts | 5 +++++ tests/unit/postTestBuildComment.ts | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/utils/asMutable.ts diff --git a/src/utils/asMutable.ts b/src/utils/asMutable.ts new file mode 100644 index 000000000000..57c49058cd14 --- /dev/null +++ b/src/utils/asMutable.ts @@ -0,0 +1,5 @@ +import type {Writable} from 'type-fest'; + +const asMutable = (value: T): Writable => value as Writable; + +export default asMutable; diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 808e85d5c8a8..1c1e9ad35564 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import type {Writable} from 'type-fest'; +import asMutable from '@src/utils/asMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; @@ -54,8 +54,6 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; -const asMutable = (value: T): Writable => value as Writable; - describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module From dfad42627dc4711bd9311b30b397f4468c414ee3 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 22 Feb 2024 09:17:26 +0000 Subject: [PATCH 025/146] [TS migration][postTestBuildComment] Changed file location and name --- src/{utils/asMutable.ts => types/utils/AsMutable.ts} | 0 tests/unit/postTestBuildComment.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{utils/asMutable.ts => types/utils/AsMutable.ts} (100%) diff --git a/src/utils/asMutable.ts b/src/types/utils/AsMutable.ts similarity index 100% rename from src/utils/asMutable.ts rename to src/types/utils/AsMutable.ts diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 1c1e9ad35564..26c0711a34cd 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import asMutable from '@src/utils/asMutable'; +import asMutable from '@src/types/utils/AsMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; From 840685854cf85f535be41e9df7792673d3572504 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 23 Feb 2024 16:15:20 +0100 Subject: [PATCH 026/146] 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 027/146] Fix comment typo --- src/libs/actions/ReimbursementAccount/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/errors.ts b/src/libs/actions/ReimbursementAccount/errors.ts index f85426f8d4fe..05c375364329 100644 --- a/src/libs/actions/ReimbursementAccount/errors.ts +++ b/src/libs/actions/ReimbursementAccount/errors.ts @@ -7,7 +7,7 @@ import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; * Set the current fields with errors. */ function setPersonalBankAccountFormValidationErrorFields(errorFields: ErrorFields) { - // We set 'errors' to null first because we don't have a way yet to replace a specific property without merging it + // We set 'errorFields' to null first because we don't have a way yet to replace a specific property without merging it Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields: null}); Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {errorFields}); } From fe351822d7fcf92f73ea1cf27f81ddee245d2391 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 23 Feb 2024 16:24:33 +0100 Subject: [PATCH 028/146] fix: tests --- src/pages/home/sidebar/SidebarLinksData.tsx | 106 ++++++++++++-------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 182f413272f0..5a5e7a8e0db2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -6,8 +6,9 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; +import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import withCurrentReportID from '@components/withCurrentReportID'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; -import useCurrentReportID from '@hooks/useCurrentReportID'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -58,21 +59,40 @@ type PickedReport = Pick< >; type SidebarLinksDataOnyxProps = { + /** List of reports */ chatReports: OnyxCollection; + + /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoadingApp: OnyxEntry; + + /** The chat priority mode */ priorityMode: OnyxEntry>; + + /** Beta features list */ betas: OnyxEntry; + + /** All report actions for all reports */ allReportActions: OnyxEntry>>; + + /** The policies which the user has access to */ policies: OnyxCollection>; - policyMembers: OnyxCollection; + + /** All of the transaction violations */ transactionViolations: OnyxCollection; -}; -type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - onLinkClick: () => void; - insets: EdgeInsets | undefined; + /** All policy members */ + policyMembers: OnyxCollection; }; +type SidebarLinksDataProps = CurrentReportIDContextValue & + SidebarLinksDataOnyxProps & { + /** Toggles the navigation menu open and closed */ + onLinkClick: () => void; + + /** Safe area insets required for mobile devices margins */ + insets: EdgeInsets | undefined; + }; + function SidebarLinksData({ allReportActions, betas, @@ -84,8 +104,8 @@ function SidebarLinksData({ priorityMode = CONST.PRIORITY_MODE.DEFAULT, policyMembers, transactionViolations, + currentReportID, }: SidebarLinksDataProps) { - const {currentReportID} = useCurrentReportID() ?? {}; const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -245,38 +265,40 @@ const policySelector = (policy: OnyxEntry) => avatar: policy.avatar, }; -export default withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, - initialValue: {}, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, -})(SidebarLinksData); +export default withCurrentReportID( + withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + initialValue: {}, + }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, + })(SidebarLinksData), +); From d2b3674759cd5be4572b09228d5a87d9801f4661 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 11:58:43 +0100 Subject: [PATCH 029/146] 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 c34bee5fd6a110df9a4a34ea4e7d28f7084aed28 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 26 Feb 2024 12:44:47 +0100 Subject: [PATCH 030/146] 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: Tue, 27 Feb 2024 14:01:08 +0100 Subject: [PATCH 031/146] fix: resolve comments --- src/components/LHNOptionsList/types.ts | 2 +- src/libs/ReportUtils.ts | 9 +-- src/libs/SidebarUtils.ts | 28 +++++---- src/libs/actions/Policy.ts | 2 +- .../home/sidebar/AvatarWithOptionalStatus.tsx | 4 +- src/pages/home/sidebar/SidebarLinks.tsx | 27 +++++---- src/pages/home/sidebar/SidebarLinksData.tsx | 59 +++++-------------- .../FloatingActionButtonAndPopover.tsx | 19 +++--- .../SignInOrAvatarWithOptionalStatus.tsx | 1 + 9 files changed, 67 insertions(+), 84 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f3d6bde9d41c..58bea97f04c9 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -45,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[] | null; + data: string[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b19cd837a033..b23da56bcd8f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -704,8 +704,8 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean /** * Checks if the supplied report has a common policy member with the array passed in params. */ -function hasParticipantInArray(report: Report, policyMemberAccountIDs: number[]) { - if (!report.participantAccountIDs) { +function hasParticipantInArray(report: OnyxEntry, policyMemberAccountIDs: number[]) { + if (!report?.participantAccountIDs) { return false; } @@ -921,9 +921,10 @@ function isConciergeChatReport(report: OnyxEntry): boolean { * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ -function doesReportBelongToWorkspace(report: Report, policyMemberAccountIDs: number[], policyID?: string) { +function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID?: string) { return ( - isConciergeChatReport(report) || (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report.policyID === policyID) + isConciergeChatReport(report) || + (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report?.policyID === policyID) ); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 44a17a6d50a0..463c796fb5ce 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -64,9 +64,9 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: OnyxEntry>, + allReports: OnyxCollection, betas: OnyxEntry, - policies: OnyxEntry>, + policies: OnyxCollection, priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, @@ -83,7 +83,7 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + !!betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -91,7 +91,7 @@ function getOrderedReportIDs( betas, policies, excludeEmptyChats: true, - doesReportHaveViolations: !!doesReportHaveViolations, + doesReportHaveViolations, }); }); @@ -114,7 +114,7 @@ function getOrderedReportIDs( // - Sorted by reportDisplayName in GSD (focus) view mode const pinnedAndGBRReports: Report[] = []; const draftReports: Report[] = []; - const nonArchivedReports: Report[] = []; + const nonArchivedReports: Array> = []; const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { @@ -125,16 +125,18 @@ function getOrderedReportIDs( // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. - // eslint-disable-next-line no-param-reassign - report.displayName = ReportUtils.getReportName(report); + if (report) { + // eslint-disable-next-line no-param-reassign + report.displayName = ReportUtils.getReportName(report); + } - const isPinned = report.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); - if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { + const isPinned = report?.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + if ((isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) && report) { pinnedAndGBRReports.push(report); - } else if (report.hasDraft) { + } else if (report?.hasDraft) { draftReports.push(report); - } else if (ReportUtils.isArchivedRoom(report)) { + } else if (ReportUtils.isArchivedRoom(report) && report) { archivedReports.push(report); } else { nonArchivedReports.push(report); @@ -160,7 +162,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); return LHNReports; } diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 850d21e4858e..0296b1a8ea6f 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -84,7 +84,7 @@ type OptimisticCustomUnits = { outputCurrency: string; }; -type PoliciesRecord = Record; +type PoliciesRecord = Record>; type NewCustomUnit = { customUnitID: string; diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index 5597d46c29bc..0e1dedaf3651 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -12,10 +12,10 @@ import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; type AvatarWithOptionalStatusProps = { /** Whether the create menu is open or not */ - isCreateMenuOpen: boolean; + isCreateMenuOpen?: boolean; /** Emoji status */ - emojiStatus: string; + emojiStatus?: string; }; function AvatarWithOptionalStatus({emojiStatus = '', isCreateMenuOpen = false}: AvatarWithOptionalStatusProps) { diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index b0dd6f5ac067..5e0e37b1a164 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -26,19 +26,30 @@ type SidebarLinksOnyxProps = { }; type SidebarLinksProps = SidebarLinksOnyxProps & { + /** Toggles the navigation menu open and closed */ onLinkClick: () => void; - insets: EdgeInsets | undefined; - optionListItems: string[] | null; + + /** Safe area insets required for mobile devices margins */ + insets: EdgeInsets; + + /** List of options to display */ + optionListItems: string[]; + + /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoading: OnyxEntry; + + /** The chat priority mode */ priorityMode?: OnyxEntry>; + + /** Method to change currently active report */ isActiveReport: (reportID: string) => boolean; - isCreateMenuOpen?: boolean; + /** ID of currently active workspace */ // eslint-disable-next-line react/no-unused-prop-types -- its used in withOnyx activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}: SidebarLinksProps) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, activePolicy}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const modal = useRef({}); @@ -105,17 +116,13 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices const reportActionID = Navigation.getTopmostReportActionId(); - if ( - !!isCreateMenuOpen || - (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || - (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID) - ) { + if ((option.reportID === Navigation.getTopmostReportId() && !reportActionID) || (isSmallScreenWidth && isActiveReport(option.reportID) && !reportActionID)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); onLinkClick(); }, - [isCreateMenuOpen, isSmallScreenWidth, isActiveReport, onLinkClick], + [isSmallScreenWidth, isActiveReport, onLinkClick], ); const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index ee99b0c9cf59..6e71e30ac1b6 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -24,43 +24,13 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; -type PickedReport = Pick< - OnyxTypes.Report, - | 'reportID' - | 'participantAccountIDs' - | 'hasDraft' - | 'isPinned' - | 'isHidden' - | 'notificationPreference' - | 'errorFields' - | 'lastMessageText' - | 'lastVisibleActionCreated' - | 'iouReportID' - | 'total' - | 'nonReimbursableTotal' - | 'hasOutstandingChildRequest' - | 'isWaitingOnBankAccount' - | 'statusNum' - | 'stateNum' - | 'chatType' - | 'type' - | 'policyID' - | 'visibility' - | 'lastReadTime' - | 'reportName' - | 'policyName' - | 'oldPolicyName' - | 'ownerAccountID' - | 'currency' - | 'managerID' - | 'parentReportActionID' - | 'parentReportID' - | 'isDeletedParentAction' ->; +type ChatReportSelector = ReturnType & {isUnreadWithMention: boolean}; +type PolicySelector = ReturnType; +type ReportActionsSelector = ReturnType; type SidebarLinksDataOnyxProps = { /** List of reports */ - chatReports: OnyxCollection; + chatReports: OnyxCollection; /** Wheather the reports are loading. When false it means they are ready to be used. */ isLoadingApp: OnyxEntry; @@ -72,10 +42,10 @@ type SidebarLinksDataOnyxProps = { betas: OnyxEntry; /** All report actions for all reports */ - allReportActions: OnyxEntry>>; + allReportActions: OnyxEntry; /** The policies which the user has access to */ - policies: OnyxCollection>; + policies: OnyxCollection; /** All of the transaction violations */ transactionViolations: OnyxCollection; @@ -90,7 +60,7 @@ type SidebarLinksDataProps = CurrentReportIDContextValue & onLinkClick: () => void; /** Safe area insets required for mobile devices margins */ - insets: EdgeInsets | undefined; + insets: EdgeInsets; }; function SidebarLinksData({ @@ -120,7 +90,7 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const optionListItems: string[] | null = useMemo(() => { + const optionListItems: string[] = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, chatReports as OnyxEntry>, @@ -133,7 +103,7 @@ function SidebarLinksData({ policyMemberAccountIDs, ); - if (deepEqual(reportIDsRef.current, reportIDs)) { + if (reportIDsRef.current && deepEqual(reportIDsRef.current, reportIDs)) { return reportIDsRef.current; } @@ -170,7 +140,7 @@ function SidebarLinksData({ const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; - const isActiveReport = useCallback((reportID: string) => currentReportIDRef.current === reportID, []); + const isActiveReport = useCallback((reportID: string): boolean => currentReportIDRef.current === reportID, []); return ( ) => report && { @@ -270,7 +239,8 @@ export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, isLoadingApp: { @@ -291,7 +261,8 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection>, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, initialValue: {}, }, policyMembers: { @@ -303,3 +274,5 @@ export default withCurrentReportID( }, })(SidebarLinksData), ); + +export type {PolicySelector}; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 208a9c0a9deb..c080272acbd2 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -14,6 +14,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; @@ -25,7 +26,7 @@ import type * as OnyxTypes from '@src/types/onyx'; type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ - allPolicies: OnyxEntry>>; + allPolicies: OnyxCollection; /** Wheater app is in loading state */ isLoading: OnyxEntry; @@ -36,7 +37,7 @@ type FloatingActionButtonAndPopoverProps = FloatingActionButtonAndPopoverOnyxPro onShowCreateMenu?: () => void; /* Callback function before the menu is hidden */ - onHideCreateMenu: () => void; + onHideCreateMenu?: () => void; }; type FloatingActionButtonAndPopoverRef = { @@ -48,7 +49,7 @@ type FloatingActionButtonAndPopoverRef = { * FAB that can open or close the menu. */ function FloatingActionButtonAndPopover( - {onHideCreateMenu = () => {}, onShowCreateMenu = () => {}, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, + {onHideCreateMenu, onShowCreateMenu, isLoading, allPolicies}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); @@ -80,7 +81,7 @@ function FloatingActionButtonAndPopover( return; } setIsCreateMenuActive(true); - onShowCreateMenu(); + onShowCreateMenu?.(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isFocused, isSmallScreenWidth], @@ -97,7 +98,7 @@ function FloatingActionButtonAndPopover( return; } setIsCreateMenuActive(false); - onHideCreateMenu(); + onHideCreateMenu?.(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [isCreateMenuActive], @@ -170,7 +171,7 @@ function FloatingActionButtonAndPopover( text: translate('sidebarScreen.saveTheWorld'), onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TEACHERS_UNITE)), }, - ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as Record) + ...(!isLoading && !Policy.hasActiveFreePolicy(allPolicies as OnyxEntry>) ? [ { displayInDefaultIconColor: true, @@ -214,9 +215,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector as unknown as ( - policy: OnyxEntry, - ) => OnyxEntry>>, + selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, }, isLoading: { key: ONYXKEYS.IS_LOADING_APP, diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx index 2a9356d78232..e8c90bb6eb08 100644 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.tsx @@ -6,6 +6,7 @@ import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; import SignInButton from './SignInButton'; type SignInOrAvatarWithOptionalStatusProps = { + /** Whether the create menu is open or not */ isCreateMenuOpen?: boolean; }; From 14f652c6b05c7f4e91d618cf1d5005f9585eefc3 Mon Sep 17 00:00:00 2001 From: Ruben Rebelo <39693995+ruben-rebelo@users.noreply.github.com> Date: Wed, 28 Feb 2024 08:33:16 +0000 Subject: [PATCH 032/146] Rename AsMutable.ts to asMutable.ts --- src/types/utils/{AsMutable.ts => asMutable.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/types/utils/{AsMutable.ts => asMutable.ts} (100%) diff --git a/src/types/utils/AsMutable.ts b/src/types/utils/asMutable.ts similarity index 100% rename from src/types/utils/AsMutable.ts rename to src/types/utils/asMutable.ts From 5a33f683158f2282b2134478411c30671d4b8492 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Wed, 28 Feb 2024 08:35:38 +0000 Subject: [PATCH 033/146] [TS migration][postTestBuildComments] Feedback --- tests/unit/postTestBuildComment.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 26c0711a34cd..24028ee4f1bd 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {when} from 'jest-when'; -import asMutable from '@src/types/utils/AsMutable'; +import asMutable from '@src/types/utils/asMutable'; import ghAction from '../../.github/actions/javascript/postTestBuildComment/postTestBuildComment'; import GithubUtils from '../../.github/libs/GithubUtils'; @@ -61,9 +61,7 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput) - .calledWith('PR_NUMBER', {required: true}) - .mockReturnValue(12 as unknown as string); + when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); @@ -94,6 +92,6 @@ describe('Post test build comments action tests', () => { } `); expect(createCommentMock).toBeCalledTimes(1); - expect(createCommentMock).toBeCalledWith('App', 12, message); + expect(createCommentMock).toBeCalledWith('App', '12', message); }); }); From e01e18bccbd26929b3a49d211a523c4723989d92 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 29 Feb 2024 09:54:53 +0100 Subject: [PATCH 034/146] fix: resolve comments --- src/libs/SidebarUtils.ts | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 4 ++-- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 463c796fb5ce..41a0f4b87a6c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -136,7 +136,7 @@ function getOrderedReportIDs( pinnedAndGBRReports.push(report); } else if (report?.hasDraft) { draftReports.push(report); - } else if (ReportUtils.isArchivedRoom(report) && report) { + } else if (report && ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); } else { nonArchivedReports.push(report); diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 6e71e30ac1b6..b4d118c5278b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -239,7 +239,7 @@ export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, initialValue: {}, }, @@ -261,7 +261,7 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but in this case it's not, this is a bug in withOnyx but it's impossible to fix it, when useOnyx will be introduce it will be fixed. + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, initialValue: {}, }, diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index c080272acbd2..a550dbd91657 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -215,6 +215,7 @@ const policySelector = (policy: OnyxEntry) => export default withOnyx, FloatingActionButtonAndPopoverOnyxProps>({ allPolicies: { key: ONYXKEYS.COLLECTION.POLICY, + // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, }, isLoading: { From 3484b06e87d161c3534329418a047a29cd28db82 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 29 Feb 2024 10:28:10 +0100 Subject: [PATCH 035/146] fix: typecheck --- tests/utils/LHNTestUtils.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 80f28002f975..f6bd01bbee74 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -282,7 +282,6 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( {}} insets={{ top: 0, @@ -290,7 +289,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { right: 0, bottom: 0, }} - isSmallScreenWidth={false} + // @ts-expect-error - normally this comes from withCurrentReportID hoc , but here we are just mocking this currentReportID={currentReportID} /> From 75cd434c4c72e8cb77ed5d8872ae136750be894b Mon Sep 17 00:00:00 2001 From: Ted Harris Date: Fri, 1 Mar 2024 17:21:01 +0000 Subject: [PATCH 036/146] 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 037/146] 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 038/146] 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 5e5289ba2f1724241b0f97ba0f669a1d27492392 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 10:40:52 +0100 Subject: [PATCH 039/146] fix: typecheck --- src/libs/SidebarUtils.ts | 2 +- src/pages/home/sidebar/SidebarLinksData.tsx | 47 ++++++++++----------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6be95b393472..81d8f5a46d53 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -74,7 +74,7 @@ function getOrderedReportIDs( betas: OnyxEntry, policies: OnyxCollection, priorityMode: OnyxEntry>, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 042f3b392d8a..1d0aa9e996ac 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -26,9 +26,13 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; -type ChatReportSelector = ReturnType & {isUnreadWithMention: boolean}; -type PolicySelector = ReturnType; -type ReportActionsSelector = ReturnType; +type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; +type PolicySelector = Pick; +type TransactionSelector = Pick< + OnyxTypes.Transaction, + 'reportID' | 'iouRequestType' | 'comment' | 'receipt' | 'merchant' | 'modifiedMerchant' | 'created' | 'modifiedCreated' | 'amount' | 'modifiedAmount' +>; +type ReportActionsSelector = Array>; type SidebarLinksDataOnyxProps = { /** List of reports */ @@ -44,7 +48,7 @@ type SidebarLinksDataOnyxProps = { betas: OnyxEntry; /** All transactions f */ - allTransactions: OnyxEntry; + allTransactions: OnyxCollection; /** All report actions for all reports */ allReportActions: OnyxEntry; @@ -82,6 +86,7 @@ function SidebarLinksData({ transactionViolations, currentReportID, }: SidebarLinksDataProps) { + console.log(allReportActions); const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -101,7 +106,7 @@ function SidebarLinksData({ return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -118,7 +123,7 @@ function SidebarLinksData({ betas, policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -166,7 +171,7 @@ function SidebarLinksData({ betas, policies as OnyxEntry>, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -222,15 +227,14 @@ SidebarLinksData.displayName = 'SidebarLinksData'; * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. */ -const chatReportSelector = (report: OnyxEntry) => - report && { +const chatReportSelector = (report: OnyxEntry): ChatReportSelector => + (report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, hasDraft: report.hasDraft, isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, - errors: report.errors, errorFields: { addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, }, @@ -252,9 +256,6 @@ const chatReportSelector = (report: OnyxEntry) => reportName: report.reportName, policyName: report.policyName, oldPolicyName: report.oldPolicyName, - isPolicyExpenseChat: report.isPolicyExpenseChat, - isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat, - isCancelledIOU: report.isCancelledIOU, // Other less obvious properites considered for sorting: ownerAccountID: report.ownerAccountID, currency: report.currency, @@ -264,7 +265,7 @@ const chatReportSelector = (report: OnyxEntry) => parentReportID: report.parentReportID, isDeletedParentAction: report.isDeletedParentAction, isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; + }) as ChatReportSelector; const reportActionsSelector = (reportActions: OnyxEntry) => reportActions && @@ -285,15 +286,15 @@ const reportActionsSelector = (reportActions: OnyxEntry }; }); -const policySelector = (policy: OnyxEntry) => - policy && { +const policySelector = (policy: OnyxEntry): PolicySelector => + (policy && { type: policy.type, name: policy.name, avatar: policy.avatar, - }; + }) as PolicySelector; -const transactionSelector = (transaction) => - transaction && { +const transactionSelector = (transaction: OnyxEntry): TransactionSelector => + (transaction && { reportID: transaction.reportID, iouRequestType: transaction.iouRequestType, comment: transaction.comment, @@ -304,14 +305,13 @@ const transactionSelector = (transaction) => modifiedAmount: transaction.modifiedAmount, created: transaction.created, modifiedCreated: transaction.modifiedCreated, - }; + }) as TransactionSelector; export default withCurrentReportID( withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. - selector: chatReportSelector as unknown as (report: OnyxEntry) => OnyxCollection, + selector: chatReportSelector, initialValue: {}, }, isLoadingApp: { @@ -337,8 +337,7 @@ export default withCurrentReportID( }, policies: { key: ONYXKEYS.COLLECTION.POLICY, - // This assertion is needed because the selector in withOnyx expects that the return type will be the same as type in ONYXKEYS but for collection keys the selector is executed for each collection item. This is a bug in withOnyx typings that we don't have a solution yet, when useOnyx hook is introduced it will be fixed. - selector: policySelector as unknown as (policy: OnyxEntry) => OnyxCollection, + selector: policySelector, initialValue: {}, }, policyMembers: { From ac55d2f98c493345f0064dc3f6014155aef57a37 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 11:33:16 +0100 Subject: [PATCH 040/146] fix: typecheck --- src/libs/SidebarUtils.ts | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 81d8f5a46d53..8d53e992cb2d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -70,11 +70,11 @@ function filterDisplayName(displayName: string): string { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: OnyxCollection, - betas: OnyxEntry, - policies: OnyxCollection, - priorityMode: OnyxEntry>, - allReportActions: OnyxCollection, + allReports: Record, + betas: Beta[], + policies: Record, + priorityMode: ValueOf, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], @@ -83,17 +83,17 @@ function getOrderedReportIDs( ): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports ?? {}); + const allReportsDictValues = Object.values(allReports); const reportIDsWithViolations = new Set(); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; - const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report?.parentReportActionID ?? '']; + const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? '']; const doesReportHaveViolations = canUseViolations && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); if (doesReportHaveViolations) { - reportIDsWithViolations.add(report?.reportID ?? ''); + reportIDsWithViolations.add(report.reportID); } return ReportUtils.shouldReportBeInOptionList({ report, @@ -124,14 +124,14 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndBrickRoadReports: Array> = []; + const pinnedAndBrickRoadReports: Report[] = []; const draftReports: Report[] = []; - const nonArchivedReports: Array> = []; + const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( - (report) => report?.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), + (report) => report.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), ); } // There are a few properties that need to be calculated for the report which are used when sorting reports. @@ -140,20 +140,17 @@ function getOrderedReportIDs( // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. // eslint-disable-next-line no-param-reassign - if (report) { - // eslint-disable-next-line no-param-reassign - report.displayName = filterDisplayName(ReportUtils.getReportName(report)); - } + report.displayName = filterDisplayName(ReportUtils.getReportName(report)); - const hasRBR = (!!report && report.reportID in reportIDsWithErrors) || reportIDsWithViolations.has(report?.reportID ?? ''); + const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID); - const isPinned = report?.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + const isPinned = report.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { pinnedAndBrickRoadReports.push(report); - } else if (report?.hasDraft) { + } else if (report.hasDraft) { draftReports.push(report); - } else if (report && ReportUtils.isArchivedRoom(report)) { + } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); } else { nonArchivedReports.push(report); @@ -179,7 +176,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); + const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); return LHNReports; } From 379485eaa374db1612b022d35aab75e67e8f818b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 15:13:04 +0100 Subject: [PATCH 041/146] fix: typecheck --- src/libs/OptionsListUtils.ts | 6 ++- src/libs/SidebarUtils.ts | 39 ++++++++-------- src/pages/home/sidebar/SidebarLinksData.tsx | 51 ++++++++++----------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 07f0df962455..1a7ea79e5d59 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -481,7 +481,11 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { +function getAllReportErrors( + report: OnyxEntry, + reportActions: OnyxEntry | ReportAction[] | undefined, + transactions: OnyxCollection = allTransactions, +): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d53e992cb2d..c22226553ffc 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -70,10 +70,10 @@ function filterDisplayName(displayName: string): string { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, - betas: Beta[], - policies: Record, - priorityMode: ValueOf, + allReports: OnyxCollection, + betas: OnyxEntry, + policies: OnyxCollection, + priorityMode: OnyxEntry>, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', @@ -83,17 +83,17 @@ function getOrderedReportIDs( ): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); const reportIDsWithViolations = new Set(); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; - const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? '']; + const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report?.parentReportActionID ?? '']; const doesReportHaveViolations = canUseViolations && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); if (doesReportHaveViolations) { - reportIDsWithViolations.add(report.reportID); + reportIDsWithViolations.add(report?.reportID ?? ''); } return ReportUtils.shouldReportBeInOptionList({ report, @@ -124,14 +124,14 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndBrickRoadReports: Report[] = []; - const draftReports: Report[] = []; - const nonArchivedReports: Report[] = []; - const archivedReports: Report[] = []; + const pinnedAndBrickRoadReports: Array> = []; + const draftReports: Array> = []; + const nonArchivedReports: Array> = []; + const archivedReports: Array> = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( - (report) => report.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), + (report) => report?.reportID === currentReportId || ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID), ); } // There are a few properties that need to be calculated for the report which are used when sorting reports. @@ -140,15 +140,18 @@ function getOrderedReportIDs( // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. // eslint-disable-next-line no-param-reassign - report.displayName = filterDisplayName(ReportUtils.getReportName(report)); + if (report) { + // eslint-disable-next-line no-param-reassign + report.displayName = filterDisplayName(ReportUtils.getReportName(report)); + } - const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID); + const hasRBR = (!!report && report?.reportID in reportIDsWithErrors) || reportIDsWithViolations.has(report?.reportID ?? ''); - const isPinned = report.isPinned ?? false; - const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); + const isPinned = report?.isPinned ?? false; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { pinnedAndBrickRoadReports.push(report); - } else if (report.hasDraft) { + } else if (report?.hasDraft) { draftReports.push(report); } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); @@ -176,7 +179,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 1d0aa9e996ac..f9368bac86d2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -51,7 +51,7 @@ type SidebarLinksDataOnyxProps = { allTransactions: OnyxCollection; /** All report actions for all reports */ - allReportActions: OnyxEntry; + allReportActions: OnyxCollection; /** The policies which the user has access to */ policies: OnyxCollection; @@ -86,7 +86,6 @@ function SidebarLinksData({ transactionViolations, currentReportID, }: SidebarLinksDataProps) { - console.log(allReportActions); const {accountID} = useCurrentUserPersonalDetails(); const network = useNetwork(); const isFocused = useIsFocused(); @@ -105,8 +104,8 @@ function SidebarLinksData({ const reportKeys = Object.keys(chatReports ?? {}); return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; - const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions as OnyxCollection) || {}; + const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS) ?? '']; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxTypes.ReportAction[], allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -119,11 +118,11 @@ function SidebarLinksData({ const optionListItems: string[] = useMemo(() => { const reportIDs = SidebarUtils.getOrderedReportIDs( null, - chatReports as OnyxEntry>, + chatReports, betas, - policies as OnyxEntry>, + policies as OnyxCollection, priorityMode, - allReportActions as OnyxCollection, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -167,11 +166,11 @@ function SidebarLinksData({ if (currentReportID && !optionListItems?.includes(currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportID, - chatReports as OnyxEntry>, + chatReports as OnyxCollection, betas, - policies as OnyxEntry>, + policies as OnyxCollection, priorityMode, - allReportActions as OnyxCollection, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -268,23 +267,23 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele }) as ChatReportSelector; const reportActionsSelector = (reportActions: OnyxEntry) => - reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors, originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; + (reportActions && + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors, originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; - return { - reportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - } as Message, - ], - originalMessage, - }; - }); + return { + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + } as Message, + ], + originalMessage, + }; + })) as ReportActionsSelector; const policySelector = (policy: OnyxEntry): PolicySelector => (policy && { From dc64a57d4501a1fc99631e73b226f660875e8f24 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 5 Mar 2024 16:43:51 +0100 Subject: [PATCH 042/146] fix: types --- src/libs/OptionsListUtils.ts | 6 +-- src/pages/home/sidebar/SidebarLinksData.tsx | 45 ++++++++++++--------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 1a7ea79e5d59..2b3125db47ce 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -481,11 +481,7 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors( - report: OnyxEntry, - reportActions: OnyxEntry | ReportAction[] | undefined, - transactions: OnyxCollection = allTransactions, -): OnyxCommon.Errors { +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry | undefined, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index f9368bac86d2..0f4daa6f5f76 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -23,7 +23,6 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; @@ -32,7 +31,7 @@ type TransactionSelector = Pick< OnyxTypes.Transaction, 'reportID' | 'iouRequestType' | 'comment' | 'receipt' | 'merchant' | 'modifiedMerchant' | 'created' | 'modifiedCreated' | 'amount' | 'modifiedAmount' >; -type ReportActionsSelector = Array>; +type ReportActionsSelector = Record>; type SidebarLinksDataOnyxProps = { /** List of reports */ @@ -105,7 +104,9 @@ function SidebarLinksData({ return reportKeys.reduce((errorsMap, reportKey) => { const report = chatReports?.[reportKey] ?? null; const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS) ?? '']; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxTypes.ReportAction[], allTransactions as OnyxCollection) || {}; + + const errors = + OptionsListUtils.getAllReportErrors(report, allReportsActions as OnyxEntry, allTransactions as OnyxCollection) || {}; if (Object.keys(errors).length === 0) { return errorsMap; } @@ -255,6 +256,9 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele reportName: report.reportName, policyName: report.policyName, oldPolicyName: report.oldPolicyName, + isPolicyExpenseChat: report.isPolicyExpenseChat, + isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat, + isCancelledIOU: report.isCancelledIOU, // Other less obvious properites considered for sorting: ownerAccountID: report.ownerAccountID, currency: report.currency, @@ -266,24 +270,29 @@ const chatReportSelector = (report: OnyxEntry): ChatReportSele isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }) as ChatReportSelector; -const reportActionsSelector = (reportActions: OnyxEntry) => +const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => (reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors, originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; + Object.fromEntries( + Object.entries(reportActions).map(([key, reportAction]) => { + const {reportActionID, actionName, errors, originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; - return { - reportActionID, - actionName, - errors, - message: [ + return [ + key, { - moderationDecision: {decision}, - } as Message, - ], - originalMessage, - }; - })) as ReportActionsSelector; + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + }, + ], + originalMessage, + }, + ]; + }), + )) as ReportActionsSelector; const policySelector = (policy: OnyxEntry): PolicySelector => (policy && { From f86fbdb470d32bfd32b95675d807661cbc914faa Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 6 Mar 2024 11:35:06 +0100 Subject: [PATCH 043/146] 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 044/146] 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 045/146] 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 96dc9e0f066cffbffa86fd53c6fc335705923fe5 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Fri, 8 Mar 2024 09:53:21 +0000 Subject: [PATCH 046/146] [TS migration][postTestBuildComment] Issue fixed --- tests/unit/postTestBuildComment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 24028ee4f1bd..5d4148134e19 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -61,7 +61,7 @@ describe('Post test build comments action tests', () => { }); test('Test GH action', async () => { - when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue(12); + when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue('12'); when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); From f7567c3e11d944d1686d439da5e4aaf89fc6e93d Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 8 Mar 2024 15:18:11 +0100 Subject: [PATCH 047/146] feat: adding delete --- src/languages/en.ts | 3 + .../CreatePolicyDistanceRateParams.ts | 4 +- .../DeletePolicyDistanceRatesParams.ts | 6 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Policy.ts | 127 +++++++++++++++++- .../distanceRates/PolicyDistanceRatesPage.tsx | 44 +++--- .../PolicyNewDistanceRatePage.tsx | 11 +- 8 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index d7d8e10eb64f..a74a22bad563 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1926,6 +1926,9 @@ export default { status: 'Status', enabled: 'Enabled', disabled: 'Disabled', + errors: { + createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + }, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts index ea06408d4402..82b796a23960 100644 --- a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts +++ b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts @@ -1,9 +1,7 @@ -import type {Rate} from '@src/types/onyx/Policy'; - type CreatePolicyDistanceRateParams = { policyID: string; customUnitID: string; - customUnitRate: Rate; + customUnitRate: string; }; export default CreatePolicyDistanceRateParams; diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts new file mode 100644 index 000000000000..801775631ef2 --- /dev/null +++ b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts @@ -0,0 +1,6 @@ +type DeletePolicyDistanceRatesParams = { + policyID: string; + customUnitRateIDs: string[]; +}; + +export default DeletePolicyDistanceRatesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 85f3d9d87f57..1d1be3ea2e28 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -161,3 +161,4 @@ export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; +export type {default as DeletePolicyDistanceRatesParams} from './DeletePolicyDistanceRatesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 51cf2721878b..a8d575bf9767 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -161,6 +161,7 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', + DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', } as const; type WriteCommand = ValueOf; @@ -320,6 +321,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; + [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8c3e2af51fd8..33480cb42f4a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -14,6 +14,7 @@ import type { CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, + DeletePolicyDistanceRatesParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, OpenDraftWorkspaceRequestParams, @@ -2719,7 +2720,11 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } -function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { +function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, policyID?: string) { + if (!policyID) { + return; + } + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2739,6 +2744,25 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom }, ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + errors: null, + pendingAction: null, + }, + }, + }, + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2749,7 +2773,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom rates: { [customUnitRate.customUnitRateID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), - pendingAction: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, }, @@ -2761,10 +2785,103 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom const params: CreatePolicyDistanceRateParams = { policyID, customUnitID, - customUnitRate, + customUnitRate: JSON.stringify(customUnitRate), + }; + + API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); +} + +function clearCreateDistanceRateError(policyID: string, currentRates: Record, customUnitID?: string, customUnitRateIDToClear?: string) { + if (!policyID || !customUnitID || !customUnitRateIDToClear) { + return; + } + + const updatedRates = {...currentRates}; + delete updatedRates[customUnitRateIDToClear]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + customUnits: { + [customUnitID]: { + rates: updatedRates, + }, + }, + }); +} + +function deletePolicyDistanceRates(policyID: string, rateIDsToDelete: string[], customUnit?: CustomUnit) { + if (!policyID || !rateIDsToDelete || !customUnit) { + return; + } + + const currentRates = customUnit.rates; + const optimisticRates: Record = {}; + const successRates: Record = {}; + + Object.keys(customUnit.rates).forEach((rateID) => { + if (rateIDsToDelete.includes(rateID)) { + optimisticRates[rateID] = { + ...customUnit.rates[rateID], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }; + successRates[rateID] = { + ...customUnit.rates[rateID], + pendingAction: null, + }; + } else { + optimisticRates[rateID] = customUnit.rates[rateID]; + successRates[rateID] = customUnit.rates[rateID]; + } + }); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: optimisticRates, + }, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: successRates, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: currentRates, + }, + errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.deleteRateGenericFailureMessage'), + }, + }, + }, + ]; + + const params: DeletePolicyDistanceRatesParams = { + policyID, + customUnitRateIDs: rateIDsToDelete, }; - API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, failureData}); + API.write(WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES, params, {optimisticData, successData, failureData}); } export { @@ -2824,4 +2941,6 @@ export { openPolicyDistanceRatesPage, generateCustomUnitID, createPolicyDistanceRate, + clearCreateDistanceRateError, + deletePolicyDistanceRates, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index dfa6817f235d..8f2b01ff69f7 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -13,6 +13,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import TableListItem from '@components/SelectionList/TableListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -33,13 +34,7 @@ import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; -type RateForList = { - value: string; - text: string; - keyForList: string; - isSelected: boolean; - rightElement: React.ReactNode; -}; +type RateForList = ListItem & {value: string}; type PolicyDistanceRatesPageOnyxProps = { /** Policy details */ @@ -56,6 +51,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const [selectedDistanceRates, setSelectedDistanceRates] = useState([]); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const dropdownButtonRef = useRef(null); + const policyID = route.params.policyID; const customUnit: CustomUnit | undefined = useMemo( () => (policy?.customUnits !== undefined ? policy?.customUnits[Object.keys(policy?.customUnits)[0]] : undefined), @@ -64,9 +60,19 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const customUnitRates: Record = useMemo(() => customUnit?.rates ?? {}, [customUnit]); function fetchDistanceRates() { - Policy.openPolicyDistanceRatesPage(route.params.policyID); + Policy.openPolicyDistanceRatesPage(policyID); } + const dismissError = useCallback( + (item: RateForList) => { + if (item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { + return; + } + Policy.clearCreateDistanceRateError(policyID, customUnitRates, customUnit?.customUnitID, item.value); + }, + [customUnit?.customUnitID, customUnitRates, policyID], + ); + const {isOffline} = useNetwork({onReconnect: fetchDistanceRates}); useEffect(() => { @@ -83,6 +89,8 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) )}`, keyForList: value.customUnitRateID ?? '', isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined, + pendingAction: value.pendingAction, + errors: value.errors ?? undefined, rightElement: ( @@ -101,15 +109,15 @@ 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(policyID)); }; const openSettings = () => { - // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(route.params.policyID)); + // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(policyID)); }; const editRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(route.params.policyID, rateID)); + // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(policyID, rateID)); }; const disableRates = () => { @@ -127,7 +135,12 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - // run deleteWorkspaceDistanceRates for all selected rows + Policy.deletePolicyDistanceRates( + policyID, + selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''), + customUnit, + ); + setSelectedDistanceRates([]); return; } @@ -232,8 +245,8 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); return ( - - + + - + ({ policy: { From c6df4ac1449c22be816dfcbf73c3a1200deca69e Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 8 Mar 2024 16:13:43 +0100 Subject: [PATCH 048/146] Add return types and missing return --- src/libs/actions/ReimbursementAccount/store.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/ReimbursementAccount/store.ts b/src/libs/actions/ReimbursementAccount/store.ts index 6e14cf72a569..c82843020f66 100644 --- a/src/libs/actions/ReimbursementAccount/store.ts +++ b/src/libs/actions/ReimbursementAccount/store.ts @@ -12,16 +12,16 @@ Onyx.connect({ }, }); -function getBankAccountList() { +function getBankAccountList(): OnyxEntry { return bankAccountList; } -function hasCreditBankAccount() { +function hasCreditBankAccount(): boolean { if (!bankAccountList) { return false; } - Object.values(bankAccountList).some((bankAccountJSON) => { + return Object.values(bankAccountList).some((bankAccountJSON) => { const bankAccount = new BankAccount(bankAccountJSON); return bankAccount.isDefaultCredit(); }); From a85e54136d2f034a2a4266c60f8bfc7a7bb21e1f Mon Sep 17 00:00:00 2001 From: Eric Han Date: Sat, 9 Mar 2024 10:05:27 +0800 Subject: [PATCH 049/146] fix lint and improve comment and naming --- src/CONST.ts | 6 +-- src/libs/ReportActionsUtils.ts | 8 +-- src/pages/home/report/ReportActionItem.tsx | 62 +++++++++++----------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 5430c7cbae74..50c76676c990 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -573,20 +573,20 @@ const CONST = { SPLIT_REPORTID: '-2', ACTIONS: { LIMIT: 50, - // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMarkedReimbursedMessage in ReportActionsUtils.ts + // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts TYPE: { ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', ADDCOMMENT: 'ADDCOMMENT', ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST', APPROVED: 'APPROVED', - CHANGEFIELD: 'CHANGEFIELD', // 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 + DELETEDACCOUNT: 'DELETEDACCOUNT', // OldDot Action DONATION: 'DONATION', // OldDot Action EXPORTEDTOINTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action EXPORTEDTOQUICKBOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index cc2f8612d9cc..fcea05473007 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -809,10 +809,10 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): } /** - * MARKEDREIMBURSED reportActions come from marking a report as reimbursed in OldDot. For now, we just - * concat all of the text elements of the message to create the full message. + * Helper method to format message of OldDot Actions. + * For now, we just concat all of the text elements of the message to create the full message. */ -function getMarkedReimbursedMessage(reportAction: OnyxEntry): string { +function getMessageOfOldDotReportAction(reportAction: OnyxEntry): string { return reportAction?.message?.map((element) => element.text).join('') ?? ''; } @@ -977,7 +977,7 @@ export { getFirstVisibleReportActionID, isMemberChangeAction, getMemberChangeMessageFragment, - getMarkedReimbursedMessage, + getMessageOfOldDotReportAction, getMemberChangeMessagePlainText, isReimbursementDeQueuedAction, isActionableMentionWhisper, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index f93f4a5c69b4..efc3dc28f948 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -517,37 +517,39 @@ function ReportActionItem({ children = ; } else if (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.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 - ].find(oldDotActionName => oldDotActionName === action.actionName)) { + } 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.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, + ].find((oldDotActionName) => oldDotActionName === action.actionName) + ) { // This handles all historical actions from OldDot that we just want to display the message text - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.UNHOLD) { From 8cc0c0349afda7772ca5b4db26874fce9fce4aa7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 11 Mar 2024 11:41:20 +0100 Subject: [PATCH 050/146] fix: resolve comment --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 50481f7f1619..3dd23752d5db 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -480,7 +480,7 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry | undefined, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( From d0d8d2c58791f0db6bd7233f846359375d00bd28 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 12:22:19 +0100 Subject: [PATCH 051/146] feat: add --- src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts | 1 + src/libs/actions/Policy.ts | 1 + src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts index 801775631ef2..d9c31930897e 100644 --- a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts +++ b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts @@ -1,5 +1,6 @@ type DeletePolicyDistanceRatesParams = { policyID: string; + customUnitID: string; customUnitRateIDs: string[]; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 42b98bf0742d..a390df8d2332 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2929,6 +2929,7 @@ function deletePolicyDistanceRates(policyID: string, rateIDsToDelete: string[], const params: DeletePolicyDistanceRatesParams = { policyID, + customUnitID: customUnit.customUnitID, customUnitRateIDs: rateIDsToDelete, }; diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx index 8debf6a69cdd..f7ebdf27cb73 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -45,7 +45,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) enabled: true, }; - createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + createPolicyDistanceRate(newRate, customUnitID, route.params.policyID); Navigation.goBack(); }; From e4d85a65888d68b656d1f661224da2284a4e89db Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:38:01 +0100 Subject: [PATCH 052/146] Revert "Revert "[New architecture] Replace `@oguzhnatly/react-native-image-manipulator` with `expo-image-manipulator`"" This reverts commit 52491bd5da97c3c5465d650eec8ee4119b584299. --- ios/Podfile.lock | 22 ++- package-lock.json | 27 +++- package.json | 2 +- ...react-native-image-manipulator+1.0.5.patch | 19 --- src/CONST.ts | 6 + src/libs/cropOrRotateImage/getSaveFormat.ts | 17 +++ src/libs/cropOrRotateImage/index.native.ts | 6 +- src/libs/cropOrRotateImage/index.ts | 136 ++---------------- src/libs/cropOrRotateImage/types.ts | 25 ++-- 9 files changed, 90 insertions(+), 170 deletions(-) delete mode 100644 patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch create mode 100644 src/libs/cropOrRotateImage/getSaveFormat.ts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c93dfba50f5a..491ec28b59e5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -35,6 +35,9 @@ PODS: - EXAV (13.10.4): - ExpoModulesCore - ReactCommon/turbomodule/core + - EXImageLoader (4.6.0): + - ExpoModulesCore + - React-Core - Expo (50.0.4): - ExpoModulesCore - ExpoImage (1.10.1): @@ -43,6 +46,9 @@ PODS: - SDWebImageAVIFCoder (~> 0.10.1) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.13.0) + - ExpoImageManipulator (11.8.0): + - EXImageLoader + - ExpoModulesCore - ExpoModulesCore (1.11.8): - glog - RCT-Folly (= 2022.05.16.00) @@ -1175,8 +1181,6 @@ PODS: - React-Core - react-native-geolocation (3.0.6): - React-Core - - react-native-image-manipulator (1.0.5): - - React - react-native-image-picker (7.0.3): - React-Core - react-native-key-command (1.0.6): @@ -1475,8 +1479,10 @@ DEPENDENCIES: - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXAV (from `../node_modules/expo-av/ios`) + - EXImageLoader (from `../node_modules/expo-image-loader/ios`) - Expo (from `../node_modules/expo`) - ExpoImage (from `../node_modules/expo-image/ios`) + - ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) @@ -1536,7 +1542,6 @@ DEPENDENCIES: - react-native-config (from `../node_modules/react-native-config`) - react-native-document-picker (from `../node_modules/react-native-document-picker`) - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - - "react-native-image-manipulator (from `../node_modules/@oguzhnatly/react-native-image-manipulator`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-key-command (from `../node_modules/react-native-key-command`) - react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`) @@ -1658,10 +1663,14 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXAV: :path: "../node_modules/expo-av/ios" + EXImageLoader: + :path: "../node_modules/expo-image-loader/ios" Expo: :path: "../node_modules/expo" ExpoImage: :path: "../node_modules/expo-image/ios" + ExpoImageManipulator: + :path: "../node_modules/expo-image-manipulator/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" FBLazyVector: @@ -1731,8 +1740,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-document-picker" react-native-geolocation: :path: "../node_modules/@react-native-community/geolocation" - react-native-image-manipulator: - :path: "../node_modules/@oguzhnatly/react-native-image-manipulator" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-key-command: @@ -1860,8 +1867,10 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 EXAV: 09a4d87fa6b113fbb0ada3aade6799f78271cb44 + EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b Expo: 1e3bcf9dd99de57a636127057f6b488f0609681a ExpoImage: 1cdaa65a6a70bb01067e21ad1347ff2d973885f5 + ExpoImageManipulator: c1d7cb865eacd620a35659f3da34c70531f10b59 ExpoModulesCore: 96d1751929ad10622773bb729ab28a8423f0dd0c FBLazyVector: fbc4957d9aa695250b55d879c1d86f79d7e69ab4 FBReactNativeSpec: 86de768f89901ef6ed3207cd686362189d64ac88 @@ -1935,7 +1944,6 @@ SPEC CHECKSUMS: react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e react-native-document-picker: 3599b238843369026201d2ef466df53f77ae0452 react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903 - react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56 react-native-image-picker: 2381c008bbb09e72395a2d043c147b11bd1523d9 react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d @@ -1999,7 +2007,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 diff --git a/package-lock.json b/package-lock.json index 0f11dc0a485a..29f5e23eb40f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", "@kie/mock-github": "^1.0.0", - "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "10.6.0", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", @@ -56,6 +55,7 @@ "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", + "expo-image-manipulator": "11.8.0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -8048,12 +8048,6 @@ "@octokit/openapi-types": "^12.11.0" } }, - "node_modules/@oguzhnatly/react-native-image-manipulator": { - "version": "1.0.5", - "resolved": "git+ssh://git@github.com/Expensify/react-native-image-manipulator.git#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "integrity": "sha512-C9Br1BQqm6io6lvYHptlLcOHbzlaqxp9tS35P8Qj3pdiiYRTzU3KPvZ61rQ+ZnZ4FOQ6MwPsKsmB8+6WHkAY6Q==", - "license": "MIT" - }, "node_modules/@onfido/active-video-capture": { "version": "0.28.6", "resolved": "https://registry.npmjs.org/@onfido/active-video-capture/-/active-video-capture-0.28.6.tgz", @@ -31150,6 +31144,25 @@ "expo": "*" } }, + "node_modules/expo-image-loader": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.6.0.tgz", + "integrity": "sha512-RHQTDak7/KyhWUxikn2yNzXL7i2cs16cMp6gEAgkHOjVhoCJQoOJ0Ljrt4cKQ3IowxgCuOrAgSUzGkqs7omj8Q==", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-manipulator": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-11.8.0.tgz", + "integrity": "sha512-ZWVrHnYmwJq6h7auk+ropsxcNi+LyZcPFKQc8oy+JA0SaJosfShvkCm7RADWAunHmfPCmjHrhwPGEu/rs7WG/A==", + "dependencies": { + "expo-image-loader": "~4.6.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-keep-awake": { "version": "12.8.2", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz", diff --git a/package.json b/package.json index 46e41b04ab2c..93853d88ba67 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", "@kie/mock-github": "^1.0.0", - "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "10.6.0", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", @@ -105,6 +104,7 @@ "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", + "expo-image-manipulator": "11.8.0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", diff --git a/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch b/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch deleted file mode 100644 index d5a390daf201..000000000000 --- a/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle b/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle -index 3a1a548..fe030bb 100644 ---- a/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle -+++ b/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle -@@ -13,12 +13,12 @@ buildscript { - apply plugin: 'com.android.library' - - android { -- compileSdkVersion 28 -+ compileSdkVersion 34 - buildToolsVersion "28.0.3" - - defaultConfig { - minSdkVersion 16 -- targetSdkVersion 28 -+ targetSdkVersion 34 - versionCode 1 - versionName "1.0" - } diff --git a/src/CONST.ts b/src/CONST.ts index ce2029c78713..645746c38a7d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1035,6 +1035,12 @@ const CONST = { VIDEO: 'video', }, + IMAGE_FILE_FORMAT: { + PNG: 'image/png', + WEBP: 'image/webp', + JPEG: 'image/jpeg', + }, + FILE_TYPE_REGEX: { // Image MimeTypes allowed by iOS photos app. IMAGE: /\.(jpg|jpeg|png|webp|gif|tiff|bmp|heic|heif)$/, diff --git a/src/libs/cropOrRotateImage/getSaveFormat.ts b/src/libs/cropOrRotateImage/getSaveFormat.ts new file mode 100644 index 000000000000..6d1ff280c5c5 --- /dev/null +++ b/src/libs/cropOrRotateImage/getSaveFormat.ts @@ -0,0 +1,17 @@ +import {SaveFormat} from 'expo-image-manipulator'; +import CONST from '@src/CONST'; + +function getSaveFormat(type: string) { + switch (type) { + case CONST.IMAGE_FILE_FORMAT.PNG: + return SaveFormat.PNG; + case CONST.IMAGE_FILE_FORMAT.WEBP: + return SaveFormat.WEBP; + case CONST.IMAGE_FILE_FORMAT.JPEG: + return SaveFormat.JPEG; + default: + return SaveFormat.JPEG; + } +} + +export default getSaveFormat; diff --git a/src/libs/cropOrRotateImage/index.native.ts b/src/libs/cropOrRotateImage/index.native.ts index c3fed59ba4fa..03c233450435 100644 --- a/src/libs/cropOrRotateImage/index.native.ts +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -1,5 +1,6 @@ -import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator'; +import {manipulateAsync} from 'expo-image-manipulator'; import RNFetchBlob from 'react-native-blob-util'; +import getSaveFormat from './getSaveFormat'; import type {CropOrRotateImage} from './types'; /** @@ -7,7 +8,8 @@ import type {CropOrRotateImage} from './types'; */ const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => new Promise((resolve) => { - RNImageManipulator.manipulate(uri, actions, options).then((result) => { + const format = getSaveFormat(options.type); + manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => { RNFetchBlob.fs.stat(result.uri.replace('file://', '')).then(({size}) => { resolve({ ...result, diff --git a/src/libs/cropOrRotateImage/index.ts b/src/libs/cropOrRotateImage/index.ts index ea3d51a465ec..1b669999a1ee 100644 --- a/src/libs/cropOrRotateImage/index.ts +++ b/src/libs/cropOrRotateImage/index.ts @@ -1,127 +1,19 @@ -import type {CropOptions, CropOrRotateImage, CropOrRotateImageOptions} from './types'; - -type SizeFromAngle = { - width: number; - height: number; -}; - -/** - * Calculates a size of canvas after rotation - */ -function sizeFromAngle(width: number, height: number, angle: number): SizeFromAngle { - const radians = (angle * Math.PI) / 180; - let sine = Math.cos(radians); - let cosine = Math.sin(radians); - if (cosine < 0) { - cosine = -cosine; - } - if (sine < 0) { - sine = -sine; - } - return {width: height * cosine + width * sine, height: height * sine + width * cosine}; -} - -/** - * Creates a new rotated canvas - */ -function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasElement { - const {width, height} = sizeFromAngle(canvas.width, canvas.height, degrees); - - // We have to create a new canvas because it is not possible to change already drawn - // elements. Transformations such as rotation have to be applied before drawing - const result = document.createElement('canvas'); - result.width = width; - result.height = height; - - const context = result.getContext('2d'); - if (context) { - // In order to rotate image along its center we have to apply next transformation - context.translate(result.width / 2, result.height / 2); - - const radians = (degrees * Math.PI) / 180; - context.rotate(radians); - - context.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); - } - return result; -} - -/** - * Creates new cropped canvas and returns it - */ -function cropCanvas(canvas: HTMLCanvasElement, options: CropOptions) { - let {originX = 0, originY = 0, width = 0, height = 0} = options; - const clamp = (value: number, max: number) => Math.max(0, Math.min(max, value)); - - width = clamp(width, canvas.width); - height = clamp(height, canvas.height); - originX = clamp(originX, canvas.width); - originY = clamp(originY, canvas.height); - - width = Math.min(originX + width, canvas.width) - originX; - height = Math.min(originY + height, canvas.height) - originY; - - const result = document.createElement('canvas'); - result.width = width; - result.height = height; - - const context = result.getContext('2d'); - context?.drawImage(canvas, originX, originY, width, height, 0, 0, width, height); - - return result; -} - -function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateImageOptions): Promise { - return new Promise((resolve) => { - canvas.toBlob((blob) => { - if (!blob) { - return; - } - const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); - file.uri = URL.createObjectURL(file); - resolve(file); - }); - }); -} - -/** - * Loads image from specified url - */ -function loadImageAsync(uri: string): Promise { - return new Promise((resolve, reject) => { - const imageSource = new Image(); - imageSource.crossOrigin = 'anonymous'; - const canvas = document.createElement('canvas'); - imageSource.onload = () => { - canvas.width = imageSource.naturalWidth; - canvas.height = imageSource.naturalHeight; - - const context = canvas.getContext('2d'); - context?.drawImage(imageSource, 0, 0, imageSource.naturalWidth, imageSource.naturalHeight); - - resolve(canvas); - }; - imageSource.onerror = () => reject(canvas); - imageSource.src = uri; - }); -} - -/** - * Crops and rotates the image on web - */ +import {manipulateAsync} from 'expo-image-manipulator'; +import getSaveFormat from './getSaveFormat'; +import type {CropOrRotateImage} from './types'; const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => - loadImageAsync(uri).then((originalCanvas) => { - const resultCanvas = actions.reduce((canvas, action) => { - if (action.crop) { - return cropCanvas(canvas, action.crop); - } - if (action.rotate) { - return rotateCanvas(canvas, action.rotate); - } - return canvas; - }, originalCanvas); - return convertCanvasToFile(resultCanvas, options); + new Promise((resolve) => { + const format = getSaveFormat(options.type); + manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => { + fetch(result.uri) + .then((res) => res.blob()) + .then((blob) => { + const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); + file.uri = URL.createObjectURL(file); + resolve(file); + }); + }); }); export default cropOrRotateImage; diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts index f882e4f9bea2..9d9b9ff98080 100644 --- a/src/libs/cropOrRotateImage/types.ts +++ b/src/libs/cropOrRotateImage/types.ts @@ -1,4 +1,4 @@ -import type {RNImageManipulatorResult} from '@oguzhnatly/react-native-image-manipulator'; +import type {ImageResult} from 'expo-image-manipulator'; type CropOrRotateImageOptions = { type: string; @@ -6,20 +6,21 @@ type CropOrRotateImageOptions = { compress: number; }; -type CropOptions = { - originX: number; - originY: number; - width: number; - height: number; +type CropAction = { + crop: { + originX: number; + originY: number; + width: number; + height: number; + }; }; -type Action = { - crop?: CropOptions; - rotate?: number; -}; +type RotateOption = {rotate: number}; + +type Action = CropAction | RotateOption; -type CustomRNImageManipulatorResult = RNImageManipulatorResult & {size: number; type: string; name: string}; +type CustomRNImageManipulatorResult = ImageResult & {size: number; type: string; name: string}; type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise; -export type {CropOrRotateImage, CropOptions, Action, CropOrRotateImageOptions, CustomRNImageManipulatorResult}; +export type {CustomRNImageManipulatorResult, CropOrRotateImage}; From d27b9fed4059e68cb9808204fccd75d6a70ba0e2 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:21:14 +0100 Subject: [PATCH 053/146] fix: ts error --- src/languages/en.ts | 1 + src/languages/es.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index be2d0dc569fd..8beffa9e8545 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1928,6 +1928,7 @@ export default { enabled: 'Enabled', disabled: 'Disabled', errors: { + deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e2418d89233..97cd315dc072 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1952,6 +1952,10 @@ export default { status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', + errors: { + deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', + createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + }, }, editor: { nameInputLabel: 'Nombre', From cfa91452f167636ea4ea6952a22975551e0e2864 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:32:40 +0100 Subject: [PATCH 054/146] fix: scrap delete from this PR --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - .../DeletePolicyDistanceRatesParams.ts | 7 -- src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 2 - src/libs/actions/Policy.ts | 79 ------------------- .../distanceRates/PolicyDistanceRatesPage.tsx | 8 +- 7 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 8beffa9e8545..be2d0dc569fd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1928,7 +1928,6 @@ export default { enabled: 'Enabled', disabled: 'Disabled', errors: { - deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 97cd315dc072..b85071197298 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1953,7 +1953,6 @@ export default { enabled: 'Activada', disabled: 'Desactivada', errors: { - deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts deleted file mode 100644 index d9c31930897e..000000000000 --- a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts +++ /dev/null @@ -1,7 +0,0 @@ -type DeletePolicyDistanceRatesParams = { - policyID: string; - customUnitID: string; - customUnitRateIDs: string[]; -}; - -export default DeletePolicyDistanceRatesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 1d1be3ea2e28..85f3d9d87f57 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -161,4 +161,3 @@ export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; -export type {default as DeletePolicyDistanceRatesParams} from './DeletePolicyDistanceRatesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a8d575bf9767..51cf2721878b 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -161,7 +161,6 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', - DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', } as const; type WriteCommand = ValueOf; @@ -321,7 +320,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; - [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index a390df8d2332..e6fcbe9e1ba9 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -14,7 +14,6 @@ import type { CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, - DeletePolicyDistanceRatesParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, OpenDraftWorkspaceRequestParams, @@ -2859,83 +2858,6 @@ function clearCreateDistanceRateError(policyID: string, currentRates: Record = {}; - const successRates: Record = {}; - - Object.keys(customUnit.rates).forEach((rateID) => { - if (rateIDsToDelete.includes(rateID)) { - optimisticRates[rateID] = { - ...customUnit.rates[rateID], - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - }; - successRates[rateID] = { - ...customUnit.rates[rateID], - pendingAction: null, - }; - } else { - optimisticRates[rateID] = customUnit.rates[rateID]; - successRates[rateID] = customUnit.rates[rateID]; - } - }); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: optimisticRates, - }, - }, - }, - }, - ]; - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: successRates, - }, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: currentRates, - }, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.deleteRateGenericFailureMessage'), - }, - }, - }, - ]; - - const params: DeletePolicyDistanceRatesParams = { - policyID, - customUnitID: customUnit.customUnitID, - customUnitRateIDs: rateIDsToDelete, - }; - - API.write(WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES, params, {optimisticData, successData, failureData}); -} - export { removeMembers, updateWorkspaceMembersRole, @@ -2994,5 +2916,4 @@ export { generateCustomUnitID, createPolicyDistanceRate, clearCreateDistanceRateError, - deletePolicyDistanceRates, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 8f2b01ff69f7..a2838e9fd04c 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -135,13 +135,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - Policy.deletePolicyDistanceRates( - policyID, - selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''), - customUnit, - ); - setSelectedDistanceRates([]); - return; + // run deleteWorkspaceDistanceRates for all selected rows } setIsWarningModalVisible(true); From 2b1baf71e918bb6017578c8d627ac7e01e7ec2c7 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:34:05 +0100 Subject: [PATCH 055/146] fix: one line --- src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index a2838e9fd04c..b9e1ca47b8a8 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -136,6 +136,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { // run deleteWorkspaceDistanceRates for all selected rows + return; } setIsWarningModalVisible(true); From 1b53b39dfd7f390eddf51ad184f3c2f5776b9ed2 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:51:34 +0100 Subject: [PATCH 056/146] feat: add validation --- .../PolicyNewDistanceRatePage.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx index f7ebdf27cb73..b0745cb52055 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -4,12 +4,16 @@ 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 InputWrapperWithRef from '@components/Form/InputWrapper'; +import type {FormInputErrors, 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 * as CurrencyUtils from '@libs/CurrencyUtils'; +import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import * as NumberUtils from '@libs/NumberUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -30,12 +34,27 @@ type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScr function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = 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 validate = (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + const rate = values.rate; + const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); + const decimalSeparator = toLocaleDigit('.'); + // Allow one more decimal place for accuracy + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i'); + if (!rateValueRegex.test(parsedRate) || parsedRate === '') { + errors.rate = 'workspace.reimburse.invalidRateError'; + } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { + errors.rate = 'workspace.reimburse.lowRateError'; + } + return errors; + }; + const submit = (values: FormOnyxValues) => { const newRate: Rate = { currency, @@ -62,13 +81,14 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) formID={ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM} submitButtonText={translate('common.save')} onSubmit={submit} + validate={validate} enabledWhenOffline style={[styles.flexGrow1]} shouldHideFixErrorsAlert submitFlexEnabled={false} submitButtonStyles={[styles.mh5, styles.mt0]} > - Date: Tue, 12 Mar 2024 11:43:41 +0800 Subject: [PATCH 057/146] add oldDot action type EXPORTEDTOCSV --- src/CONST.ts | 1 + src/pages/home/report/ReportActionItem.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 50c76676c990..72da362a3bce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -588,6 +588,7 @@ const CONST = { DELEGATESUBMIT: 'DELEGATESUBMIT', // OldDot Action DELETEDACCOUNT: 'DELETEDACCOUNT', // OldDot Action DONATION: 'DONATION', // OldDot Action + EXPORTEDTOCSV: 'EXPORTEDTOCSV', // OldDot Action EXPORTEDTOINTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action EXPORTEDTOQUICKBOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action FORWARDED: 'FORWARDED', // OldDot Action diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index efc3dc28f948..d0a9a2a5f40e 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -525,6 +525,7 @@ function ReportActionItem({ CONST.REPORT.ACTIONS.TYPE.DELEGATESUBMIT, CONST.REPORT.ACTIONS.TYPE.DELETEDACCOUNT, CONST.REPORT.ACTIONS.TYPE.DONATION, + CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOCSV, CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOINTEGRATION, CONST.REPORT.ACTIONS.TYPE.EXPORTEDTOQUICKBOOKS, CONST.REPORT.ACTIONS.TYPE.FORWARDED, From 686e637d655567c24da99c07561da2c830a653f1 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 12 Mar 2024 09:06:45 +0100 Subject: [PATCH 058/146] TS fixes --- src/components/Onfido/BaseOnfidoWeb.tsx | 2 +- src/components/Onfido/types.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/Onfido/BaseOnfidoWeb.tsx b/src/components/Onfido/BaseOnfidoWeb.tsx index 58f137af7331..ea5cdb090ceb 100644 --- a/src/components/Onfido/BaseOnfidoWeb.tsx +++ b/src/components/Onfido/BaseOnfidoWeb.tsx @@ -103,7 +103,7 @@ 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 as OnfidoSDK.SupportedLanguages), + locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale, // 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. diff --git a/src/components/Onfido/types.ts b/src/components/Onfido/types.ts index 90403f1ab179..4005dc2597cb 100644 --- a/src/components/Onfido/types.ts +++ b/src/components/Onfido/types.ts @@ -1,21 +1,22 @@ import type {OnfidoResult} from '@onfido/react-native-sdk'; -import type * as OnfidoSDK from 'onfido-sdk-ui'; +import type {Handle} from 'onfido-sdk-ui/types/Onfido'; +import type {CompleteData} from 'onfido-sdk-ui/types/Types'; import type {OnyxEntry} from 'react-native-onyx'; -type OnfidoData = OnfidoSDK.SdkResponse | OnfidoResult; +type OnfidoData = CompleteData | OnfidoResult; type OnfidoDataWithApplicantID = OnfidoData & { applicantID: OnyxEntry; }; -type OnfidoElement = HTMLDivElement & {onfidoOut?: OnfidoSDK.SdkHandle}; +type OnfidoElement = HTMLDivElement & {onfidoOut?: Handle}; 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; + onUserExit: () => void; /** Called when the user is totally done with Onfido */ onSuccess: (data: OnfidoData) => void; From ce2d2ea6bf0eba7ec6150436f67d45d5c4d5dd28 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:44:38 +0100 Subject: [PATCH 059/146] fix crash on the release build --- src/libs/actions/PersonalDetails.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts index 5becaee1593c..b58bace9ce67 100644 --- a/src/libs/actions/PersonalDetails.ts +++ b/src/libs/actions/PersonalDetails.ts @@ -382,7 +382,14 @@ function updateAvatar(file: File | CustomRNImageManipulatorResult) { }, ]; - const parameters: UpdateUserAvatarParams = {file}; + let parameters: UpdateUserAvatarParams = {file}; + + // We need to remove the base64 value from the file object, as it is causing crashes on Release builds. + // More info: https://github.com/Expensify/App/issues/37963#issuecomment-1989260033 + if ('base64' in file) { + const {base64, ...fileWithoutBase64} = file; + parameters = {file: fileWithoutBase64}; + } API.write(WRITE_COMMANDS.UPDATE_USER_AVATAR, parameters, {optimisticData, successData, failureData}); } From eb61a5499e0c727de69beacfbe2f7cefeb6871cb Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 08:46:04 +0100 Subject: [PATCH 060/146] refactor: remove base64 in cropOrRotateImage --- src/libs/actions/PersonalDetails.ts | 9 +-------- src/libs/cropOrRotateImage/index.native.ts | 4 +++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts index b58bace9ce67..5becaee1593c 100644 --- a/src/libs/actions/PersonalDetails.ts +++ b/src/libs/actions/PersonalDetails.ts @@ -382,14 +382,7 @@ function updateAvatar(file: File | CustomRNImageManipulatorResult) { }, ]; - let parameters: UpdateUserAvatarParams = {file}; - - // We need to remove the base64 value from the file object, as it is causing crashes on Release builds. - // More info: https://github.com/Expensify/App/issues/37963#issuecomment-1989260033 - if ('base64' in file) { - const {base64, ...fileWithoutBase64} = file; - parameters = {file: fileWithoutBase64}; - } + const parameters: UpdateUserAvatarParams = {file}; API.write(WRITE_COMMANDS.UPDATE_USER_AVATAR, parameters, {optimisticData, successData, failureData}); } diff --git a/src/libs/cropOrRotateImage/index.native.ts b/src/libs/cropOrRotateImage/index.native.ts index 03c233450435..f418d4d3358b 100644 --- a/src/libs/cropOrRotateImage/index.native.ts +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -9,7 +9,9 @@ import type {CropOrRotateImage} from './types'; const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => new Promise((resolve) => { const format = getSaveFormat(options.type); - manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => { + // We need to remove the base64 value from the result, as it is causing crashes on Release builds. + // More info: https://github.com/Expensify/App/issues/37963#issuecomment-1989260033 + manipulateAsync(uri, actions, {compress: options.compress, format}).then(({base64, ...result}) => { RNFetchBlob.fs.stat(result.uri.replace('file://', '')).then(({size}) => { resolve({ ...result, From a379886256c4a905e92b4439822aa8447ba8cce6 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 13 Mar 2024 15:10:04 +0700 Subject: [PATCH 061/146] fix: unselect category when editing a split bill --- src/pages/iou/request/step/IOURequestStepCategory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepCategory.js b/src/pages/iou/request/step/IOURequestStepCategory.js index 1945edbc24c4..38f3f8803c53 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.js +++ b/src/pages/iou/request/step/IOURequestStepCategory.js @@ -93,7 +93,7 @@ function IOURequestStepCategory({ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { - IOU.setDraftSplitTransaction(transaction.transactionID, {category: category.searchText}); + IOU.setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory}); navigateBack(); return; } From f6df548a36eb79272df5b4e4ad034652bcb4bfa5 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 13 Mar 2024 11:01:31 +0100 Subject: [PATCH 062/146] fix: cr fixes --- src/languages/en.ts | 3 --- src/languages/es.ts | 3 --- .../AppNavigator/ModalStackNavigators.tsx | 2 +- src/libs/actions/Policy.ts | 10 +++------- ...atePage.tsx => CreateDistanceRatePage.tsx} | 19 ++++++++++--------- 5 files changed, 14 insertions(+), 23 deletions(-) rename src/pages/workspace/distanceRates/{PolicyNewDistanceRatePage.tsx => CreateDistanceRatePage.tsx} (88%) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8a402f7f7f42..7e442eee2236 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1983,9 +1983,6 @@ export default { status: 'Status', enabled: 'Enabled', disabled: 'Disabled', - errors: { - createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', - }, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/languages/es.ts b/src/languages/es.ts index e0e40333947d..267581f043ee 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2008,9 +2008,6 @@ export default { status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', - errors: { - createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', - }, }, editor: { nameInputLabel: 'Nombre', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index a78be0ab5267..148cd6e6cbc9 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -262,7 +262,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, - [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('@pages/workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../pages/workspace/tags/WorkspaceCreateTagPage').default as React.ComponentType, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 7c6bfe2148bb..b4425ae5803a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3449,11 +3449,7 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } -function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, policyID?: string) { - if (!policyID) { - return; - } - +function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -3501,8 +3497,8 @@ function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, po [customUnitID]: { rates: { [customUnitRate.customUnitRateID ?? '']: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), + pendingAction: null, }, }, }, diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx similarity index 88% rename from src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx rename to src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index b0745cb52055..627fba15e9e3 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -26,18 +26,18 @@ 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 = { +type CreateDistanceRatePageOnyxProps = { policy: OnyxEntry; }; -type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScreenProps; +type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreenProps; -function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { +function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; - const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; + const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); const validate = (values: FormOnyxValues): FormInputErrors => { @@ -45,6 +45,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) const rate = values.rate; const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); + // Allow one more decimal place for accuracy const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i'); if (!rateValueRegex.test(parsedRate) || parsedRate === '') { @@ -64,7 +65,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) enabled: true, }; - createPolicyDistanceRate(newRate, customUnitID, route.params.policyID); + createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); Navigation.goBack(); }; @@ -74,7 +75,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) ({ +export default withOnyx({ policy: { key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, }, -})(PolicyNewDistanceRatePage); +})(CreateDistanceRatePage); From e7b824973be3a2508174f0d11f5fbd3916e3df38 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 13 Mar 2024 18:21:58 +0700 Subject: [PATCH 063/146] update optimistic data --- src/libs/actions/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 48b9948fc1ff..6d0cc2fd156d 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -440,7 +440,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency value: { autoReporting: enabled, harvesting: { - enabled: true, + enabled, }, autoReportingFrequency: frequency, pendingFields: {isAutoApprovalEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, From 6e315bcbd34c622c8e7da4faed6dc97356fa5d2b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Mar 2024 19:22:43 +0800 Subject: [PATCH 064/146] wrap it in useCallback to have stable ref --- src/components/AddPlaidBankAccount.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index b6fc639546a8..b676722f3c6c 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -218,6 +218,10 @@ function AddPlaidBankAccount({ ); } + const onError = useCallback((error) => { + Log.hmmm('[PlaidLink] Error: ', error.message); + }, []); + const renderPlaidLink = () => { if (Boolean(token) && !bankName) { return ( @@ -227,9 +231,7 @@ function AddPlaidBankAccount({ Log.info('[PlaidLink] Success!'); BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, allowDebit, bankAccountID); }} - onError={(error) => { - Log.hmmm('[PlaidLink] Error: ', error.message); - }} + onError={onError} onEvent={(event, metadata) => { BankAccounts.setPlaidEvent(event); // Handle Plaid login errors (will potentially reset plaid token and item depending on the error) From ba3f3b36fa586f9231eda92b5878926a9e24efae Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 13 Mar 2024 14:43:09 +0100 Subject: [PATCH 065/146] fix: error handling --- src/libs/actions/Policy.ts | 9 ++------- .../distanceRates/PolicyDistanceRatesPage.tsx | 11 ++++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b4425ae5803a..378dc3f5078a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3498,7 +3498,6 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom rates: { [customUnitRate.customUnitRateID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), - pendingAction: null, }, }, }, @@ -3516,11 +3515,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); } -function clearCreateDistanceRateError(policyID: string, currentRates: Record, customUnitID?: string, customUnitRateIDToClear?: string) { - if (!policyID || !customUnitID || !customUnitRateIDToClear) { - return; - } - +function clearCreateDistanceRateItemAndError(policyID: string, currentRates: Record, customUnitID: string, customUnitRateIDToClear: string) { const updatedRates = {...currentRates}; delete updatedRates[customUnitRateIDToClear]; @@ -3604,7 +3599,7 @@ export { openPolicyDistanceRatesPage, generateCustomUnitID, createPolicyDistanceRate, - clearCreateDistanceRateError, + clearCreateDistanceRateItemAndError, createPolicyTag, clearWorkspaceReimbursementErrors, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 404748156c86..56da635d2725 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -65,10 +65,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const dismissError = useCallback( (item: RateForList) => { - if (item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - return; - } - Policy.clearCreateDistanceRateError(policyID, customUnitRates, customUnit?.customUnitID, item.value); + Policy.clearCreateDistanceRateItemAndError(policyID, customUnitRates, customUnit?.customUnitID ?? '', item.value); }, [customUnit?.customUnitID, customUnitRates, policyID], ); @@ -269,13 +266,13 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) {Object.values(customUnitRates).length > 0 && ( From f069f19910b7acefb19813e4e8af02b950b86fb6 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 13 Mar 2024 14:57:26 +0100 Subject: [PATCH 066/146] fix: typecheck --- src/pages/home/sidebar/PressableAvatarWithIndicator.tsx | 2 +- src/pages/home/sidebar/SidebarLinks.tsx | 2 +- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx index 7210aef47965..53bae3d8a770 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.tsx @@ -22,7 +22,7 @@ type PressableAvatarWithIndicatorProps = PressableAvatarWithIndicatorOnyxProps & isSelected: boolean; /** Callback called when the avatar is pressed */ - onPress: () => void; + onPress?: () => void; }; function PressableAvatarWithIndicator({isLoading = true, isSelected = false, onPress}: PressableAvatarWithIndicatorProps) { diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index 5a2d1416c250..ef81aba715aa 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -43,7 +43,7 @@ type SidebarLinksProps = { activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen}: SidebarLinksProps) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const modal = useRef({}); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index a550dbd91657..90a4fc6fffa0 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -28,7 +28,7 @@ type FloatingActionButtonAndPopoverOnyxProps = { /** The list of policies the user has access to. */ allPolicies: OnyxCollection; - /** Wheater app is in loading state */ + /** Whether app is in loading state */ isLoading: OnyxEntry; }; From ef571f71d3448431a58353e97fb2f4cea1e7b36f Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 13 Mar 2024 22:22:44 +0700 Subject: [PATCH 067/146] update failure data --- src/libs/actions/Policy.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 6d0cc2fd156d..fee836404fd5 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -433,6 +433,7 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[] } function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency: ValueOf) { + const policy = ReportUtils.getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -453,7 +454,9 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - autoReporting: !enabled, + autoReporting: policy.autoReporting, + harvesting: policy.harvesting, + autoReportingFrequency: policy.autoReportingFrequency, pendingFields: {isAutoApprovalEnabled: null, harvesting: null}, }, }, @@ -475,6 +478,8 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency } function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf) { + const policy = ReportUtils.getPolicy(policyID); + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -491,6 +496,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { + autoReportingFrequency: policy.autoReportingFrequency, pendingFields: {autoReportingFrequency: null}, }, }, @@ -512,6 +518,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingOffset: number | ValueOf) { const value = JSON.stringify({autoReportingOffset: autoReportingOffset.toString()}); + const policy = ReportUtils.getPolicy(policyID); const optimisticData: OnyxUpdate[] = [ { @@ -529,6 +536,7 @@ function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingO onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { + autoReportingOffset: policy.autoReportingOffset, pendingFields: {autoReportingOffset: null}, }, }, @@ -550,6 +558,7 @@ function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingO function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMode: ValueOf) { const isAutoApprovalEnabled = approvalMode === CONST.POLICY.APPROVAL_MODE.BASIC; + const policy = ReportUtils.getPolicy(policyID); const value = { approver, @@ -573,6 +582,9 @@ function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMo onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { + approver: policy.approver, + approvalMode: policy.approvalMode, + isAutoApprovalEnabled: policy.isAutoApprovalEnabled, pendingFields: {approvalMode: null}, }, }, From 750ca3fc04625e363663a668574f99b4b27e5606 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 14 Mar 2024 00:28:48 +0700 Subject: [PATCH 068/146] add null value as a fallback --- src/libs/actions/Policy.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index a2bb039971d7..d7cc3aba9322 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -457,9 +457,11 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - autoReporting: policy.autoReporting, - harvesting: policy.harvesting, - autoReportingFrequency: policy.autoReportingFrequency, + autoReporting: policy.autoReporting ?? null, + harvesting: { + enabled: policy?.harvesting?.enabled ?? null, + }, + autoReportingFrequency: policy.autoReportingFrequency ?? null, pendingFields: {isAutoApprovalEnabled: null, harvesting: null}, }, }, @@ -499,7 +501,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - autoReportingFrequency: policy.autoReportingFrequency, + autoReportingFrequency: policy.autoReportingFrequency ?? null, pendingFields: {autoReportingFrequency: null}, }, }, @@ -539,7 +541,7 @@ function setWorkspaceAutoReportingMonthlyOffset(policyID: string, autoReportingO onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - autoReportingOffset: policy.autoReportingOffset, + autoReportingOffset: policy.autoReportingOffset ?? null, pendingFields: {autoReportingOffset: null}, }, }, @@ -585,9 +587,9 @@ function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMo onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - approver: policy.approver, - approvalMode: policy.approvalMode, - isAutoApprovalEnabled: policy.isAutoApprovalEnabled, + approver: policy.approver ?? null, + approvalMode: policy.approvalMode ?? null, + isAutoApprovalEnabled: policy.isAutoApprovalEnabled ?? null, pendingFields: {approvalMode: null}, }, }, @@ -639,8 +641,8 @@ function setWorkspacePayer(policyID: string, reimburserEmail: string, reimburser onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - reimburserEmail: policy.reimburserEmail, - reimburserAccountID: policy.reimburserAccountID, + reimburserEmail: policy.reimburserEmail ?? null, + reimburserAccountID: policy.reimburserAccountID ?? null, errorFields: {reimburserEmail: ErrorUtils.getMicroSecondOnyxError('workflowsPayerPage.genericErrorMessage')}, pendingFields: {reimburserEmail: null}, }, @@ -689,9 +691,9 @@ function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueO onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - reimbursementChoice: policy.reimbursementChoice, - reimburserAccountID: policy.reimburserAccountID, - reimburserEmail: policy.reimburserEmail, + reimbursementChoice: policy.reimbursementChoice ?? null, + reimburserAccountID: policy.reimburserAccountID ?? null, + reimburserEmail: policy.reimburserEmail ?? null, errorFields: {reimbursementChoice: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, pendingFields: {reimbursementChoice: null}, }, From 723b0503ee7b97cf95e1555c2c4a0a6357714f04 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 13 Mar 2024 14:49:21 -0600 Subject: [PATCH 069/146] Remove deprecated methods --- ios/Podfile.lock | 2 +- src/libs/ReportUtils.ts | 33 +++++++++++++++++++++++++++++---- src/libs/TransactionUtils.ts | 32 +------------------------------- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d0007ec51668..6c67fecc7ffc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1917,4 +1917,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 -COCOAPODS: 1.13.0 +COCOAPODS: 1.12.1 diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8dc1c9967f13..8f8785d36fd3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2345,6 +2345,31 @@ function hasMissingSmartscanFields(iouReportID: string): boolean { return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction)); } +/** + * Get the transactions related to a report preview with receipts + * Get the details linked to the IOU reportAction + * + * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. + */ +function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { + let transactionID = ''; + + if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; + } + + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; +} + +/** + * Retrieve the particular transaction object given its ID. + * + * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. + */ +function getTransaction(transactionID: string): OnyxEntry | EmptyObject { + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; +} + /** * Given a parent IOU report action get report name for the LHN. */ @@ -2357,7 +2382,7 @@ function getTransactionReportName(reportAction: OnyxEntry) return Localize.translateLocal(translationKey, {amount: formattedAmount, payer: ''}); } - const transaction = TransactionUtils.getTransaction(originalMessage.IOUTransactionID ?? ''); + const transaction = getTransaction(originalMessage.IOUTransactionID ?? ''); const transactionDetails = getTransactionDetails(!isEmptyObject(transaction) ? transaction : null); const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency); const isRequestSettled = isSettled(originalMessage.IOUReportID); diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index f8f9ed0e0d47..bc94c8fee8fc 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -4,16 +4,13 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {RecentWaypoint, Report, ReportAction, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx'; -import type {IOUMessage} from '@src/types/onyx/OriginalMessage'; +import type {RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; import DateUtils from './DateUtils'; import * as NumberUtils from './NumberUtils'; import {getCleanedTagName} from './PolicyUtils'; -import type {OptimisticIOUReportAction} from './ReportUtils'; let allTransactions: OnyxCollection = {}; @@ -248,15 +245,6 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra return updatedTransaction; } -/** - * Retrieve the particular transaction object given its ID. - * - * @deprecated Use withOnyx() or Onyx.connect() instead - */ -function getTransaction(transactionID: string): OnyxEntry | EmptyObject { - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; -} - /** * Return the comment field (referred to as description in the App) from the transaction. * The comment does not have its modifiedComment counterpart. @@ -493,22 +481,6 @@ function hasRoute(transaction: OnyxEntry): boolean { return !!transaction?.routes?.route0?.geometry?.coordinates; } -/** - * Get the transactions related to a report preview with receipts - * Get the details linked to the IOU reportAction - * - * @deprecated Use Onyx.connect() or withOnyx() instead - */ -function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { - let transactionID = ''; - - if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; - } - - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; -} - function getAllReportTransactions(reportID?: string): Transaction[] { // `reportID` from the `/CreateDistanceRequest` endpoint return's number instead of string for created `transaction`. // For reference, https://github.com/Expensify/App/pull/26536#issuecomment-1703573277. @@ -617,7 +589,6 @@ export { calculateTaxAmount, getEnabledTaxRateCount, getUpdatedTransaction, - getTransaction, getDescription, getHeaderTitleTranslationKey, getRequestType, @@ -638,7 +609,6 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, - getLinkedTransaction, getAllReportTransactions, hasReceipt, hasEReceipt, From 64867e5ce23be2c57bd1c8ab332bd5ec1a0c17ae Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 13 Mar 2024 14:55:50 -0600 Subject: [PATCH 070/146] Remove changes to POD lockfile --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6c67fecc7ffc..d0007ec51668 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1917,4 +1917,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 From 1fa0d917bea633db625f08559c52d1f62473bbdb Mon Sep 17 00:00:00 2001 From: DylanDylann <141406735+DylanDylann@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:02:50 +0700 Subject: [PATCH 071/146] Update src/libs/actions/Policy.ts Co-authored-by: Carlos Martins --- src/libs/actions/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index d7cc3aba9322..d69594548218 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -459,7 +459,7 @@ function setWorkspaceAutoReporting(policyID: string, enabled: boolean, frequency value: { autoReporting: policy.autoReporting ?? null, harvesting: { - enabled: policy?.harvesting?.enabled ?? null, + enabled: policy.harvesting?.enabled ?? null, }, autoReportingFrequency: policy.autoReportingFrequency ?? null, pendingFields: {isAutoApprovalEnabled: null, harvesting: null}, From ea29ad9c0a3b8d855ca0e5ef32de7789efb73ae0 Mon Sep 17 00:00:00 2001 From: Eric Han Date: Thu, 14 Mar 2024 15:24:50 +0800 Subject: [PATCH 072/146] extract check to method isOldDotReportAction --- src/libs/ReportActionsUtils.ts | 34 ++++++++++++++++++++++ src/pages/home/report/ReportActionItem.tsx | 33 +-------------------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index fcea05473007..1ebadef1e6f0 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -808,6 +808,39 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): }; } +function isOldDotReportAction(action: ReportAction): boolean { + return [ + 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.EXPORTEDTOCSV, + 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.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, + ].some((oldDotActionName) => oldDotActionName === action.actionName); +} + /** * Helper method to format message of OldDot Actions. * For now, we just concat all of the text elements of the message to create the full message. @@ -977,6 +1010,7 @@ export { getFirstVisibleReportActionID, isMemberChangeAction, getMemberChangeMessageFragment, + isOldDotReportAction, getMessageOfOldDotReportAction, getMemberChangeMessagePlainText, isReimbursementDeQueuedAction, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index d0a9a2a5f40e..519e7ffed88f 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -517,38 +517,7 @@ function ReportActionItem({ children = ; } else if (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.EXPORTEDTOCSV, - 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.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, - ].find((oldDotActionName) => oldDotActionName === action.actionName) - ) { + } else if (ReportActionsUtils.isOldDotReportAction(action)) { // This handles all historical actions from OldDot that we just want to display the message text children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { From 082aaf5bac52059fef759d2c7ada564e7701fd08 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 14 Mar 2024 10:33:08 +0200 Subject: [PATCH 073/146] Fix icons after regression --- src/components/ContextMenuItem.tsx | 2 +- src/components/DistanceRequest/DistanceRequestFooter.tsx | 2 +- src/styles/utils/spacing.ts | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx index b80d6a138c9e..ef580eaea3b6 100644 --- a/src/components/ContextMenuItem.tsx +++ b/src/components/ContextMenuItem.tsx @@ -101,7 +101,7 @@ function ContextMenuItem( > {({hovered, pressed}) => ( diff --git a/src/components/DistanceRequest/DistanceRequestFooter.tsx b/src/components/DistanceRequest/DistanceRequestFooter.tsx index f86d5369d4ac..624ea940888b 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.tsx +++ b/src/components/DistanceRequest/DistanceRequestFooter.tsx @@ -96,7 +96,7 @@ function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, navig onPress={() => navigateToWaypointEditPage(Object.keys(transaction?.comment?.waypoints ?? {}).length)} text={translate('distance.addStop')} isDisabled={numberOfWaypoints === MAX_WAYPOINTS} - innerStyles={[styles.ph10]} + innerStyles={[styles.pl10, styles.pr10]} /> )} diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts index 56000a851e7b..740bf053c2d0 100644 --- a/src/styles/utils/spacing.ts +++ b/src/styles/utils/spacing.ts @@ -485,6 +485,10 @@ export default { paddingRight: 32, }, + pr10: { + paddingRight: 40, + }, + pr15: { paddingRight: 60, }, From 0fd52588a50be8adfa7493293db4eaedf8c06d76 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 14 Mar 2024 09:50:52 +0100 Subject: [PATCH 074/146] fix: cr fixes --- src/libs/actions/Policy.ts | 12 +++++------- .../distanceRates/CreateDistanceRatePage.tsx | 19 ++++++++++++++++++- .../distanceRates/PolicyDistanceRatesPage.tsx | 4 ++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index dba57a3207ba..e5221eea5f0a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3547,7 +3547,6 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom [customUnitID]: { rates: { [customUnitRate.customUnitRateID ?? '']: { - errors: null, pendingAction: null, }, }, @@ -3576,7 +3575,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom ]; const params: CreatePolicyDistanceRateParams = { - policyID, + policyID: `${policyID}1`, customUnitID, customUnitRate: JSON.stringify(customUnitRate), }; @@ -3584,14 +3583,13 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); } -function clearCreateDistanceRateItemAndError(policyID: string, currentRates: Record, customUnitID: string, customUnitRateIDToClear: string) { - const updatedRates = {...currentRates}; - delete updatedRates[customUnitRateIDToClear]; - +function clearCreateDistanceRateItemAndError(policyID: string, customUnitID: string, customUnitRateIDToClear: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { customUnits: { [customUnitID]: { - rates: updatedRates, + rates: { + [customUnitRateIDToClear]: null, + }, }, }, }); diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index 627fba15e9e3..67fb46ec42ad 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -1,5 +1,6 @@ +import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; @@ -7,6 +8,7 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -39,6 +41,20 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); + const textInputRef = useRef(null); + const focusTimeoutRef = useRef(null); + + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => textInputRef.current?.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, []), + ); const validate = (values: FormOnyxValues): FormInputErrors => { const errors: FormInputErrors = {}; @@ -95,6 +111,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { extraDecimals={1} isCurrencyPressable={false} currency={currency} + ref={(e) => (textInputRef.current = e)} /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 56da635d2725..c15cdd6006a4 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -65,9 +65,9 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const dismissError = useCallback( (item: RateForList) => { - Policy.clearCreateDistanceRateItemAndError(policyID, customUnitRates, customUnit?.customUnitID ?? '', item.value); + Policy.clearCreateDistanceRateItemAndError(policyID, customUnit?.customUnitID ?? '', item.value); }, - [customUnit?.customUnitID, customUnitRates, policyID], + [customUnit?.customUnitID, policyID], ); const {isOffline} = useNetwork({onReconnect: fetchDistanceRates}); From 0b9b0272e6455fef0f8045c67cda32eb7ddc1a59 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 14 Mar 2024 11:15:02 +0200 Subject: [PATCH 075/146] Fix visibility of back button in Workspaces --- src/pages/workspace/WorkspacesListPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index e1a7e1e94ca9..4ffe2e3ce33b 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -348,7 +348,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r > Navigation.goBack()} >