From 9f07370908dd9a0f5b66781b6b756c3c92d882a9 Mon Sep 17 00:00:00 2001 From: Nathalie Kuoch Date: Mon, 4 Mar 2024 16:13:12 +0100 Subject: [PATCH 001/116] Clean up vbba code and save each substep --- src/hooks/useSubStep/index.ts | 6 ++- src/hooks/useSubStep/types.ts | 3 ++ .../AcceptACHContractForBankAccount.ts | 2 +- .../parameters/ConnectBankAccountParams.ts | 1 - .../OpenReimbursementAccountPageParams.ts | 1 - ...ateBeneficialOwnersForBankAccountParams.ts | 4 +- ...eCompanyInformationForBankAccountParams.ts | 2 +- ...PersonalInformationForBankAccountParams.ts | 2 +- .../VerifyIdentityForBankAccountParams.ts | 1 - src/libs/actions/BankAccounts.ts | 18 ++++----- .../BusinessInfo/BusinessInfo.tsx | 38 ++++++++++++------- .../PersonalInfo/PersonalInfo.tsx | 19 ++++++++-- 12 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/hooks/useSubStep/index.ts b/src/hooks/useSubStep/index.ts index ad4cf032858d..2d9069abff88 100644 --- a/src/hooks/useSubStep/index.ts +++ b/src/hooks/useSubStep/index.ts @@ -6,8 +6,9 @@ import type {SubStepProps, UseSubStep} from './types'; * @param bodyContent - array of components to display in particular step * @param onFinished - callback triggered after finish last step * @param startFrom - initial index for bodyContent array + * @param onNextSubStep - callback triggered after finish each step */ -export default function useSubStep({bodyContent, onFinished, startFrom = 0}: UseSubStep) { +export default function useSubStep({bodyContent, onFinished, startFrom = 0, onNextSubStep = () => {}}: UseSubStep) { const [screenIndex, setScreenIndex] = useState(startFrom); const isEditing = useRef(false); @@ -35,9 +36,10 @@ export default function useSubStep({bodyContent, on if (nextScreenIndex === bodyContent.length) { onFinished(); } else { + onNextSubStep(); setScreenIndex(nextScreenIndex); } - }, [screenIndex, bodyContent.length, onFinished]); + }, [screenIndex, bodyContent.length, onFinished, onNextSubStep]); const moveTo = useCallback((step: number) => { isEditing.current = true; diff --git a/src/hooks/useSubStep/types.ts b/src/hooks/useSubStep/types.ts index ffdee5825197..f3b7939502a8 100644 --- a/src/hooks/useSubStep/types.ts +++ b/src/hooks/useSubStep/types.ts @@ -21,6 +21,9 @@ type UseSubStep = { /** array of components that will become sub steps */ bodyContent: Array>; + /** called after each sub step */ + onNextSubStep?: () => void; + /** called on last sub step */ onFinished: () => void; diff --git a/src/libs/API/parameters/AcceptACHContractForBankAccount.ts b/src/libs/API/parameters/AcceptACHContractForBankAccount.ts index de4ce4e86857..11ea73bd3a34 100644 --- a/src/libs/API/parameters/AcceptACHContractForBankAccount.ts +++ b/src/libs/API/parameters/AcceptACHContractForBankAccount.ts @@ -1,5 +1,5 @@ import type {ACHContractStepProps} from '@src/types/form/ReimbursementAccountForm'; -type AcceptACHContractForBankAccount = ACHContractStepProps & {bankAccountID: number; policyID: string; canUseNewVbbaFlow?: boolean}; +type AcceptACHContractForBankAccount = ACHContractStepProps & {bankAccountID: number; policyID: string}; export default AcceptACHContractForBankAccount; diff --git a/src/libs/API/parameters/ConnectBankAccountParams.ts b/src/libs/API/parameters/ConnectBankAccountParams.ts index fb0e3422d08c..b4a4f1d71150 100644 --- a/src/libs/API/parameters/ConnectBankAccountParams.ts +++ b/src/libs/API/parameters/ConnectBankAccountParams.ts @@ -8,7 +8,6 @@ type ConnectBankAccountParams = { plaidMask?: string; isSavings?: boolean; policyID?: string; - canUseNewVbbaFlow?: boolean; }; export default ConnectBankAccountParams; diff --git a/src/libs/API/parameters/OpenReimbursementAccountPageParams.ts b/src/libs/API/parameters/OpenReimbursementAccountPageParams.ts index 31eb443ce80e..8e4ae5208a2e 100644 --- a/src/libs/API/parameters/OpenReimbursementAccountPageParams.ts +++ b/src/libs/API/parameters/OpenReimbursementAccountPageParams.ts @@ -7,7 +7,6 @@ type OpenReimbursementAccountPageParams = { stepToOpen: ReimbursementAccountStep; subStep: ReimbursementAccountSubStep; localCurrentStep: ReimbursementAccountStep; - canUseNewVbbaFlow?: boolean; policyID: string; }; diff --git a/src/libs/API/parameters/UpdateBeneficialOwnersForBankAccountParams.ts b/src/libs/API/parameters/UpdateBeneficialOwnersForBankAccountParams.ts index dedc45d0365f..310565573454 100644 --- a/src/libs/API/parameters/UpdateBeneficialOwnersForBankAccountParams.ts +++ b/src/libs/API/parameters/UpdateBeneficialOwnersForBankAccountParams.ts @@ -1,5 +1,5 @@ -import type {ACHContractStepProps} from '@src/types/form/ReimbursementAccountForm'; +import type {BeneficialOwnersStepProps} from '@src/types/form/ReimbursementAccountForm'; -type UpdateBeneficialOwnersForBankAccountParams = Partial & {bankAccountID: number; policyID: string; canUseNewVbbaFlow?: boolean}; +type UpdateBeneficialOwnersForBankAccountParams = Partial & {bankAccountID: number; policyID: string}; export default UpdateBeneficialOwnersForBankAccountParams; diff --git a/src/libs/API/parameters/UpdateCompanyInformationForBankAccountParams.ts b/src/libs/API/parameters/UpdateCompanyInformationForBankAccountParams.ts index 6421fe02f571..c427e26d6c92 100644 --- a/src/libs/API/parameters/UpdateCompanyInformationForBankAccountParams.ts +++ b/src/libs/API/parameters/UpdateCompanyInformationForBankAccountParams.ts @@ -2,6 +2,6 @@ import type {BankAccountStepProps, CompanyStepProps, ReimbursementAccountProps} type BankAccountCompanyInformation = BankAccountStepProps & CompanyStepProps & ReimbursementAccountProps; -type UpdateCompanyInformationForBankAccountParams = Partial & {bankAccountID: number; policyID: string; canUseNewVbbaFlow?: boolean}; +type UpdateCompanyInformationForBankAccountParams = Partial & {bankAccountID: number; policyID: string; confirm: boolean}; export default UpdateCompanyInformationForBankAccountParams; diff --git a/src/libs/API/parameters/UpdatePersonalInformationForBankAccountParams.ts b/src/libs/API/parameters/UpdatePersonalInformationForBankAccountParams.ts index c1a29ddd9cec..4b4876b2863f 100644 --- a/src/libs/API/parameters/UpdatePersonalInformationForBankAccountParams.ts +++ b/src/libs/API/parameters/UpdatePersonalInformationForBankAccountParams.ts @@ -1,5 +1,5 @@ import type {RequestorStepProps} from '@src/types/form/ReimbursementAccountForm'; -type UpdatePersonalInformationForBankAccountParams = RequestorStepProps & {bankAccountID: number; policyID: string; canUseNewVbbaFlow: boolean}; +type UpdatePersonalInformationForBankAccountParams = RequestorStepProps & {bankAccountID: number; policyID: string; confirm: boolean}; export default UpdatePersonalInformationForBankAccountParams; diff --git a/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts b/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts index c11aec9be239..6ef6b3712439 100644 --- a/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts +++ b/src/libs/API/parameters/VerifyIdentityForBankAccountParams.ts @@ -2,6 +2,5 @@ type VerifyIdentityForBankAccountParams = { bankAccountID: number; onfidoData: string; policyID: string; - canUseNewVbbaFlow?: boolean; }; export default VerifyIdentityForBankAccountParams; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 0f4e1aed36a7..367826e4e644 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -160,7 +160,6 @@ function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAcc plaidAccessToken: selectedPlaidBankAccount.plaidAccessToken, plaidMask: selectedPlaidBankAccount.mask, isSavings: selectedPlaidBankAccount.isSavings, - canUseNewVbbaFlow: true, policyID, }; @@ -254,15 +253,17 @@ function deletePaymentBankAccount(bankAccountID: number) { * This action is called by the requestor step in the Verified Bank Account flow * @param bankAccountID - ID for bank account * @param params - User personal data + * @param policyID - ID of the policy we're setting the bank account on + * @param isConfirmPage - If we're submitting from the confirmation substep, to trigger all external checks */ -function updatePersonalInformationForBankAccount(bankAccountID: number, params: RequestorStepProps, policyID: string) { +function updatePersonalInformationForBankAccount(bankAccountID: number, params: RequestorStepProps, policyID: string, isConfirmPage: boolean) { API.write( WRITE_COMMANDS.UPDATE_PERSONAL_INFORMATION_FOR_BANK_ACCOUNT, { ...params, bankAccountID, policyID, - canUseNewVbbaFlow: true, + confirm: isConfirmPage, }, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.REQUESTOR), ); @@ -356,7 +357,6 @@ function openReimbursementAccountPage(stepToOpen: ReimbursementAccountStep, subS subStep, localCurrentStep, policyID, - canUseNewVbbaFlow: true, }; return API.read(READ_COMMANDS.OPEN_REIMBURSEMENT_ACCOUNT_PAGE, parameters, onyxData); @@ -365,15 +365,17 @@ function openReimbursementAccountPage(stepToOpen: ReimbursementAccountStep, subS /** * Updates the bank account in the database with the company step data * @param params - Business step form data + * @param policyID - ID of the policy we're setting the bank account on + * @param isConfirmPage - If we're submitting from the confirmation substep, to trigger all external checks */ -function updateCompanyInformationForBankAccount(bankAccountID: number, params: Partial, policyID: string) { +function updateCompanyInformationForBankAccount(bankAccountID: number, params: Partial, policyID: string, isConfirmPage: boolean) { API.write( WRITE_COMMANDS.UPDATE_COMPANY_INFORMATION_FOR_BANK_ACCOUNT, { ...params, bankAccountID, policyID, - canUseNewVbbaFlow: true, + confirm: isConfirmPage, }, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.COMPANY), ); @@ -390,7 +392,6 @@ function updateBeneficialOwnersForBankAccount(bankAccountID: number, params: Par ...params, bankAccountID, policyID, - canUseNewVbbaFlow: true, }, getVBBADataForOnyx(), ); @@ -407,7 +408,6 @@ function acceptACHContractForBankAccount(bankAccountID: number, params: ACHContr ...params, bankAccountID, policyID, - canUseNewVbbaFlow: true, }, getVBBADataForOnyx(), ); @@ -426,7 +426,6 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan plaidAccessToken: bankAccount.plaidAccessToken, plaidMask: bankAccount.mask, isSavings: bankAccount.isSavings, - canUseNewVbbaFlow: true, policyID, }; @@ -441,7 +440,6 @@ function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: Record< bankAccountID, onfidoData: JSON.stringify(onfidoData), policyID, - canUseNewVbbaFlow: true, }; API.write(WRITE_COMMANDS.VERIFY_IDENTITY_FOR_BANK_ACCOUNT, parameters, getVBBADataForOnyx()); diff --git a/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx b/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx index f63cf72f8a4f..593408b60589 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/BusinessInfo.tsx @@ -71,22 +71,34 @@ function BusinessInfo({reimbursementAccount, reimbursementAccountDraft, onBackBu const policyID = reimbursementAccount?.achData?.policyID ?? ''; const values = useMemo(() => getSubstepValues(BUSINESS_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); - const submit = useCallback(() => { - BankAccounts.updateCompanyInformationForBankAccount( - Number(reimbursementAccount?.achData?.bankAccountID ?? '0'), - { - ...values, - ...getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), - companyTaxID: values.companyTaxID?.replace(CONST.REGEX.NON_NUMERIC, ''), - companyPhone: parsePhoneNumber(values.companyPhone ?? '', {regionCode: CONST.COUNTRY.US}).number?.significant, - }, - policyID, - ); - }, [reimbursementAccount, values, getBankAccountFields, policyID]); + const submit = useCallback( + (isConfirmPage: boolean) => { + BankAccounts.updateCompanyInformationForBankAccount( + Number(reimbursementAccount?.achData?.bankAccountID ?? '0'), + { + ...values, + ...getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), + companyTaxID: values.companyTaxID?.replace(CONST.REGEX.NON_NUMERIC, ''), + companyPhone: parsePhoneNumber(values.companyPhone ?? '', {regionCode: CONST.COUNTRY.US}).number?.significant, + }, + policyID, + isConfirmPage, + ); + }, + [reimbursementAccount, values, getBankAccountFields, policyID], + ); const startFrom = useMemo(() => getInitialSubstepForBusinessInfo(values), [values]); - const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom, onFinished: submit}); + const { + componentToRender: SubStep, + isEditing, + screenIndex, + nextScreen, + prevScreen, + moveTo, + goToTheLastStep, + } = useSubStep({bodyContent, startFrom, onFinished: () => submit(true), onNextSubStep: () => submit(false)}); const handleBackButtonPress = () => { if (isEditing) { diff --git a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx index ce37cd4aa4e2..41c08ddc95dd 100644 --- a/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.tsx @@ -46,12 +46,23 @@ function PersonalInfo({reimbursementAccount, reimbursementAccountDraft, onBackBu const policyID = reimbursementAccount?.achData?.policyID ?? ''; const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); const bankAccountID = Number(reimbursementAccount?.achData?.bankAccountID ?? '0'); - const submit = useCallback(() => { - BankAccounts.updatePersonalInformationForBankAccount(bankAccountID, {...values}, policyID); - }, [values, bankAccountID, policyID]); + const submit = useCallback( + (isConfirmPage: boolean) => { + BankAccounts.updatePersonalInformationForBankAccount(bankAccountID, {...values}, policyID, isConfirmPage); + }, + [values, bankAccountID, policyID], + ); const startFrom = useMemo(() => getInitialSubstepForPersonalInfo(values), [values]); - const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom, onFinished: submit}); + const { + componentToRender: SubStep, + isEditing, + screenIndex, + nextScreen, + prevScreen, + moveTo, + goToTheLastStep, + } = useSubStep({bodyContent, startFrom, onFinished: () => submit(true), onNextSubStep: () => submit(false)}); const handleBackButtonPress = () => { if (isEditing) { From 532dcacf98ff02b8999695dfc469a06fb4151926 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:01:20 +0100 Subject: [PATCH 002/116] refactor all base pages to tsx --- ...e.js => IOURequestRedirectToStartPage.tsx} | 23 +++------- ...stStartPage.js => IOURequestStartPage.tsx} | 0 ...StepAmount.js => IOURequestStepAmount.tsx} | 0 ...Distance.js => IOURequestStepDistance.tsx} | 45 +++++++------------ 4 files changed, 21 insertions(+), 47 deletions(-) rename src/pages/iou/request/{IOURequestRedirectToStartPage.js => IOURequestRedirectToStartPage.tsx} (73%) rename src/pages/iou/request/{IOURequestStartPage.js => IOURequestStartPage.tsx} (100%) rename src/pages/iou/request/step/{IOURequestStepAmount.js => IOURequestStepAmount.tsx} (100%) rename src/pages/iou/request/step/{IOURequestStepDistance.js => IOURequestStepDistance.tsx} (92%) diff --git a/src/pages/iou/request/IOURequestRedirectToStartPage.js b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx similarity index 73% rename from src/pages/iou/request/IOURequestRedirectToStartPage.js rename to src/pages/iou/request/IOURequestRedirectToStartPage.tsx index 2da235743705..c34fa6065028 100644 --- a/src/pages/iou/request/IOURequestRedirectToStartPage.js +++ b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx @@ -1,34 +1,22 @@ import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: PropTypes.shape({ - /** Route specific parameters used on this screen */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired, - - /** The type of IOU Request, i.e. manual, scan, distance */ - iouRequestType: PropTypes.oneOf(_.values(CONST.IOU.REQUEST_TYPE)).isRequired, - }), - }).isRequired, -}; +type IOURequestRedirectToStartPageProps = WithWritableReportOrNotFoundProps; function IOURequestRedirectToStartPage({ route: { params: {iouType, iouRequestType}, }, -}) { - const isIouTypeValid = _.values(CONST.IOU.TYPE).includes(iouType); - const isIouRequestTypeValid = _.values(CONST.IOU.REQUEST_TYPE).includes(iouRequestType); +}: IOURequestRedirectToStartPageProps) { + const isIouTypeValid = Object.values(CONST.IOU.TYPE).includes(iouType); + const isIouRequestTypeValid = Object.values(CONST.IOU.REQUEST_TYPE).includes(iouRequestType); useEffect(() => { if (!isIouTypeValid || !isIouRequestTypeValid) { @@ -64,6 +52,5 @@ function IOURequestRedirectToStartPage({ } IOURequestRedirectToStartPage.displayName = 'IOURequestRedirectToStartPage'; -IOURequestRedirectToStartPage.propTypes = propTypes; export default IOURequestRedirectToStartPage; diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.tsx similarity index 100% rename from src/pages/iou/request/IOURequestStartPage.js rename to src/pages/iou/request/IOURequestStartPage.tsx diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.tsx similarity index 100% rename from src/pages/iou/request/step/IOURequestStepAmount.js rename to src/pages/iou/request/step/IOURequestStepAmount.tsx diff --git a/src/pages/iou/request/step/IOURequestStepDistance.js b/src/pages/iou/request/step/IOURequestStepDistance.tsx similarity index 92% rename from src/pages/iou/request/step/IOURequestStepDistance.js rename to src/pages/iou/request/step/IOURequestStepDistance.tsx index dad610cbc636..d725cdfe152f 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.js +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -1,7 +1,8 @@ import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import { withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Button from '@components/Button'; import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter'; @@ -17,7 +18,6 @@ import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as MapboxToken from '@userActions/MapboxToken'; @@ -25,50 +25,39 @@ import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import type * as OnyxTypes from '@src/types/onyx'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; +import type { WithWritableReportOrNotFoundProps } from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, - - /* Onyx Props */ - /** The report that the transaction belongs to */ - report: reportPropTypes, - - /** The transaction object being modified in Onyx */ - transaction: transactionPropTypes, - - /** backup version of the original transaction */ - transactionBackup: transactionPropTypes, -}; +type IOURequestStepDistanceOnyxProps = { + transactionBackup?: OnyxEntry, +} -const defaultProps = { - report: {}, - transaction: {}, - transactionBackup: {}, +type IOURequestStepDistanceProps = WithWritableReportOrNotFoundProps & IOURequestStepDistanceOnyxProps & { + report?: OnyxEntry, + transaction?: OnyxEntry, }; function IOURequestStepDistance({ - report, route: { params: {action, iouType, reportID, transactionID, backTo}, }, + report, transaction, transactionBackup, -}) { +}: IOURequestStepDistanceProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); const waypoints = useMemo(() => optimisticWaypoints || lodashGet(transaction, 'comment.waypoints', {waypoint0: {}, waypoint1: {}}), [optimisticWaypoints, transaction]); - const waypointsList = _.keys(waypoints); + const waypointsList = Object.keys(waypoints); const previousWaypoints = usePrevious(waypoints); - const numberOfWaypoints = _.size(waypoints); - const numberOfPreviousWaypoints = _.size(previousWaypoints); + const numberOfWaypoints = Object.keys(waypoints).length; + const numberOfPreviousWaypoints = Object.keys(previousWaypoints).length; const scrollViewRef = useRef(null); const isLoadingRoute = lodashGet(transaction, 'comment.isLoading', false); const isLoading = lodashGet(transaction, 'isLoading', false); @@ -82,7 +71,7 @@ function IOURequestStepDistance({ const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false); const nonEmptyWaypointsCount = useMemo(() => _.filter(_.keys(waypoints), (key) => !_.isEmpty(waypoints[key])).length, [waypoints]); const duplicateWaypointsError = useMemo(() => nonEmptyWaypointsCount >= 2 && _.size(validatedWaypoints) !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints]); - const atLeastTwoDifferentWaypointsError = useMemo(() => _.size(validatedWaypoints) < 2, [validatedWaypoints]); + const atLeastTwoDifferentWaypointsError = useMemo(() => Object.keys(validatedWaypoints) < 2, [validatedWaypoints]); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = Navigation.getActiveRoute().includes('start'); @@ -284,8 +273,6 @@ function IOURequestStepDistance({ } IOURequestStepDistance.displayName = 'IOURequestStepDistance'; -IOURequestStepDistance.propTypes = propTypes; -IOURequestStepDistance.defaultProps = defaultProps; export default compose( withWritableReportOrNotFound, From 9f68f82a9901ddad5012226fa009f3128adc6226 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:09:11 +0100 Subject: [PATCH 003/116] add correct props for IOURequestStartPage --- src/pages/iou/request/IOURequestStartPage.tsx | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index cb078fac133c..3852e04dfe04 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,9 +1,9 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import { withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; @@ -11,7 +11,6 @@ import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; @@ -23,53 +22,34 @@ import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import reportPropTypes from '@pages/reportPropTypes'; +import type * as OnyxTypes from '@src/types/onyx'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import IOURequestStepAmount from './step/IOURequestStepAmount'; import IOURequestStepDistance from './step/IOURequestStepDistance'; -import IOURequestStepRoutePropTypes from './step/IOURequestStepRoutePropTypes'; import IOURequestStepScan from './step/IOURequestStepScan'; +import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, - - /* Onyx Props */ - /** The report that holds the transaction */ - report: reportPropTypes, - - /** The policy tied to the report */ - policy: PropTypes.shape({ - /** Type of the policy */ - type: PropTypes.string, - }), - - /** The tab to select by default (whatever the user visited last) */ - selectedTab: PropTypes.oneOf(_.values(CONST.TAB_REQUEST)), - - /** The transaction being modified */ - transaction: transactionPropTypes, +type IOURequestStartPageOnyxProps = { + report?: OnyxEntry, + policy?: OnyxEntry, + selectedTab?: typeof CONST.TAB_REQUEST[keyof typeof CONST.TAB_REQUEST], + transaction?: OnyxEntry, }; -const defaultProps = { - report: {}, - policy: {}, - selectedTab: CONST.TAB_REQUEST.SCAN, - transaction: {}, -}; +type IOURequestStartPageProps = WithWritableReportOrNotFoundProps & IOURequestStartPageOnyxProps; function IOURequestStartPage({ - report, - policy, route, route: { params: {iouType, reportID}, }, + report, + policy, selectedTab, transaction, -}) { +}: IOURequestStartPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const navigation = useNavigation(); From e86e35ef2307c031d32fc9a00e7f1f171815878f Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 2 Apr 2024 14:57:24 +0700 Subject: [PATCH 004/116] fix scan expense report and transaction are not hidden --- src/libs/SidebarUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2c628f397390..cab74d7c9e2e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -12,6 +12,7 @@ import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; @@ -91,7 +92,10 @@ function getOrderedReportIDs( const isFocused = report.reportID === currentReportId; const hasErrors = Object.keys(OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const shouldOverrideHidden = hasBrickError || isFocused || report.isPinned; + const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); + const hasBrickErrorOfNonFailedReceipt = + hasBrickError && (isEmptyObject(allReportErrors) || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage')); + const shouldOverrideHidden = hasBrickErrorOfNonFailedReceipt || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; } From cc63feef2ecde6e2ba2c11e0c63e11f69eb6c51b Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Apr 2024 16:47:37 +0700 Subject: [PATCH 005/116] fix update hasNonFailedReceiptError variable --- src/libs/SidebarUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index cab74d7c9e2e..8d1ba8450aca 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -90,12 +90,12 @@ function getOrderedReportIDs( betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; - const hasErrors = Object.keys(OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}).length !== 0; + const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; + const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); - const hasBrickErrorOfNonFailedReceipt = - hasBrickError && (isEmptyObject(allReportErrors) || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage')); - const shouldOverrideHidden = hasBrickErrorOfNonFailedReceipt || isFocused || report.isPinned; + + const hasNonFailedReceiptError = isEmptyObject(allReportErrors) || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); + const shouldOverrideHidden = (hasBrickError && hasNonFailedReceiptError) || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; } From 20acf25c97e3c2c4c2032257def0543c62d8c1b3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Apr 2024 16:54:18 +0700 Subject: [PATCH 006/116] fix remove redundant line break --- src/libs/SidebarUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d1ba8450aca..ad30c9f71f7a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -93,7 +93,6 @@ function getOrderedReportIDs( const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const hasNonFailedReceiptError = isEmptyObject(allReportErrors) || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = (hasBrickError && hasNonFailedReceiptError) || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { From 699a61acdec808c959a74c4f4330fee3f1098b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 3 Apr 2024 12:16:25 +0200 Subject: [PATCH 007/116] Enable live markdown in form text inputs --- .../TextInput/BaseTextInput/index.tsx | 18 ++++++++++++++---- .../TextInput/BaseTextInput/types.ts | 2 ++ src/pages/tasks/NewTaskDetailsPage.tsx | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 9681f7e7fde5..64997f7be178 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -8,12 +8,16 @@ import FormHelpMessage from '@components/FormHelpMessage'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; +import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import RNTextInput from '@components/RNTextInput'; import SwipeInterceptPanResponder from '@components/SwipeInterceptPanResponder'; import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; import TextInputLabel from '@components/TextInput/TextInputLabel'; import useLocalize from '@hooks/useLocalize'; +import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -56,10 +60,13 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, + liveMarkdown = false, ...inputProps }: BaseTextInputProps, ref: ForwardedRef, ) { + const InputComponent = liveMarkdown ? RNMarkdownTextInput : RNTextInput; + const theme = useTheme(); const styles = useThemeStyles(); const {hasError = false} = inputProps; @@ -81,6 +88,7 @@ function BaseTextInput( const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); + const markdownStyle = useMarkdownStyle(); // AutoFocus which only works on mount: useEffect(() => { @@ -341,13 +349,14 @@ function BaseTextInput( )} - { + { + const baseTextInputRef = element as BaseTextInputRef | null; if (typeof ref === 'function') { - ref(element); + ref(baseTextInputRef); } else if (ref && 'current' in ref) { // eslint-disable-next-line no-param-reassign - ref.current = element; + ref.current = baseTextInputRef; } input.current = element as HTMLInputElement | null; @@ -396,6 +405,7 @@ function BaseTextInput( selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} + markdownStyle={markdownStyle} /> {inputProps.isLoading && ( From 1f331cd2da9a3c0caac29ec56fc1087f7a48b491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 3 Apr 2024 18:14:14 +0200 Subject: [PATCH 008/116] Bump react-native-live-markdown version to fix onSubmitEditing prop --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4729cffea763..171d4cd72ea8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.35", + "@expensify/react-native-live-markdown": "0.1.37", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -3097,9 +3097,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.35", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.35.tgz", - "integrity": "sha512-W0FFIiU/sT+AwIrIOUHiNAHYjODAkEdYsf75tfBbkA6v2byHPxUlbzaJrZEQc0HgbvtAfTf9iQQqGWjNqe4pog==", + "version": "0.1.37", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.37.tgz", + "integrity": "sha512-XCiiyoDGUl+kXovqGOfKvOzyFpx8xrGhUjdn9F5wygecFCCS+0ynoyoQ6roKi539tkhd6IYFUr5KrOgiGMwaFg==", "engines": { "node": ">= 18.0.0" }, diff --git a/package.json b/package.json index 61e3557bbe67..fd8dc6184286 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.35", + "@expensify/react-native-live-markdown": "0.1.37", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", From da3a7fa95c012fc1a4c49673968995b90ad40290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 3 Apr 2024 18:16:02 +0200 Subject: [PATCH 009/116] Add live markdown to new task description page --- src/pages/tasks/NewTaskDescriptionPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index a5eb79497707..95d831859929 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -89,6 +89,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { autoGrowHeight shouldSubmitForm containerStyles={styles.autoGrowHeightMultilineInput} + liveMarkdown /> From 5784171992a25a8b908ff7d05e0cd79cdf288d6e Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 4 Apr 2024 14:11:30 +0700 Subject: [PATCH 010/116] fix rename variable --- src/libs/SidebarUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index ad30c9f71f7a..0ab9adc2705c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -93,8 +93,8 @@ function getOrderedReportIDs( const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const hasNonFailedReceiptError = isEmptyObject(allReportErrors) || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); - const shouldOverrideHidden = (hasBrickError && hasNonFailedReceiptError) || isFocused || report.isPinned; + const hasErrorsOtherThanFailedReceipt = hasErrors || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); + const shouldOverrideHidden = (hasBrickError && hasErrorsOtherThanFailedReceipt) || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; } From 890efed12db417952d38e5f08252c29f0ebbe6ab Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 4 Apr 2024 14:19:02 +0700 Subject: [PATCH 011/116] fix lint --- src/libs/SidebarUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0ab9adc2705c..64650f86fe8c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -12,7 +12,6 @@ import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; From a06fb15de4ce43ff1b65716e0ae5d9948dd0d1d3 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 4 Apr 2024 14:37:29 +0700 Subject: [PATCH 012/116] Clear hold reason of all transactions when the admin approves all requests --- src/libs/actions/IOU.ts | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5c92cd87a2bc..cacb8e2b2b80 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4673,7 +4673,10 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full?: boolean) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; - if (ReportUtils.hasHeldExpenses(expenseReport.reportID) && !full && !!expenseReport.unheldTotal) { + const transactions = TransactionUtils.getAllReportTransactions(expenseReport.reportID); + const hasHeldExpenses = transactions.some((transaction) => TransactionUtils.isOnHold(transaction)); + + if (hasHeldExpenses && !full && !!expenseReport.unheldTotal) { total = expenseReport.unheldTotal; } const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(total, expenseReport.currency ?? '', expenseReport.reportID); @@ -4768,6 +4771,33 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full }, ]; + // Clear hold reason of all transactions if we approve all requests + if (full && hasHeldExpenses) { + const heldTransactions = transactions.filter(TransactionUtils.isOnHold); + heldTransactions.forEach((heldTransaction) => { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${heldTransaction.transactionID}`, + value: { + comment: { + ...heldTransaction.comment, + hold: '', + }, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${heldTransaction.transactionID}`, + value: { + comment: { + ...heldTransaction.comment, + hold: heldTransaction.comment.hold, + }, + }, + }); + }); + } + const parameters: ApproveMoneyRequestParams = { reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID, From d82b1c02326f17915257fe8a9f17572a75486b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 4 Apr 2024 14:34:19 +0200 Subject: [PATCH 013/116] Change markdown enabling prop name --- src/components/TextInput/BaseTextInput/index.tsx | 4 ++-- src/components/TextInput/BaseTextInput/types.ts | 3 ++- src/pages/tasks/NewTaskDescriptionPage.tsx | 2 +- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 64997f7be178..165c4b6ef0b1 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -60,12 +60,12 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, - liveMarkdown = false, + markdownEnabled = false, ...inputProps }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = liveMarkdown ? RNMarkdownTextInput : RNTextInput; + const InputComponent = markdownEnabled ? RNMarkdownTextInput : RNTextInput; const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index baf0014d474d..c38f38a1697d 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -105,7 +105,8 @@ type CustomBaseTextInputProps = { /** Type of autocomplete */ autoCompleteType?: string; - liveMarkdown?: boolean; + /** Should live markdown be enabled. Changes RNTextInput component to RNMarkdownTextInput */ + markdownEnabled?: boolean; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index 95d831859929..13708267fc8b 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -89,7 +89,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { autoGrowHeight shouldSubmitForm containerStyles={styles.autoGrowHeightMultilineInput} - liveMarkdown + markdownEnabled /> diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 6ad6c4e5598f..4c16734ae0e7 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -117,7 +117,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { defaultValue={parser.htmlToMarkdown(parser.replace(taskDescription))} value={taskDescription} onValueChange={setTaskDescription} - liveMarkdown + markdownEnabled /> diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index ffc3d5880bba..24ea13e9987c 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -120,6 +120,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti autoGrowHeight shouldSubmitForm containerStyles={[styles.autoGrowHeightMultilineInput]} + markdownEnabled /> From cb7ae41d0bc4f840643cbf3f711707646994d0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 4 Apr 2024 15:08:53 +0200 Subject: [PATCH 014/116] Update Podfile --- ios/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 24ef0704be25..ca867240e5d3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1363,7 +1363,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.35): + - RNLiveMarkdown (0.1.37): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1904,7 +1904,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 25b969a1ffc806b9f9ad2e170d4a3b049c6af85e RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: aaf5afb231515d8ddfdef5f2928581e8ff606ad4 + RNLiveMarkdown: 2f3bb37f2ca85a5bfb841be155a9e3ce502aaf53 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: fcf7f1cbdc8bd7569c267d07284e8a5c7bee06ed RNPermissions: 9b086c8f05b2e2faa587fdc31f4c5ab4509728aa From 932937bc2e239150d6467cb274a1b9f1d01743a2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 4 Apr 2024 22:05:55 +0700 Subject: [PATCH 015/116] remove unnecessary code --- src/libs/actions/IOU.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cacb8e2b2b80..87438672dcd4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4780,7 +4780,6 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full key: `${ONYXKEYS.COLLECTION.TRANSACTION}${heldTransaction.transactionID}`, value: { comment: { - ...heldTransaction.comment, hold: '', }, }, @@ -4790,7 +4789,6 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full key: `${ONYXKEYS.COLLECTION.TRANSACTION}${heldTransaction.transactionID}`, value: { comment: { - ...heldTransaction.comment, hold: heldTransaction.comment.hold, }, }, From 7af629255727477478c1473a1b3df586ab68194a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 13:37:47 +0700 Subject: [PATCH 016/116] create getAllHeldTransactions --- src/libs/ReportUtils.ts | 9 +++++++++ src/libs/actions/IOU.ts | 8 +++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fec64efaac7f..fe4c1bc891fc 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5446,6 +5446,14 @@ function isHoldCreator(transaction: OnyxEntry, reportID: string): b return isActionCreator(holdReportAction); } +/** + * Get all held transactions of a iouReport + */ +function getAllHeldTransactions(iouReportID: string): Transaction[] { + const transactions = TransactionUtils.getAllReportTransactions(iouReportID); + return transactions.filter((transaction) => TransactionUtils.isOnHold(transaction)); +} + /** * Check if Report has any held expenses */ @@ -5966,6 +5974,7 @@ export { hasActionsWithErrors, getGroupChatName, getOutstandingChildRequest, + getAllHeldTransactions, }; export type { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 40918b0e3cab..1ebc5816ce6c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4634,10 +4634,8 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full?: boolean) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; - const transactions = TransactionUtils.getAllReportTransactions(expenseReport.reportID); - const hasHeldExpenses = transactions.some((transaction) => TransactionUtils.isOnHold(transaction)); - if (hasHeldExpenses && !full && !!expenseReport.unheldTotal) { + if (ReportUtils.hasHeldExpenses(expenseReport.reportID) && !full && !!expenseReport.unheldTotal) { total = expenseReport.unheldTotal; } const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(total, expenseReport.currency ?? '', expenseReport.reportID); @@ -4733,8 +4731,8 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full ]; // Clear hold reason of all transactions if we approve all requests - if (full && hasHeldExpenses) { - const heldTransactions = transactions.filter(TransactionUtils.isOnHold); + const heldTransactions = ReportUtils.getAllHeldTransactions(expenseReport.reportID); + if (full && heldTransactions.length) { heldTransactions.forEach((heldTransaction) => { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, From eeccc2b78e8167777924e7fec08f9bcd2f64ae0e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 13:39:06 +0700 Subject: [PATCH 017/116] remove empty line --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1ebc5816ce6c..90302fc95361 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4634,7 +4634,6 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full?: boolean) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; - if (ReportUtils.hasHeldExpenses(expenseReport.reportID) && !full && !!expenseReport.unheldTotal) { total = expenseReport.unheldTotal; } From 8d82be126edb23b270d9c5f6d5cbd9736b7148f4 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 14:55:48 +0700 Subject: [PATCH 018/116] fix: expense chat doesn't scroll to bottom when we create a distance request --- src/libs/actions/IOU.ts | 4 +++- src/pages/home/report/ReportActionsList.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ab49305b5f0b..dca4fca7d74d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1391,7 +1391,8 @@ function createDistanceRequest( API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData); Navigation.dismissModal(isMoneyRequestReport ? report.reportID : chatReport.reportID); - Report.notifyNewAction(chatReport.reportID, userAccountID); + const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; + Report.notifyNewAction(activeReportID, userAccountID); } /** @@ -2135,6 +2136,7 @@ function requestMoney( payeeEmail, moneyRequestReportID, ); + const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; const parameters: RequestMoneyParams = { diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d1b9c420b0af..2a325531baff 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -197,7 +197,8 @@ function ReportActionsList({ const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedReportActions?.[0].created === report.lastVisibleActionCreated; - + const hasNewestReportActionRef = useRef(hasNewestReportAction); + hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -322,14 +323,13 @@ function ReportActionsList({ (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. - if (!isFromCurrentUser || !hasNewestReportAction) { + if (!isFromCurrentUser || !hasNewestReportActionRef.current) { return; } InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); }, - [hasNewestReportAction, reportScrollManager], + [reportScrollManager], ); - useEffect(() => { // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? // Answer: On web, when navigating to another report screen, the previous report screen doesn't get unmounted, From 1636ef6ab7f1f397efab7104c3501ef86358c53e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 15:23:52 +0700 Subject: [PATCH 019/116] fix lint --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index dca4fca7d74d..df0355cda92b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2136,7 +2136,6 @@ function requestMoney( payeeEmail, moneyRequestReportID, ); - const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; const parameters: RequestMoneyParams = { From 1ec5cc30deb34c344afa1df1a167459ed528f636 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 5 Apr 2024 11:01:21 +0200 Subject: [PATCH 020/116] make icons optional inside mentions render component --- src/components/MentionSuggestions.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index 23040a242807..c7dd025e623a 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -22,7 +22,7 @@ type Mention = { login?: string; /** Array of icons of the user. We use the first element of this array */ - icons: Icon[]; + icons?: Icon[]; }; type MentionSuggestionsProps = { @@ -67,16 +67,18 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe return ( - - - + {item.icons && ( + + + + )} Date: Fri, 5 Apr 2024 11:23:42 +0200 Subject: [PATCH 021/116] implement room mentions suggestions --- .../ComposerWithSuggestions.tsx | 10 ++ .../ReportActionCompose.tsx | 3 + .../ReportActionCompose/SuggestionMention.tsx | 107 +++++++++++++++--- .../ReportActionCompose/Suggestions.tsx | 10 ++ 4 files changed, 116 insertions(+), 14 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index bfcef66e7c54..9369c3504c7d 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -171,6 +171,12 @@ type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & /** The parent report ID */ // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC parentReportID: string | undefined; + + /** Whether chat is a room type */ + isChatRoom: boolean; + + /** policy ID of the report */ + policyID: string; }; const {RNTextInputReset} = NativeModules; @@ -217,6 +223,8 @@ function ComposerWithSuggestions( isEmptyChat, lastReportAction, parentReportActionID, + isChatRoom, + policyID, // Focus onFocus, @@ -769,6 +777,8 @@ function ComposerWithSuggestions( composerHeight={composerHeight} measureParentContainer={measureParentContainer} isAutoSuggestionPickerLarge={isAutoSuggestionPickerLarge} + isChatRoom={isChatRoom} + policyID={policyID} // Input value={value} setValue={setValue} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 5f7f5adebfc9..b5ee6e2d03fa 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -342,6 +342,7 @@ function ReportActionCompose({ [], ); + const isChatRoom = useMemo(() => ReportUtils.isGroupPolicy(report), [report]); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); const reportRecipient = personalDetails[reportRecipientAcountIDs[0]]; const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused; @@ -431,9 +432,11 @@ function ReportActionCompose({ isScrollLikelyLayoutTriggered={isScrollLikelyLayoutTriggered} raiseIsScrollLikelyLayoutTriggered={raiseIsScrollLikelyLayoutTriggered} reportID={reportID} + policyID={report?.policyID ?? ''} parentReportID={report?.parentReportID} parentReportActionID={report?.parentReportActionID} includeChronos={ReportUtils.chatIncludesChronos(report)} + isChatRoom={isChatRoom} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} isMenuVisible={isMenuVisible} diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 5bf172e82388..d81e1f09472d 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -1,7 +1,9 @@ import Str from 'expensify-common/lib/str'; import lodashSortBy from 'lodash/sortBy'; -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import type {Mention} from '@components/MentionSuggestions'; import MentionSuggestions from '@components/MentionSuggestions'; @@ -11,10 +13,13 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useLocalize from '@hooks/useLocalize'; import * as LoginUtils from '@libs/LoginUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; import * as UserUtils from '@libs/UserUtils'; +import {isValidRoomName} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; -import type {PersonalDetailsList} from '@src/types/onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PersonalDetailsList, Report} from '@src/types/onyx'; import type {SuggestionsRef} from './ReportActionCompose'; import type {SuggestionProps} from './Suggestions'; @@ -23,6 +28,12 @@ type SuggestionValues = { atSignIndex: number; shouldShowSuggestionMenu: boolean; mentionPrefix: string; + prefixType: string; +}; + +type RoomMentionOnyxProps = { + /** All reports shared with the user */ + reports: OnyxCollection; }; /** @@ -35,10 +46,22 @@ const defaultSuggestionsValues: SuggestionValues = { atSignIndex: -1, shouldShowSuggestionMenu: false, mentionPrefix: '', + prefixType: '', }; function SuggestionMention( - {value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused}: SuggestionProps, + { + value, + selection, + setSelection, + updateComment, + isAutoSuggestionPickerLarge, + measureParentContainer, + isComposerFocused, + reports, + isChatRoom, + policyID, + }: SuggestionProps & RoomMentionOnyxProps, ref: ForwardedRef, ) { const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; @@ -83,6 +106,17 @@ function SuggestionMention( [currentUserPersonalDetails.login], ); + const getMentionCode = useCallback( + (mention: Mention, mentionType: string): string => { + if (mentionType === '#') { + // room mention case + return mention.login ?? ''; + } + return mention.text === CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT ? CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT : `@${formatLoginPrivateDomain(mention.login, mention.login)}`; + }, + [formatLoginPrivateDomain], + ); + /** * Replace the code of mention and update selection */ @@ -90,10 +124,7 @@ function SuggestionMention( (highlightedMentionIndexInner: number) => { const commentBeforeAtSign = value.slice(0, suggestionValues.atSignIndex); const mentionObject = suggestionValues.suggestedMentions[highlightedMentionIndexInner]; - const mentionCode = - mentionObject.text === CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT - ? CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT - : `@${formatLoginPrivateDomain(mentionObject.login, mentionObject.login)}`; + const mentionCode = getMentionCode(mentionObject, suggestionValues.prefixType); const commentAfterMention = value.slice(suggestionValues.atSignIndex + suggestionValues.mentionPrefix.length + 1); updateComment(`${commentBeforeAtSign}${mentionCode} ${SuggestionsUtils.trimLeadingSpace(commentAfterMention)}`, true); @@ -108,7 +139,16 @@ function SuggestionMention( suggestedMentions: [], })); }, - [value, suggestionValues.atSignIndex, suggestionValues.suggestedMentions, suggestionValues.mentionPrefix, updateComment, setSelection, formatLoginPrivateDomain], + [ + value, + suggestionValues.atSignIndex, + suggestionValues.suggestedMentions, + suggestionValues.prefixType, + suggestionValues.mentionPrefix.length, + getMentionCode, + updateComment, + setSelection, + ], ); /** @@ -146,7 +186,7 @@ function SuggestionMention( [highlightedMentionIndex, insertSelectedMention, resetSuggestions, suggestionValues.suggestedMentions.length], ); - const getMentionOptions = useCallback( + const getUserMentionOptions = useCallback( (personalDetailsParam: PersonalDetailsList, searchValue = ''): Mention[] => { const suggestions = []; @@ -211,6 +251,30 @@ function SuggestionMention( [translate, formatPhoneNumber, formatLoginPrivateDomain], ); + const getRoomMentionOptions = useCallback( + (searchTerm: string, reportBatch: OnyxCollection): Mention[] => { + const filteredRoomMentions: Mention[] = []; + Object.values(reportBatch ?? {}).forEach((report) => { + if (report?.policyID !== policyID) { + return; + } + if (!ReportUtils.isGroupPolicy(report) || !isValidRoomName(report?.reportName ?? '')) { + // checking validity of room name removes Policy Expense Chats + return; + } + if (report?.reportName?.toLowerCase().includes(searchTerm.toLowerCase())) { + filteredRoomMentions.push({ + text: report.reportName, + login: report.reportName, + alternateText: report.reportName, + }); + } + }); + return filteredRoomMentions.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS); + }, + [policyID], + ); + const calculateMentionSuggestion = useCallback( (selectionEnd: number) => { if (shouldBlockCalc.current || selectionEnd < 1 || !isComposerFocused) { @@ -239,13 +303,15 @@ function SuggestionMention( let atSignIndex: number | undefined; let suggestionWord = ''; let prefix: string; + let prefixType = ''; // Detect if the last two words contain a mention (two words are needed to detect a mention with a space in it) - if (lastWord.startsWith('@')) { + if (lastWord.startsWith('@') || lastWord.startsWith('#')) { atSignIndex = leftString.lastIndexOf(lastWord) + afterLastBreakLineIndex; suggestionWord = lastWord; prefix = suggestionWord.substring(1); + prefixType = suggestionWord.substring(0, 1); } else if (secondToLastWord && secondToLastWord.startsWith('@') && secondToLastWord.length > 1) { atSignIndex = leftString.lastIndexOf(secondToLastWord) + afterLastBreakLineIndex; suggestionWord = `${secondToLastWord} ${lastWord}`; @@ -259,23 +325,32 @@ function SuggestionMention( suggestedMentions: [], atSignIndex, mentionPrefix: prefix, + prefixType, }; const isCursorBeforeTheMention = valueAfterTheCursor.startsWith(suggestionWord); - if (!isCursorBeforeTheMention && isMentionCode(suggestionWord)) { - const suggestions = getMentionOptions(personalDetails, prefix); + if (!isCursorBeforeTheMention && isMentionCode(suggestionWord) && prefixType === '@') { + const suggestions = getUserMentionOptions(personalDetails, prefix); nextState.suggestedMentions = suggestions; nextState.shouldShowSuggestionMenu = !!suggestions.length; } + const shouldDisplayMenetionsSuggestions = (isValidRoomName(suggestionWord.toLowerCase()) || prefix === '') && isChatRoom; + if (!isCursorBeforeTheMention && prefixType === '#' && shouldDisplayMenetionsSuggestions) { + // filter reports by room name and current policy + const filteredRoomMentions = getRoomMentionOptions(prefix, reports); + nextState.suggestedMentions = filteredRoomMentions; + nextState.shouldShowSuggestionMenu = !!filteredRoomMentions.length; + } + setSuggestionValues((prevState) => ({ ...prevState, ...nextState, })); setHighlightedMentionIndex(0); }, - [getMentionOptions, personalDetails, resetSuggestions, setHighlightedMentionIndex, value, isComposerFocused], + [isComposerFocused, value, isChatRoom, setHighlightedMentionIndex, resetSuggestions, getUserMentionOptions, personalDetails, getRoomMentionOptions, reports], ); useEffect(() => { @@ -330,4 +405,8 @@ function SuggestionMention( SuggestionMention.displayName = 'SuggestionMention'; -export default forwardRef(SuggestionMention); +export default withOnyx, RoomMentionOnyxProps>({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, +})(forwardRef(SuggestionMention)); diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index 61026a792919..ed9050f686e2 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -46,6 +46,12 @@ type SuggestionProps = { /** The height of the composer */ composerHeight?: number; + + /** if current composer is connected with chat room or not */ + isChatRoom: boolean; + + /** policy ID connected to current composer */ + policyID?: string; }; /** @@ -66,6 +72,8 @@ function Suggestions( measureParentContainer, isAutoSuggestionPickerLarge = true, isComposerFocused, + isChatRoom, + policyID, }: SuggestionProps, ref: ForwardedRef, ) { @@ -155,6 +163,8 @@ function Suggestions( isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused, + isChatRoom, + policyID, }; return ( From 77813bb557245eb13f009767ab09964cde9a3365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 5 Apr 2024 12:08:15 +0200 Subject: [PATCH 022/116] Enable live markdown in native BaseTextInput --- .../TextInput/BaseTextInput/index.native.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index e7f4507df7c6..864c83d07cc7 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -8,6 +8,9 @@ import FormHelpMessage from '@components/FormHelpMessage'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; +import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import RNTextInput from '@components/RNTextInput'; import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; @@ -54,10 +57,13 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, + markdownEnabled = false, ...props }: BaseTextInputProps, ref: ForwardedRef, ) { + const InputComponent = markdownEnabled ? RNMarkdownTextInput : RNTextInput; + const inputProps = {shouldSaveDraft: false, shouldUseDefaultValue: false, ...props}; const theme = useTheme(); const styles = useThemeStyles(); @@ -321,13 +327,14 @@ function BaseTextInput( )} - { + { + const baseTextInputRef = element as BaseTextInputRef | null; if (typeof ref === 'function') { - ref(element); + ref(baseTextInputRef); } else if (ref && 'current' in ref) { // eslint-disable-next-line no-param-reassign - ref.current = element; + ref.current = baseTextInputRef; } input.current = element; From d16be1654c3e39c1264b628c47e0ea12473a5543 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 8 Apr 2024 14:45:50 +0700 Subject: [PATCH 023/116] refactor logic --- src/libs/actions/IOU.ts | 4 ++-- src/pages/home/report/ReportActionsList.tsx | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 46eadc06018f..bb698cb21571 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1370,6 +1370,7 @@ function createDistanceRequest( moneyRequestReportID, ); + const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; const parameters: CreateDistanceRequestParams = { comment, iouReportID: iouReport.reportID, @@ -1390,8 +1391,7 @@ function createDistanceRequest( }; API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData); - Navigation.dismissModal(isMoneyRequestReport ? report.reportID : chatReport.reportID); - const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; + Navigation.dismissModal(activeReportID); Report.notifyNewAction(activeReportID, userAccountID); } diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 2a325531baff..55604a4aec95 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -198,7 +198,6 @@ function ReportActionsList({ const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedReportActions?.[0].created === report.lastVisibleActionCreated; const hasNewestReportActionRef = useRef(hasNewestReportAction); - hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -216,6 +215,10 @@ function ReportActionsList({ opacity.value = withTiming(1, {duration: 100}); }, [opacity]); + useEffect(() => { + hasNewestReportActionRef.current = hasNewestReportAction; + }, [hasNewestReportAction]); + useEffect(() => { if ( scrollingVerticalOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD && From 4651dd8ca69c059bde89586aa83bfae1952edf0a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 8 Apr 2024 15:29:05 +0700 Subject: [PATCH 024/116] remove unnecessary useEffect --- src/pages/home/report/ReportActionsList.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 55604a4aec95..ef6a7450acc7 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -215,10 +215,6 @@ function ReportActionsList({ opacity.value = withTiming(1, {duration: 100}); }, [opacity]); - useEffect(() => { - hasNewestReportActionRef.current = hasNewestReportAction; - }, [hasNewestReportAction]); - useEffect(() => { if ( scrollingVerticalOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD && @@ -230,6 +226,7 @@ function ReportActionsList({ } previousLastIndex.current = lastActionIndex; reportActionSize.current = sortedVisibleReportActions.length; + hasNewestReportActionRef.current = hasNewestReportAction; }, [lastActionIndex, sortedVisibleReportActions, reportScrollManager, hasNewestReportAction, linkedReportActionID]); useEffect(() => { From d44456c3028f552f34457fd71ebf892518ed791d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 8 Apr 2024 15:55:51 +0700 Subject: [PATCH 025/116] create a const for held request check --- src/libs/actions/IOU.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0657729ff2e9..784f86c20b92 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4634,7 +4634,8 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full?: boolean) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; - if (ReportUtils.hasHeldExpenses(expenseReport.reportID) && !full && !!expenseReport.unheldTotal) { + const hasHeldExpenses = ReportUtils.hasHeldExpenses(expenseReport.reportID); + if (hasHeldExpenses && !full && !!expenseReport.unheldTotal) { total = expenseReport.unheldTotal; } const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(total, expenseReport.currency ?? '', expenseReport.reportID); @@ -4730,8 +4731,8 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full ]; // Clear hold reason of all transactions if we approve all requests - const heldTransactions = ReportUtils.getAllHeldTransactions(expenseReport.reportID); - if (full && heldTransactions.length) { + if (full && hasHeldExpenses) { + const heldTransactions = ReportUtils.getAllHeldTransactions(expenseReport.reportID); heldTransactions.forEach((heldTransaction) => { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, From b9b79520e2869b106a7134597e3ef2554e67dcef Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 8 Apr 2024 18:45:59 +0200 Subject: [PATCH 026/116] wip --- .../BaseHTMLEngineProvider.tsx | 1 + .../HTMLRenderers/MentionRoomRenderer.tsx | 93 +++++++++++++++++++ .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 + src/pages/home/report/ReportActionsView.tsx | 6 ++ 4 files changed, 102 insertions(+) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index a06e2dbbf421..9551e9d8f7c8 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -58,6 +58,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim contentModel: HTMLContentModel.textual, }), 'mention-user': HTMLElementModel.fromCustomModel({tagName: 'mention-user', contentModel: HTMLContentModel.textual}), + 'mention-report': HTMLElementModel.fromCustomModel({tagName: 'mention-report', contentModel: HTMLContentModel.textual}), 'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}), 'next-step': HTMLElementModel.fromCustomModel({ tagName: 'next-step', diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx new file mode 100644 index 000000000000..08435b5c2727 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -0,0 +1,93 @@ +import Str from 'expensify-common/lib/str'; +import cloneDeep from 'lodash/cloneDeep'; +import isEmpty from 'lodash/isEmpty'; +import React from 'react'; +import {StyleSheet, type TextStyle} from 'react-native'; +import {OnyxCollection, withOnyx} from 'react-native-onyx'; +import {CustomRendererProps, TNodeChildrenRenderer, TPhrasing, TText} from 'react-native-render-html'; +import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; +import Text from '@components/Text'; +import useCurrentReportID from '@hooks/useCurrentReportID'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import Navigation from '@navigation/Navigation'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES, {Route} from '@src/ROUTES'; +import type {Report} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type RoomMentionOnyxProps = { + /** All reports shared with the user */ + reports: OnyxCollection; +}; + +type MentionRoomRendererProps = RoomMentionOnyxProps & CustomRendererProps; + +function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const htmlAttributeReportID = tnode.attributes.reportid; + const currentReportID = useCurrentReportID(); + + let reportID: number | undefined; + let mentionDisplayText: string; + + const tnodeClone = cloneDeep(tnode); + + if (!isEmpty(htmlAttributeReportID)) { + const report = reports!['report_' + htmlAttributeReportID]; + + reportID = report?.reportID ? parseInt(report.reportID, 10) : undefined; + mentionDisplayText = report?.displayName ?? report?.reportName ?? htmlAttributeReportID; + } else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) { + mentionDisplayText = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1); + + Object.values(reports ?? {}).some((report) => { + if (report?.reportName === mentionDisplayText || report?.reportName === tnodeClone.data) { + reportID = Number(report?.reportID); + return true; + } + return false; + }); + + mentionDisplayText = Str.removeSMSDomain(mentionDisplayText); + } else { + return null; + } + + const navigationRoute: Route | undefined = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; + + const isOurMention = String(reportID) === currentReportID?.currentReportID; + + const flattenStyle = StyleSheet.flatten(style as TextStyle); + const {color, ...styleWithoutColor} = flattenStyle; + + return ( + + {() => ( + { + event.preventDefault(); + Navigation.navigate(navigationRoute); + }} + role={CONST.ROLE.LINK} + accessibilityLabel={`/${navigationRoute}`} + > + {htmlAttributeReportID ? `#${mentionDisplayText}` : } + + )} + + ); +} + +MentionRoomRenderer.displayName = 'MentionRoomRenderer'; + +export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, +})(MentionRoomRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index fdd0c89ec5a0..537ce305fc4c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -5,6 +5,7 @@ import EditedRenderer from './EditedRenderer'; import EmojiRenderer from './EmojiRenderer'; import ImageRenderer from './ImageRenderer'; import MentionHereRenderer from './MentionHereRenderer'; +import MentionRoomRenderer from './MentionRoomRenderer'; import MentionUserRenderer from './MentionUserRenderer'; import NextStepEmailRenderer from './NextStepEmailRenderer'; import PreRenderer from './PreRenderer'; @@ -25,6 +26,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { pre: PreRenderer, /* eslint-disable @typescript-eslint/naming-convention */ 'mention-user': MentionUserRenderer, + 'mention-report': MentionRoomRenderer, 'mention-here': MentionHereRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index ef436e57dc10..c6bef1985b1d 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -5,6 +5,7 @@ import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, use import {InteractionManager} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import RenderHTML from '@components/RenderHTML'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import useNetwork from '@hooks/useNetwork'; @@ -530,6 +531,11 @@ function ReportActionsView({ shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScroll} /> + #admins'} /> + '} /> + '} /> + #dupa'} /> + '} /> ); } From 6f86eeea9e050d705c899ee1764f6db485b294e0 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:31:16 +0200 Subject: [PATCH 027/116] refactor IOURequest pages --- .../DistanceRequestRenderItem.tsx | 2 +- src/libs/Navigation/OnyxTabNavigator.tsx | 5 +- src/libs/Navigation/types.ts | 27 +++- src/libs/TransactionUtils.ts | 7 +- src/libs/actions/IOU.ts | 56 ++++--- .../request/IOURequestRedirectToStartPage.tsx | 4 +- src/pages/iou/request/IOURequestStartPage.tsx | 56 +++---- .../iou/request/step/IOURequestStepAmount.tsx | 80 ++++------ .../request/step/IOURequestStepDistance.tsx | 151 ++++++++++-------- .../step/withWritableReportOrNotFound.tsx | 8 +- src/types/onyx/Transaction.ts | 3 +- 11 files changed, 220 insertions(+), 179 deletions(-) diff --git a/src/components/DistanceRequest/DistanceRequestRenderItem.tsx b/src/components/DistanceRequest/DistanceRequestRenderItem.tsx index 57e4fb0b530e..f4b11c159326 100644 --- a/src/components/DistanceRequest/DistanceRequestRenderItem.tsx +++ b/src/components/DistanceRequest/DistanceRequestRenderItem.tsx @@ -17,7 +17,7 @@ type DistanceRequestProps = { onSecondaryInteraction?: () => void; /** Function to get the index of the item */ - getIndex?: () => number; + getIndex?: () => number | undefined; /** Whether the item is active */ isActive?: boolean; diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index deab975e067b..91c25dc82084 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -9,6 +9,7 @@ import Tab from '@userActions/Tab'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SelectedTabRequest} from '@src/types/onyx'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type { IOURequestType } from '@libs/actions/IOU'; import {defaultScreenOptions} from './OnyxTabNavigatorConfig'; type OnyxTabNavigatorOnyxProps = { @@ -24,7 +25,7 @@ type OnyxTabNavigatorProps = OnyxTabNavigatorOnyxProps & selectedTab?: SelectedTabRequest; /** A function triggered when a tab has been selected */ - onTabSelected?: (newIouType: string) => void; + onTabSelected?: (newIouType: IOURequestType) => void; tabBar: (props: TabSelectorProps) => React.ReactNode; @@ -52,7 +53,7 @@ function OnyxTabNavigator({id, selectedTab, children, onTabSelected = () => {}, const index = state.index; const routeNames = state.routeNames; Tab.setSelectedTab(id, routeNames[index] as SelectedTabRequest); - onTabSelected(routeNames[index]); + onTabSelected(routeNames[index] as IOURequestType); }, ...(screenListeners ?? {}), }} diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 10ca31816b10..8e147604b8e1 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -17,6 +17,7 @@ import type NAVIGATORS from '@src/NAVIGATORS'; import type {HybridAppRoute, Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type EXIT_SURVEY_REASON_FORM_INPUT_IDS from '@src/types/form/ExitSurveyReasonForm'; +import type { IOURequestType } from '@libs/actions/IOU'; type NavigationRef = NavigationContainerRefWithCurrent; @@ -330,13 +331,6 @@ type RoomInviteNavigatorParamList = { }; type MoneyRequestNavigatorParamList = { - [SCREENS.MONEY_REQUEST.STEP_AMOUNT]: { - action: ValueOf; - iouType: ValueOf; - transactionID: string; - reportID: string; - backTo: string; - }; [SCREENS.MONEY_REQUEST.PARTICIPANTS]: { iouType: string; reportID: string; @@ -419,7 +413,7 @@ type MoneyRequestNavigatorParamList = { threadReportID: number; }; [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: { - action: string; + action: ValueOf; iouType: ValueOf; transactionID: string; reportID: string; @@ -429,6 +423,23 @@ type MoneyRequestNavigatorParamList = { iouType: string; reportID: string; }; + [SCREENS.MONEY_REQUEST.CREATE]: { + iouType: ValueOf; + transactionID: string; + reportID: string; + }; + [SCREENS.MONEY_REQUEST.START]: { + iouType: ValueOf; + reportID: string; + iouRequestType: IOURequestType; + }; + [SCREENS.MONEY_REQUEST.STEP_AMOUNT]: { + iouType: ValueOf; + reportID: string; + transactionID: string; + backTo: Routes; + action: ValueOf; + }; }; type NewTaskNavigatorParamList = { diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 96c5ba8c78ec..a7af69d6961b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -12,6 +12,7 @@ import DateUtils from './DateUtils'; import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import {getCleanedTagName} from './PolicyUtils'; +import type { IOURequestType } from './actions/IOU'; let allTransactions: OnyxCollection = {}; @@ -45,16 +46,16 @@ function isDistanceRequest(transaction: OnyxEntry): boolean { return type === CONST.TRANSACTION.TYPE.CUSTOM_UNIT && customUnitName === CONST.CUSTOM_UNITS.NAME_DISTANCE; } -function isScanRequest(transaction: Transaction): boolean { +function isScanRequest(transaction: OnyxEntry): boolean { // This is used during the request creation flow before the transaction has been saved to the server if (lodashHas(transaction, 'iouRequestType')) { - return transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; } return Boolean(transaction?.receipt?.source); } -function getRequestType(transaction: Transaction): ValueOf { +function getRequestType(transaction: OnyxEntry): IOURequestType { if (isDistanceRequest(transaction)) { return CONST.IOU.REQUEST_TYPE.DISTANCE; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 540b9143ba6e..f39190dcf8f6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2172,15 +2172,24 @@ function updateMoneyRequestTaxRate( API.write('UpdateMoneyRequestTaxRate', params, onyxData); } +type UpdateMoneyRequestDistanceParams = { + transactionID: string; + transactionThreadReportID: string; + waypoints: WaypointCollection; + policy?: OnyxEntry; + policyTagList?: OnyxEntry; + policyCategories?: OnyxEntry; +} + /** Updates the waypoints of a distance money request */ -function updateMoneyRequestDistance( - transactionID: string, - transactionThreadReportID: string, - waypoints: WaypointCollection, - policy: OnyxEntry, - policyTagList: OnyxEntry, - policyCategories: OnyxEntry, -) { +function updateMoneyRequestDistance({ + transactionID, + transactionThreadReportID, + waypoints, + policy = {} as OnyxTypes.Policy, + policyTagList = {}, + policyCategories = {}, +}: UpdateMoneyRequestDistanceParams) { const transactionChanges: TransactionChanges = { waypoints, }; @@ -3780,21 +3789,31 @@ function editMoneyRequest( } } +type UpdateMoneyRequestAmountAndCurrencyParams = { + transactionID: string; + transactionThreadReportID: string; + currency: string; + amount: number; + policy?: OnyxEntry; + policyTagList?: OnyxEntry; + policyCategories?: OnyxEntry; +}; + /** Updates the amount and currency fields of a money request */ -function updateMoneyRequestAmountAndCurrency( - transactionID: string, - transactionThreadReportID: string, - currency: string, - amount: number, - policy: OnyxEntry, - policyTagList: OnyxEntry, - policyCategories: OnyxEntry, -) { +function updateMoneyRequestAmountAndCurrency({ + transactionID, + transactionThreadReportID, + currency, + amount, + policy, + policyTagList, + policyCategories, +}: UpdateMoneyRequestAmountAndCurrencyParams) { const transactionChanges = { amount, currency, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true); + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy ?? null, policyTagList ?? null, policyCategories ?? null, true); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY, params, onyxData); } @@ -5433,6 +5452,7 @@ function savePreferredPaymentMethod(policyID: string, paymentMethod: PaymentMeth Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod}); } +export type {IOURequestType} export { setMoneyRequestParticipants, createDistanceRequest, diff --git a/src/pages/iou/request/IOURequestRedirectToStartPage.tsx b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx index c34fa6065028..ea459392481e 100644 --- a/src/pages/iou/request/IOURequestRedirectToStartPage.tsx +++ b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -6,9 +5,10 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; -type IOURequestRedirectToStartPageProps = WithWritableReportOrNotFoundProps; +type IOURequestRedirectToStartPageProps = WithWritableReportOrNotFoundProps; function IOURequestRedirectToStartPage({ route: { diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 3852e04dfe04..9f14ad1d989b 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,10 +1,9 @@ +import _ from 'lodash'; import {useFocusEffect, useNavigation} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import { withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -22,31 +21,36 @@ import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import type * as OnyxTypes from '@src/types/onyx'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type { Policy, Report, SelectedTabRequest, Transaction } from '@src/types/onyx'; +import type SCREENS from '@src/SCREENS'; +import type { IOURequestType } from '@userActions/IOU'; import IOURequestStepAmount from './step/IOURequestStepAmount'; import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepScan from './step/IOURequestStepScan'; import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; type IOURequestStartPageOnyxProps = { - report?: OnyxEntry, - policy?: OnyxEntry, - selectedTab?: typeof CONST.TAB_REQUEST[keyof typeof CONST.TAB_REQUEST], - transaction?: OnyxEntry, + report: OnyxEntry; + + policy: OnyxEntry; + + selectedTab: OnyxEntry; + + transaction: OnyxEntry; }; -type IOURequestStartPageProps = WithWritableReportOrNotFoundProps & IOURequestStartPageOnyxProps; +type IOURequestStartPageProps = IOURequestStartPageOnyxProps & WithWritableReportOrNotFoundProps; function IOURequestStartPage({ + report, + policy, route, route: { params: {iouType, reportID}, }, - report, - policy, selectedTab, transaction, }: IOURequestStartPageProps) { @@ -63,11 +67,11 @@ function IOURequestStartPage({ const transactionRequestType = useRef(TransactionUtils.getRequestType(transaction)); const previousIOURequestType = usePrevious(transactionRequestType.current); const {canUseP2PDistanceRequests} = usePermissions(); - const isFromGlobalCreate = _.isEmpty(report.reportID); + const isFromGlobalCreate = _.isEmpty(report?.reportID); useFocusEffect( useCallback(() => { - const handler = (event) => { + const handler = (event: KeyboardEvent) => { if (event.code !== CONST.KEYBOARD_SHORTCUTS.TAB.shortcutKey) { return; } @@ -82,7 +86,7 @@ function IOURequestStartPage({ // Clear out the temporary money request if the reportID in the URL has changed from the transaction's reportID useEffect(() => { - if (transaction.reportID === reportID) { + if (transaction?.reportID === reportID) { return; } IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, transactionRequestType.current); @@ -90,30 +94,30 @@ function IOURequestStartPage({ const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isExpenseReport = ReportUtils.isExpenseReport(report); - const shouldDisplayDistanceRequest = iouType !== CONST.IOU.TYPE.TRACK_EXPENSE && (canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate); + const shouldDisplayDistanceRequest = iouType !== CONST.IOU.TYPE.TRACK_EXPENSE && (canUseP2PDistanceRequests ?? isExpenseChat ?? isExpenseReport ?? isFromGlobalCreate); // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(report.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); + const isAllowedToCreateRequest = _.isEmpty(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); const navigateBack = () => { Navigation.dismissModal(); }; const resetIOUTypeIfChanged = useCallback( - (newIouType) => { + (newIouType: IOURequestType) => { if (newIouType === previousIOURequestType) { return; } - if (iouType === CONST.IOU.TYPE.SPLIT && transaction.isFromGlobalCreate) { - IOU.updateMoneyRequestTypeParams(navigation.getState().routes, CONST.IOU.TYPE.REQUEST, newIouType); + if (iouType === CONST.IOU.TYPE.SPLIT && transaction?.isFromGlobalCreate) { + IOU.updateMoneyRequestTypeParams(navigation.getState()?.routes ?? [], CONST.IOU.TYPE.REQUEST, newIouType); } IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, - [policy, previousIOURequestType, reportID, isFromGlobalCreate, iouType, navigation, transaction.isFromGlobalCreate], + [policy, previousIOURequestType, reportID, isFromGlobalCreate, iouType, navigation, transaction?.isFromGlobalCreate], ); - if (!transaction.transactionID) { + if (!transaction?.transactionID) { // The draft transaction is initialized only after the component is mounted, // which will lead to briefly displaying the Not Found page without this loader. return ; @@ -141,7 +145,7 @@ function IOURequestStartPage({ {iouType !== CONST.IOU.TYPE.SEND ? ( @@ -161,20 +165,18 @@ function IOURequestStartPage({ } IOURequestStartPage.displayName = 'IOURequestStartPage'; -IOURequestStartPage.propTypes = propTypes; -IOURequestStartPage.defaultProps = defaultProps; -export default withOnyx({ +export default withOnyx({ report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route?.params?.reportID}`, }, policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, }, selectedTab: { key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.IOU_REQUEST_TYPE}`, }, transaction: { - key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${lodashGet(route, 'params.transactionID', '0')}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${route?.params.transactionID ?? 0}`, }, })(IOURequestStartPage); diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index b392ee310205..8d25a39086b9 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -1,9 +1,8 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; import lodashIsEmpty from 'lodash/isEmpty'; import React, {useCallback, useEffect, useRef} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import transactionPropTypes from '@components/transactionPropTypes'; +import type {OnyxEntry} from 'react-native-onyx'; +import { withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import * as TransactionEdit from '@libs/actions/TransactionEdit'; import compose from '@libs/compose'; @@ -13,39 +12,25 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import {getRequestType} from '@libs/TransactionUtils'; import MoneyRequestAmountForm from '@pages/iou/steps/MoneyRequestAmountForm'; -import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import type { Transaction } from '@src/types/onyx'; +import type SCREENS from '@src/SCREENS'; +import type { BaseTextInputRef } from '@components/TextInput/BaseTextInput/types'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; -import withWritableReportOrNotFound from './withWritableReportOrNotFound'; +import type { WithWritableReportOrNotFoundProps } from './withWritableReportOrNotFound'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, +type IOURequestStepAmountOnyxProps = { + splitDraftTransaction: OnyxEntry; - /* Onyx Props */ - /** The report that the transaction belongs to */ - report: reportPropTypes, - - /** The transaction object being modified in Onyx */ - transaction: transactionPropTypes, - - /** The draft transaction that holds data to be persisted on the current transaction */ - splitDraftTransaction: transactionPropTypes, - - /** The draft transaction object being modified in Onyx */ - draftTransaction: transactionPropTypes, + draftTransaction: OnyxEntry; }; -const defaultProps = { - report: {}, - transaction: {}, - splitDraftTransaction: {}, - draftTransaction: {}, +type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & WithWritableReportOrNotFoundProps & { + transaction: OnyxEntry; }; function IOURequestStepAmount({ @@ -56,22 +41,22 @@ function IOURequestStepAmount({ transaction, splitDraftTransaction, draftTransaction, -}) { +}: IOURequestStepAmountProps) { const {translate} = useLocalize(); - const textInput = useRef(null); - const focusTimeoutRef = useRef(null); + const textInput = useRef(null); + const focusTimeoutRef = useRef(null); const isSaveButtonPressed = useRef(false); - const originalCurrency = useRef(null); + const originalCurrency = useRef(null); const iouRequestType = getRequestType(transaction); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isEditingSplitBill = isEditing && isSplitBill; - const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction); - const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction); + const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? { amount: 0 }; + const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? { currency: '' }; useFocusEffect( useCallback(() => { - focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); + focusTimeoutRef.current = setTimeout(() => textInput.current?.focus(), CONST.ANIMATED_TRANSITION); return () => { if (!focusTimeoutRef.current) { return; @@ -92,10 +77,10 @@ function IOURequestStepAmount({ if (isSaveButtonPressed.current) { return; } - TransactionEdit.removeBackupTransaction(transaction.transactionID || ''); + TransactionEdit.removeBackupTransaction(transaction?.transactionID ?? ''); }; } - if (transaction.originalCurrency) { + if (transaction?.originalCurrency) { originalCurrency.current = transaction.originalCurrency; } else { originalCurrency.current = currency; @@ -105,7 +90,7 @@ function IOURequestStepAmount({ if (isSaveButtonPressed.current) { return; } - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true); + IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current ?? '', true); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -118,10 +103,14 @@ function IOURequestStepAmount({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, backTo ? 'confirm' : '', Navigation.getActiveRouteWithoutParams())); }; + type AmountParams = { + amount: string; + }; + /** - * @param {Number} amount + * @param amount */ - const navigateToNextPage = ({amount}) => { + const navigateToNextPage = ({amount}: AmountParams) => { isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); @@ -135,7 +124,7 @@ function IOURequestStepAmount({ // If a reportID exists in the report object, it's because the user started this flow from using the + button in the composer // inside a report. In this case, the participants can be automatically assigned from the report and the user can skip the participants step and go straight // to the confirm step. - if (report.reportID) { + if (report?.reportID) { IOU.setMoneyRequestParticipantsFromReport(transactionID, report); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; @@ -146,7 +135,7 @@ function IOURequestStepAmount({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); }; - const saveAmountAndCurrency = ({amount}) => { + const saveAmountAndCurrency = ({amount}: AmountParams) => { if (!isEditing) { navigateToNextPage({amount}); return; @@ -166,7 +155,7 @@ function IOURequestStepAmount({ return; } - IOU.updateMoneyRequestAmountAndCurrency(transactionID, reportID, currency, newAmount); + IOU.updateMoneyRequestAmountAndCurrency({transactionID, transactionThreadReportID: reportID, currency, amount: newAmount}); Navigation.dismissModal(); }; @@ -191,23 +180,20 @@ function IOURequestStepAmount({ ); } -IOURequestStepAmount.propTypes = propTypes; -IOURequestStepAmount.defaultProps = defaultProps; IOURequestStepAmount.displayName = 'IOURequestStepAmount'; export default compose( - withWritableReportOrNotFound, withFullTransactionOrNotFound, - withOnyx({ + withOnyx({ splitDraftTransaction: { key: ({route}) => { - const transactionID = lodashGet(route, 'params.transactionID', 0); + const transactionID = route.params.transactionID ?? 0; return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; }, }, draftTransaction: { key: ({route}) => { - const transactionID = lodashGet(route, 'params.transactionID', 0); + const transactionID = route.params.transactionID ?? 0; return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; }, }, diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 7b3c49e80cf5..0378102ef649 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -1,15 +1,12 @@ -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import { withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type { OnyxEntry } from 'react-native-onyx'; +import {withOnyx } from 'react-native-onyx'; import Button from '@components/Button'; import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter'; import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import DraggableList from '@components/DraggableList'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; @@ -18,33 +15,37 @@ import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; -import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as MapboxToken from '@userActions/MapboxToken'; -import * as Transaction from '@userActions/Transaction'; +import * as TransactionAction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type SCREENS from '@src/SCREENS'; +import { isEmpty, isEqual } from 'lodash'; +// eslint-disable-next-line no-restricted-imports +import type { ScrollView as RNScrollView } from 'react-native'; +import type { WaypointCollection } from '@src/types/onyx/Transaction'; +import type { RenderItemParams } from 'react-native-draggable-flatlist/lib/typescript/types'; +import type { Errors } from '@src/types/onyx/OnyxCommon'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type { WithWritableReportOrNotFoundProps } from './withWritableReportOrNotFound'; -import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type IOURequestStepDistanceOnyxProps = { - transactionBackup?: OnyxEntry, -} + transactionBackup: OnyxEntry; +}; -type IOURequestStepDistanceProps = WithWritableReportOrNotFoundProps & IOURequestStepDistanceOnyxProps & { - report?: OnyxEntry, - transaction?: OnyxEntry, +type IOURequestStepDistanceProps = IOURequestStepDistanceOnyxProps & WithWritableReportOrNotFoundProps & { + transaction: OnyxEntry; }; function IOURequestStepDistance({ + report, route: { params: {action, iouType, reportID, transactionID, backTo}, }, - report, transaction, transactionBackup, }: IOURequestStepDistanceProps) { @@ -52,26 +53,26 @@ function IOURequestStepDistance({ const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); - const waypoints = useMemo(() => optimisticWaypoints || lodashGet(transaction, 'comment.waypoints', {waypoint0: {}, waypoint1: {}}), [optimisticWaypoints, transaction]); + const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); + const waypoints = useMemo(() => optimisticWaypoints ?? transaction?.comment?.waypoints ?? { waypoint0: {}, waypoint1: {} }, [optimisticWaypoints, transaction]); const waypointsList = Object.keys(waypoints); const previousWaypoints = usePrevious(waypoints); const numberOfWaypoints = Object.keys(waypoints).length; const numberOfPreviousWaypoints = Object.keys(previousWaypoints).length; - const scrollViewRef = useRef(null); - const isLoadingRoute = lodashGet(transaction, 'comment.isLoading', false); - const isLoading = lodashGet(transaction, 'isLoading', false); - const hasRouteError = !!lodashGet(transaction, 'errorFields.route'); + const scrollViewRef = useRef(null); + const isLoadingRoute = transaction?.comment?.isLoading ?? false; + const isLoading = transaction?.isLoading ?? false; + const hasRouteError = !!transaction?.errorFields?.route; const hasRoute = TransactionUtils.hasRoute(transaction); const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); const previousValidatedWaypoints = usePrevious(validatedWaypoints); - const haveValidatedWaypointsChanged = !_.isEqual(previousValidatedWaypoints, validatedWaypoints); + const haveValidatedWaypointsChanged = isEqual(previousValidatedWaypoints, validatedWaypoints); const isRouteAbsentWithoutErrors = !hasRoute && !hasRouteError; - const shouldFetchRoute = (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && _.size(validatedWaypoints) > 1; + const shouldFetchRoute = (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1; const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false); - const nonEmptyWaypointsCount = useMemo(() => _.filter(_.keys(waypoints), (key) => !_.isEmpty(waypoints[key])).length, [waypoints]); - const duplicateWaypointsError = useMemo(() => nonEmptyWaypointsCount >= 2 && _.size(validatedWaypoints) !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints]); - const atLeastTwoDifferentWaypointsError = useMemo(() => Object.keys(validatedWaypoints) < 2, [validatedWaypoints]); + const nonEmptyWaypointsCount = useMemo(() => Object.keys(waypoints).filter((key) => isEmpty(waypoints[key])).length, [waypoints]); + const duplicateWaypointsError = useMemo(() => nonEmptyWaypointsCount >= 2 && Object.keys(validatedWaypoints).length !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints]); + const atLeastTwoDifferentWaypointsError = useMemo(() => Object.keys(validatedWaypoints).length < 2, [validatedWaypoints]); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = !(backTo || isEditing); @@ -84,14 +85,14 @@ function IOURequestStepDistance({ if (isOffline || !shouldFetchRoute) { return; } - Transaction.getRoute(transactionID, validatedWaypoints, action === CONST.IOU.ACTION.CREATE); + TransactionAction.getRoute(transactionID, validatedWaypoints, action === CONST.IOU.ACTION.CREATE); }, [shouldFetchRoute, transactionID, validatedWaypoints, isOffline, action]); useEffect(() => { if (numberOfWaypoints <= numberOfPreviousWaypoints) { return; } - scrollViewRef.current.scrollToEnd({animated: true}); + scrollViewRef.current?.scrollToEnd({animated: true}); }, [numberOfPreviousWaypoints, numberOfWaypoints]); useEffect(() => { @@ -107,10 +108,11 @@ function IOURequestStepDistance({ /** * Takes the user to the page for editing a specific waypoint - * @param {Number} index of the waypoint to edit + * @param index of the waypoint to edit */ - const navigateToWaypointEditPage = (index) => { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report.reportID, index, Navigation.getActiveRouteWithoutParams())); + // eslint-disable-next-line react-hooks/exhaustive-deps + const navigateToWaypointEditPage = (index: number) => { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report?.reportID ?? '', index.toString(), Navigation.getActiveRouteWithoutParams())); }; const navigateToNextStep = useCallback(() => { @@ -122,7 +124,7 @@ function IOURequestStepDistance({ // If a reportID exists in the report object, it's because the user started this flow from using the + button in the composer // inside a report. In this case, the participants can be automatically assigned from the report and the user can skip the participants step and go straight // to the confirm step. - if (report.reportID) { + if (report?.reportID) { IOU.setMoneyRequestParticipantsFromReport(transactionID, report); Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); return; @@ -139,36 +141,40 @@ function IOURequestStepDistance({ return ErrorUtils.getLatestErrorField(transaction, 'route'); } if (duplicateWaypointsError) { - return {0: translate('iou.error.duplicateWaypointsErrorMessage')}; + return {duplicateWaypointsError: translate('iou.error.duplicateWaypointsErrorMessage')} as Errors; } if (atLeastTwoDifferentWaypointsError) { - return {0: 'iou.error.atLeastTwoDifferentWaypoints'}; + return {atLeastTwoDifferentWaypointsError: 'iou.error.atLeastTwoDifferentWaypoints'} as Errors; } + return {}; + }; + + type DataParams = { + data: string[] }; const updateWaypoints = useCallback( - ({data}) => { - if (_.isEqual(waypointsList, data)) { + ({data}: DataParams) => { + if (isEqual(waypointsList, data)) { return; } - const newWaypoints = {}; + const newWaypoints: WaypointCollection = {}; let emptyWaypointIndex = -1; - _.each(data, (waypoint, index) => { - newWaypoints[`waypoint${index}`] = lodashGet(waypoints, waypoint, {}); + data.forEach((waypoint, index) => { + newWaypoints[`waypoint${index}`] = waypoints.waypoint ?? {}; // Find waypoint that BECOMES empty after dragging - if (_.isEmpty(newWaypoints[`waypoint${index}`]) && !_.isEmpty(lodashGet(waypoints, `waypoint${index}`, {}))) { + if (isEmpty(newWaypoints[`waypoint${index}`]) && !isEmpty(waypoints[`waypoint${index}`] ?? {})) { emptyWaypointIndex = index; } }); setOptimisticWaypoints(newWaypoints); - // eslint-disable-next-line rulesdir/no-thenable-actions-in-views Promise.all([ - Transaction.removeWaypoint(transaction, emptyWaypointIndex.toString(), action === CONST.IOU.ACTION.CREATE), - Transaction.updateWaypoints(transactionID, newWaypoints, action === CONST.IOU.ACTION.CREATE), + TransactionAction.removeWaypoint(transaction, emptyWaypointIndex.toString(), action === CONST.IOU.ACTION.CREATE), + TransactionAction.updateWaypoints(transactionID, newWaypoints, action === CONST.IOU.ACTION.CREATE), ]).then(() => { - setOptimisticWaypoints(undefined); + setOptimisticWaypoints(null); }); }, [transactionID, transaction, waypoints, waypointsList, action], @@ -183,15 +189,19 @@ function IOURequestStepDistance({ if (isEditing) { // If nothing was changed, simply go to transaction thread // We compare only addresses because numbers are rounded while backup - const oldWaypoints = lodashGet(transactionBackup, 'comment.waypoints', {}); - const oldAddresses = _.mapObject(oldWaypoints, (waypoint) => _.pick(waypoint, 'address')); - const addresses = _.mapObject(waypoints, (waypoint) => _.pick(waypoint, 'address')); - if (_.isEqual(oldAddresses, addresses)) { - Navigation.dismissModal(report.reportID); + const oldWaypoints = transactionBackup?.comment.waypoints ?? {}; + const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map( + ([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}] + )); + const addresses = Object.fromEntries(Object.entries(waypoints).map( + ([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}] + )); + if (isEqual(oldAddresses, addresses)) { + Navigation.dismissModal(); return; } - IOU.updateMoneyRequestDistance(transaction.transactionID, report.reportID, waypoints); - Navigation.dismissModal(report.reportID); + IOU.updateMoneyRequestDistance({transactionID: transaction?.transactionID ?? '', transactionThreadReportID: report?.reportID ?? '', waypoints}); + Navigation.dismissModal(); return; } @@ -206,10 +216,25 @@ function IOURequestStepDistance({ navigateToNextStep, transactionBackup, waypoints, - transaction.transactionID, - report.reportID, + transaction?.transactionID, + report?.reportID, ]); + const renderItem = useCallback( + ({item, drag, isActive, getIndex}: RenderItemParams) => ( + + ), + [isLoadingRoute, navigateToWaypointEditPage, waypoints], + ); + return ( item} shouldUsePortal onDragEnd={updateWaypoints} - scrollEventThrottle={variables.distanceScrollEventThrottle} ref={scrollViewRef} - renderItem={({item, drag, isActive, getIndex}) => ( - - )} + renderItem={renderItem} ListFooterComponent={ @@ -275,11 +288,13 @@ function IOURequestStepDistance({ IOURequestStepDistance.displayName = 'IOURequestStepDistance'; export default compose( - withWritableReportOrNotFound, withFullTransactionOrNotFound, - withOnyx({ + withOnyx({ transactionBackup: { - key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${props.transactionID}`, + key: ({route}) => { + const transactionID = route.params.transactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; + }, }, }), )(IOURequestStepDistance); diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 00ebba2b56c4..eca42bc14a8c 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -21,7 +21,11 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT | typeof SCREENS.MONEY_REQUEST.STEP_DESCRIPTION | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT; + | typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT + | typeof SCREENS.MONEY_REQUEST.STEP_AMOUNT + | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE + | typeof SCREENS.MONEY_REQUEST.CREATE + | typeof SCREENS.MONEY_REQUEST.START; type Route = RouteProp; @@ -53,7 +57,7 @@ export default function , WithWritableReportOrNotFoundOnyxProps>({ report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID ?? '0'}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`, }, })(forwardRef(WithWritableReportOrNotFound)); } diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 1750fa61e514..5f08da7afa4a 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -2,6 +2,7 @@ import type {KeysOfUnion, ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; +import type { IOURequestType } from '@libs/actions/IOU'; import type {Participant, Split} from './IOU'; import type * as OnyxCommon from './OnyxCommon'; import type RecentWaypoint from './RecentWaypoint'; @@ -133,7 +134,7 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< filename?: string; /** Used during the creation flow before the transaction is saved to the server */ - iouRequestType?: ValueOf; + iouRequestType?: IOURequestType; /** The original merchant name */ merchant: string; From 8e333dca6c01b5c9d8a24ce4c8eca89e96974a7b Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:31:53 +0200 Subject: [PATCH 028/116] fix prettier --- src/libs/Navigation/OnyxTabNavigator.tsx | 2 +- src/libs/Navigation/types.ts | 2 +- src/libs/TransactionUtils.ts | 2 +- src/libs/actions/IOU.ts | 14 ++++-- .../request/IOURequestRedirectToStartPage.tsx | 2 +- src/pages/iou/request/IOURequestStartPage.tsx | 12 ++--- .../iou/request/step/IOURequestStepAmount.tsx | 19 ++++---- .../request/step/IOURequestStepDistance.tsx | 48 ++++++++++--------- src/types/onyx/Transaction.ts | 2 +- 9 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index 91c25dc82084..734d8ee2a373 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -5,11 +5,11 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {TabSelectorProps} from '@components/TabSelector/TabSelector'; +import type {IOURequestType} from '@libs/actions/IOU'; import Tab from '@userActions/Tab'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SelectedTabRequest} from '@src/types/onyx'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type { IOURequestType } from '@libs/actions/IOU'; import {defaultScreenOptions} from './OnyxTabNavigatorConfig'; type OnyxTabNavigatorOnyxProps = { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8e147604b8e1..095a06a67675 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -11,13 +11,13 @@ import type { Route, } from '@react-navigation/native'; import type {ValueOf} from 'type-fest'; +import type {IOURequestType} from '@libs/actions/IOU'; import type CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; import type {HybridAppRoute, Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type EXIT_SURVEY_REASON_FORM_INPUT_IDS from '@src/types/form/ExitSurveyReasonForm'; -import type { IOURequestType } from '@libs/actions/IOU'; type NavigationRef = NavigationContainerRefWithCurrent; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index a7af69d6961b..efd5a08e5bd3 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -7,12 +7,12 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {RecentWaypoint, Report, TaxRate, TaxRates, TaxRatesWithDefault, Transaction, TransactionViolation} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {IOURequestType} from './actions/IOU'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import {getCleanedTagName} from './PolicyUtils'; -import type { IOURequestType } from './actions/IOU'; let allTransactions: OnyxCollection = {}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f39190dcf8f6..f5a78ddb0eed 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2179,7 +2179,7 @@ type UpdateMoneyRequestDistanceParams = { policy?: OnyxEntry; policyTagList?: OnyxEntry; policyCategories?: OnyxEntry; -} +}; /** Updates the waypoints of a distance money request */ function updateMoneyRequestDistance({ @@ -3813,7 +3813,15 @@ function updateMoneyRequestAmountAndCurrency({ amount, currency, }; - const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy ?? null, policyTagList ?? null, policyCategories ?? null, true); + const {params, onyxData} = getUpdateMoneyRequestParams( + transactionID, + transactionThreadReportID, + transactionChanges, + policy ?? null, + policyTagList ?? null, + policyCategories ?? null, + true, + ); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY, params, onyxData); } @@ -5452,7 +5460,7 @@ function savePreferredPaymentMethod(policyID: string, paymentMethod: PaymentMeth Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: paymentMethod}); } -export type {IOURequestType} +export type {IOURequestType}; export { setMoneyRequestParticipants, createDistanceRequest, diff --git a/src/pages/iou/request/IOURequestRedirectToStartPage.tsx b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx index ea459392481e..033b3b3e2edd 100644 --- a/src/pages/iou/request/IOURequestRedirectToStartPage.tsx +++ b/src/pages/iou/request/IOURequestRedirectToStartPage.tsx @@ -6,7 +6,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './step/withWritableReportOrNotFound'; type IOURequestRedirectToStartPageProps = WithWritableReportOrNotFoundProps; diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 9f14ad1d989b..cb062d22dde3 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,9 +1,9 @@ -import _ from 'lodash'; import {useFocusEffect, useNavigation} from '@react-navigation/native'; +import _ from 'lodash'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import { withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -22,15 +22,15 @@ import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; +import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type { Policy, Report, SelectedTabRequest, Transaction } from '@src/types/onyx'; import type SCREENS from '@src/SCREENS'; -import type { IOURequestType } from '@userActions/IOU'; +import type {Policy, Report, SelectedTabRequest, Transaction} from '@src/types/onyx'; import IOURequestStepAmount from './step/IOURequestStepAmount'; import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepScan from './step/IOURequestStepScan'; -import type { WithWritableReportOrNotFoundProps } from './step/withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './step/withWritableReportOrNotFound'; type IOURequestStartPageOnyxProps = { report: OnyxEntry; @@ -38,7 +38,7 @@ type IOURequestStartPageOnyxProps = { policy: OnyxEntry; selectedTab: OnyxEntry; - + transaction: OnyxEntry; }; diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 8d25a39086b9..893da88c468c 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -2,7 +2,8 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashIsEmpty from 'lodash/isEmpty'; import React, {useCallback, useEffect, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import { withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import * as TransactionEdit from '@libs/actions/TransactionEdit'; import compose from '@libs/compose'; @@ -16,12 +17,11 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type { Transaction } from '@src/types/onyx'; import type SCREENS from '@src/SCREENS'; -import type { BaseTextInputRef } from '@components/TextInput/BaseTextInput/types'; +import type {Transaction} from '@src/types/onyx'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; -import type { WithWritableReportOrNotFoundProps } from './withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; type IOURequestStepAmountOnyxProps = { splitDraftTransaction: OnyxEntry; @@ -29,9 +29,10 @@ type IOURequestStepAmountOnyxProps = { draftTransaction: OnyxEntry; }; -type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & WithWritableReportOrNotFoundProps & { - transaction: OnyxEntry; -}; +type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & + WithWritableReportOrNotFoundProps & { + transaction: OnyxEntry; + }; function IOURequestStepAmount({ report, @@ -51,8 +52,8 @@ function IOURequestStepAmount({ const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isEditingSplitBill = isEditing && isSplitBill; - const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? { amount: 0 }; - const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? { currency: '' }; + const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? {amount: 0}; + const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? {currency: ''}; useFocusEffect( useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 0378102ef649..09eb1898743c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -1,7 +1,11 @@ +import {isEmpty, isEqual} from 'lodash'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import type { OnyxEntry } from 'react-native-onyx'; -import {withOnyx } from 'react-native-onyx'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView as RNScrollView} from 'react-native'; +import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter'; import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem'; @@ -21,25 +25,22 @@ import * as TransactionAction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type * as OnyxTypes from '@src/types/onyx'; import type SCREENS from '@src/SCREENS'; -import { isEmpty, isEqual } from 'lodash'; -// eslint-disable-next-line no-restricted-imports -import type { ScrollView as RNScrollView } from 'react-native'; -import type { WaypointCollection } from '@src/types/onyx/Transaction'; -import type { RenderItemParams } from 'react-native-draggable-flatlist/lib/typescript/types'; -import type { Errors } from '@src/types/onyx/OnyxCommon'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {WaypointCollection} from '@src/types/onyx/Transaction'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; -import type { WithWritableReportOrNotFoundProps } from './withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; type IOURequestStepDistanceOnyxProps = { transactionBackup: OnyxEntry; }; -type IOURequestStepDistanceProps = IOURequestStepDistanceOnyxProps & WithWritableReportOrNotFoundProps & { - transaction: OnyxEntry; -}; +type IOURequestStepDistanceProps = IOURequestStepDistanceOnyxProps & + WithWritableReportOrNotFoundProps & { + transaction: OnyxEntry; + }; function IOURequestStepDistance({ report, @@ -54,7 +55,7 @@ function IOURequestStepDistance({ const {translate} = useLocalize(); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); - const waypoints = useMemo(() => optimisticWaypoints ?? transaction?.comment?.waypoints ?? { waypoint0: {}, waypoint1: {} }, [optimisticWaypoints, transaction]); + const waypoints = useMemo(() => optimisticWaypoints ?? transaction?.comment?.waypoints ?? {waypoint0: {}, waypoint1: {}}, [optimisticWaypoints, transaction]); const waypointsList = Object.keys(waypoints); const previousWaypoints = usePrevious(waypoints); const numberOfWaypoints = Object.keys(waypoints).length; @@ -71,7 +72,10 @@ function IOURequestStepDistance({ const shouldFetchRoute = (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1; const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false); const nonEmptyWaypointsCount = useMemo(() => Object.keys(waypoints).filter((key) => isEmpty(waypoints[key])).length, [waypoints]); - const duplicateWaypointsError = useMemo(() => nonEmptyWaypointsCount >= 2 && Object.keys(validatedWaypoints).length !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints]); + const duplicateWaypointsError = useMemo( + () => nonEmptyWaypointsCount >= 2 && Object.keys(validatedWaypoints).length !== nonEmptyWaypointsCount, + [nonEmptyWaypointsCount, validatedWaypoints], + ); const atLeastTwoDifferentWaypointsError = useMemo(() => Object.keys(validatedWaypoints).length < 2, [validatedWaypoints]); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = !(backTo || isEditing); @@ -112,7 +116,9 @@ function IOURequestStepDistance({ */ // eslint-disable-next-line react-hooks/exhaustive-deps const navigateToWaypointEditPage = (index: number) => { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report?.reportID ?? '', index.toString(), Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report?.reportID ?? '', index.toString(), Navigation.getActiveRouteWithoutParams()), + ); }; const navigateToNextStep = useCallback(() => { @@ -150,7 +156,7 @@ function IOURequestStepDistance({ }; type DataParams = { - data: string[] + data: string[]; }; const updateWaypoints = useCallback( @@ -190,12 +196,8 @@ function IOURequestStepDistance({ // If nothing was changed, simply go to transaction thread // We compare only addresses because numbers are rounded while backup const oldWaypoints = transactionBackup?.comment.waypoints ?? {}; - const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map( - ([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}] - )); - const addresses = Object.fromEntries(Object.entries(waypoints).map( - ([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}] - )); + const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}])); + const addresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}])); if (isEqual(oldAddresses, addresses)) { Navigation.dismissModal(); return; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 5f08da7afa4a..281b6b4228ce 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -1,8 +1,8 @@ import type {KeysOfUnion, ValueOf} from 'type-fest'; +import type {IOURequestType} from '@libs/actions/IOU'; import type CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; -import type { IOURequestType } from '@libs/actions/IOU'; import type {Participant, Split} from './IOU'; import type * as OnyxCommon from './OnyxCommon'; import type RecentWaypoint from './RecentWaypoint'; From 9e9ee8f8054406d1cf579a10f07f11f83074923f Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:42:01 +0200 Subject: [PATCH 029/116] add prop comments --- src/pages/iou/request/IOURequestStartPage.tsx | 6 +++++- src/pages/iou/request/step/IOURequestStepAmount.tsx | 3 +++ src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index cb062d22dde3..2327e6e61817 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -32,13 +32,17 @@ import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepScan from './step/IOURequestStepScan'; import type {WithWritableReportOrNotFoundProps} from './step/withWritableReportOrNotFound'; -type IOURequestStartPageOnyxProps = { +type IOURequestStartPageOnyxProps = + /** The report that holds the transaction */{ report: OnyxEntry; + /** The policy tied to the report */ policy: OnyxEntry; + /** The tab to select by default (whatever the user visited last) */ selectedTab: OnyxEntry; + /** The transaction being modified */ transaction: OnyxEntry; }; diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 893da88c468c..5e21616e7750 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -24,13 +24,16 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; type IOURequestStepAmountOnyxProps = { + /** The draft transaction that holds data to be persisted on the current transaction */ splitDraftTransaction: OnyxEntry; + /** The draft transaction object being modified in Onyx */ draftTransaction: OnyxEntry; }; type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & WithWritableReportOrNotFoundProps & { + /** The transaction object being modified in Onyx */ transaction: OnyxEntry; }; diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 09eb1898743c..84bf8594dce2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -34,11 +34,13 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; type IOURequestStepDistanceOnyxProps = { + /** backup version of the original transaction */ transactionBackup: OnyxEntry; }; type IOURequestStepDistanceProps = IOURequestStepDistanceOnyxProps & WithWritableReportOrNotFoundProps & { + /** The transaction object being modified in Onyx */ transaction: OnyxEntry; }; From 9055134b8e83babf0f213045689f090bc726fd70 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 11:01:20 +0200 Subject: [PATCH 030/116] done --- .../HTMLRenderers/MentionRoomRenderer.tsx | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx index 08435b5c2727..21cae56d4aa0 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -1,20 +1,21 @@ -import Str from 'expensify-common/lib/str'; import cloneDeep from 'lodash/cloneDeep'; import isEmpty from 'lodash/isEmpty'; import React from 'react'; -import {StyleSheet, type TextStyle} from 'react-native'; -import {OnyxCollection, withOnyx} from 'react-native-onyx'; -import {CustomRendererProps, TNodeChildrenRenderer, TPhrasing, TText} from 'react-native-render-html'; +import type {TextStyle} from 'react-native'; +import {StyleSheet} from 'react-native'; +import type {OnyxCollection} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, {Route} from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -37,28 +38,26 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul const tnodeClone = cloneDeep(tnode); if (!isEmpty(htmlAttributeReportID)) { - const report = reports!['report_' + htmlAttributeReportID]; + const report = reports?.['report_'.concat(htmlAttributeReportID)]; reportID = report?.reportID ? parseInt(report.reportID, 10) : undefined; - mentionDisplayText = report?.displayName ?? report?.reportName ?? htmlAttributeReportID; + mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; } else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) { mentionDisplayText = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1); - Object.values(reports ?? {}).some((report) => { - if (report?.reportName === mentionDisplayText || report?.reportName === tnodeClone.data) { + const currentReport = reports?.['report_'.concat(currentReportID?.currentReportID ?? '')]; + + // eslint-disable-next-line rulesdir/prefer-early-return + Object.values(reports ?? {}).forEach((report) => { + if (report?.policyID === currentReport?.policyID && (report?.reportName === mentionDisplayText || report?.reportName === tnodeClone.data)) { reportID = Number(report?.reportID); - return true; } - return false; }); - - mentionDisplayText = Str.removeSMSDomain(mentionDisplayText); } else { return null; } const navigationRoute: Route | undefined = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; - const isOurMention = String(reportID) === currentReportID?.currentReportID; const flattenStyle = StyleSheet.flatten(style as TextStyle); @@ -68,16 +67,21 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul {() => ( { event.preventDefault(); - Navigation.navigate(navigationRoute); + + if (navigationRoute) { + Navigation.navigate(navigationRoute); + } }} role={CONST.ROLE.LINK} accessibilityLabel={`/${navigationRoute}`} > - {htmlAttributeReportID ? `#${mentionDisplayText}` : } + #{mentionDisplayText} )} From 02b56f767f5172a08c7ac4709347d4c84daf0073 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 11:06:29 +0200 Subject: [PATCH 031/116] remove test code --- src/pages/home/report/ReportActionsView.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index c6bef1985b1d..cf0175b2dc29 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -531,11 +531,6 @@ function ReportActionsView({ shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScroll} /> - #admins'} /> - '} /> - '} /> - #dupa'} /> - '} /> ); } From 59899dc0e259ebdb0b4d69cb7226ff09d6041c86 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:15:10 +0200 Subject: [PATCH 032/116] fix some types --- src/libs/Navigation/types.ts | 1 + src/pages/iou/request/IOURequestStartPage.tsx | 5 ++--- src/pages/iou/request/step/IOURequestStepAmount.tsx | 2 +- src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 +- src/pages/iou/request/step/withFullTransactionOrNotFound.tsx | 3 +++ 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9f56a2203021..49301cdfdb1c 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -435,6 +435,7 @@ type MoneyRequestNavigatorParamList = { iouType: ValueOf; reportID: string; iouRequestType: IOURequestType; + transactionID: string; }; [SCREENS.MONEY_REQUEST.STEP_AMOUNT]: { iouType: ValueOf; diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 2327e6e61817..e17d6012e186 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -32,8 +32,8 @@ import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepScan from './step/IOURequestStepScan'; import type {WithWritableReportOrNotFoundProps} from './step/withWritableReportOrNotFound'; -type IOURequestStartPageOnyxProps = - /** The report that holds the transaction */{ +type IOURequestStartPageOnyxProps = { + /** The report that holds the transaction */ report: OnyxEntry; /** The policy tied to the report */ @@ -149,7 +149,6 @@ function IOURequestStartPage({ {iouType !== CONST.IOU.TYPE.SEND ? ( diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 5e21616e7750..607d0689e93f 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -187,7 +187,6 @@ function IOURequestStepAmount({ IOURequestStepAmount.displayName = 'IOURequestStepAmount'; export default compose( - withFullTransactionOrNotFound, withOnyx({ splitDraftTransaction: { key: ({route}) => { @@ -202,4 +201,5 @@ export default compose( }, }, }), + withFullTransactionOrNotFound, )(IOURequestStepAmount); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 84bf8594dce2..5a42f5713127 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -292,7 +292,6 @@ function IOURequestStepDistance({ IOURequestStepDistance.displayName = 'IOURequestStepDistance'; export default compose( - withFullTransactionOrNotFound, withOnyx({ transactionBackup: { key: ({route}) => { @@ -301,4 +300,5 @@ export default compose( }, }, }), + withFullTransactionOrNotFound, )(IOURequestStepDistance); diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 5f8a981ab3bb..67cf90b8c5de 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -18,6 +18,9 @@ type WithFullTransactionOrNotFoundOnyxProps = { }; type MoneyRequestRouteName = + | typeof SCREENS.MONEY_REQUEST.CREATE + | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE + | typeof SCREENS.MONEY_REQUEST.STEP_AMOUNT | typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT | typeof SCREENS.MONEY_REQUEST.STEP_DESCRIPTION | typeof SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT From 0caff86dfe447e46649228bcc09544dc930c5af6 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 12:01:30 +0200 Subject: [PATCH 033/116] fix unexpected behaviour on android --- .../HTMLRenderers/MentionRoomRenderer.tsx | 6 ++++-- src/pages/home/report/ReportActionsView.tsx | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx index 21cae56d4aa0..0a8d6ae984a2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -37,19 +37,21 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul const tnodeClone = cloneDeep(tnode); + const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); + if (!isEmpty(htmlAttributeReportID)) { const report = reports?.['report_'.concat(htmlAttributeReportID)]; reportID = report?.reportID ? parseInt(report.reportID, 10) : undefined; mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; } else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) { - mentionDisplayText = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1); + mentionDisplayText = removeLeadingLTRAndHash(tnodeClone.data); const currentReport = reports?.['report_'.concat(currentReportID?.currentReportID ?? '')]; // eslint-disable-next-line rulesdir/prefer-early-return Object.values(reports ?? {}).forEach((report) => { - if (report?.policyID === currentReport?.policyID && (report?.reportName === mentionDisplayText || report?.reportName === tnodeClone.data)) { + if (report?.policyID === currentReport?.policyID && removeLeadingLTRAndHash(report?.reportName ?? '') === mentionDisplayText) { reportID = Number(report?.reportID); } }); diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index cf0175b2dc29..ef436e57dc10 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -5,7 +5,6 @@ import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, use import {InteractionManager} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; -import RenderHTML from '@components/RenderHTML'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import useNetwork from '@hooks/useNetwork'; From e5d00bf41eed89a85e38746c747fae65c94383ec Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:50:50 +0200 Subject: [PATCH 034/116] fix types --- src/libs/Navigation/types.ts | 9 +++++++-- src/pages/iou/request/IOURequestStartPage.tsx | 2 +- src/pages/iou/request/step/IOURequestStepAmount.tsx | 10 ++++++---- src/pages/iou/request/step/IOURequestStepDistance.tsx | 4 +++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 49301cdfdb1c..364b711edffa 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -428,14 +428,19 @@ type MoneyRequestNavigatorParamList = { }; [SCREENS.MONEY_REQUEST.CREATE]: { iouType: ValueOf; - transactionID: string; reportID: string; + transactionID: string; + + // These are not used in the screen, but are needed for the navigation + // for IOURequestStepDistance and IOURequestStepAmount components + backTo: never; + action: never; }; [SCREENS.MONEY_REQUEST.START]: { iouType: ValueOf; reportID: string; - iouRequestType: IOURequestType; transactionID: string; + iouRequestType: IOURequestType; }; [SCREENS.MONEY_REQUEST.STEP_AMOUNT]: { iouType: ValueOf; diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index e17d6012e186..5d0ed14408ad 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -98,7 +98,7 @@ function IOURequestStartPage({ const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isExpenseReport = ReportUtils.isExpenseReport(report); - const shouldDisplayDistanceRequest = iouType !== CONST.IOU.TYPE.TRACK_EXPENSE && (canUseP2PDistanceRequests ?? isExpenseChat ?? isExpenseReport ?? isFromGlobalCreate); + const shouldDisplayDistanceRequest = iouType !== CONST.IOU.TYPE.TRACK_EXPENSE && (!!canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate); // Allow the user to create the request if we are creating the request in global menu or the report can create the request const isAllowedToCreateRequest = _.isEmpty(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 607d0689e93f..27816a933bf0 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -22,6 +22,7 @@ import type {Transaction} from '@src/types/onyx'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type IOURequestStepAmountOnyxProps = { /** The draft transaction that holds data to be persisted on the current transaction */ @@ -32,7 +33,7 @@ type IOURequestStepAmountOnyxProps = { }; type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & - WithWritableReportOrNotFoundProps & { + WithWritableReportOrNotFoundProps & { /** The transaction object being modified in Onyx */ transaction: OnyxEntry; }; @@ -56,7 +57,7 @@ function IOURequestStepAmount({ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isEditingSplitBill = isEditing && isSplitBill; const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? {amount: 0}; - const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? {currency: ''}; + const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? {currency: CONST.CURRENCY.USD}; useFocusEffect( useCallback(() => { @@ -168,11 +169,11 @@ function IOURequestStepAmount({ headerTitle={translate('iou.amount')} onBackButtonPress={navigateBack} testID={IOURequestStepAmount.displayName} - shouldShowWrapper={Boolean(backTo || isEditing)} + shouldShowWrapper={Boolean(backTo) || isEditing} includeSafeAreaPaddingBottom > (textInput.current = e)} @@ -202,4 +203,5 @@ export default compose( }, }), withFullTransactionOrNotFound, + withWritableReportOrNotFound, )(IOURequestStepAmount); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 5a42f5713127..429cfd938d39 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -32,6 +32,7 @@ import type {WaypointCollection} from '@src/types/onyx/Transaction'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type IOURequestStepDistanceOnyxProps = { /** backup version of the original transaction */ @@ -39,7 +40,7 @@ type IOURequestStepDistanceOnyxProps = { }; type IOURequestStepDistanceProps = IOURequestStepDistanceOnyxProps & - WithWritableReportOrNotFoundProps & { + WithWritableReportOrNotFoundProps & { /** The transaction object being modified in Onyx */ transaction: OnyxEntry; }; @@ -301,4 +302,5 @@ export default compose( }, }), withFullTransactionOrNotFound, + withWritableReportOrNotFound, )(IOURequestStepDistance); From af8efd454c8633579cb3021d14f1f4eeadea91d9 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 12:10:53 +0200 Subject: [PATCH 035/116] make code prettier --- .../HTMLRenderers/MentionRoomRenderer.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx index 0a8d6ae984a2..7058bd77af31 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -1,4 +1,3 @@ -import cloneDeep from 'lodash/cloneDeep'; import isEmpty from 'lodash/isEmpty'; import React from 'react'; import type {TextStyle} from 'react-native'; @@ -35,8 +34,6 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul let reportID: number | undefined; let mentionDisplayText: string; - const tnodeClone = cloneDeep(tnode); - const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); if (!isEmpty(htmlAttributeReportID)) { @@ -44,8 +41,8 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul reportID = report?.reportID ? parseInt(report.reportID, 10) : undefined; mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; - } else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) { - mentionDisplayText = removeLeadingLTRAndHash(tnodeClone.data); + } else if ('data' in tnode && !isEmptyObject(tnode.data)) { + mentionDisplayText = removeLeadingLTRAndHash(tnode.data); const currentReport = reports?.['report_'.concat(currentReportID?.currentReportID ?? '')]; @@ -59,8 +56,8 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul return null; } - const navigationRoute: Route | undefined = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; - const isOurMention = String(reportID) === currentReportID?.currentReportID; + const navigationRoute = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; + const isCurrentRoomMention = String(reportID) === currentReportID?.currentReportID; const flattenStyle = StyleSheet.flatten(style as TextStyle); const {color, ...styleWithoutColor} = flattenStyle; @@ -71,7 +68,7 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul { event.preventDefault(); From 12194120bdd32973fab9ce4756968a9df27a0119 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:52:46 +0200 Subject: [PATCH 036/116] address review --- src/ROUTES.ts | 2 +- src/pages/iou/request/IOURequestStartPage.tsx | 4 +- .../iou/request/step/IOURequestStepAmount.tsx | 48 +++++++++---------- .../request/step/IOURequestStepDistance.tsx | 37 +++++++------- 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f334da7a2407..dfe44c846444 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -389,7 +389,7 @@ const ROUTES = { }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => getUrlWithBackToParam(`${action}/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), }, // This URL is used as a redirect to one of the create tabs below. This is so that we can message users with a link diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 5d0ed14408ad..00dcf894dfa1 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,5 +1,4 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'; -import _ from 'lodash'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -27,6 +26,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {Policy, Report, SelectedTabRequest, Transaction} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import IOURequestStepAmount from './step/IOURequestStepAmount'; import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepScan from './step/IOURequestStepScan'; @@ -71,7 +71,7 @@ function IOURequestStartPage({ const transactionRequestType = useRef(TransactionUtils.getRequestType(transaction)); const previousIOURequestType = usePrevious(transactionRequestType.current); const {canUseP2PDistanceRequests} = usePermissions(); - const isFromGlobalCreate = _.isEmpty(report?.reportID); + const isFromGlobalCreate = isEmptyObject(report?.reportID); useFocusEffect( useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 27816a933bf0..b29c2413a503 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -6,7 +6,6 @@ import {withOnyx} from 'react-native-onyx'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import * as TransactionEdit from '@libs/actions/TransactionEdit'; -import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -24,6 +23,10 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; +type AmountParams = { + amount: string; +}; + type IOURequestStepAmountOnyxProps = { /** The draft transaction that holds data to be persisted on the current transaction */ splitDraftTransaction: OnyxEntry; @@ -108,13 +111,6 @@ function IOURequestStepAmount({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, backTo ? 'confirm' : '', Navigation.getActiveRouteWithoutParams())); }; - type AmountParams = { - amount: string; - }; - - /** - * @param amount - */ const navigateToNextPage = ({amount}: AmountParams) => { isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); @@ -169,11 +165,11 @@ function IOURequestStepAmount({ headerTitle={translate('iou.amount')} onBackButtonPress={navigateBack} testID={IOURequestStepAmount.displayName} - shouldShowWrapper={Boolean(backTo) || isEditing} + shouldShowWrapper={!!backTo || isEditing} includeSafeAreaPaddingBottom > (textInput.current = e)} @@ -187,21 +183,21 @@ function IOURequestStepAmount({ IOURequestStepAmount.displayName = 'IOURequestStepAmount'; -export default compose( - withOnyx({ - splitDraftTransaction: { - key: ({route}) => { - const transactionID = route.params.transactionID ?? 0; - return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; +export default withWritableReportOrNotFound( + withFullTransactionOrNotFound( + withOnyx({ + splitDraftTransaction: { + key: ({route}) => { + const transactionID = route.params.transactionID ?? 0; + return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`; + }, }, - }, - draftTransaction: { - key: ({route}) => { - const transactionID = route.params.transactionID ?? 0; - return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; + draftTransaction: { + key: ({route}) => { + const transactionID = route.params.transactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; + }, }, - }, - }), - withFullTransactionOrNotFound, - withWritableReportOrNotFound, -)(IOURequestStepAmount); + })(IOURequestStepAmount), + ), +); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 429cfd938d39..8ad13893c786 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -15,7 +15,6 @@ 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 * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -117,12 +116,14 @@ function IOURequestStepDistance({ * Takes the user to the page for editing a specific waypoint * @param index of the waypoint to edit */ - // eslint-disable-next-line react-hooks/exhaustive-deps - const navigateToWaypointEditPage = (index: number) => { - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report?.reportID ?? '', index.toString(), Navigation.getActiveRouteWithoutParams()), - ); - }; + const navigateToWaypointEditPage = useCallback( + (index: number) => { + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report?.reportID, index.toString(), Navigation.getActiveRouteWithoutParams()), + ); + }, + [action, transactionID, report?.reportID], + ); const navigateToNextStep = useCallback(() => { if (backTo) { @@ -292,15 +293,15 @@ function IOURequestStepDistance({ IOURequestStepDistance.displayName = 'IOURequestStepDistance'; -export default compose( - withOnyx({ - transactionBackup: { - key: ({route}) => { - const transactionID = route.params.transactionID ?? 0; - return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; +export default withWritableReportOrNotFound( + withFullTransactionOrNotFound( + withOnyx({ + transactionBackup: { + key: ({route}) => { + const transactionID = route.params.transactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; + }, }, - }, - }), - withFullTransactionOrNotFound, - withWritableReportOrNotFound, -)(IOURequestStepDistance); + })(IOURequestStepDistance), + ), +); From 7a599c2290964e998f2c466a4debca62de566baf Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:53:45 +0200 Subject: [PATCH 037/116] adress review --- src/pages/iou/request/IOURequestStartPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 00dcf894dfa1..e7d235229fa8 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -101,7 +101,7 @@ function IOURequestStartPage({ const shouldDisplayDistanceRequest = iouType !== CONST.IOU.TYPE.TRACK_EXPENSE && (!!canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate); // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); + const isAllowedToCreateRequest = isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); const navigateBack = () => { Navigation.dismissModal(); From 61b7f77fcf1e06f30a5aa77fc4895233b04d5922 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 14:32:47 +0200 Subject: [PATCH 038/116] wip --- .../HTMLRenderers/MentionRoomRenderer.tsx | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx index 7058bd77af31..f13ca630e686 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -7,6 +7,7 @@ import {withOnyx} from 'react-native-onyx'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; +import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -25,36 +26,44 @@ type RoomMentionOnyxProps = { type MentionRoomRendererProps = RoomMentionOnyxProps & CustomRendererProps; -function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const htmlAttributeReportID = tnode.attributes.reportid; - const currentReportID = useCurrentReportID(); - - let reportID: number | undefined; - let mentionDisplayText: string; - - const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); +const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); +const getMentionDetails = (htmlAttributeReportID: string, currentReportID: CurrentReportIDContextValue | null, reports: OnyxCollection, tnode: TText | TPhrasing) => { if (!isEmpty(htmlAttributeReportID)) { const report = reports?.['report_'.concat(htmlAttributeReportID)]; - reportID = report?.reportID ? parseInt(report.reportID, 10) : undefined; - mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; - } else if ('data' in tnode && !isEmptyObject(tnode.data)) { - mentionDisplayText = removeLeadingLTRAndHash(tnode.data); + const reportID = report?.reportID ?? undefined; + const mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; + return {reportID, mentionDisplayText}; + } else if ('data' in tnode && !isEmptyObject(tnode.data)) { const currentReport = reports?.['report_'.concat(currentReportID?.currentReportID ?? '')]; - // eslint-disable-next-line rulesdir/prefer-early-return + let reportID = undefined; Object.values(reports ?? {}).forEach((report) => { if (report?.policyID === currentReport?.policyID && removeLeadingLTRAndHash(report?.reportName ?? '') === mentionDisplayText) { - reportID = Number(report?.reportID); + reportID = report?.reportID; } }); - } else { + const mentionDisplayText = removeLeadingLTRAndHash(tnode.data); + + return {reportID, mentionDisplayText}; + } + + return null; +}; + +function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const htmlAttributeReportID = tnode.attributes.reportid; + const currentReportID = useCurrentReportID(); + + const mentionDetails = getMentionDetails(htmlAttributeReportID, currentReportID, reports, tnode); + if (mentionDetails === null) { return null; } + const {reportID, mentionDisplayText} = mentionDetails; const navigationRoute = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; const isCurrentRoomMention = String(reportID) === currentReportID?.currentReportID; @@ -70,13 +79,14 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul {...defaultRendererProps} style={[styles.link, styleWithoutColor, StyleUtils.getMentionStyle(isCurrentRoomMention), {color: StyleUtils.getMentionTextColor(isCurrentRoomMention)}]} suppressHighlighting - onPress={(event) => { - event.preventDefault(); - - if (navigationRoute) { - Navigation.navigate(navigationRoute); - } - }} + onPress={ + navigationRoute + ? (event) => { + event.preventDefault(); + Navigation.navigate(navigationRoute); + } + : undefined + } role={CONST.ROLE.LINK} accessibilityLabel={`/${navigationRoute}`} > From e563db07a1f3a276864b70a3255c5f4d05bfcf90 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 9 Apr 2024 15:36:57 +0200 Subject: [PATCH 039/116] review suggestions --- .../HTMLRenderers/MentionRoomRenderer.tsx | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx index f13ca630e686..f83af9f3e114 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx @@ -2,19 +2,17 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; import type {TextStyle} from 'react-native'; import {StyleSheet} from 'react-native'; -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; -import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -28,29 +26,30 @@ type MentionRoomRendererProps = RoomMentionOnyxProps & CustomRendererProps value.replace(CONST.UNICODE.LTR, '').slice(1); -const getMentionDetails = (htmlAttributeReportID: string, currentReportID: CurrentReportIDContextValue | null, reports: OnyxCollection, tnode: TText | TPhrasing) => { +const getMentionDetails = (htmlAttributeReportID: string, currentReportID: string, reports: OnyxCollection, tnode: TText | TPhrasing) => { + let reportID: string | undefined; + let mentionDisplayText: string; + if (!isEmpty(htmlAttributeReportID)) { const report = reports?.['report_'.concat(htmlAttributeReportID)]; - const reportID = report?.reportID ?? undefined; - const mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; - - return {reportID, mentionDisplayText}; + reportID = report?.reportID ?? undefined; + mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; } else if ('data' in tnode && !isEmptyObject(tnode.data)) { - const currentReport = reports?.['report_'.concat(currentReportID?.currentReportID ?? '')]; + mentionDisplayText = removeLeadingLTRAndHash(tnode.data); - let reportID = undefined; + const currentReport = reports?.['report_'.concat(currentReportID)]; + // eslint-disable-next-line rulesdir/prefer-early-return Object.values(reports ?? {}).forEach((report) => { if (report?.policyID === currentReport?.policyID && removeLeadingLTRAndHash(report?.reportName ?? '') === mentionDisplayText) { reportID = report?.reportID; } }); - const mentionDisplayText = removeLeadingLTRAndHash(tnode.data); - - return {reportID, mentionDisplayText}; + } else { + return null; } - return null; + return {reportID, mentionDisplayText}; }; function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { @@ -59,12 +58,12 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul const htmlAttributeReportID = tnode.attributes.reportid; const currentReportID = useCurrentReportID(); - const mentionDetails = getMentionDetails(htmlAttributeReportID, currentReportID, reports, tnode); - if (mentionDetails === null) { + const mentionDetails = getMentionDetails(htmlAttributeReportID, currentReportID?.currentReportID ?? '', reports, tnode); + if (!mentionDetails) { return null; } - const {reportID, mentionDisplayText} = mentionDetails; + const {reportID, mentionDisplayText} = mentionDetails; const navigationRoute = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; const isCurrentRoomMention = String(reportID) === currentReportID?.currentReportID; @@ -99,8 +98,21 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul MentionRoomRenderer.displayName = 'MentionRoomRenderer'; +/** + * This function 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 => + (report && { + reportID: report.reportID, + reportName: report.reportName, + displayName: report.displayName, + policyID: report.policyID, + }) as Report; + export default withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, }, })(MentionRoomRenderer); From 23b1e92fb618809655499d2ab997ac1bfec2f139 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 9 Apr 2024 15:40:26 +0200 Subject: [PATCH 040/116] adress review comments --- src/components/MentionSuggestions.tsx | 4 ++-- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 8 ++++---- .../report/ReportActionCompose/ReportActionCompose.tsx | 4 ++-- .../home/report/ReportActionCompose/SuggestionMention.tsx | 6 +++--- src/pages/home/report/ReportActionCompose/Suggestions.tsx | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index c7dd025e623a..8108929fc451 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -21,7 +21,7 @@ type Mention = { /** Email/phone number of the user */ login?: string; - /** Array of icons of the user. We use the first element of this array */ + /** Array of icons of the user. If present, we use the first element of this array. For room suggestions, the icons are not used */ icons?: Icon[]; }; @@ -67,7 +67,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe return ( - {item.icons && ( + {item.icons && !!item.icons.length && ( ReportUtils.isGroupPolicy(report), [report]); + const isGroupPolicyReport = useMemo(() => ReportUtils.isGroupPolicy(report), [report]); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); const reportRecipient = personalDetails[reportRecipientAcountIDs[0]]; const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused; @@ -436,7 +436,7 @@ function ReportActionCompose({ parentReportID={report?.parentReportID} parentReportActionID={report?.parentReportActionID} includeChronos={ReportUtils.chatIncludesChronos(report)} - isChatRoom={isChatRoom} + isGroupPolicyReport={isGroupPolicyReport} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} isMenuVisible={isMenuVisible} diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index d81e1f09472d..5533a2a72af3 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -59,7 +59,7 @@ function SuggestionMention( measureParentContainer, isComposerFocused, reports, - isChatRoom, + isGroupPolicyReport, policyID, }: SuggestionProps & RoomMentionOnyxProps, ref: ForwardedRef, @@ -336,7 +336,7 @@ function SuggestionMention( nextState.shouldShowSuggestionMenu = !!suggestions.length; } - const shouldDisplayMenetionsSuggestions = (isValidRoomName(suggestionWord.toLowerCase()) || prefix === '') && isChatRoom; + const shouldDisplayMenetionsSuggestions = isGroupPolicyReport && (isValidRoomName(suggestionWord.toLowerCase()) || prefix === ''); if (!isCursorBeforeTheMention && prefixType === '#' && shouldDisplayMenetionsSuggestions) { // filter reports by room name and current policy const filteredRoomMentions = getRoomMentionOptions(prefix, reports); @@ -350,7 +350,7 @@ function SuggestionMention( })); setHighlightedMentionIndex(0); }, - [isComposerFocused, value, isChatRoom, setHighlightedMentionIndex, resetSuggestions, getUserMentionOptions, personalDetails, getRoomMentionOptions, reports], + [isComposerFocused, value, isGroupPolicyReport, setHighlightedMentionIndex, resetSuggestions, getUserMentionOptions, personalDetails, getRoomMentionOptions, reports], ); useEffect(() => { diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index ed9050f686e2..d1abe0e3880c 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -47,8 +47,8 @@ type SuggestionProps = { /** The height of the composer */ composerHeight?: number; - /** if current composer is connected with chat room or not */ - isChatRoom: boolean; + /** if current composer is connected with report from group policy */ + isGroupPolicyReport: boolean; /** policy ID connected to current composer */ policyID?: string; @@ -72,7 +72,7 @@ function Suggestions( measureParentContainer, isAutoSuggestionPickerLarge = true, isComposerFocused, - isChatRoom, + isGroupPolicyReport, policyID, }: SuggestionProps, ref: ForwardedRef, @@ -163,7 +163,7 @@ function Suggestions( isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused, - isChatRoom, + isGroupPolicyReport, policyID, }; From 019637240064a06ba0230fdaa75522f81dc24e1f Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:12:49 +0200 Subject: [PATCH 041/116] fix issues on distance page --- src/pages/iou/request/step/IOURequestStepDistance.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 8ad13893c786..88468556bc8c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -69,11 +69,11 @@ function IOURequestStepDistance({ const hasRoute = TransactionUtils.hasRoute(transaction); const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints); const previousValidatedWaypoints = usePrevious(validatedWaypoints); - const haveValidatedWaypointsChanged = isEqual(previousValidatedWaypoints, validatedWaypoints); + const haveValidatedWaypointsChanged = !isEqual(previousValidatedWaypoints, validatedWaypoints); const isRouteAbsentWithoutErrors = !hasRoute && !hasRouteError; const shouldFetchRoute = (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1; const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false); - const nonEmptyWaypointsCount = useMemo(() => Object.keys(waypoints).filter((key) => isEmpty(waypoints[key])).length, [waypoints]); + const nonEmptyWaypointsCount = useMemo(() => Object.keys(waypoints).filter((key) => !isEmpty(waypoints[key])).length, [waypoints]); const duplicateWaypointsError = useMemo( () => nonEmptyWaypointsCount >= 2 && Object.keys(validatedWaypoints).length !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints], From 305c9769232bffa87224c603e7c1dcfaa706004c Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 09:29:31 -0500 Subject: [PATCH 042/116] remove getAllReportActions export --- src/libs/ReportActionsUtils.ts | 1 - tests/actions/EnforceActionExportRestrictions.ts | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 21e452de4730..5323a6855468 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1069,7 +1069,6 @@ function getReportActionMessageText(reportAction: OnyxEntry | Empt export { extractLinksFromMessageHtml, getOneTransactionThreadReportID, - getAllReportActions, getIOUReportIDFromReportActionPreview, getLastClosedReportAction, getLastVisibleAction, diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index 6af42e5f93c1..e18cfa2182be 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -22,6 +22,11 @@ describe('ReportUtils', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.getPolicy).toBeUndefined(); }); + + it('does not export getAllReportActions', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(ReportUtils.getAllReportActions).toBeUndefined(); + }); }); describe('Policy', () => { From e15bf622127e3319c884217b140f4ad8100432cb Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 09:51:14 -0500 Subject: [PATCH 043/116] remove getAllReportActions from SidebarUtils --- src/libs/SidebarUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 789029779b55..6fff2f1a7288 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -23,6 +23,7 @@ import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; import * as UserUtils from './UserUtils'; +const reportActionsByReport: OnyxCollection = {}; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ @@ -31,7 +32,9 @@ Onyx.connect({ if (!key || !actions) { return; } + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActionsByReport[reportID] = actions; const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions)); @@ -84,7 +87,7 @@ function getOrderedReportIDs( const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`; const parentReportActions = allReportActions?.[parentReportActionsKey]; - const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const reportActions = reportActionsByReport?.[report.reportID] ?? {}; const parentReportAction = parentReportActions?.find((action) => action && action?.reportActionID === report.parentReportActionID); const doesReportHaveViolations = betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); From 03c50ac0258b997c7c77a8b430d10dea79619051 Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 09:51:59 -0500 Subject: [PATCH 044/116] remove getAllReportActions from ReportUtils --- src/libs/ReportUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c2bb77bd97ce..da0bfd5ea709 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4362,7 +4362,7 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection, currentReportId: string): boolean { const currentReport = getReport(currentReportId); const parentReport = getParentReport(!isEmptyObject(currentReport) ? currentReport : null); - const reportActions = ReportActionsUtils.getAllReportActions(report?.reportID ?? ''); + const reportActions = reportActionsByReport?.[report?.reportID ?? ''] ?? {}; const isChildReportHasComment = Object.values(reportActions ?? {})?.some((reportAction) => (reportAction?.childVisibleActionCount ?? 0) > 0); return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } @@ -5701,7 +5701,7 @@ function getIndicatedMissingPaymentMethod(userWallet: OnyxEntry, rep * Checks if report chat contains missing payment method */ function hasMissingPaymentMethod(userWallet: OnyxEntry, iouReportID: string): boolean { - const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); + const reportActions = reportActionsByReport?.[iouReportID] ?? {}; return Object.values(reportActions).some((action) => getIndicatedMissingPaymentMethod(userWallet, iouReportID, action) !== undefined); } @@ -5720,7 +5720,7 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry * Checks if report contains actions with errors */ function hasActionsWithErrors(reportID: string): boolean { - const reportActions = ReportActionsUtils.getAllReportActions(reportID ?? ''); + const reportActions = reportActionsByReport?.[reportID]; return Object.values(reportActions ?? {}).some((action) => !isEmptyObject(action.errors)); } From 08340056f6e0ba0d9c4e0aaa113bf9ff7b0f2471 Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 09:57:24 -0500 Subject: [PATCH 045/116] remove getAllReportActions from WorkspacesSettingsUtils --- src/libs/WorkspacesSettingsUtils.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 30820fc0c48b..64fa8cbc9220 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -4,13 +4,13 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, PolicyMembers, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {Policy, PolicyMembers, ReimbursementAccount, Report, ReportActions} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; +import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import type {Phrase, PhraseParameters} from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; import {hasCustomUnitsError, hasPolicyError, hasPolicyMemberError, hasTaxRateError} from './PolicyUtils'; -import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; type CheckingMethod = () => boolean; @@ -52,12 +52,25 @@ Onyx.connect({ }, }); +const reportActionsByReport: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActionsByReport[reportID] = actions; + }, +}); + /** * @param report * @returns BrickRoad for the policy passed as a param */ const getBrickRoadForPolicy = (report: Report): BrickRoad => { - const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const reportActions = reportActionsByReport?.[report.reportID]; const reportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); const doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; if (doesReportContainErrors) { @@ -67,7 +80,7 @@ const getBrickRoadForPolicy = (report: Report): BrickRoad => { // To determine if the report requires attention from the current user, we need to load the parent report action let itemParentReportAction = {}; if (report.parentReportID) { - const itemParentReportActions = ReportActionsUtils.getAllReportActions(report.parentReportID); + const itemParentReportActions = reportActionsByReport[report.parentReportID] ?? {}; itemParentReportAction = report.parentReportActionID ? itemParentReportActions[report.parentReportActionID] : {}; } const reportOption = {...report, isUnread: ReportUtils.isUnread(report), isUnreadWithMention: ReportUtils.isUnreadWithMention(report)}; From 2e950e30ff4750ae249ce22eb8984970574d1fc4 Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 10:02:27 -0500 Subject: [PATCH 046/116] remove getAllReportActions from IOU --- src/libs/actions/IOU.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 95fdeb5e60e0..0109f6f78be4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -25,6 +25,7 @@ import type { UpdateMoneyRequestParams, } from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; +import * as CollectionUtils from '@libs/CollectionUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -253,6 +254,19 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); +const reportActionsByReport: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActionsByReport[reportID] = actions; + }, +}); + /** * Returns the policy of the report */ @@ -4812,7 +4826,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean { - const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? ''); + const chatReportActions: OnyxTypes.ReportActions = reportActionsByReport?.[chatReport?.reportID ?? ''] ?? {}; return Object.values(chatReportActions).some((action) => { const iouReport = ReportUtils.getReport(action.childReportID ?? ''); From b4068ec400e3926da5dc19c343eadab28ef0ae55 Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Tue, 9 Apr 2024 10:11:55 -0500 Subject: [PATCH 047/116] remove getAllReportActions from ReportActions --- src/libs/actions/ReportActions.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index ae886e0309dc..610c5c67a0fa 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,8 +1,11 @@ +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import * as CollectionUtils from '@libs/CollectionUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as Report from './Report'; @@ -56,6 +59,19 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k }); } +const reportActionsByReport: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActionsByReport[reportID] = actions; + }, +}); + /** * ignore: `undefined` means we want to check both parent and children report actions @@ -78,7 +94,7 @@ function clearAllRelatedReportActionErrors(reportID: string, reportAction: Repor } if (reportAction.childReportID && ignore !== 'child') { - const childActions = ReportActionUtils.getAllReportActions(reportAction.childReportID); + const childActions = reportActionsByReport?.[reportAction.childReportID] ?? {}; Object.values(childActions).forEach((action) => { const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err)); clearAllRelatedReportActionErrors(reportAction.childReportID ?? '', action, 'parent', childErrorKeys); From 9902923869871a3ae1b59445a9b107ed7452a177 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:27:32 +0200 Subject: [PATCH 048/116] address review --- src/pages/iou/request/step/IOURequestStepAmount.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index b29c2413a503..1936a132c665 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -1,5 +1,4 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashIsEmpty from 'lodash/isEmpty'; import React, {useCallback, useEffect, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -18,6 +17,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Transaction} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; @@ -59,7 +59,7 @@ function IOURequestStepAmount({ const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isEditingSplitBill = isEditing && isSplitBill; - const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? {amount: 0}; + const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction) ?? {amount: 0}; const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction) ?? {currency: CONST.CURRENCY.USD}; useFocusEffect( @@ -79,7 +79,7 @@ function IOURequestStepAmount({ // A temporary solution to not prevent users from editing the currency // We create a backup transaction and use it to save the currency and remove this transaction backup if we don't save the amount // It should be removed after this issue https://github.com/Expensify/App/issues/34607 is fixed - TransactionEdit.createBackupTransaction(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction); + TransactionEdit.createBackupTransaction(isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction); return () => { if (isSaveButtonPressed.current) { From f5370a99652f3f92f89279b894590f60efc074a9 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:57:14 -0400 Subject: [PATCH 049/116] Delete docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md https://github.com/Expensify/Expensify/issues/381301#issuecomment-2025642424 --- .../Removing-Members.md | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md diff --git a/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md b/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md deleted file mode 100644 index 65acc3630582..000000000000 --- a/docs/articles/expensify-classic/copilots-and-delegates/Removing-Members.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Remove a Workspace Member -description: How to remove a member from a Workspace in Expensify ---- - -Removing a member from a workspace disables their ability to use the workspace. Please note that it does not delete their account or deactivate the Expensify Card. - -## How to Remove a Workspace Member -1. Important: Make sure the employee has submitted all Draft reports and the reports have been approved, reimbursed, etc. -2. Go to Settings > Workspaces > Group > [Workspace Name] > Members > Workspace Members -3. Select the member you'd like to remove and click the **Remove** button at the top of the Members table. -4. If this member was an approver, make sure that reports are not routing to them in the workflow. - -![image of members table in a workspace]({{site.url}}/assets/images/ExpensifyHelp_RemovingMembers.png){:width="100%"} - -{% include faq-begin.md %} - -## Will reports from this member on this workspace still be available? -Yes, as long as the reports have been submitted. You can navigate to the Reports page and enter the member's email in the search field to find them. However, Draft reports will be removed from the workspace, so these will no longer be visible to the Workspace Admin. - -## Can members still access their reports on a workspace after they have been removed? -Yes. Any report that has been approved will now show the workspace as “(not shared)” in their account. If it is a Draft Report they will still be able to edit it and add it to a new workspace. If the report is Approved or Reimbursed they will not be able to edit it further. - -## Who can remove members from a workspace? -Only Workspace Admins. It is not possible for a member to add or remove themselves from a workspace. It is not possible for a Domain Admin who is not also a Workspace Admin to remove a member from a workspace. - -## How do I remove a member from a workspace if I am seeing an error message? -If a member is a **preferred exporter, billing owner, report approver** or has **processing reports**, to remove them the workspace you will first need to: - -* **Preferred Exporter** - go to Settings > Workspaces > Group > [Workspace Name] > Connections > Configure and select a different Workspace Admin in the dropdown for **Preferred Exporter**. -* **Billing Owner** - take over billing on the Settings > Workspaces > Group > [Workspace Name] > Overview page. -* **Processing reports** - approve or reject the member’s reports on your Reports page. -* **Approval Workflow** - remove them as a workflow approver on your Settings > Workspaces > Group > [Workspace Name] > Members > Approval Mode > page by changing the "**Submit reports to**" field. - -## How do I remove a user completely from a company account? -If you have a Control Workspace and have Domain Control enabled, you will need to remove them from the domain to delete members' accounts entirely and deactivate the Expensify Card. - -{% include faq-end.md %} From b3f22ff3d9fd5e09d021a5aedd776f887a53a1d2 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:36:12 -0500 Subject: [PATCH 050/116] Create Add-expenses-to-a-report.md New article for adding expenses to a report --- .../reports/Add-expenses-to-a-report.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md diff --git a/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md new file mode 100644 index 000000000000..e3c6c5fa2a46 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Add-expenses-to-a-report.md @@ -0,0 +1,81 @@ +--- +title: Add expenses to a report +description: Put your expenses on a report to submit them for reimbursement +--- +
+ +Once you’ve created your expenses, they may be automatically added to an expense report if your company has this feature enabled. If not, your next step will be to add your expenses to a report and submit them for payment. + +You can either create a new report or add expenses to an existing report. + +{% include info.html %} +There may be restrictions on your ability to create reports depending on your workspace settings. +{% include end-info.html %} + +# Add expenses to an existing report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click the report. +3. Click **Add Expenses** at the top of the report. +4. Select the expenses to add to the report. + - If an expense you already added does not appear in the list, use the filter on the left to search by the merchant name or change the date range. *Note: Only expenses that are not already on a report will appear.* +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. +3. Tap the report. +4. Tap **Add Expense**, then tap an expense to add it to the report. +{% include end-option.html %} + +{% include end-selector.html %} + +# Create a new report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. + - If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted. + - If a report has not been automatically created, follow the steps below. +2. Click **New Report**, or click the New Report dropdown and select **Expense Report** (*The other report types are explained in the FAQ section below*). +3. Click **Add Expenses**. +4. Click an expense to add it to the report. + - If an expense you already added does not appear in the list, use the filter on the left to search by the merchant name or change the date range. *Note: Only expenses that are not already on a report will appear.* +5. Once all your expenses are added to the report, click the X to close the pop-up. +6. (Optional) Make any desired changes to the report and/or expenses. + - Click the Edit icon next to the report name to change it. If this icon is not visible, the option has been disabled by your workspace. + - Click the X icon next to an expense to remove it from the report. + - Click the Expense Details icon to review or edit the expense details. + - At the bottom of the report, add comments to include more information. + - Click the Attachments icon to add additional attachments. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. + - If a report has been automatically created for your most recently submitted expense, then you don’t have to do anything else—your report is already created and will also be automatically submitted. + - If a report has not been automatically created, follow the steps below. +3. Tap the + icon and tap **Expense Report** (*The other report types are explained in the FAQ section below*). +4. Tap **Add Expenses**, then tap an expense to add it to the report. Repeat this step until all desired expenses are added. *Note: Only expenses that are not already on a report will appear.* +5. (Optional) Make any desired changes to the report and/or expenses. + - Tap the report name to change it. + - Tap an expense to review or edit the expense details. + - At the bottom of the report, add comments to include more information. + - Click the Attachments icon to add additional attachments. +{% include end-option.html %} + +{% include end-selector.html %} + +# FAQs + +**What’s the difference between expense reports, bills, and invoices?** + +- **Expense Report**: Expense reports are submitted by an employee to their employer. This may include reimbursable expenses like business travel paid for with personal funds, or non-reimbursable expenses like a lunch paid for with a company card. +- **Invoice**: Invoices are reports that a business or contractor will send to another business to charge them for goods or services the business received. For example, a contractor that provides an hourly-rate service (like landscaping) may provide their clients with an invoice to detail the different services and products they provided, how many hours they worked, what their rate per hour is for each service, etc. Invoices are generally expected to be paid within a duration of time (for example, within 30 days of receipt). +- **Bill**: Each invoice will have a matching bill owned by the recipient so they may use it to pay the invoice sender. Bills are for businesses and contractors who provide their client with a bill for goods or services. For example, a restaurant, store, or hair salon provides bills. Bills are generally expected to be paid upon receipt. + +
From 57c02b9aa9ec8cc2e826d936f88f9d17b8a25377 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:46:58 -0500 Subject: [PATCH 051/116] Create Add-comments-and-attachments-to-a-report.md New article for adding comments and attachments to a report --- ...dd-comments-and-attachments-to-a-report.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md diff --git a/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md b/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md new file mode 100644 index 000000000000..b647a02190bc --- /dev/null +++ b/docs/articles/expensify-classic/reports/Add-comments-and-attachments-to-a-report.md @@ -0,0 +1,32 @@ +--- +title: Add comments & attachments to a report +description: Add clarification for expenses by adding comments and attachments to a report +--- +
+ +You can add comments and attachments to a report to help clarify or provide justification for the expenses. + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click the report. +3. Scroll down to the bottom of the report and add a comment or attachment. + - **To add a comment**: Type a comment into the field and click the Send icon, or press the Enter key on your keyboard. + - **To add an attachment**: Click the paperclip icon, then select a jpeg, jpg, png, gif, csv, or pdf file to attach to the report. Then click **Upload**. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap **Reports**. +3. Tap the report. +4. At the bottom of the report, add a comment or attachment. + - **To add a comment**: Type a comment into the field and click the Send icon. + - **To add an attachment**: Click the paperclip icon, then select a jpeg, jpg, png, gif, csv, or pdf file to attach to the report. Then click **Confirm**. +{% include end-option.html %} + +{% include end-selector.html %} + +In this section at the bottom of the report, Expensify also logs actions taken on the report. + +
From 8cd17f21f6ab8a4b025a2ed6ba5220c44c0e6c21 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:08:11 -0500 Subject: [PATCH 052/116] Create Edit-a-report.md New article for editing expenses --- .../reports/Edit-a-report.md | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/articles/expensify-classic/reports/Edit-a-report.md diff --git a/docs/articles/expensify-classic/reports/Edit-a-report.md b/docs/articles/expensify-classic/reports/Edit-a-report.md new file mode 100644 index 000000000000..b10dd2ce3019 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Edit-a-report.md @@ -0,0 +1,133 @@ +--- +title: Edit a report +description: Make updates to a report +--- +
+ +You can update a report’s details such as the report title, workspace, report type, layout, and the attached expenses. + +{% include info.html %} +Some report details may be restricted from editing depending on your workspace settings. +{% include end-info.html %} + +# Edit report title + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click the pencil icon next to the name and edit the name as desired. +3. Press Enter on your keyboard to save the changes. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap the report name to edit it. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report workspace + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the Workspace dropdown list and select the correct workspace. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap **Edit** in the top right. +4. Tap the current workspace name to select a new one. +5. Tap **Done**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report type + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the Type dropdown and select either Expense Report or Invoice. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap **Edit** in the top right. +4. Tap either Expense Report or Invoice. +5. Tap **Done**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Change the report layout + +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the view option that you want to change: + - **View**: Choose between a basic or detailed report view. + - **Group By**: Group expenses on the report based on their category or tag. + - **Split By**: Split out the expenses based on their reimbursable or billable status. + +# Edit expenses + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click **Details** in the top right of the report. +3. Click the pencil icon at the top of the menu. +4. Hover over an expense and edit: + - A specific field by clicking the pencil icon next to it. + - Multiple fields by clicking the pencil icon to the left of the expense. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Tap an expense to open it. +4. Tap any field on the expense to edit it. +{% include end-option.html %} + +{% include end-selector.html %} + +# Remove expenses + +{% include info.html %} +This process only removes the expense from the report—it does not permanently delete the expense. +{% include end-info.html %} + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab and select the report. +2. Click the X icon to the left of the expense to remove it from the report. +{% include end-option.html %} + +{% include option.html value="mobile" %} + +**Android** + +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Hold the expense and tap Delete to remove it from the report. + +**iOS** + +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab then tap the report. +3. Swipe the expense to the left and tap Delete to remove it from the report. + +{% include end-option.html %} + +{% include end-selector.html %} + +
From ae8d388f097135e6ac5337ed64e07a34ce840730 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:26:56 -0500 Subject: [PATCH 053/116] Create Submit-or-retract-a-report.md New article for submitting/retracting a report --- .../reports/Submit-or-retract-a-report.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md diff --git a/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md b/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md new file mode 100644 index 000000000000..857217189e50 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Submit-or-retract-a-report.md @@ -0,0 +1,65 @@ +--- +title: Submit or retract a report +description: Submit a report for reimbursement or retract a submitted report to make corrections +--- +
+ +Once your report is ready to send, you can submit your expenses for approval. Depending on your workspace settings, your reports may be automatically submitted for you, or you may have to manually submit them. + +{% include info.html %} +Depending on your workspace settings, your reports may be automatically submitted or approved. In this case, you will not need to manually submit your reports. +{% include end-info.html %} + +# Manually submit a report + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click a report to open it. +3. Review the report for completion and click **Submit**. +4. Verify or enter the details for who will receive a notification email about your report and what they will receive: + - **To**: Enter the name(s) who will be approving your report (if they are not already listed). + - **CC**: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one. + - **Memo**: Enter any relevant notes. + - **Attach PDF**: Select this checkbox to attach a copy of your report to the email. +5. Click **Send**. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab. +3. Tap a report to open it. +4. Review the report for completion and tap Submit Report. +5. Verify the details for who will receive a notification email about your report and what they will receive: + - **To**: Enter the name(s) who will be approving your report (if they are not already listed). + - **CC**: Enter the email address of anyone else who should be notified that your expense report has been submitted. Add a comma between each email address if adding more than one. + - **Memo**: Enter any relevant notes. + - **Attach PDF**: Select this checkbox to attach a copy of your report to the email. +6. Tap **Submit**. +{% include end-option.html %} + +{% include end-selector.html %} + +# Retract a report + +You can retract a submitted report to edit the reported expenses and re-submit the report. + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the **Reports** tab. +2. Click a report to open it. +3. Click **Undo Submit** on the top left of the report. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the ☰ menu icon in the top left. +2. Tap the **Reports** tab. +3. Tap a report to open it. +4. Tap **Retract** at the top of the report. +{% include end-option.html %} + +{% include end-selector.html %} + +
From 3f202fe887b2f10df7d2c7d8ee2109d803d98d4d Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:31:04 -0500 Subject: [PATCH 054/116] Update and rename The-Reports-Page.md to Report-statuses.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New article for report statuses—just used an old article that we were going to delete to house this one --- .../reports/Report-statuses.md | 13 ++++++ .../reports/The-Reports-Page.md | 44 ------------------- 2 files changed, 13 insertions(+), 44 deletions(-) create mode 100644 docs/articles/expensify-classic/reports/Report-statuses.md delete mode 100644 docs/articles/expensify-classic/reports/The-Reports-Page.md diff --git a/docs/articles/expensify-classic/reports/Report-statuses.md b/docs/articles/expensify-classic/reports/Report-statuses.md new file mode 100644 index 000000000000..7fbdefc5a999 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Report-statuses.md @@ -0,0 +1,13 @@ +--- +title: Report statuses +description: What your report status means +--- +Each report is given a status based on where it is in the approval process: + +- **Open**: The report is “In Progress” and has not yet been submitted. If an open report is also labeled as Rejected, that means that the report was submitted but then rejected by an Approver because it requires adjustments. Open the report to review the comments for clarification on the rejection and next steps to take. +- **Processing**: The report has been submitted and is pending approval. +- **Approved**: The report has been approved but has not been reimbursed. For non-reimbursable reports, this is the final status. +- **Reimbursed**: The report has been successfully reimbursed. If a reimbursed report is also labeled as + - **Withdrawing**, an ACH process is initiated. + - **Confirmed**, the ACH process is in progress or complete. +- **Closed**: The report is closed. diff --git a/docs/articles/expensify-classic/reports/The-Reports-Page.md b/docs/articles/expensify-classic/reports/The-Reports-Page.md deleted file mode 100644 index 9c55cd9b4b8d..000000000000 --- a/docs/articles/expensify-classic/reports/The-Reports-Page.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: The Reports Page -description: Details about the Reports Page filters and CSV export options ---- - -## How to use the Reports Page -The Reports page is your central hub for a high-level view of a Reports' status. You can see the Reports page on a web browser when you sign into your Expensify account. -Here, you can quickly see which reports need submission (referred to as **Open**), which are currently awaiting approval (referred to as **Processing**), and which reports have successfully been **Approved** or **Reimbursed**. -To streamline your experience, we've incorporated user-friendly filters on the Reports page. These filters allow you to refine your report search by specific criteria, such as dates, submitters, or their association with a workspace. - -## Report filters -- **Reset Filters/Show Filters:** You can reset or display your filters at the top of the Reports page. -- **From & To:** Use these fields to refine your search to a specific date range. -- **Report ID, Name, or Email:** Narrow your search by entering a Report ID, Report Name, or the submitter's email. -- **Report Types:** If you're specifically looking for Bills or Invoices, you can select this option. -- **Submitters:** Choose between "All Submitters" or enter a specific employee's email to view their reports. -- **Policies:** Select "All Policies" or specify a particular policy associated with the reports you're interested in. - -## Report status -- **Open icon:** These reports are still "In Progress" and must be submitted by the creator. If they contain company card expenses, a domain admin can submit them. If labeled as “Rejected," an Approver has rejected it, typically requiring some adjustments. Click into the report and review the History for any comments from your Approver. -- **Processing icon:** These reports have been submitted for Approval but have not received the final approval. -- **Approved icon:** Reports in this category have been Approved but have yet to be Reimbursed. For non-reimbursable reports, this is the final status. -- **Reimbursed icon:** These reports have been successfully Reimbursed. If you see "Withdrawing," it means the ACH (Automated Clearing House) process is initiated. "Confirmed" indicates the ACH process is in progress or complete. No additional status means your Admin is handling reimbursement outside of Expensify. -- **Closed icon:** This status represents an officially closed report. - - -## How to Export a report to a CSV -To export a report to a CSV file, follow these steps on the Reports page: - -1. Click the checkbox on the far left of the report row you want to export. -2. Navigate to the upper right corner of the page and click the "Export to" button. -3. From the drop-down options that appear, select your preferred export format. - -{% include faq-begin.md %} -## What does it mean if the integration icon for a report is grayed out? -If the integration icon for a report appears grayed out, the report has yet to be fully exported. -To address this, consider these options: -- Go to **Settings > Policies > Group > Connections** within the workspace associated with the report to check for any errors with the accounting integration (i.e., The connection to NetSuite, QuickBooks Online, Xero, Sage Intacct shows an error). -- Alternatively, click the “Sync Now" button on the Connections page to see if any error prevents the export. - -## How can I see a specific expense on a report? -To locate a specific expense within a report, click on the Report from the Reports page and then click on an expense to view the expense details. - -{% include faq-end.md %} From 27a7903ceb9c51b84a3e5995f565f6735388e2e0 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:35:58 -0500 Subject: [PATCH 055/116] Update and rename Report-Audit-Log-and-Comments.md to Print-or-download-report.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New article for printing or downloading a report—just updated an older article that we were going to delete --- .../reports/Print-or-download-report.md | 15 +++++ .../reports/Report-Audit-Log-and-Comments.md | 62 ------------------- 2 files changed, 15 insertions(+), 62 deletions(-) create mode 100644 docs/articles/expensify-classic/reports/Print-or-download-report.md delete mode 100644 docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md diff --git a/docs/articles/expensify-classic/reports/Print-or-download-report.md b/docs/articles/expensify-classic/reports/Print-or-download-report.md new file mode 100644 index 000000000000..d21b945d5a83 --- /dev/null +++ b/docs/articles/expensify-classic/reports/Print-or-download-report.md @@ -0,0 +1,15 @@ +--- +title: Print or download report +description: Share, print, or download a report +--- +
+ +1. Click the **Reports** tab. +2. Select the report. +3. Click **Details** in the top right of the report. +4. Use the icons at the top to print, download, or share the report. + - Click the Print icon to print the report. + - Click the Download icon to download a PDF of the report. + - Click the Share icon to share the report via email or SMS. + +
diff --git a/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md b/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md deleted file mode 100644 index 04183608e3d1..000000000000 --- a/docs/articles/expensify-classic/reports/Report-Audit-Log-and-Comments.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Report Audit Log and Comments -description: Details on the expense report audit log and how to leave comments on reports ---- - -# Overview - -At the bottom of each expense report, there’s a section that acts as an audit log for the report. This section details report actions, such as submitting, approving, or exporting. The audit log records the user who completed the action as well as the timestamp for the action. - -This section also doubles as the space where submitters, approvers, and admins can converse with each other by leaving comments. Comments trigger notifications to everyone connected to the report and help facilitate communication inside of Expensify. - -# How to use the audit log - -All report actions are recorded in the audit log. Anytime you need to identify who touched a report or track its progress through the approval process, simply scroll down to the bottom of the report and review the log. - -Each recorded action is timestamped - tap or mouse over the timestamp to see the exact date and time the action occurred. - -# How to use report comments - -There’s a freeform field just under the audit log where you can leave a comment on the report. Type in your comment and click or tap the green arrow to send. The comment will be visible to anyone with visibility on the report, and also automatically sent to anyone who has actioned the report. - -# Deep Dive - -## Audit log - -Here’s a list of actions recorded by the audit log: - -- Report creation -- Report submission -- Report approval -- Report reimbursement -- Exports to accounting or to CSV/Excel files -- Report and expense rejections -- Changes made to expenses by approvers/admins -- Changes made to report fields by approvers/admins -- Automated actions taken by Concierge - -Both manual and automated actions are recorded. If a report action is completed by Concierge, that generally indicates an automation feature triggered the action. For example, an entry that shows a report submitted by Concierge indicates that the **Scheduled Submit** feature is enabled. - -Note that timestamps for actions recorded in the log reflect your own timezone. You can either set a static time zone manually, or we can trace your location data to set a time zone automatically for you. - -To set your time zone manually, head to **Settings > Account > Preferences > Time Zone** and check **Automatically Set my Time Zone**, or uncheck the box and manually choose your time zone from the searchable list of locations. - -## Comments - -Anyone with visibility on a report can leave a comment. Comments are interspersed with audit log entries. - -Report comments initially trigger a mobile app notification to report participants. If you don't read the notification within a certain amount of time, you'll receive an email notification with the report comment instead. The email will include a link to the report, allowing you to view and add additional comments directly on the report. You can also reply directly to the email, which will record your response as a comment. - -Comments can be formatted with bold, italics, or strikethrough using basic Markdown formatting. You can also add receipts and supporting documents to a report by clicking the paperclip icon on the right side of the comment field. - -{% include faq-begin.md %} - -## Why don’t some timestamps in Expensify match up with what’s shown in the report audit log? - -While the audit log is localized to your own timezone, some other features in Expensify (like times shown on the reports page) are not. Those use UTC as a baseline, so it’s possible that some times may look mismatched at first glance. In reality, it’s just a timezone discrepancy. - -## Is commenting on a report a billable action? - -Yes. If you comment on a report, you become a billable actor for the current month. - -{% include faq-end.md %} From 0710c68fddeb9d578d9596299e63b9ac675a2edf Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:37:16 -0500 Subject: [PATCH 056/116] Update and rename Print-or-download-report.md to Print-or-download-a-report.md Added "a" to make it consistent with the other articles in this folder --- ...rint-or-download-report.md => Print-or-download-a-report.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/articles/expensify-classic/reports/{Print-or-download-report.md => Print-or-download-a-report.md} (93%) diff --git a/docs/articles/expensify-classic/reports/Print-or-download-report.md b/docs/articles/expensify-classic/reports/Print-or-download-a-report.md similarity index 93% rename from docs/articles/expensify-classic/reports/Print-or-download-report.md rename to docs/articles/expensify-classic/reports/Print-or-download-a-report.md index d21b945d5a83..b2e55b09e6c5 100644 --- a/docs/articles/expensify-classic/reports/Print-or-download-report.md +++ b/docs/articles/expensify-classic/reports/Print-or-download-a-report.md @@ -1,5 +1,5 @@ --- -title: Print or download report +title: Print or download a report description: Share, print, or download a report ---
From 1975bda87672bb2799f9d3d83a2ede5e19feb490 Mon Sep 17 00:00:00 2001 From: caitlinwhite1 Date: Wed, 10 Apr 2024 01:33:06 -0500 Subject: [PATCH 057/116] Update redirects.csv lots of broken lins from moving things around on HelpDot. redirecting those --- docs/redirects.csv | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 8e9e6350a326..7eb4140f7b37 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -102,3 +102,53 @@ https://help.expensify.com/articles/expensify-classic/reports/Expense-Rules,http https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/reports/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page https://help.expensify.com/articles/expensify-classic/reports/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses +https://help.expensify.com/articles/expensify-classic/account-settings/Close-Account,https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account +https://help.expensify.com/articles/expensify-classic/account-settings/Copilot,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ +https://help.expensify.com/articles/expensify-classic/account-settings/Notification-Troubleshooting,https://help.expensify.com/articles/expensify-classic/settings/Notification-Troubleshooting +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Billing-Overview,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Billing-Owner,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing,https://help.expensify.com/articles/expensify-classic/expensify-billing/Consolidated-Domain-Billing +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Receipt-Breakdown,https://help.expensify.com/articles/expensify-classic/expensify-billing/Receipt-Breakdown +https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt,https://help.expensify.com/articles/expensify-classic/expensify-billing/Tax-Exempt +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports,https://help.expensify.com/expensify-classic/hubs/reports/ +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members,https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles +https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Types,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Types +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Report-Audit-Log-and-Comments,https://help.expensify.com/articles/expensify-classic/reports/Report-Audit-Log-and-Comments +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page +https://help.expensify.com/articles/expensify-classic/expense-and-report-features/The-Reports-Page,https://help.expensify.com/articles/expensify-classic/reports/The-Reports-Page +https://help.expensify.com/articles/expensify-classic/expenses/Per-Diem-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/Track-per-diem-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/Distance-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-mileage-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Apply-Tax,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Apply-Tax +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Create-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Merge-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense +https://help.expensify.com/articles/expensify-classic/get-paid-back/Per-Diem-Expenses,https://help.expensify.com/articles/expensify-classic/expenses/Track-per-diem-expenses +https://help.expensify.com/articles/expensify-classic/get-paid-back/Referral-Program,https://help.expensify.com/articles/new-expensify/expenses/Referral-Program +https://help.expensify.com/articles/expensify-classic/get-paid-back/reports/Create-A-Report,https://help.expensify.com/articles/expensify-classic/expenses/reports/Create-A-Report +https://help.expensify.com/articles/expensify-classic/get-paid-back/reports/Reimbursements,https://help.expensify.com/articles/expensify-classic/expenses/reports/Reimbursements +https://help.expensify.com/articles/expensify-classic/get-paid-back/Trips,https://help.expensify.com/articles/expensify-classic/expenses/Trips +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Default-Export-Templates +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Fringe-Benefits,https://help.expensify.com/articles/expensify-classic/spending-insights/Fringe-Benefits +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Insights,https://help.expensify.com/articles/expensify-classic/spending-insights/Insights +https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options,https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members +https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ +https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency +https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports +https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO,https://help.expensify.com/articles/expensify-classic/settings/Enable-two-factor-authentication +https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Set-budgets +https://help.expensify.com/articles/expensify-classic/workspaces/Categories,https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories +https://help.expensify.com/articles/expensify-classic/workspaces/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags +https://help.expensify.com/expensify-classic/hubs/manage-employees-and-report-approvals,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows From 398b32f1557872c3ff6239ffed7a0a67bcaae743 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 09:28:57 +0200 Subject: [PATCH 058/116] use canReportBeMentionedWithinPolicy function --- src/libs/ReportUtils.ts | 9 +++++++++ .../report/ReportActionCompose/SuggestionMention.tsx | 6 +----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c9241054e74c..df6e4c748553 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5752,6 +5752,14 @@ function getOutstandingChildRequest(iouReport: OnyxEntry | EmptyObject): return {}; } +function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyId: string): boolean { + if (report?.policyID !== policyId) { + return false; + } + + return isChatRoom(report) && !isThread(report); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -5978,6 +5986,7 @@ export { hasActionsWithErrors, getGroupChatName, getOutstandingChildRequest, + canReportBeMentionedWithinPolicy, }; export type { diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 5533a2a72af3..6118ef4f8139 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -255,11 +255,7 @@ function SuggestionMention( (searchTerm: string, reportBatch: OnyxCollection): Mention[] => { const filteredRoomMentions: Mention[] = []; Object.values(reportBatch ?? {}).forEach((report) => { - if (report?.policyID !== policyID) { - return; - } - if (!ReportUtils.isGroupPolicy(report) || !isValidRoomName(report?.reportName ?? '')) { - // checking validity of room name removes Policy Expense Chats + if (!ReportUtils.canReportBeMentionedWithinPolicy(report, policyID ?? '')) { return; } if (report?.reportName?.toLowerCase().includes(searchTerm.toLowerCase())) { From 1a13943bee341e73457cba569a7e89e7496d19eb Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 10 Apr 2024 14:49:44 +0700 Subject: [PATCH 059/116] fix expense report is not hidden --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d59d36a92b0..8d9e9e5dd707 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -96,7 +96,7 @@ function getOrderedReportIDs( const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const hasErrorsOtherThanFailedReceipt = hasErrors || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); + const hasErrorsOtherThanFailedReceipt = !hasErrors || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = (hasBrickError && hasErrorsOtherThanFailedReceipt) || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; From bf7ccedc641b0015af72b8bb364fabdd2f1432a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 10 Apr 2024 11:16:32 +0200 Subject: [PATCH 060/116] Add markdown styles on native platforms --- src/components/TextInput/BaseTextInput/index.native.tsx | 3 +++ src/components/TextInput/BaseTextInput/index.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 864c83d07cc7..9b25fbbba60e 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -16,6 +16,7 @@ import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; import TextInputLabel from '@components/TextInput/TextInputLabel'; import useLocalize from '@hooks/useLocalize'; +import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -67,6 +68,7 @@ function BaseTextInput( const inputProps = {shouldSaveDraft: false, shouldUseDefaultValue: false, ...props}; const theme = useTheme(); const styles = useThemeStyles(); + const markdownStyle = useMarkdownStyle(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -377,6 +379,7 @@ function BaseTextInput( selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} + markdownStyle={markdownStyle} /> {inputProps.isLoading && ( (null); const isLabelActive = useRef(initialActiveLabel); - const markdownStyle = useMarkdownStyle(); // AutoFocus which only works on mount: useEffect(() => { From 1c17d75d6ea4f7890dc1a4620c2f7fd4c691e71d Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 10 Apr 2024 17:41:26 +0700 Subject: [PATCH 061/116] fix update hasErrorsOtherThanFailedReceipt --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d9e9e5dd707..ce244d406640 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -96,7 +96,7 @@ function getOrderedReportIDs( const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const hasErrorsOtherThanFailedReceipt = !hasErrors || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); + const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = (hasBrickError && hasErrorsOtherThanFailedReceipt) || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; From 8d58578541e1a35a05f893df0c1aa33fb4fb82b9 Mon Sep 17 00:00:00 2001 From: Sonia Liapounova Date: Wed, 10 Apr 2024 04:00:03 -0700 Subject: [PATCH 062/116] Create Business-Bank-Accounts-AUD.md --- .../Business-Bank-Accounts-AUD.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md new file mode 100644 index 000000000000..8c5ead911da4 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-AUD.md @@ -0,0 +1,51 @@ +--- +title: Add a Business Bank Account +description: This article provides insight on setting up and using an Australian Business Bank account in Expensify. +--- + +# How to add an Australian business bank account (for admins) +A withdrawal account is the business bank account that you want to use to pay your employee reimbursements. + +_Your policy currency must be set to AUD and reimbursement setting set to Indirect to continue. If your main policy is used for something other than AUD, then you will need to create a new one and set that policy to AUD._ + +To set this up, you’ll run through the following steps: + +1. Go to **Settings > Your Account > Payments** and click **Add Verified Bank Account** +![Click the Verified Bank Account button in the bottom right-hand corner of the screen](https://help.expensify.com/assets/images/add-vba-australian-account.png){:width="100%"} + +2. Enter the required information to connect to your business bank account. If you don't know your Bank User ID/Direct Entry ID/APCA Number, please contact your bank and they will be able to provide this. +![Enter your information in each of the required fields](https://help.expensify.com/assets/images/add-vba-australian-account-modal.png){:width="100%"} + +3. Link the withdrawal account to your policy by heading to **Settings > Policies > Group > [Policy name] > Reimbursement** +4. Click **Direct reimbursement** +5. Set the default withdrawal account for processing reimbursements +6. Tell your employees to add their deposit accounts and start reimbursing. + +# How to delete a bank account +If you’re no longer using a bank account you previously connected to Expensify, you can delete it by doing the following: + +1. Navigate to Settings > Accounts > Payments +2. Click **Delete** +![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} + +You can complete this process either via the web app (on a computer), or via the mobile app. + +# Deep Dive +## Bank-specific batch payment support + +If you are new to using Batch Payments in Australia, to reimburse your staff or process payroll, you may want to check out these bank-specific instructions for how to upload your .aba file: + +- ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) +- CommBank - [Importing and using Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) +- Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) +- NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) +- Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) +- Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +**Note:** Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. + +## Enable Global Reimbursement + +If you have employees in other countries outside of Australia, you can now reimburse them directly using Global Reimbursement. + +To do this, you’ll first need to delete any existing Australian business bank accounts. Then, you’ll want to follow the instructions to enable Global Reimbursements From 9f9e2ce8a3c15745609443d44f81247b4013cd38 Mon Sep 17 00:00:00 2001 From: Sonia Liapounova Date: Wed, 10 Apr 2024 04:11:52 -0700 Subject: [PATCH 063/116] Create Business-Bank-Accounts-USD.md --- .../Business-Bank-Accounts-USD.md | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md new file mode 100644 index 000000000000..4ae2c669561f --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Business-Bank-Accounts-USD.md @@ -0,0 +1,161 @@ +--- +title: Business Bank Accounts - USD +description: How to add/remove Business Bank Accounts (US) +--- +# Overview +Adding a verified business bank account unlocks a myriad of features and automation in Expensify. +Once you connect your business bank account, you can: +- Pay employee expense reports via direct deposit (US) +- Settle company bills via direct transfer +- Accept invoice payments through direct transfer +- Access the Expensify Card + +# How to add a verified business bank account +To connect a business bank account to Expensify, follow the below steps: +1. Go to **Settings > Account > Payments** +2. Click **Add Verified Bank Account** +3. Click **Log into your bank** +4. Click **Continue** +5. When you hit the **Plaid** screen, you'll be shown a list of compatible banks that offer direct online login access +6. Login to the business bank account +- If the bank is not listed, click the X to go back to the connection type +- Here you’ll see the option to **Connect Manually** +- Enter your account and routing numbers +7. Enter your bank login credentials. +- If your bank requires additional security measures, you will be directed to obtain and enter a security code +- If you have more than one account available to choose from, you will be directed to choose the desired account +Next, to verify the bank account, you’ll enter some details about the business as well as some personal information. + +## Enter company information +This is where you’ll add the legal business name as well as several other company details. + +### Company address +The company address must: +- Be located in the US +- Be a physical location +If you input a maildrop address (PO box, UPS Store, etc.), the address will likely be flagged for review and adding the bank account to Expensify will be delayed. + +### Tax Identification Number +This is the identification number that was assigned to the business by the IRS. +### Company website +A company website is required to use most of Expensify’s payment features. When adding the website of the business, format it as, https://www.domain.com. +### Industry Classification Code +You can locate a list of Industry Classification Codes here. +## Enter personal information +Whoever is connecting the bank account to Expensify, must enter their details under the Requestor Information section: +- The address must be a physical address +- The address must be located in the US +- The SSN must be US-issued +This does not need to be a signor on the bank account. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review and adding the bank account to Expensify will be delayed. + +## Upload ID +After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: +1. Upload the front and back of your ID +2. Use your device to take a selfie and record a short video of yourself +It’s required that your ID is: +- Issued in the US +- Unexpired + +## Additional Information +Check the appropriate box under **Additional Information**, accept the agreement terms, and verify that all of the information is true and accurate: +- A Beneficial Owner refers to an **individual** who owns 25% or more of the business. +- If you or another **individual** owns 25% or more of the business, please check the appropriate box +- If someone else owns 25% or more of the business, you will be prompted to provide their personal information +If no individual owns more than 25% of the company you do not need to list any beneficial owners. In that case, be sure to leave both boxes unchecked under the Beneficial Owner Additional Information section. + +# How to validate the bank account +The account you set up can be found under **Settings > Account > Payment > Bank Accounts** section in either **Verifying** or **Pending** status. +If it is **Verifying**, then this means we sent you a message and need more information from you. Please check your Concierge chat which should include a message with specific details about what we require to move forward. +If it is **Pending**, then in 1-2 business days Expensify will administer 3 test transactions to your bank account. Please check your Concierge chat for further instructions. If you do not see these test transactions +After these transactions (2 withdrawals and 1 deposit) have been processed in your account, visit your Expensify Inbox, where you'll see a prompt to input the transaction amounts. +Once you've finished these steps, your business bank account is ready to use in Expensify! + +# How to share a verified bank account +Only admins with access to the verified bank account can reimburse employees or pay vendor bills. To grant another admin access to the bank account in Expensify, go to **Settings > Account > Payments > Bank Accounts** and click **"Share"**. Enter their email address, and they will receive instructions from us. Please note, they must be a policy admin on a policy you also have access to in order to share the bank account with them. +When a bank account is shared, it must be revalidated with three new microtransactions to ensure the shared admin has access. This process takes 1-2 business days. Once received, the shared admin can enter the transactions via their Expensify account's Inbox tab. + +Note: A report is shared with all individuals with access to the same business bank account in Expensify for audit purposes. + + +# How to remove access to a verified bank account +This step is important when accountants and staff leave your business. +To remove an admin's access to a shared bank account, go to **Settings > Account > Payments > Shared Business Bank Accounts**. +You'll find a list of individuals who have access to the bank account. Next to each user, you'll see the option to Unshare the bank account. + +# How to delete a verified bank account +If you need to delete a bank account from Expensify, run through the following steps: +1. Head to **Settings > Account > Payments** +2. Click the red **Delete** button under the corresponding bank account + +Be cautious, as if it hasn't been shared with someone else, the next user will need to set it up from the beginning. + +If the bank account is set as the settlement account for your Expensify Cards, you’ll need to designate another bank account as your settlement account under **Settings > Domains > Company Cards > Settings** before this account can be deleted. + +# Deep Dive + +## Verified bank account requirements + +To add a business bank account to issue reimbursements via ACH (US), to pay invoices (US) or utilize the Expensify Card: +- You must enter a physical address for yourself, any Beneficial Owner (if one exists), and the business associated with the bank account. We **cannot** accept a PO Box or MailDrop location. +- If you are adding the bank account to Expensify, you must add it from **your** Expensify account settings. +- If you are adding a bank account to Expensify, we are required by law to verify your identity. Part of this process requires you to verify a US issued photo ID. For utilizing features related to US ACH, your idea must be issued by the United States. You and any Beneficial Owner (if one exists), must also have a US address +- You must have a valid website for your business to utilize the Expensify Card, or to pay invoices with Expensify. + +## Locked bank account +When you reimburse a report, you authorize Expensify to withdraw the funds from your account. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. + +Withdrawal requests can be rejected due to insufficient funds, or if the bank account has not been enabled for direct debit. +If you need to enable direct debits from your verified bank account, your bank will require the following details: +- The ACH CompanyIDs (1270239450 and 4270239450) +- The ACH Originator Name (Expensify) +To request to unlock the bank account, click **Fix** on your bank account under **Settings > Account > Payments > Bank Accounts**. +This sends a request to our support team to review exactly why the bank account was locked. +Please note, unlocking a bank account can take 4-5 business days to process. + +## Error adding ID to Onfido +Expensify is required by both our sponsor bank and federal law to verify the identity of the individual that is initiating the movement of money. We use Onfido to confirm that the person adding a payment method is genuine and not impersonating someone else. + +If you get a generic error message that indicates, "Something's gone wrong", please go through the following steps: + +1. Ensure you are using either Safari (on iPhone) or Chrome (on Android) as your web browser. +2. Check your browser's permissions to make sure that the camera and microphone settings are set to "Allow" +3. Clear your web cache for Safari (on iPhone) or Chrome (on Android). +4. If using a corporate Wi-Fi network, confirm that your corporate firewall isn't blocking the website. +5. Make sure no other apps are overlapping your screen, such as the Facebook Messenger bubble, while recording the video. +6. On iPhone, if using iOS version 15 or later, disable the Hide IP address feature in Safari. +7. If possible, try these steps on another device +8. If you have another phone available, try to follow these steps on that device +If the issue persists, please contact your Account Manager or Concierge for further troubleshooting assistance. + +{% include faq-begin.md %} +## What is a Beneficial Owner? + +A Beneficial Owner refers to an **individual** who owns 25% or more of the business. If no individual owns 25% or more of the business, the company does not have a Beneficial Owner. + + +## What do I do if the Beneficial Owner section only asks for personal details, but our business is owned by another company? + + +Please only indicate you have a Beneficial Owner, if it is an individual that owns 25% or more of the business. + +## Why can’t I input my address or upload my ID? + + +Are you entering a US address? When adding a verified business bank account in Expensify, the individual adding the account, and any beneficial owner (if one exists) are required to have a US address, US photo ID, and a US SSN. If you do not meet these requirements, you’ll need to have another admin add the bank account, and then share access with you once verified. + + +## Why am I being asked for documentation when adding my bank account? +When a bank account is added to Expensify, we complete a series of checks to verify the information provided to us. We conduct these checks to comply with both our sponsor bank's requirements and federal government regulations, specifically the Bank Secrecy Act / Anti-Money Laundering (BSA / AML) laws. Expensify also has anti-fraud measures in place. +If automatic verification fails, we may request manual verification, which could involve documents such as address verification for your business, a letter from your bank confirming bank account ownership, etc. + +If you have any questions regarding the documentation request you received, please contact Concierge and they will be happy to assist. + + +## I don’t see all three microtransactions I need to validate my bank account. What should I do? + +It's a good idea to wait till the end of that second business day. If you still don’t see them, please reach out to your bank and ask them to whitelist our ACH ID's **1270239450** and **4270239450**. Expensify’s ACH Originator Name is "Expensify". + +Make sure to reach out to your Account Manager or to Concierge once you have done so and our team will be able to re-trigger those 3 transactions! + + +{% include faq-end.md %} From 35143a3bcb4051f21b520a9118d64b5eba7773d8 Mon Sep 17 00:00:00 2001 From: Sonia Liapounova Date: Wed, 10 Apr 2024 04:18:35 -0700 Subject: [PATCH 064/116] Create Global-Reimbursements.md --- .../Global-Reimbursements.md | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md new file mode 100644 index 000000000000..2ff74760b376 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Global-Reimbursements.md @@ -0,0 +1,106 @@ +--- +title: International Reimbursements +description: International Reimbursements +--- +# Overview + +If your company’s business bank account is in the US, Canada, the UK, Europe, or Australia, you now have the option to send direct reimbursements to nearly any country across the globe! +The process to enable global reimbursements is dependent on the currency of your reimbursement bank account, so be sure to review the corresponding instructions below. + +# How to request international reimbursements + +## The reimbursement account is in USD + +If your reimbursement bank account is in USD, the first step is connecting the bank account to Expensify. +The individual who plans on sending reimbursements internationally should head to **Settings > Account > Payments > Add Verified Bank Account**. From there, you will provide company details, input personal information, and upload a copy of your ID. + +Once the USD bank account is verified (or if you already had a USD business bank account connected), click the support icon in your Expensify account to inform your Setup Specialist, Account Manager, or Concierge that you’d like to enable international reimbursements. From there, Expensify will ask you to confirm the currencies of the reimbursement and employee bank accounts. + +Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. + +## The reimbursement account is in AUD, CAD, GBP, EUR + +To request international reimbursements, contact Expensify Support to make that request. + +You can do this by clicking on the support icon and informing your Setup Specialist, Account Manager, or Concierge that you’d like to set up global reimbursements on your account. +From there, Expensify will ask you to confirm both the currencies of the reimbursement and employee bank accounts. + +Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. + +# How to verify the bank account for sending international payments + +Once international payments are enabled on your Expensify account, the next step is verifying the bank account to send the reimbursements. + +## The reimbursement account is in USD + +First, confirm the workspace settings are set up correctly by doing the following: +1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reports** and check that the workspace currency is USD +2. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the reimbursement method to direct +3. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the USD bank account to the default account + +Once that’s all set, head to **Settings > Account > Payments**, and click **Enable Global Reimbursement** on the bank account (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account). + +From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. + +## The reimbursement account is in AUD, CAD, GBP, EUR + +First, confirm the workspace currency corresponds with the currency of the reimbursement bank account. You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reports**. It should be AUD, CAD, GBP, or EUR. + +Next, add the bank account to Expensify: +1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** and set the reimbursement method to direct (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account) +2. Click **Add Business Bank Account** +3. If the incorrect country shows as the default, click **Switch Country** to select the correct country +4. Enter the bank account details +5. Click **Save & Continue** + +From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. + +# How to start reimbursing internationally + +After the bank account is verified for international payments, set the correct bank account as the reimbursement account. + +You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** by selecting the reimbursement account as the default account. + +Finally, have your employees add their deposit-only bank accounts. They can do this by logging into their Expensify accounts, heading to **Settings > Account > Payments**, and clicking **Add Deposit-Only Bank Account**. + +# Deep Dive + +## Documents requested + +Our Compliance Team may ask for additional information depending on who initiates the verification or what information you provide on the DocuSign form. + +Examples of additional requested information: +- The reimburser’s proof of address and ID +- Company directors’ proofs of address and IDs +- An authorization letter +- An independently certified documentation such as shareholder agreement from a lawyer, notary, or public accountant if an individual owns more than 25% of the company + +{% include faq-begin.md %} + +## How many people can send reimbursements internationally? + +Once your company is authorized to send global payments, the individual who verified the bank account can share it with additional admins on the workspace. That way, multiple workspace members can send international reimbursements. + +## How long does it take to verify an account for international payments? + +It varies! The verification process can take a few business days to several weeks. It depends on whether or not the information in the DocuSign form is correct if our Compliance Team requires any additional information, and how responsive the employee verifying the company’s details is to our requests. + +## If I already have a USD bank account connected to Expensify, do I need to go through the verification process again to enable international payments? + +If you’ve already connected a US business bank account, you can request to enable global reimbursements by contacting Expensify Support immediately. However, additional steps are required to verify the bank account for international payments. + +## My employee says they don’t have the option to add their non-USD bank account as a deposit account – what should they do? + +Have the employee double-check that their default workspace is set as the workspace that's connected to the bank you're using to send international payments. + +An employee can confirm their default workspace is under **Settings > Workspaces > Group**. The default workspace has a green checkmark next to it. They can change their default workspace by clicking **Default Workspace** on the correct workspace. + +## Who is the “Authorized User” on the International Reimbursement DocuSign form? + +This is the person who will process international reimbursements. The authorized user should be the same person who manages the bank account connection in Expensify. + +## Who should I enter as the “User” on the International Reimbursement form? + +You can leave this form section blank since the “User” is Expensify. + +{% include faq-end.md %} From 87cc912c7137c3cb0721c5f46f1548ecf666a02e Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Wed, 10 Apr 2024 14:34:23 +0200 Subject: [PATCH 065/116] feat: bump react-native-screens --- ios/Podfile.lock | 8 +- package-lock.json | 7 +- package.json | 2 +- ...ct-native-screens+3.29.0+001+initial.patch | 49 ---- ...e-screens+3.29.0+002+fixLayoutIssues.patch | 214 ------------------ ...creens+3.29.0+003+fixIOSHeaderHeight.patch | 35 --- 6 files changed, 9 insertions(+), 306 deletions(-) delete mode 100644 patches/react-native-screens+3.29.0+001+initial.patch delete mode 100644 patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch delete mode 100644 patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2f53f00918bf..08095f4c7fa2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1945,7 +1945,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.29.0): + - RNScreens (3.30.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1963,9 +1963,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 3.29.0) + - RNScreens/common (= 3.30.1) - Yoga - - RNScreens/common (3.29.0): + - RNScreens/common (3.30.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2571,7 +2571,7 @@ SPEC CHECKSUMS: RNPermissions: 8990fc2c10da3640938e6db1647cb6416095b729 RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37 RNReanimated: 605409e0d0ced6f2e194ae585fedc2f8a1935bf2 - RNScreens: f7b8bb892b4957f6f91e5dfd9a191e7f13ce8baa + RNScreens: 65a936f4e227b91e4a8e2a7d4c4607355bfefda0 RNShare: 2a4cdfc0626ad56b0ef583d424f2038f772afe58 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 RNSVG: db32cfcad0a221fd175e0882eff7bcba7690380a diff --git a/package-lock.json b/package-lock.json index 6a31f9009d10..ad041271ea89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,7 +107,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.29.0", + "react-native-screens": "3.30.1", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", @@ -39850,8 +39850,9 @@ } }, "node_modules/react-native-screens": { - "version": "3.29.0", - "license": "MIT", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.30.1.tgz", + "integrity": "sha512-/muEvjocCtFb+j5J3YmLvB25+f4rIU8hnnxgGTkXcAf2omPBY8uhPjJaaFUlvj64VEoEzJcRpugbXWsjfPPIFg==", "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/package.json b/package.json index ddee8708bc46..d80fee787adc 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "react-native-release-profiler": "^0.1.6", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.8.2", - "react-native-screens": "3.29.0", + "react-native-screens": "3.30.1", "react-native-share": "^10.0.2", "react-native-sound": "^0.11.2", "react-native-svg": "14.1.0", diff --git a/patches/react-native-screens+3.29.0+001+initial.patch b/patches/react-native-screens+3.29.0+001+initial.patch deleted file mode 100644 index dbe65b2abf3f..000000000000 --- a/patches/react-native-screens+3.29.0+001+initial.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -index d9e0e58..2d946c4 100644 ---- a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -+++ b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -@@ -3,7 +3,6 @@ package com.swmansion.rnscreens - import android.view.ViewGroup - import androidx.annotation.UiThread - import com.facebook.react.bridge.ReactContext --import com.facebook.react.bridge.ReadableMap - import com.facebook.react.bridge.WritableMap - import com.facebook.react.bridge.WritableNativeMap - import com.facebook.react.uimanager.FabricViewStateManager -@@ -13,6 +12,9 @@ import kotlin.math.abs - abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context), FabricViewStateManager.HasFabricViewStateManager { - private val mFabricViewStateManager: FabricViewStateManager = FabricViewStateManager() - -+ private var lastSetWidth = 0f -+ private var lastSetHeight = 0f -+ - override fun getFabricViewStateManager(): FabricViewStateManager { - return mFabricViewStateManager - } -@@ -28,17 +30,16 @@ abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : View - - // Check incoming state values. If they're already the correct value, return early to prevent - // infinite UpdateState/SetState loop. -- val currentState: ReadableMap? = mFabricViewStateManager.getStateData() -- if (currentState != null) { -- val delta = 0.9f -- val stateFrameHeight: Float = if (currentState.hasKey("frameHeight")) currentState.getDouble("frameHeight").toFloat() else 0f -- val stateFrameWidth: Float = if (currentState.hasKey("frameWidth")) currentState.getDouble("frameWidth").toFloat() else 0f -- if (abs(stateFrameWidth - realWidth) < delta && -- abs(stateFrameHeight - realHeight) < delta -- ) { -- return -- } -+ val delta = 0.9f -+ if (abs(lastSetWidth - realWidth) < delta && -+ abs(lastSetHeight - realHeight) < delta -+ ) { -+ return - } -+ -+ lastSetWidth = realWidth -+ lastSetHeight = realHeight -+ - mFabricViewStateManager.setState { - val map: WritableMap = WritableNativeMap() - map.putDouble("frameWidth", realWidth.toDouble()) diff --git a/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch b/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch deleted file mode 100644 index 9654c9cfcb42..000000000000 --- a/patches/react-native-screens+3.29.0+002+fixLayoutIssues.patch +++ /dev/null @@ -1,214 +0,0 @@ -diff --git a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -index 2d946c4..ccda8f3 100644 ---- a/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -+++ b/node_modules/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt -@@ -12,38 +12,36 @@ import kotlin.math.abs - abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context), FabricViewStateManager.HasFabricViewStateManager { - private val mFabricViewStateManager: FabricViewStateManager = FabricViewStateManager() - -- private var lastSetWidth = 0f -- private var lastSetHeight = 0f -+ private var lastHeaderHeight: Double = 0.0 - - override fun getFabricViewStateManager(): FabricViewStateManager { - return mFabricViewStateManager - } - -- protected fun updateScreenSizeFabric(width: Int, height: Int) { -- updateState(width, height) -+ protected fun updateScreenSizeFabric(width: Int, height: Int, headerHeight: Double) { -+ updateState(width, height, headerHeight) - } - - @UiThread -- fun updateState(width: Int, height: Int) { -+ fun updateState(width: Int, height: Int, headerHeight: Double) { - val realWidth: Float = PixelUtil.toDIPFromPixel(width.toFloat()) - val realHeight: Float = PixelUtil.toDIPFromPixel(height.toFloat()) - - // Check incoming state values. If they're already the correct value, return early to prevent - // infinite UpdateState/SetState loop. -- val delta = 0.9f -- if (abs(lastSetWidth - realWidth) < delta && -- abs(lastSetHeight - realHeight) < delta -- ) { -+ val delta = 0.9 -+ if (abs(lastHeaderHeight - headerHeight) < delta) { - return - } - -- lastSetWidth = realWidth -- lastSetHeight = realHeight -+ lastHeaderHeight = headerHeight - - mFabricViewStateManager.setState { - val map: WritableMap = WritableNativeMap() - map.putDouble("frameWidth", realWidth.toDouble()) - map.putDouble("frameHeight", realHeight.toDouble()) -+ map.putDouble("contentOffsetX", 0.0) -+ map.putDouble("contentOffsetY", headerHeight) - map - } - } -diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -index a7d28f9..e0b0d8e 100644 ---- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt -@@ -72,9 +72,9 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - val width = r - l - val height = b - t - -- calculateHeaderHeight() -+ val headerHeight = if (container is ScreenStack) calculateHeaderHeight().first else 0.0 - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { -- updateScreenSizeFabric(width, height) -+ updateScreenSizeFabric(width, height, headerHeight) - } else { - updateScreenSizePaper(width, height) - } -@@ -246,7 +246,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - mNativeBackButtonDismissalEnabled = enableNativeBackButtonDismissal - } - -- private fun calculateHeaderHeight() { -+ private fun calculateHeaderHeight(): Pair { - val actionBarTv = TypedValue() - val resolvedActionBarSize = context.theme.resolveAttribute(android.R.attr.actionBarSize, actionBarTv, true) - -@@ -265,6 +265,8 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex - val totalHeight = actionBarHeight + statusBarHeight - UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id) - ?.dispatchEvent(HeaderHeightChangeEvent(id, totalHeight)) -+ -+ return actionBarHeight to statusBarHeight - } - - enum class StackPresentation { -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -index 67194d3..c1a1b40 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h -@@ -13,7 +13,7 @@ class RNSScreenComponentDescriptor final - using ConcreteComponentDescriptor::ConcreteComponentDescriptor; - - void adopt(ShadowNode& shadowNode) const override { -- react_native_assert( -+ react_native_assert( - dynamic_cast(&shadowNode)); - auto& screenShadowNode = - static_cast(shadowNode); -@@ -28,10 +28,7 @@ class RNSScreenComponentDescriptor final - shadowNode.getState()); - auto stateData = state->getData(); - -- if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { -- layoutableShadowNode.setSize( -- Size{stateData.frameSize.width, stateData.frameSize.height}); -- } -+ layoutableShadowNode.setPadding({.bottom = stateData.contentOffset.y}); - - ConcreteComponentDescriptor::adopt(shadowNode); - } -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -index ba61ed8..0e3746e 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp -@@ -5,5 +5,11 @@ namespace react { - - extern const char RNSScreenComponentName[] = "RNSScreen"; - -+Point RNSScreenShadowNode::getContentOriginOffset() const { -+ auto stateData = getStateData(); -+ auto contentOffset = stateData.contentOffset; -+ return {contentOffset.x, contentOffset.y}; -+} -+ - } // namespace react - } // namespace facebook -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -index ef25dd1..bbd7599 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h -@@ -19,9 +19,11 @@ class JSI_EXPORT RNSScreenShadowNode final : public ConcreteViewShadowNode< - public: - using ConcreteViewShadowNode::ConcreteViewShadowNode; - -+ Point getContentOriginOffset() const override; -+ - static ShadowNodeTraits BaseTraits() { - auto traits = ConcreteViewShadowNode::BaseTraits(); -- traits.set(ShadowNodeTraits::Trait::RootNodeKind); -+ // traits.set(ShadowNodeTraits::Trait::RootNodeKind); - return traits; - } - }; -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -index 69c77a6..6c3b8ca 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.cpp -@@ -6,8 +6,8 @@ namespace react { - #ifdef ANDROID - folly::dynamic RNSScreenState::getDynamic() const { - return folly::dynamic::object("frameWidth", frameSize.width)( -- "frameHeight", frameSize.height); --} -+ "frameHeight", frameSize.height)("contentOffsetX", contentOffset.x)("contentOffsetY", contentOffset.y); -+ } - #endif - - } // namespace react -diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -index ce09807..e26d411 100644 ---- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenState.h -@@ -17,7 +17,7 @@ class JSI_EXPORT RNSScreenState final { - using Shared = std::shared_ptr; - - RNSScreenState(){}; -- RNSScreenState(Size frameSize_) : frameSize(frameSize_){}; -+ RNSScreenState(Size frameSize_, Point contentOffset_) : frameSize(frameSize_), contentOffset(contentOffset_){}; - - #ifdef ANDROID - RNSScreenState( -@@ -25,10 +25,14 @@ class JSI_EXPORT RNSScreenState final { - folly::dynamic data) - : frameSize(Size{ - (Float)data["frameWidth"].getDouble(), -- (Float)data["frameHeight"].getDouble()}){}; -+ (Float)data["frameHeight"].getDouble()}), -+ contentOffset(Point{ -+ (Float)data["contentOffsetX"].getDouble(), -+ (Float)data["contentOffsetY"].getDouble()}){}; - #endif - - const Size frameSize{}; -+ Point contentOffset; - - #ifdef ANDROID - folly::dynamic getDynamic() const; -diff --git a/node_modules/react-native-screens/ios/RNSScreen.h b/node_modules/react-native-screens/ios/RNSScreen.h -index f1bd9d8..797fc12 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.h -+++ b/node_modules/react-native-screens/ios/RNSScreen.h -@@ -42,6 +42,7 @@ namespace react = facebook::react; - #ifdef RCT_NEW_ARCH_ENABLED - - (void)setViewToSnapshot:(UIView *)snapshot; - - (void)resetViewToScreen; -+- (CGFloat)calculateHeaderHeightIsModal:(BOOL)isModal; - #endif - - @end -diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm -index 4b24cff..8f480ca 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.mm -+++ b/node_modules/react-native-screens/ios/RNSScreen.mm -@@ -107,7 +107,8 @@ - (void)updateBounds - { - #ifdef RCT_NEW_ARCH_ENABLED - if (_state != nullptr) { -- auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size)}; -+ CGFloat headerHeight = [_controller calculateHeaderHeightIsModal:self.isPresentedAsNativeModal]; -+ auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, headerHeight))}; - _state->updateState(std::move(newState)); - UINavigationController *navctr = _controller.navigationController; - [navctr.view setNeedsLayout]; diff --git a/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch b/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch deleted file mode 100644 index ae162204a692..000000000000 --- a/patches/react-native-screens+3.29.0+003+fixIOSHeaderHeight.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm -index 8f480ca..4cc5e7b 100644 ---- a/node_modules/react-native-screens/ios/RNSScreen.mm -+++ b/node_modules/react-native-screens/ios/RNSScreen.mm -@@ -108,7 +108,7 @@ - (void)updateBounds - #ifdef RCT_NEW_ARCH_ENABLED - if (_state != nullptr) { - CGFloat headerHeight = [_controller calculateHeaderHeightIsModal:self.isPresentedAsNativeModal]; -- auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, headerHeight))}; -+ auto newState = react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size), RCTPointFromCGPoint(CGPointMake(0, 0))}; - _state->updateState(std::move(newState)); - UINavigationController *navctr = _controller.navigationController; - [navctr.view setNeedsLayout]; -@@ -1106,17 +1106,11 @@ - (CGFloat)calculateHeaderHeightIsModal:(BOOL)isModal - { - UINavigationController *navctr = [self getVisibleNavigationControllerIsModal:isModal]; - -- // If navigation controller doesn't exists (or it is hidden) we want to handle two possible cases. -- // If there's no navigation controller for the modal, we simply don't want to return header height, as modal possibly -- // does not have header and we don't want to count status bar. If there's no navigation controller for the view we -- // just want to return status bar height (if it's hidden, it will simply return 0). -+ // If there's no navigation controller for the modal (or the navigation bar is hidden), we simply don't want to -+ // return header height, as modal possibly does not have header when navigation controller is nil, -+ // and we don't want to count status bar if navigation bar is hidden (inset could be negative). - if (navctr == nil || navctr.isNavigationBarHidden) { -- if (isModal) { -- return 0; -- } else { -- CGSize statusBarSize = [self getStatusBarHeightIsModal:isModal]; -- return MIN(statusBarSize.width, statusBarSize.height); -- } -+ return 0; - } - - CGFloat navbarHeight = navctr.navigationBar.frame.size.height; From 8cf101aadfbfc6be1c116a94838fd76b9a50b41c Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Wed, 10 Apr 2024 09:29:25 -0500 Subject: [PATCH 066/116] Revert "remove getAllReportActions from IOU" This reverts commit 2e950e30ff4750ae249ce22eb8984970574d1fc4. --- src/libs/actions/IOU.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0109f6f78be4..95fdeb5e60e0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -25,7 +25,6 @@ import type { UpdateMoneyRequestParams, } from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; -import * as CollectionUtils from '@libs/CollectionUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -254,19 +253,6 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); -const reportActionsByReport: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - callback: (actions, key) => { - if (!key || !actions) { - return; - } - - const reportID = CollectionUtils.extractCollectionItemID(key); - reportActionsByReport[reportID] = actions; - }, -}); - /** * Returns the policy of the report */ @@ -4826,7 +4812,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean { - const chatReportActions: OnyxTypes.ReportActions = reportActionsByReport?.[chatReport?.reportID ?? ''] ?? {}; + const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? ''); return Object.values(chatReportActions).some((action) => { const iouReport = ReportUtils.getReport(action.childReportID ?? ''); From 48c752e13954a5e99ff703dd94489d852eb933c8 Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Wed, 10 Apr 2024 09:35:18 -0500 Subject: [PATCH 067/116] decided to keep formatting changes --- src/libs/actions/IOU.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 95fdeb5e60e0..cdc0959402ab 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -25,6 +25,7 @@ import type { UpdateMoneyRequestParams, } from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; +import * as CollectionUtils from '@libs/CollectionUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -253,6 +254,19 @@ Onyx.connect({ callback: (value) => (allPolicies = value), }); +const reportActionsByReport: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActionsByReport[reportID] = actions; + }, +}); + /** * Returns the policy of the report */ @@ -4812,7 +4826,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean { - const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? ''); + const chatReportActions = reportActionsByReport?.[chatReport?.reportID ?? ''] ?? {}; return Object.values(chatReportActions).some((action) => { const iouReport = ReportUtils.getReport(action.childReportID ?? ''); From 291b0997297c1d783b4239a59f90ea3efa657f7d Mon Sep 17 00:00:00 2001 From: Ezra Ellette Date: Wed, 10 Apr 2024 09:46:44 -0500 Subject: [PATCH 068/116] fixes --- src/libs/ReportUtils.ts | 4 ++-- src/libs/WorkspacesSettingsUtils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9ed7f4dda863..3c78ae6c20e7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5722,8 +5722,8 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry * Checks if report contains actions with errors */ function hasActionsWithErrors(reportID: string): boolean { - const reportActions = reportActionsByReport?.[reportID]; - return Object.values(reportActions ?? {}).some((action) => !isEmptyObject(action.errors)); + const reportActions = reportActionsByReport?.[reportID ?? ''] ?? {}; + return Object.values(reportActions).some((action) => !isEmptyObject(action.errors)); } /** diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 64fa8cbc9220..995bcba06a5c 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -70,7 +70,7 @@ Onyx.connect({ * @returns BrickRoad for the policy passed as a param */ const getBrickRoadForPolicy = (report: Report): BrickRoad => { - const reportActions = reportActionsByReport?.[report.reportID]; + const reportActions = reportActionsByReport?.[report.reportID] ?? {}; const reportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); const doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; if (doesReportContainErrors) { From 9dcc78d0f363f846a4f75250b696c2ca624cf541 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 21:21:40 +0200 Subject: [PATCH 069/116] change name to MentionReportRenderer --- ...ionRoomRenderer.tsx => MentionReportRenderer.tsx} | 12 +++++------- .../HTMLEngineProvider/HTMLRenderers/index.ts | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) rename src/components/HTMLEngineProvider/HTMLRenderers/{MentionRoomRenderer.tsx => MentionReportRenderer.tsx} (89%) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx similarity index 89% rename from src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx rename to src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index f83af9f3e114..ec99a8b6eba3 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionRoomRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -30,11 +30,13 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReportID: strin let reportID: string | undefined; let mentionDisplayText: string; + // get mention details based on reportID from tag attribute if (!isEmpty(htmlAttributeReportID)) { const report = reports?.['report_'.concat(htmlAttributeReportID)]; reportID = report?.reportID ?? undefined; mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; + // get mention details from name inside tnode } else if ('data' in tnode && !isEmptyObject(tnode.data)) { mentionDisplayText = removeLeadingLTRAndHash(tnode.data); @@ -52,7 +54,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReportID: strin return {reportID, mentionDisplayText}; }; -function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { +function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const htmlAttributeReportID = tnode.attributes.reportid; @@ -96,12 +98,8 @@ function MentionRoomRenderer({style, tnode, TDefaultRenderer, reports, ...defaul ); } -MentionRoomRenderer.displayName = 'MentionRoomRenderer'; +MentionReportRenderer.displayName = 'MentionReportRenderer'; -/** - * This function 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 => (report && { reportID: report.reportID, @@ -115,4 +113,4 @@ export default withOnyx({ key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, }, -})(MentionRoomRenderer); +})(MentionReportRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index 537ce305fc4c..ce24584048b0 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -5,7 +5,7 @@ import EditedRenderer from './EditedRenderer'; import EmojiRenderer from './EmojiRenderer'; import ImageRenderer from './ImageRenderer'; import MentionHereRenderer from './MentionHereRenderer'; -import MentionRoomRenderer from './MentionRoomRenderer'; +import MentionReportRenderer from './MentionReportRenderer'; import MentionUserRenderer from './MentionUserRenderer'; import NextStepEmailRenderer from './NextStepEmailRenderer'; import PreRenderer from './PreRenderer'; @@ -26,7 +26,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { pre: PreRenderer, /* eslint-disable @typescript-eslint/naming-convention */ 'mention-user': MentionUserRenderer, - 'mention-report': MentionRoomRenderer, + 'mention-report': MentionReportRenderer, 'mention-here': MentionHereRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, From 3c6ff594c8b4ae00975f0b3a486e0413041958d6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 21:29:20 +0200 Subject: [PATCH 070/116] additional review changes (typos & variable name changes) --- src/libs/ReportUtils.ts | 4 ++-- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- .../home/report/ReportActionCompose/SuggestionMention.tsx | 4 ++-- src/pages/home/report/ReportActionCompose/Suggestions.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index df6e4c748553..9247b61c175c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5752,8 +5752,8 @@ function getOutstandingChildRequest(iouReport: OnyxEntry | EmptyObject): return {}; } -function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyId: string): boolean { - if (report?.policyID !== policyId) { +function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: string): boolean { + if (report?.policyID !== policyID) { return false; } diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 072a55ddb111..7c130bb445f6 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -172,7 +172,7 @@ type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC parentReportID: string | undefined; - /** Whether chat is a reoprt from group policy */ + /** Whether report is from group policy */ isGroupPolicyReport: boolean; /** policy ID of the report */ diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 6118ef4f8139..65db300a95f2 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -332,8 +332,8 @@ function SuggestionMention( nextState.shouldShowSuggestionMenu = !!suggestions.length; } - const shouldDisplayMenetionsSuggestions = isGroupPolicyReport && (isValidRoomName(suggestionWord.toLowerCase()) || prefix === ''); - if (!isCursorBeforeTheMention && prefixType === '#' && shouldDisplayMenetionsSuggestions) { + const shouldDisplayRoomMentionsSuggestions = isGroupPolicyReport && (isValidRoomName(suggestionWord.toLowerCase()) || prefix === ''); + if (!isCursorBeforeTheMention && prefixType === '#' && shouldDisplayRoomMentionsSuggestions) { // filter reports by room name and current policy const filteredRoomMentions = getRoomMentionOptions(prefix, reports); nextState.suggestedMentions = filteredRoomMentions; diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index d1abe0e3880c..87f975bdc30e 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -47,7 +47,7 @@ type SuggestionProps = { /** The height of the composer */ composerHeight?: number; - /** if current composer is connected with report from group policy */ + /** If current composer is connected with report from group policy */ isGroupPolicyReport: boolean; /** policy ID connected to current composer */ From 5dfe2b58834f763661d6b9ff4b5cbeb73670bc00 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 21:32:12 +0200 Subject: [PATCH 071/116] Update update policyID comment Co-authored-by: Puneet Lath --- src/pages/home/report/ReportActionCompose/Suggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index 87f975bdc30e..d7f745145c5e 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -50,7 +50,7 @@ type SuggestionProps = { /** If current composer is connected with report from group policy */ isGroupPolicyReport: boolean; - /** policy ID connected to current composer */ + /** The policyID of the report connected to current composer */ policyID?: string; }; From 1af1b5819552beafdaa1aee347898d49aab2e0e3 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 21:42:06 +0200 Subject: [PATCH 072/116] run prettier --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- src/pages/home/report/ReportActionCompose/Suggestions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 7c130bb445f6..8f42da5a1575 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -172,7 +172,7 @@ type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC parentReportID: string | undefined; - /** Whether report is from group policy */ + /** Whether report is from group policy */ isGroupPolicyReport: boolean; /** policy ID of the report */ diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index d7f745145c5e..8ebd52f62428 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -47,7 +47,7 @@ type SuggestionProps = { /** The height of the composer */ composerHeight?: number; - /** If current composer is connected with report from group policy */ + /** If current composer is connected with report from group policy */ isGroupPolicyReport: boolean; /** The policyID of the report connected to current composer */ From 3aa0c5d585faf5d9ff4ac0ad43035857394214e5 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 21:47:13 +0200 Subject: [PATCH 073/116] prettier --- .../HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index ec99a8b6eba3..1a0b6855f386 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -36,7 +36,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReportID: strin reportID = report?.reportID ?? undefined; mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; - // get mention details from name inside tnode + // get mention details from name inside tnode } else if ('data' in tnode && !isEmptyObject(tnode.data)) { mentionDisplayText = removeLeadingLTRAndHash(tnode.data); From e6082c2b75fb883125cdfc1ce8b06ea311a13ebd Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 11 Apr 2024 10:46:09 +0200 Subject: [PATCH 074/116] feat: migrate ProfileAvatar to TS --- .../sidebar/ProfileAvatarWithIndicator.js | 63 ------------------- .../sidebar/ProfileAvatarWithIndicator.tsx | 44 +++++++++++++ 2 files changed, 44 insertions(+), 63 deletions(-) delete mode 100644 src/pages/home/sidebar/ProfileAvatarWithIndicator.js create mode 100644 src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.js b/src/pages/home/sidebar/ProfileAvatarWithIndicator.js deleted file mode 100644 index bd9c01aba001..000000000000 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.js +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import AvatarWithIndicator from '@components/AvatarWithIndicator'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as UserUtils from '@libs/UserUtils'; -import personalDetailsPropType from '@pages/personalDetailsPropType'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const propTypes = { - /** The personal details of the person who is logged in */ - currentUserPersonalDetails: personalDetailsPropType, - - /** Indicates whether the app is loading initial data */ - isLoading: PropTypes.bool, - - /** Whether the avatar is selected */ - isSelected: PropTypes.bool, -}; - -const defaultProps = { - currentUserPersonalDetails: { - pendingFields: {avatar: ''}, - accountID: '', - avatar: '', - }, - isLoading: true, - isSelected: false, -}; - -function ProfileAvatarWithIndicator({currentUserPersonalDetails, isLoading, isSelected}) { - const styles = useThemeStyles(); - - return ( - - - - - - ); -} - -ProfileAvatarWithIndicator.propTypes = propTypes; -ProfileAvatarWithIndicator.defaultProps = defaultProps; -ProfileAvatarWithIndicator.displayName = 'ProfileAvatarWithIndicator'; -export default compose( - withCurrentUserPersonalDetails, - withOnyx({ - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, - }), -)(ProfileAvatarWithIndicator); diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx new file mode 100644 index 000000000000..cd5d243a1fa4 --- /dev/null +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import AvatarWithIndicator from '@components/AvatarWithIndicator'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as UserUtils from '@libs/UserUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type ProfileAvatarWithIndicatorOnyxProps = { + /** Indicates whether the app is loading initial data */ + isLoading: OnyxEntry; +}; + +type ProfileAvatarWithIndicatorProps = ProfileAvatarWithIndicatorOnyxProps & { + /** Whether the avatar is selected */ + isSelected: boolean; +}; + +function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: ProfileAvatarWithIndicatorProps) { + const styles = useThemeStyles(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + return ( + + + + + + ); +} + +ProfileAvatarWithIndicator.displayName = 'ProfileAvatarWithIndicator'; +export default withOnyx({ + isLoading: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(ProfileAvatarWithIndicator); From 21d7f6f4ff4d5b8db9678ff9d4e8e84810718f11 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 11 Apr 2024 10:47:54 +0200 Subject: [PATCH 075/116] fix: formatting --- src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index cd5d243a1fa4..217b9db69e63 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -37,6 +37,7 @@ function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: Prof } ProfileAvatarWithIndicator.displayName = 'ProfileAvatarWithIndicator'; + export default withOnyx({ isLoading: { key: ONYXKEYS.IS_LOADING_APP, From 5707e34f8d185d090a2b90d4f989e4a21e204fd5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 11 Apr 2024 10:57:10 +0200 Subject: [PATCH 076/116] fix: resolve comments --- src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index 217b9db69e63..838e990b1be9 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -16,7 +16,7 @@ type ProfileAvatarWithIndicatorOnyxProps = { type ProfileAvatarWithIndicatorProps = ProfileAvatarWithIndicatorOnyxProps & { /** Whether the avatar is selected */ - isSelected: boolean; + isSelected?: boolean; }; function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: ProfileAvatarWithIndicatorProps) { @@ -24,7 +24,7 @@ function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: Prof const currentUserPersonalDetails = useCurrentUserPersonalDetails(); return ( - + Date: Thu, 11 Apr 2024 10:57:50 +0200 Subject: [PATCH 077/116] cast boolean --- src/pages/iou/request/IOURequestStartPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 7d29ca7e6468..6c69598893c5 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -98,7 +98,7 @@ function IOURequestStartPage({ const isExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isExpenseReport = ReportUtils.isExpenseReport(report); - const shouldDisplayDistanceRequest = canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate; + const shouldDisplayDistanceRequest = !!canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate; // Allow the user to create the request if we are creating the request in global menu or the report can create the request const isAllowedToCreateRequest = isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); From 759a751ff8c88962d23bf7013b73a637aaa7124c Mon Sep 17 00:00:00 2001 From: rmm-fl <143930462+rmm-fl@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:49:40 +0530 Subject: [PATCH 078/116] Handle max length for markdown description --- src/pages/workspace/WorkspaceNewRoomPage.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 06ad1d2943c9..46a5b533372a 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -183,6 +183,14 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli ErrorUtils.addErrorMessage(errors, 'roomName', ['common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT}]); } + const descriptionLength = ReportUtils.getCommentLength(values.reportDescription); + if (descriptionLength > CONST.REPORT_DESCRIPTION.MAX_LENGTH) { + ErrorUtils.addErrorMessage(errors, 'reportDescription', [ + 'common.error.characterLimitExceedCounter', + {length: descriptionLength, limit: CONST.REPORT_DESCRIPTION.MAX_LENGTH}, + ]); + } + if (!values.policyID) { errors.policyID = 'newRoomPage.pleaseSelectWorkspace'; } From c055d060f8a9b313b47105ce83793eab0dd0f2f3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 Apr 2024 18:51:49 +0800 Subject: [PATCH 079/116] migrate to ts --- src/libs/Navigation/types.ts | 8 ++ ...Currency.js => IOURequestStepCurrency.tsx} | 101 +++++++----------- .../step/withFullTransactionOrNotFound.tsx | 3 +- 3 files changed, 48 insertions(+), 64 deletions(-) rename src/pages/iou/request/step/{IOURequestStepCurrency.js => IOURequestStepCurrency.tsx} (63%) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 4a9850e1f625..c768f3b9c07f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -454,6 +454,14 @@ type MoneyRequestNavigatorParamList = { pageIndex?: string; backTo?: string; }; + [SCREENS.MONEY_REQUEST.STEP_CURRENCY]: { + action: ValueOf; + iouType: ValueOf; + transactionID: string; + reportID: string; + pageIndex?: string; + backTo?: Routes; + }; }; type NewTaskNavigatorParamList = { diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.tsx similarity index 63% rename from src/pages/iou/request/step/IOURequestStepCurrency.js rename to src/pages/iou/request/step/IOURequestStepCurrency.tsx index 51dba5858cb5..88683abda8e8 100644 --- a/src/pages/iou/request/step/IOURequestStepCurrency.js +++ b/src/pages/iou/request/step/IOURequestStepCurrency.tsx @@ -1,15 +1,12 @@ import Str from 'expensify-common/lib/str'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useMemo, useRef, useState} from 'react'; +import React, {useMemo, useState} from 'react'; import {Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; -import transactionPropTypes from '@components/transactionPropTypes'; +import type {ListItem} from '@components/SelectionList/types'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -17,39 +14,26 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES, {getUrlWithBackToParam} from '@src/ROUTES'; -import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import type {Route} from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {CurrencyList, Transaction} from '@src/types/onyx'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; +import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; -/** - * IOU Currency selection for selecting currency - */ -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, +type IOURequestStepCurrencyOnyxProps = { + /** Constant, list of available currencies */ + currencyList: OnyxEntry; - /** The currency list constant object from Onyx */ - currencyList: PropTypes.objectOf( - PropTypes.shape({ - /** Symbol for the currency */ - symbol: PropTypes.string, - - /** Name of the currency */ - name: PropTypes.string, - - /** ISO4217 Code for the currency */ - ISO4217: PropTypes.string, - }), - ), - - /* Onyx Props */ /** The draft transaction object being modified in Onyx */ - draftTransaction: transactionPropTypes, + draftTransaction: OnyxEntry; }; -const defaultProps = { - currencyList: {}, - draftTransaction: {}, +type IOURequestStepCurrencyProps = IOURequestStepCurrencyOnyxProps & WithFullTransactionOrNotFoundProps; + +type CurrencyListItem = ListItem & { + currencyName: string; + currencyCode: string; }; function IOURequestStepCurrency({ @@ -58,11 +42,10 @@ function IOURequestStepCurrency({ params: {backTo, iouType, pageIndex, reportID, transactionID, action}, }, draftTransaction, -}) { +}: IOURequestStepCurrencyProps) { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); - const optionsSelectorRef = useRef(); - const {currency} = ReportUtils.getTransactionDetails(draftTransaction); + const {currency = ''} = ReportUtils.getTransactionDetails(draftTransaction) ?? {}; const navigateBack = () => { // If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency) @@ -71,30 +54,26 @@ function IOURequestStepCurrency({ // to the confirmation page if (pageIndex === 'confirm') { const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam( - backTo, + backTo as string, `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)}`, ); - Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo); + Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo as Route); return; } Navigation.goBack(backTo); }; - /** - * @param {Object} option - * @param {String} options.currencyCode - */ - const confirmCurrencySelection = (option) => { + const confirmCurrencySelection = (option: CurrencyListItem) => { Keyboard.dismiss(); IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode, false, action === CONST.IOU.ACTION.EDIT); navigateBack(); }; const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => { - const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { + const currencyOptions: CurrencyListItem[] = Object.entries(currencyList ?? {}).map(([currencyCode, currencyInfo]) => { const isSelectedCurrency = currencyCode === currency.toUpperCase(); return { - currencyName: currencyInfo.name, + currencyName: currencyInfo?.name ?? '', text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, currencyCode, keyForList: currencyCode, @@ -103,14 +82,11 @@ function IOURequestStepCurrency({ }); const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); - const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); + const filteredCurrencies = currencyOptions.filter((currencyOption) => searchRegex.test(currencyOption.text ?? '') || searchRegex.test(currencyOption.currencyName)); const isEmpty = searchValue.trim() && !filteredCurrencies.length; return { - initiallyFocusedOptionKey: _.get( - _.find(filteredCurrencies, (filteredCurrency) => filteredCurrency.currencyCode === currency.toUpperCase()), - 'keyForList', - ), + initiallyFocusedOptionKey: filteredCurrencies.find((filteredCurrency) => filteredCurrency.currencyCode === currency.toUpperCase())?.keyForList, sections: isEmpty ? [] : [ @@ -126,7 +102,6 @@ function IOURequestStepCurrency({ optionsSelectorRef.current && optionsSelectorRef.current.focus()} shouldShowWrapper testID={IOURequestStepCurrency.displayName} > @@ -153,18 +128,18 @@ function IOURequestStepCurrency({ } IOURequestStepCurrency.displayName = 'IOURequestStepCurrency'; -IOURequestStepCurrency.propTypes = propTypes; -IOURequestStepCurrency.defaultProps = defaultProps; -export default compose( - withFullTransactionOrNotFound, - withOnyx({ - currencyList: {key: ONYXKEYS.CURRENCY_LIST}, - draftTransaction: { - key: ({route}) => { - const transactionID = lodashGet(route, 'params.transactionID', 0); - return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; - }, +const IOURequestStepCurrencyWithOnyx = withOnyx({ + currencyList: {key: ONYXKEYS.CURRENCY_LIST}, + draftTransaction: { + key: ({route}) => { + const transactionID = route?.params?.transactionID ?? 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; }, - }), -)(IOURequestStepCurrency); + }, +})(IOURequestStepCurrency); + +/* eslint-disable rulesdir/no-negated-variables */ +const IOURequestStepCurrencyWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepCurrencyWithOnyx); + +export default IOURequestStepCurrencyWithFullTransactionOrNotFound; diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 3d741725032b..18b31109a4a7 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -26,7 +26,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_TAG | typeof SCREENS.MONEY_REQUEST.STEP_CONFIRMATION | typeof SCREENS.MONEY_REQUEST.STEP_CATEGORY - | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE; + | typeof SCREENS.MONEY_REQUEST.STEP_TAX_RATE + | typeof SCREENS.MONEY_REQUEST.STEP_CURRENCY; type Route = RouteProp; From 90647ddeac53f5d77db7ca6b2c94eabd721fa6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 11 Apr 2024 15:18:34 +0200 Subject: [PATCH 080/116] comment the QR code download button until it can be fixed --- src/pages/ShareCodePage.tsx | 10 +++++++--- src/pages/workspace/WorkspaceProfileSharePage.tsx | 12 ++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 4f1bac01b556..65af93c28fb4 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -76,6 +76,10 @@ function ShareCodePage({report}: ShareCodePageProps) { /> + {/* + This is a temporary measure because right now it's broken because of the Fabric update. + We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + + /> */} @@ -98,7 +102,7 @@ function ShareCodePage({report}: ShareCodePageProps) { shouldLimitWidth={false} /> - {isNative && ( + {/* {isNative && ( qrCodeRef.current?.download?.()} /> - )} + )} */} - + /> */} @@ -72,7 +76,7 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { shouldLimitWidth={false} wrapperStyle={themeStyles.sectionMenuItemTopDescription} /> - {shouldAllowDownloadQRCode && ( + {/* {shouldAllowDownloadQRCode && ( qrCodeRef.current?.download?.()} wrapperStyle={themeStyles.sectionMenuItemTopDescription} /> - )} + )} */} From f8deb981da2798f05b8d848d6d122a16df78380a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 11 Apr 2024 15:35:00 +0200 Subject: [PATCH 081/116] add GH to comment to add back the download button as soon as it's possible --- src/pages/ShareCodePage.tsx | 3 ++- src/pages/workspace/WorkspaceProfileSharePage.tsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 65af93c28fb4..bb3faa94165b 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -79,7 +79,8 @@ function ShareCodePage({report}: ShareCodePageProps) { {/* This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. + Date: Thu, 11 Apr 2024 15:48:51 +0200 Subject: [PATCH 082/116] fix lint --- src/pages/ShareCodePage.tsx | 56 ++++++++++--------- .../workspace/WorkspaceProfileSharePage.tsx | 22 ++++---- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index bb3faa94165b..77dc23c0def8 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,14 +1,14 @@ -import React, {useMemo, useRef} from 'react'; +import React from 'react'; import {View} from 'react-native'; -import type {ImageSourcePropType} from 'react-native'; +// import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; +// import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; -import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; -import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; +// import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; +// import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -16,11 +16,11 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; -import getPlatform from '@libs/getPlatform'; +// import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +// import * as ReportUtils from '@libs/ReportUtils'; import * as Url from '@libs/Url'; -import * as UserUtils from '@libs/UserUtils'; +// import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; @@ -36,36 +36,36 @@ function ShareCodePage({report}: ShareCodePageProps) { const themeStyles = useThemeStyles(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - const qrCodeRef = useRef(null); + // const qrCodeRef = useRef(null); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isReport = !!report?.reportID; - const subtitle = useMemo(() => { - if (isReport) { - if (ReportUtils.isExpenseReport(report)) { - return ReportUtils.getPolicyName(report); - } - if (ReportUtils.isMoneyRequestReport(report)) { - // generate subtitle from participants - return ReportUtils.getVisibleMemberIDs(report) - .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) - .join(' & '); - } + // const subtitle = useMemo(() => { + // if (isReport) { + // if (ReportUtils.isExpenseReport(report)) { + // return ReportUtils.getPolicyName(report); + // } + // if (ReportUtils.isMoneyRequestReport(report)) { + // // generate subtitle from participants + // return ReportUtils.getVisibleMemberIDs(report) + // .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) + // .join(' & '); + // } - return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); - } + // return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); + // } - return currentUserPersonalDetails.login; - }, [report, currentUserPersonalDetails, isReport]); + // return currentUserPersonalDetails.login; + // }, [report, currentUserPersonalDetails, isReport]); - const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; + // const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID ?? '')}`; - const platform = getPlatform(); - const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; + // const platform = getPlatform(); + // const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( @@ -79,6 +79,8 @@ function ShareCodePage({report}: ShareCodePageProps) { {/* This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + That's why you see parts of the code commented. + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. (null); + // const qrCodeRef = useRef(null); const {isSmallScreenWidth} = useWindowDimensions(); const session = useSession(); - const policyName = policy?.name ?? ''; + // const policyName = policy?.name ?? ''; const id = policy?.id ?? ''; const adminEmail = session?.email ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); @@ -54,6 +54,8 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { {/* This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + That's why you see parts of the code commented. + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. Date: Thu, 11 Apr 2024 20:56:58 +0700 Subject: [PATCH 083/116] fix remove hasBrickError --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index ce244d406640..15aa4bb6dbb9 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -97,7 +97,7 @@ function getOrderedReportIDs( const hasErrors = Object.keys(allReportErrors).length !== 0; const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); - const shouldOverrideHidden = (hasBrickError && hasErrorsOtherThanFailedReceipt) || isFocused || report.isPinned; + const shouldOverrideHidden = hasErrorsOtherThanFailedReceipt || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { return false; } From dbd8a9c0addd96ec0d91d7a6e0b5489db6876406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 11 Apr 2024 15:58:18 +0200 Subject: [PATCH 084/116] delete unused code --- src/pages/ShareCodePage.tsx | 30 ------------------- .../workspace/WorkspaceProfileSharePage.tsx | 10 ------- 2 files changed, 40 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 77dc23c0def8..d0d832b2b615 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,14 +1,10 @@ import React from 'react'; import {View} from 'react-native'; -// import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -// import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; -// import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; -// import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -16,11 +12,8 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; -// import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; -// import * as ReportUtils from '@libs/ReportUtils'; import * as Url from '@libs/Url'; -// import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; @@ -36,36 +29,14 @@ function ShareCodePage({report}: ShareCodePageProps) { const themeStyles = useThemeStyles(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - // const qrCodeRef = useRef(null); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isReport = !!report?.reportID; - // const subtitle = useMemo(() => { - // if (isReport) { - // if (ReportUtils.isExpenseReport(report)) { - // return ReportUtils.getPolicyName(report); - // } - // if (ReportUtils.isMoneyRequestReport(report)) { - // // generate subtitle from participants - // return ReportUtils.getVisibleMemberIDs(report) - // .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) - // .join(' & '); - // } - - // return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); - // } - - // return currentUserPersonalDetails.login; - // }, [report, currentUserPersonalDetails, isReport]); - - // const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID ?? '')}`; - // const platform = getPlatform(); - // const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( @@ -79,7 +50,6 @@ function ShareCodePage({report}: ShareCodePageProps) { {/* This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - That's why you see parts of the code commented. Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. diff --git a/src/pages/workspace/WorkspaceProfileSharePage.tsx b/src/pages/workspace/WorkspaceProfileSharePage.tsx index 5d7cc9b3e58d..0e81df7cb40b 100644 --- a/src/pages/workspace/WorkspaceProfileSharePage.tsx +++ b/src/pages/workspace/WorkspaceProfileSharePage.tsx @@ -1,14 +1,9 @@ import React from 'react'; import {View} from 'react-native'; -// import type {ImageSourcePropType} from 'react-native'; -// import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; -// import MenuItem from '@components/MenuItem'; import {useSession} from '@components/OnyxProvider'; -// import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; -// import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useEnvironment from '@hooks/useEnvironment'; @@ -17,9 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Clipboard from '@libs/Clipboard'; import Navigation from '@libs/Navigation/Navigation'; -// import shouldAllowDownloadQRCode from '@libs/shouldAllowDownloadQRCode'; import * as Url from '@libs/Url'; -// import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import withPolicy from './withPolicy'; import type {WithPolicyProps} from './withPolicy'; @@ -28,11 +21,9 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { const themeStyles = useThemeStyles(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - // const qrCodeRef = useRef(null); const {isSmallScreenWidth} = useWindowDimensions(); const session = useSession(); - // const policyName = policy?.name ?? ''; const id = policy?.id ?? ''; const adminEmail = session?.email ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); @@ -54,7 +45,6 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { {/* This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - That's why you see parts of the code commented. Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. From f9874383ea9c029f3beb7d0bd0c84c136ddbbf60 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 11 Apr 2024 20:58:40 +0700 Subject: [PATCH 085/116] fix lint --- src/libs/SidebarUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 15aa4bb6dbb9..5a6e31a56e58 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -94,8 +94,6 @@ function getOrderedReportIDs( const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; - const hasErrors = Object.keys(allReportErrors).length !== 0; - const hasBrickError = hasErrors || doesReportHaveViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = hasErrorsOtherThanFailedReceipt || isFocused || report.isPinned; if (isHidden && !shouldOverrideHidden) { From 67d6942cc489509b18c7a0bfbef6620c99c35018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 11 Apr 2024 16:24:26 +0200 Subject: [PATCH 086/116] only keep comment --- src/pages/ShareCodePage.tsx | 26 ++------------- .../workspace/WorkspaceProfileSharePage.tsx | 32 ++++--------------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index d0d832b2b615..0a3b421e802f 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -46,23 +46,13 @@ function ShareCodePage({report}: ShareCodePageProps) { shouldShowBackButton /> - - {/* + {/* + Right now QR code download button is not shown anymore This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - - */} - + */} - {/* {isNative && ( - qrCodeRef.current?.download?.()} - /> - )} */} - - - {/* - This is a temporary measure because right now it's broken because of the Fabric update. - We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - - Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - - */} - + {/* + Right now QR code download button is not shown anymore + This is a temporary measure because right now it's broken because of the Fabric update. + We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. + */} - {/* {shouldAllowDownloadQRCode && ( - qrCodeRef.current?.download?.()} - wrapperStyle={themeStyles.sectionMenuItemTopDescription} - /> - )} */} From 1d978c3657195ea398208bc2496eb3951da7a46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 11 Apr 2024 16:35:12 +0200 Subject: [PATCH 087/116] fix prettier --- src/pages/ShareCodePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 0a3b421e802f..ce2d8e005e4d 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -46,7 +46,7 @@ function ShareCodePage({report}: ShareCodePageProps) { shouldShowBackButton /> - {/* + {/* Right now QR code download button is not shown anymore This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. From 5fa6e389986301297686b79d0925a8a017971455 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:48:39 +0200 Subject: [PATCH 088/116] fix types --- .../iou/request/step/IOURequestStepDistance.tsx | 2 +- .../step/IOURequestStepScan/index.native.tsx | 12 +----------- .../iou/request/step/IOURequestStepScan/index.tsx | 14 ++------------ .../iou/request/step/IOURequestStepScan/types.ts | 10 +++++++++- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 4e91c86ce47f..13d6f9360a84 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -128,7 +128,7 @@ function IOURequestStepDistance({ if (transactionWasSaved.current) { return; } - TransactionEdit.restoreOriginalTransactionFromBackup(lodashGet(transaction, 'transactionID', ''), action === CONST.IOU.ACTION.CREATE); + TransactionEdit.restoreOriginalTransactionFromBackup(transaction?.transactionID ?? '', action === CONST.IOU.ACTION.CREATE); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index e084a3db7422..c1b360f89e48 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -3,7 +3,6 @@ import React, {useCallback, useRef, useState} from 'react'; import {ActivityIndicator, Alert, AppState, InteractionManager, View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; import {RESULTS} from 'react-native-permissions'; import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withDelay, withSequence, withSpring, withTiming} from 'react-native-reanimated'; import type {Camera, PhotoFile, Point} from 'react-native-vision-camera'; @@ -26,23 +25,14 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; -import type {WithWritableReportOrNotFoundProps} from '@pages/iou/request/step/withWritableReportOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; import CameraPermission from './CameraPermission'; import NavigationAwareCamera from './NavigationAwareCamera'; -import type IOURequestStepOnyxProps from './types'; - -type IOURequestStepScanProps = IOURequestStepOnyxProps & - WithWritableReportOrNotFoundProps & { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - transaction: OnyxEntry; - }; +import type {IOURequestStepOnyxProps, IOURequestStepScanProps} from './types'; function IOURequestStepScan({ report, diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index b9c4f866d493..995e52c67fc0 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import type Webcam from 'react-webcam'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; @@ -26,23 +25,14 @@ import Navigation from '@libs/Navigation/Navigation'; import ReceiptDropUI from '@pages/iou/ReceiptDropUI'; import StepScreenDragAndDropWrapper from '@pages/iou/request/step/StepScreenDragAndDropWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; -import type {WithWritableReportOrNotFoundProps} from '@pages/iou/request/step/withWritableReportOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import NavigationAwareCamera from './NavigationAwareCamera'; -import type IOURequestStepOnyxProps from './types'; - -type IOURequestStepScanProps = IOURequestStepOnyxProps & - WithWritableReportOrNotFoundProps & { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - transaction: OnyxEntry; - }; +import type {IOURequestStepScanProps} from './types'; function IOURequestStepScan({ report, @@ -50,7 +40,7 @@ function IOURequestStepScan({ params: {action, iouType, reportID, transactionID, backTo}, }, transaction, -}: IOURequestStepScanProps) { +}: Omit) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/pages/iou/request/step/IOURequestStepScan/types.ts b/src/pages/iou/request/step/IOURequestStepScan/types.ts index adf3e5c81748..60af94aca12e 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/types.ts +++ b/src/pages/iou/request/step/IOURequestStepScan/types.ts @@ -1,8 +1,16 @@ import type {OnyxEntry} from 'react-native-onyx'; +import type {WithWritableReportOrNotFoundProps} from '@pages/iou/request/step/withWritableReportOrNotFound'; +import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; type IOURequestStepOnyxProps = { user: OnyxEntry; }; -export default IOURequestStepOnyxProps; +type IOURequestStepScanProps = IOURequestStepOnyxProps & + WithWritableReportOrNotFoundProps & { + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + transaction: OnyxEntry; + }; + +export type {IOURequestStepOnyxProps, IOURequestStepScanProps}; From 8836cf2ae68317820098d2a127a3d4947f0d1bd1 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 11 Apr 2024 20:29:18 +0200 Subject: [PATCH 089/116] generalize mention type to be less user specific --- src/components/MentionSuggestions.tsx | 8 ++++---- .../home/report/ReportActionCompose/SuggestionMention.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index 8108929fc451..ecb64fd3a0b8 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -12,14 +12,14 @@ import Avatar from './Avatar'; import Text from './Text'; type Mention = { - /** Display name of the user */ + /** Display name of the mention */ text: string; - /** The formatted email/phone number of the user */ + /** The formatted text of the mention */ alternateText: string; - /** Email/phone number of the user */ - login?: string; + /** handle of the mention */ + handle?: string; /** Array of icons of the user. If present, we use the first element of this array. For room suggestions, the icons are not used */ icons?: Icon[]; diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 65db300a95f2..604799ab9e1e 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -110,9 +110,9 @@ function SuggestionMention( (mention: Mention, mentionType: string): string => { if (mentionType === '#') { // room mention case - return mention.login ?? ''; + return mention.handle ?? ''; } - return mention.text === CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT ? CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT : `@${formatLoginPrivateDomain(mention.login, mention.login)}`; + return mention.text === CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT ? CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT : `@${formatLoginPrivateDomain(mention.handle, mention.handle)}`; }, [formatLoginPrivateDomain], ); @@ -261,7 +261,7 @@ function SuggestionMention( if (report?.reportName?.toLowerCase().includes(searchTerm.toLowerCase())) { filteredRoomMentions.push({ text: report.reportName, - login: report.reportName, + handle: report.reportName, alternateText: report.reportName, }); } From f8345309b4d0c59dff50789813611af771de8283 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 11 Apr 2024 23:13:35 +0200 Subject: [PATCH 090/116] sort room mentions --- .../home/report/ReportActionCompose/SuggestionMention.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 604799ab9e1e..0b22a318bc3e 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -266,7 +266,8 @@ function SuggestionMention( }); } }); - return filteredRoomMentions.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS); + + return lodashSortBy(filteredRoomMentions, 'handle').slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS); }, [policyID], ); From 45cd559d2a4af67b8610a87bb472840da087cf80 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 12 Apr 2024 01:10:57 +0200 Subject: [PATCH 091/116] prettier --- .../home/report/ReportActionCompose/SuggestionMention.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 0b22a318bc3e..147799ee118b 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -266,8 +266,8 @@ function SuggestionMention( }); } }); - - return lodashSortBy(filteredRoomMentions, 'handle').slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS); + + return lodashSortBy(filteredRoomMentions, 'handle').slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS); }, [policyID], ); From e615d8ed789f37cf7c49c931c1a245e15d3b3100 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 12 Apr 2024 10:43:59 +0700 Subject: [PATCH 092/116] update ref whenever rendering --- src/pages/home/report/ReportActionsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 62f6dc77034d..361cedd56354 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -198,6 +198,7 @@ function ReportActionsList({ const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedReportActions?.[0].created === report.lastVisibleActionCreated; const hasNewestReportActionRef = useRef(hasNewestReportAction); + hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -226,7 +227,6 @@ function ReportActionsList({ } previousLastIndex.current = lastActionIndex; reportActionSize.current = sortedVisibleReportActions.length; - hasNewestReportActionRef.current = hasNewestReportAction; }, [lastActionIndex, sortedVisibleReportActions, reportScrollManager, hasNewestReportAction, linkedReportActionID]); useEffect(() => { From c7d692662d069ef8d9c756833767c47e1f7410a2 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 12 Apr 2024 07:07:57 +0000 Subject: [PATCH 093/116] Update version to 1.4.62-4 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2a5fd87db3e4..aaf2dc81b3f6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046203 - versionName "1.4.62-3" + versionCode 1001046204 + versionName "1.4.62-4" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f7cff60e90fd..8b25084df439 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.62.3 + 1.4.62.4 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index f7e41253a922..96d8c25d570c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.62.3 + 1.4.62.4 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index a215a2d9f43b..0781526824b3 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.62 CFBundleVersion - 1.4.62.3 + 1.4.62.4 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 615f94ad6092..0076cd9c1744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.62-3", + "version": "1.4.62-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.62-3", + "version": "1.4.62-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e089d874b71a..86ec27b2046b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.62-3", + "version": "1.4.62-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 24e637dfeb096e87c1d6494cb49255bbe4b8e7fe Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:14:53 +0200 Subject: [PATCH 094/116] fix issue --- src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 13d6f9360a84..fae07ad2249c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -197,7 +197,7 @@ function IOURequestStepDistance({ const newWaypoints: WaypointCollection = {}; let emptyWaypointIndex = -1; data.forEach((waypoint, index) => { - newWaypoints[`waypoint${index}`] = waypoints.waypoint ?? {}; + newWaypoints[`waypoint${index}`] = waypoints[waypoint] ?? {}; // Find waypoint that BECOMES empty after dragging if (isEmpty(newWaypoints[`waypoint${index}`]) && !isEmpty(waypoints[`waypoint${index}`] ?? {})) { emptyWaypointIndex = index; From 192aa7fc15e2095df6aed025cf0864526ee4a425 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 10:20:11 +0200 Subject: [PATCH 095/116] unpausing the queue --- src/libs/actions/OnyxUpdateManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index 40f522e215b5..88276d127aee 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -46,6 +46,7 @@ export default () => { if (isLoadingApp) { console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); } + SequentialQueue.unpause(); return; } // This key is shared across clients, thus every client/tab will have a copy and try to execute this method. From a0cd3d781caf5c8cab3cce216d31c3073785eeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 12 Apr 2024 10:39:37 +0200 Subject: [PATCH 096/116] Change prop name --- src/components/TextInput/BaseTextInput/index.native.tsx | 4 ++-- src/components/TextInput/BaseTextInput/index.tsx | 4 ++-- src/components/TextInput/BaseTextInput/types.ts | 2 +- src/pages/tasks/NewTaskDescriptionPage.tsx | 2 +- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 9b25fbbba60e..3039d7327d37 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -58,12 +58,12 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, - markdownEnabled = false, + isMarkdownEnabled = false, ...props }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = markdownEnabled ? RNMarkdownTextInput : RNTextInput; + const InputComponent = isMarkdownEnabled ? RNMarkdownTextInput : RNTextInput; const inputProps = {shouldSaveDraft: false, shouldUseDefaultValue: false, ...props}; const theme = useTheme(); diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 02f6a383cce0..519a52fd85ec 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -60,12 +60,12 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, - markdownEnabled = false, + isMarkdownEnabled = false, ...inputProps }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = markdownEnabled ? RNMarkdownTextInput : RNTextInput; + const InputComponent = isMarkdownEnabled ? RNMarkdownTextInput : RNTextInput; const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index c38f38a1697d..1529fbe4c7c6 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -106,7 +106,7 @@ type CustomBaseTextInputProps = { autoCompleteType?: string; /** Should live markdown be enabled. Changes RNTextInput component to RNMarkdownTextInput */ - markdownEnabled?: boolean; + isMarkdownEnabled?: boolean; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index 13708267fc8b..e33241c52f4a 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -89,7 +89,7 @@ function NewTaskDescriptionPage({task}: NewTaskDescriptionPageProps) { autoGrowHeight shouldSubmitForm containerStyles={styles.autoGrowHeightMultilineInput} - markdownEnabled + isMarkdownEnabled /> diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 4c16734ae0e7..9e857b22c5c4 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -117,7 +117,7 @@ function NewTaskDetailsPage({task}: NewTaskDetailsPageProps) { defaultValue={parser.htmlToMarkdown(parser.replace(taskDescription))} value={taskDescription} onValueChange={setTaskDescription} - markdownEnabled + isMarkdownEnabled /> diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index 24ea13e9987c..9abe15a5bb80 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -120,7 +120,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti autoGrowHeight shouldSubmitForm containerStyles={[styles.autoGrowHeightMultilineInput]} - markdownEnabled + isMarkdownEnabled /> From c38e38c0aa8feb02c7e753fcfd5cda52ea512602 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 12 Apr 2024 10:44:30 +0200 Subject: [PATCH 097/116] feat: use useOnyx instead of withOnyx --- .../sidebar/ProfileAvatarWithIndicator.tsx | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index 838e990b1be9..e7726fb89537 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -1,7 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -9,19 +8,16 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -type ProfileAvatarWithIndicatorOnyxProps = { - /** Indicates whether the app is loading initial data */ - isLoading: OnyxEntry; -}; - -type ProfileAvatarWithIndicatorProps = ProfileAvatarWithIndicatorOnyxProps & { +type ProfileAvatarWithIndicatorProps = { /** Whether the avatar is selected */ isSelected?: boolean; }; -function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: ProfileAvatarWithIndicatorProps) { +function ProfileAvatarWithIndicator({isSelected = false}: ProfileAvatarWithIndicatorProps) { const styles = useThemeStyles(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const [isLoadingOnyxValue] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isLoading = isLoadingOnyxValue ?? true; return ( @@ -29,7 +25,7 @@ function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: Prof @@ -38,8 +34,4 @@ function ProfileAvatarWithIndicator({isLoading = true, isSelected = false}: Prof ProfileAvatarWithIndicator.displayName = 'ProfileAvatarWithIndicator'; -export default withOnyx({ - isLoading: { - key: ONYXKEYS.IS_LOADING_APP, - }, -})(ProfileAvatarWithIndicator); +export default ProfileAvatarWithIndicator; From 9a2d4e14f61f83a041997435a090a1a70fbd4a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 12 Apr 2024 12:21:55 +0200 Subject: [PATCH 098/116] Revert "Merge pull request #40109 from Expensify/lucien/temp-fix-qrCode-downloadBtn" This reverts commit 4e4f55ab148e09f08a41381575c536e630614f59, reversing changes made to bbc676100f7309a4495307885d0c6823b2825d47. --- src/pages/ShareCodePage.tsx | 59 ++++++++++++++++--- .../workspace/WorkspaceProfileSharePage.tsx | 37 +++++++++--- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index ce2d8e005e4d..4f1bac01b556 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,10 +1,14 @@ -import React from 'react'; +import React, {useMemo, useRef} from 'react'; import {View} from 'react-native'; +import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; +import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; +import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -12,8 +16,11 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; +import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as Url from '@libs/Url'; +import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; @@ -29,14 +36,36 @@ function ShareCodePage({report}: ShareCodePageProps) { const themeStyles = useThemeStyles(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); + const qrCodeRef = useRef(null); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isReport = !!report?.reportID; + const subtitle = useMemo(() => { + if (isReport) { + if (ReportUtils.isExpenseReport(report)) { + return ReportUtils.getPolicyName(report); + } + if (ReportUtils.isMoneyRequestReport(report)) { + // generate subtitle from participants + return ReportUtils.getVisibleMemberIDs(report) + .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) + .join(' & '); + } + + return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); + } + + return currentUserPersonalDetails.login; + }, [report, currentUserPersonalDetails, isReport]); + + const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID ?? '')}`; + const platform = getPlatform(); + const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( @@ -46,13 +75,17 @@ function ShareCodePage({report}: ShareCodePageProps) { shouldShowBackButton /> - {/* - Right now QR code download button is not shown anymore - This is a temporary measure because right now it's broken because of the Fabric update. - We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - - Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} + + + + {isNative && ( + qrCodeRef.current?.download?.()} + /> + )} + (null); const {isSmallScreenWidth} = useWindowDimensions(); const session = useSession(); + const policyName = policy?.name ?? ''; const id = policy?.id ?? ''; const adminEmail = session?.email ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); @@ -41,13 +50,16 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { /> - {/* - Right now QR code download button is not shown anymore - This is a temporary measure because right now it's broken because of the Fabric update. - We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. - - Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} + + + + {shouldAllowDownloadQRCode && ( + qrCodeRef.current?.download?.()} + wrapperStyle={themeStyles.sectionMenuItemTopDescription} + /> + )} From b9e32ab47f0a4fbd478bcb2fdc19c0911cad44a6 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 12 Apr 2024 12:27:47 +0200 Subject: [PATCH 099/116] fix: mock useOnyx hook --- __mocks__/react-native-onyx.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__mocks__/react-native-onyx.ts b/__mocks__/react-native-onyx.ts index 253e3db47a96..9c6a36380c04 100644 --- a/__mocks__/react-native-onyx.ts +++ b/__mocks__/react-native-onyx.ts @@ -5,7 +5,7 @@ /* eslint-disable rulesdir/prefer-onyx-connect-in-libs */ import type {ConnectOptions, OnyxKey} from 'react-native-onyx'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; let connectCallbackDelay = 0; function addDelayToConnectCallback(delay: number) { @@ -40,4 +40,4 @@ const reactNativeOnyxMock: ReactNativeOnyxMock = { }; export default reactNativeOnyxMock; -export {withOnyx}; +export {withOnyx, useOnyx}; From 19b670aefa45ba2a49d07a19d6521462ff525619 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 12 Apr 2024 12:39:58 +0200 Subject: [PATCH 100/116] disable linking in non-policy chats --- .../HTMLRenderers/MentionReportRenderer.tsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index 1a0b6855f386..648aa19d1ee0 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -1,5 +1,5 @@ import isEmpty from 'lodash/isEmpty'; -import React from 'react'; +import React, {useMemo} from 'react'; import type {TextStyle} from 'react-native'; import {StyleSheet} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -10,11 +10,14 @@ import Text from '@components/Text'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import {getReport} from '@libs/ReportUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type RoomMentionOnyxProps = { @@ -26,13 +29,13 @@ type MentionRoomRendererProps = RoomMentionOnyxProps & CustomRendererProps value.replace(CONST.UNICODE.LTR, '').slice(1); -const getMentionDetails = (htmlAttributeReportID: string, currentReportID: string, reports: OnyxCollection, tnode: TText | TPhrasing) => { +const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEntry | EmptyObject, reports: OnyxCollection, tnode: TText | TPhrasing) => { let reportID: string | undefined; let mentionDisplayText: string; // get mention details based on reportID from tag attribute if (!isEmpty(htmlAttributeReportID)) { - const report = reports?.['report_'.concat(htmlAttributeReportID)]; + const report = getReport(htmlAttributeReportID); reportID = report?.reportID ?? undefined; mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; @@ -40,7 +43,6 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReportID: strin } else if ('data' in tnode && !isEmptyObject(tnode.data)) { mentionDisplayText = removeLeadingLTRAndHash(tnode.data); - const currentReport = reports?.['report_'.concat(currentReportID)]; // eslint-disable-next-line rulesdir/prefer-early-return Object.values(reports ?? {}).forEach((report) => { if (report?.policyID === currentReport?.policyID && removeLeadingLTRAndHash(report?.reportName ?? '') === mentionDisplayText) { @@ -58,16 +60,19 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defa const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const htmlAttributeReportID = tnode.attributes.reportid; + const currentReportID = useCurrentReportID(); + const currentReport = getReport(currentReportID?.currentReportID); + const isGroupPolicyReport = useMemo(() => (currentReport && !isEmptyObject(currentReport) ? ReportUtils.isGroupPolicy(currentReport) : false), [currentReport]); - const mentionDetails = getMentionDetails(htmlAttributeReportID, currentReportID?.currentReportID ?? '', reports, tnode); + const mentionDetails = getMentionDetails(htmlAttributeReportID, currentReport, reports, tnode); if (!mentionDetails) { return null; } - const {reportID, mentionDisplayText} = mentionDetails; - const navigationRoute = reportID ? ROUTES.REPORT_WITH_ID.getRoute(String(reportID)) : undefined; - const isCurrentRoomMention = String(reportID) === currentReportID?.currentReportID; + + const navigationRoute = reportID ? ROUTES.REPORT_WITH_ID.getRoute(reportID) : undefined; + const isCurrentRoomMention = reportID === currentReportID?.currentReportID; const flattenStyle = StyleSheet.flatten(style as TextStyle); const {color, ...styleWithoutColor} = flattenStyle; @@ -78,18 +83,22 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defa { event.preventDefault(); Navigation.navigate(navigationRoute); } : undefined } - role={CONST.ROLE.LINK} - accessibilityLabel={`/${navigationRoute}`} + role={isGroupPolicyReport ? CONST.ROLE.LINK : undefined} + accessibilityLabel={isGroupPolicyReport ? `/${navigationRoute}` : undefined} > #{mentionDisplayText} From ac0fc5223a3bf5cfb0bb35dd7f5867751ebb4ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 12 Apr 2024 12:59:43 +0200 Subject: [PATCH 101/116] show the qr code but not the download button --- src/pages/ShareCodePage.tsx | 57 +++++-------------- .../workspace/WorkspaceProfileSharePage.tsx | 21 ++----- 2 files changed, 18 insertions(+), 60 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 4f1bac01b556..323143f584ed 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useRef} from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -7,8 +7,6 @@ import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; -import QRShareWithDownload from '@components/QRShare/QRShareWithDownload'; -import type QRShareWithDownloadHandle from '@components/QRShare/QRShareWithDownload/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -16,7 +14,6 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; -import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as Url from '@libs/Url'; @@ -24,6 +21,8 @@ import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; +import QRShare from '@components/QRShare'; +import type { QRShareHandle } from '@components/QRShare/types'; type ShareCodePageOnyxProps = { /** The report currently being looked at */ @@ -36,36 +35,16 @@ function ShareCodePage({report}: ShareCodePageProps) { const themeStyles = useThemeStyles(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); - const qrCodeRef = useRef(null); + const qrCodeRef = useRef(null); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isReport = !!report?.reportID; - const subtitle = useMemo(() => { - if (isReport) { - if (ReportUtils.isExpenseReport(report)) { - return ReportUtils.getPolicyName(report); - } - if (ReportUtils.isMoneyRequestReport(report)) { - // generate subtitle from participants - return ReportUtils.getVisibleMemberIDs(report) - .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) - .join(' & '); - } - - return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); - } - - return currentUserPersonalDetails.login; - }, [report, currentUserPersonalDetails, isReport]); - const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(report.reportID)}` : `${urlWithTrailingSlash}${ROUTES.PROFILE.getRoute(currentUserPersonalDetails.accountID ?? '')}`; - const platform = getPlatform(); - const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( @@ -76,15 +55,15 @@ function ShareCodePage({report}: ShareCodePageProps) { /> - + @@ -98,16 +77,6 @@ function ShareCodePage({report}: ShareCodePageProps) { shouldLimitWidth={false} /> - {isNative && ( - qrCodeRef.current?.download?.()} - /> - )} - (null); + const qrCodeRef = useRef(null); const {isSmallScreenWidth} = useWindowDimensions(); const session = useSession(); @@ -51,14 +49,14 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { - + /> @@ -72,15 +70,6 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { shouldLimitWidth={false} wrapperStyle={themeStyles.sectionMenuItemTopDescription} /> - {shouldAllowDownloadQRCode && ( - qrCodeRef.current?.download?.()} - wrapperStyle={themeStyles.sectionMenuItemTopDescription} - /> - )} From 4ddabf3d5e43ef3d063686bfe4060a1a43f1b6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 12 Apr 2024 13:05:02 +0200 Subject: [PATCH 102/116] add back comment --- src/pages/ShareCodePage.tsx | 7 +++++++ src/pages/workspace/WorkspaceProfileSharePage.tsx | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 323143f584ed..0a40dcb472ad 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -55,6 +55,13 @@ function ShareCodePage({report}: ShareCodePageProps) { /> + {/* + Right now QR code download button is not shown anymore + This is a temporary measure because right now it's broken because of the Fabric update. + We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. + */} + {/* + Right now QR code download button is not shown anymore + This is a temporary measure because right now it's broken because of the Fabric update. + We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. + + Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. + */} Date: Fri, 12 Apr 2024 13:14:47 +0200 Subject: [PATCH 103/116] fix prettier --- src/pages/ShareCodePage.tsx | 26 +++++++++---------- .../workspace/WorkspaceProfileSharePage.tsx | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 0a40dcb472ad..97c9e313b320 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -7,6 +7,8 @@ import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; +import QRShare from '@components/QRShare'; +import type {QRShareHandle} from '@components/QRShare/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -21,8 +23,6 @@ import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; -import QRShare from '@components/QRShare'; -import type { QRShareHandle } from '@components/QRShare/types'; type ShareCodePageOnyxProps = { /** The report currently being looked at */ @@ -55,22 +55,22 @@ function ShareCodePage({report}: ShareCodePageProps) { /> - {/* + {/* Right now QR code download button is not shown anymore This is a temporary measure because right now it's broken because of the Fabric update. We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} - + */} + diff --git a/src/pages/workspace/WorkspaceProfileSharePage.tsx b/src/pages/workspace/WorkspaceProfileSharePage.tsx index f839dcc96e32..73f24af7c325 100644 --- a/src/pages/workspace/WorkspaceProfileSharePage.tsx +++ b/src/pages/workspace/WorkspaceProfileSharePage.tsx @@ -6,6 +6,8 @@ import ContextMenuItem from '@components/ContextMenuItem'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import {useSession} from '@components/OnyxProvider'; +import QRShare from '@components/QRShare'; +import type {QRShareHandle} from '@components/QRShare/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useEnvironment from '@hooks/useEnvironment'; @@ -17,8 +19,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as Url from '@libs/Url'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import QRShare from '@components/QRShare'; -import type { QRShareHandle } from '@components/QRShare/types'; import withPolicy from './withPolicy'; import type {WithPolicyProps} from './withPolicy'; @@ -55,7 +55,7 @@ function WorkspaceProfileSharePage({policy}: WithPolicyProps) { We need to wait for react-native v0.74 to be released so react-native-view-shot gets fixed. Please see https://github.com/Expensify/App/issues/40110 to see if it can be re-enabled. - */} + */} + /> From 6d8c349dbe1d2500089e2d52492d281c860ecc4f Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 12 Apr 2024 13:15:57 +0200 Subject: [PATCH 104/116] rename props to match component name --- .../HTMLRenderers/MentionReportRenderer.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index 648aa19d1ee0..e27236c14beb 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -20,12 +20,12 @@ import type {Report} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type RoomMentionOnyxProps = { +type MentionReportOnyxProps = { /** All reports shared with the user */ reports: OnyxCollection; }; -type MentionRoomRendererProps = RoomMentionOnyxProps & CustomRendererProps; +type MentionReportRendererProps = MentionReportOnyxProps & CustomRendererProps; const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); @@ -56,7 +56,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEnt return {reportID, mentionDisplayText}; }; -function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionRoomRendererProps) { +function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defaultRendererProps}: MentionReportRendererProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const htmlAttributeReportID = tnode.attributes.reportid; @@ -117,7 +117,7 @@ const chatReportSelector = (report: OnyxEntry): Report => policyID: report.policyID, }) as Report; -export default withOnyx({ +export default withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, From 4f6506b3054ba8623094e42728af21f3d845b1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 12 Apr 2024 13:21:17 +0200 Subject: [PATCH 105/116] add back subtitle --- src/pages/ShareCodePage.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index 97c9e313b320..f8b8d9b3de7a 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -1,4 +1,4 @@ -import React, {useRef} from 'react'; +import React, {useMemo, useRef} from 'react'; import {View} from 'react-native'; import type {ImageSourcePropType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -40,6 +40,24 @@ function ShareCodePage({report}: ShareCodePageProps) { const isReport = !!report?.reportID; + const subtitle = useMemo(() => { + if (isReport) { + if (ReportUtils.isExpenseReport(report)) { + return ReportUtils.getPolicyName(report); + } + if (ReportUtils.isMoneyRequestReport(report)) { + // generate subtitle from participants + return ReportUtils.getVisibleMemberIDs(report) + .map((accountID) => ReportUtils.getDisplayNameForParticipant(accountID)) + .join(' & '); + } + + return ReportUtils.getParentNavigationSubtitle(report).workspaceName ?? ReportUtils.getChatRoomSubtitle(report); + } + + return currentUserPersonalDetails.login; + }, [report, currentUserPersonalDetails, isReport]); + const title = isReport ? ReportUtils.getReportName(report) : currentUserPersonalDetails.displayName ?? ''; const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); const url = isReport @@ -66,7 +84,7 @@ function ShareCodePage({report}: ShareCodePageProps) { ref={qrCodeRef} url={url} title={title} - subtitle={title} + subtitle={subtitle} logo={isReport ? expensifyLogo : (UserUtils.getAvatarUrl(currentUserPersonalDetails?.avatar, currentUserPersonalDetails?.accountID) as ImageSourcePropType)} logoRatio={CONST.QR.DEFAULT_LOGO_SIZE_RATIO} logoMarginRatio={CONST.QR.DEFAULT_LOGO_MARGIN_RATIO} From 90f0ada11d015f0b7cb523e54e13795043cf93cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 12 Apr 2024 13:42:40 +0200 Subject: [PATCH 106/116] fix logoRatio --- src/pages/ShareCodePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ShareCodePage.tsx b/src/pages/ShareCodePage.tsx index f8b8d9b3de7a..d7e86b3101f5 100644 --- a/src/pages/ShareCodePage.tsx +++ b/src/pages/ShareCodePage.tsx @@ -86,8 +86,8 @@ function ShareCodePage({report}: ShareCodePageProps) { title={title} subtitle={subtitle} logo={isReport ? expensifyLogo : (UserUtils.getAvatarUrl(currentUserPersonalDetails?.avatar, currentUserPersonalDetails?.accountID) as ImageSourcePropType)} - logoRatio={CONST.QR.DEFAULT_LOGO_SIZE_RATIO} - logoMarginRatio={CONST.QR.DEFAULT_LOGO_MARGIN_RATIO} + logoRatio={isReport ? CONST.QR.EXPENSIFY_LOGO_SIZE_RATIO : CONST.QR.DEFAULT_LOGO_SIZE_RATIO} + logoMarginRatio={isReport ? CONST.QR.EXPENSIFY_LOGO_MARGIN_RATIO : CONST.QR.DEFAULT_LOGO_MARGIN_RATIO} /> From 55fc7f0a570157e01424eaaf7783cecf0150e6ff Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 12 Apr 2024 16:49:09 +0200 Subject: [PATCH 107/116] fix double ## for room mentions --- .../HTMLRenderers/MentionReportRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index e27236c14beb..5b679f8e6c3b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -27,7 +27,7 @@ type MentionReportOnyxProps = { type MentionReportRendererProps = MentionReportOnyxProps & CustomRendererProps; -const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').slice(1); +const removeLeadingLTRAndHash = (value: string) => value.replace(CONST.UNICODE.LTR, '').replace('#', ''); const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEntry | EmptyObject, reports: OnyxCollection, tnode: TText | TPhrasing) => { let reportID: string | undefined; @@ -38,7 +38,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEnt const report = getReport(htmlAttributeReportID); reportID = report?.reportID ?? undefined; - mentionDisplayText = report?.reportName ?? report?.displayName ?? htmlAttributeReportID; + mentionDisplayText = removeLeadingLTRAndHash(report?.reportName ?? report?.displayName ?? htmlAttributeReportID); // get mention details from name inside tnode } else if ('data' in tnode && !isEmptyObject(tnode.data)) { mentionDisplayText = removeLeadingLTRAndHash(tnode.data); From 052e4ceb6d5dbecc0862ed2d966d153b11bc5a89 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 12 Apr 2024 16:49:43 +0200 Subject: [PATCH 108/116] typos --- .../HTMLRenderers/MentionReportRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index 5b679f8e6c3b..52377fa20b4d 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -33,13 +33,13 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEnt let reportID: string | undefined; let mentionDisplayText: string; - // get mention details based on reportID from tag attribute + // Get mention details based on reportID from tag attribute if (!isEmpty(htmlAttributeReportID)) { const report = getReport(htmlAttributeReportID); reportID = report?.reportID ?? undefined; mentionDisplayText = removeLeadingLTRAndHash(report?.reportName ?? report?.displayName ?? htmlAttributeReportID); - // get mention details from name inside tnode + // Get mention details from name inside tnode } else if ('data' in tnode && !isEmptyObject(tnode.data)) { mentionDisplayText = removeLeadingLTRAndHash(tnode.data); From 16e6a3f677b422c54be8e5a5f52e6cdb3ddb8195 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 17:10:35 +0200 Subject: [PATCH 109/116] adding more comments --- src/libs/actions/OnyxUpdateManager.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index 88276d127aee..bb6676ac5dc8 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -41,12 +41,16 @@ export default () => { Onyx.connect({ key: ONYXKEYS.ONYX_UPDATES_FROM_SERVER, callback: (value) => { - // When the OpenApp command hasn't finished yet, we should not process any updates. + // When value is set to null (which we do once we finish processing this method), we can just return early. + // Alternatively, if we're isLoadingApp is positive, that means that OpenApp command hasn't finished yet, + // so we should not process any updates that are being set here. if (!value || isLoadingApp) { if (isLoadingApp) { + // Some of the places that set ONYX_UPDATES_FROM_SERVER also stop the sequential queue. If we reached + // this point, let's unpause it to guarantee that it's still possible to use the app. + SequentialQueue.unpause(); console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); } - SequentialQueue.unpause(); return; } // This key is shared across clients, thus every client/tab will have a copy and try to execute this method. From 15a1fcdd79a70d7950c752ac02bda3687d0211e9 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 17:12:29 +0200 Subject: [PATCH 110/116] prettier --- src/libs/actions/OnyxUpdateManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index bb6676ac5dc8..4a5e1c5ea13b 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -42,12 +42,12 @@ export default () => { key: ONYXKEYS.ONYX_UPDATES_FROM_SERVER, callback: (value) => { // When value is set to null (which we do once we finish processing this method), we can just return early. - // Alternatively, if we're isLoadingApp is positive, that means that OpenApp command hasn't finished yet, - // so we should not process any updates that are being set here. + // Alternatively, if isLoadingApp is positive, that means that OpenApp command hasn't finished yet, so we + // should not process any updates that are being set. if (!value || isLoadingApp) { if (isLoadingApp) { // Some of the places that set ONYX_UPDATES_FROM_SERVER also stop the sequential queue. If we reached - // this point, let's unpause it to guarantee that it's still possible to use the app. + // this point, let's unpause it to guarantee the app won't be stuck without executing the queue. SequentialQueue.unpause(); console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); } From f9f9a0969637475c270d11d3e6f5a898e3b34fbf Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 17:40:06 +0200 Subject: [PATCH 111/116] making comments better --- src/libs/actions/OnyxUpdateManager.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index 4a5e1c5ea13b..81fc76a76e74 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -41,16 +41,18 @@ export default () => { Onyx.connect({ key: ONYXKEYS.ONYX_UPDATES_FROM_SERVER, callback: (value) => { - // When value is set to null (which we do once we finish processing this method), we can just return early. - // Alternatively, if isLoadingApp is positive, that means that OpenApp command hasn't finished yet, so we - // should not process any updates that are being set. - if (!value || isLoadingApp) { - if (isLoadingApp) { - // Some of the places that set ONYX_UPDATES_FROM_SERVER also stop the sequential queue. If we reached - // this point, let's unpause it to guarantee the app won't be stuck without executing the queue. - SequentialQueue.unpause(); - console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); - } + // When there's no value, there's nothing to process, so let's return early. + if (!value) { + return; + } + // If isLoadingApp is positive it means that OpenApp command hasn't finished yet, and in that case + // we don't have base state of the app (reports, policies, etc) setup. If we apply this update, + // we'll only have them overriten by the openApp response. So let's skip it and return. + if (isLoadingApp) { + // When ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT is set, we pause the queue. Let's unpause + // it so the app is not stuck forever without processing requests. + SequentialQueue.unpause(); + console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); return; } // This key is shared across clients, thus every client/tab will have a copy and try to execute this method. From 8a63405654c746b9cb8c573decd648e5df47c616 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 17:42:36 +0200 Subject: [PATCH 112/116] moving the place where we pause the queue --- src/libs/actions/OnyxUpdates.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index e093ce9a964b..2e33d776a3e9 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -119,6 +119,9 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { + // Pause the Sequential queue so we don't do any other requests while we're fetching missing onyxUpdates + SequentialQueue.unpause(); + // Always use set() here so that the updateParams are never merged and always unique to the request that came in Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, updateParams); } @@ -148,9 +151,6 @@ function applyOnyxUpdatesReliably(updates: OnyxUpdatesFromServer) { apply(updates); return; } - - // If we reached this point, we need to pause the queue while we prepare to fetch older OnyxUpdates. - SequentialQueue.pause(); saveUpdateInformation(updates); } From 7b4887656d30434f1f1680a1ce15dd1e97628b16 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Fri, 12 Apr 2024 17:42:47 +0200 Subject: [PATCH 113/116] removing unecessary pause inside callback --- src/libs/actions/OnyxUpdateManager.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index 81fc76a76e74..94ba18a4959d 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -87,11 +87,6 @@ export default () => { // fully migrating to the reliable updates mode. // 2. This client already has the reliable updates mode enabled, but it's missing some updates and it // needs to fetch those. - // - // For both of those, we need to pause the sequential queue. This is important so that the updates are - // applied in their correct and specific order. If this queue was not paused, then there would be a lot of - // onyx data being applied while we are fetching the missing updates and that would put them all out of order. - SequentialQueue.pause(); let canUnpauseQueuePromise; // The flow below is setting the promise to a reconnect app to address flow (1) explained above. From 1f3829c346d38f4b3d4d3a21db444af789099d80 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 12 Apr 2024 10:17:54 -0600 Subject: [PATCH 114/116] Correct the comments --- src/libs/actions/OnyxUpdateManager.ts | 2 +- src/libs/actions/OnyxUpdates.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager.ts b/src/libs/actions/OnyxUpdateManager.ts index 94ba18a4959d..f1f26e259ab1 100644 --- a/src/libs/actions/OnyxUpdateManager.ts +++ b/src/libs/actions/OnyxUpdateManager.ts @@ -49,7 +49,7 @@ export default () => { // we don't have base state of the app (reports, policies, etc) setup. If we apply this update, // we'll only have them overriten by the openApp response. So let's skip it and return. if (isLoadingApp) { - // When ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT is set, we pause the queue. Let's unpause + // When ONYX_UPDATES_FROM_SERVER is set, we pause the queue. Let's unpause // it so the app is not stuck forever without processing requests. SequentialQueue.unpause(); console.debug(`[OnyxUpdateManager] Ignoring Onyx updates while OpenApp hans't finished yet.`); diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 2e33d776a3e9..9f74ce47c9ee 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -119,7 +119,7 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { - // Pause the Sequential queue so we don't do any other requests while we're fetching missing onyxUpdates + // Unpause the Sequential queue so we go back to processing requests to guarantee we don't make the app unusable SequentialQueue.unpause(); // Always use set() here so that the updateParams are never merged and always unique to the request that came in From 117a0939d00621de6bda9b38c93788ad819ab744 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 12 Apr 2024 18:40:14 +0200 Subject: [PATCH 115/116] update mentions comments --- src/components/MentionSuggestions.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index ecb64fd3a0b8..fbfa0b3abc04 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -12,16 +12,26 @@ import Avatar from './Avatar'; import Text from './Text'; type Mention = { - /** Display name of the mention */ + /** + * Main display text of the mention + * always visible right after icon (if present) + */ text: string; - /** The formatted text of the mention */ + /** + * additional text for the mention + * visible if it's value is different than Mention.text value + * rendered after Mention.text + */ alternateText: string; - /** handle of the mention */ + /** + * handle of the mention + * used as a value for the mention (e.g. in for the filtering or putting the mention in the message) + */ handle?: string; - /** Array of icons of the user. If present, we use the first element of this array. For room suggestions, the icons are not used */ + /** Array of icons of the mention. If present, we use the first element of this array. For room suggestions, the icons are not used */ icons?: Icon[]; }; From 8b64eb2e0af25c13600e8a121da229412a366007 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 12 Apr 2024 19:23:19 +0200 Subject: [PATCH 116/116] Make capitalization consistent Co-authored-by: Puneet Lath --- src/components/MentionSuggestions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index fbfa0b3abc04..3866911fff9e 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -19,14 +19,14 @@ type Mention = { text: string; /** - * additional text for the mention + * Additional text for the mention * visible if it's value is different than Mention.text value * rendered after Mention.text */ alternateText: string; /** - * handle of the mention + * Handle of the mention * used as a value for the mention (e.g. in for the filtering or putting the mention in the message) */ handle?: string;