From 6463ecf8598bfd8f07989c3a3938058c1242f5ea Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 23 Dec 2024 09:33:32 +0530 Subject: [PATCH 01/63] feat: [Auth Violations] Support isDismissed on newDot to hide violations. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 5 ++- src/components/MoneyRequestHeader.tsx | 3 +- .../MoneyRequestPreviewContent.tsx | 20 +++++------ .../ReportActionItem/MoneyRequestView.tsx | 4 +-- .../ReportActionItem/ReportPreview.tsx | 3 +- src/libs/TransactionUtils/index.ts | 33 +++++++++++++++---- src/libs/actions/IOU.ts | 2 +- .../DebugTransactionViolations.tsx | 6 ++-- .../DebugTransactionViolationCreatePage.tsx | 4 +-- .../DebugTransactionViolationPage.tsx | 4 +-- src/pages/TransactionDuplicate/Review.tsx | 3 +- .../request/step/IOURequestStepAttendees.tsx | 4 +-- 12 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index c54bd0058f99..12573c042418 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -1,13 +1,12 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report} from '@src/types/onyx'; import TextLink from './TextLink'; @@ -26,7 +25,7 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f253c757050f..13c9880f65b3 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -56,7 +56,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1 }`, ); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); const styles = useThemeStyles(); @@ -112,7 +111,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ), }; } - if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } if (isScanning) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ba0cda25d59e..d25eed67739a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -83,7 +83,8 @@ function MoneyRequestPreviewContent({ const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : '-1'; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const transactionViolations = TransactionUtils.getTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; @@ -117,9 +118,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', allViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); @@ -134,11 +135,8 @@ function MoneyRequestPreviewContent({ // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates const allDuplicates = useMemo( - () => - transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( - (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, - )?.data?.duplicates ?? [], - [transaction?.transactionID, transactionViolations], + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], ); // Remove settled transactions from duplicates @@ -209,7 +207,7 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = TransactionUtils.getTransactionViolations(transaction.transactionID, transactionViolations); + const violations = TransactionUtils.getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } @@ -256,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 18750bfc7a29..19e3e8feca19 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -73,7 +73,7 @@ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS. const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; - return originalMessage?.IOUTransactionID ?? -1; + return originalMessage?.IOUTransactionID ?? undefined; }; function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates = false}: MoneyRequestViewProps) { @@ -95,7 +95,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, { canEvict: false, }); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getTransactionID(report, parentReportActions)}`); + const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 79497e5fab88..e9f4978e6fe1 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -188,8 +188,7 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = - numberOfRequests === 1 && - TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1', transactionViolations)); + numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1')); const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID ?? '-1', iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions.at(0)) : null; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 6643cd721d45..62f2007e01d6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -703,8 +703,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; +function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { + if (!transactionID) { + return null; + } + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; } /** @@ -724,7 +727,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is broken connection violation. */ function hasBrokenConnectionViolation(transactionID: string): boolean { - const violations = getTransactionViolations(transactionID, allTransactionViolations); + const violations = getTransactionViolations(transactionID); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -747,7 +750,7 @@ function shouldShowBrokenConnectionViolation(transactionID: string, report: Onyx */ function allHavePendingRTERViolation(transactionIds: string[]): boolean { const transactionsWithRTERViolations = transactionIds.map((transactionId) => { - const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations); + const transactionViolations = getTransactionViolations(transactionId); return hasPendingRTERViolation(transactionViolations); }); return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); @@ -878,12 +881,22 @@ function isOnHoldByTransactionID(transactionID: string): boolean { return isOnHold(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]); } +/** + * Checks if a violation is dismissed for the given transaction + */ +function isViolationDismissed(transactionID: string, violation: TransactionViolation): boolean { + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; +} + /** * Checks if any violations for the provided transaction are of type 'violation' */ function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.VIOLATION && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ); } @@ -892,7 +905,10 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti */ function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.NOTICE && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ); } @@ -903,7 +919,10 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.WARNING && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c8d6fb36f60d..5a3bb14fb527 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3293,7 +3293,7 @@ function updateMoneyRequestAttendees( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, - violations: OnyxEntry, + violations?: OnyxEntry, ) { const transactionChanges: TransactionChanges = { attendees, diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index d3e37f726a96..a7cf469ffa0a 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,6 +1,5 @@ import React from 'react'; import type {ListRenderItemInfo} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FlatList from '@components/FlatList'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -9,7 +8,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; +import * as TransactionUtils from '@libs/TransactionUtils'; import ROUTES from '@src/ROUTES'; import type {TransactionViolation} from '@src/types/onyx'; @@ -19,7 +18,8 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index b5f3d0d603d5..8d9df99a7dfa 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -14,6 +13,7 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import * as TransactionUtils from '@libs/TransactionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -62,7 +62,7 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index f615060ab6df..a9293592eee5 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useMemo} from 'react'; import {InteractionManager, View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; @@ -13,6 +12,7 @@ import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import * as TransactionUtils from '@libs/TransactionUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -29,7 +29,7 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); const styles = useThemeStyles(); diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index cb27ecfcbb3c..e7f1ec355d57 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -30,7 +30,8 @@ function TransactionDuplicateReview() { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID ?? '-1') ?? '-1'; - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 409ef1cfe02d..105cedd62dae 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -43,7 +43,7 @@ function IOURequestStepAttendees({ const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const violations = TransactionUtils.getTransactionViolations(transactionID); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { @@ -52,7 +52,7 @@ function IOURequestStepAttendees({ if (!lodashIsEqual(previousAttendees, attendees)) { IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations); + IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); } } From 2a179a29c1d374f88eae29c3ff34031e0fb67054 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:00:39 +0530 Subject: [PATCH 02/63] fix eslint. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 14 ++++++++++---- .../Transaction/DebugTransactionViolations.tsx | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1b09260f7e79..30e4e507a12b 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -750,8 +750,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | null): TransactionViolations | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; +function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { + if (transactionID) { + return null; + } + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; } /** @@ -771,7 +774,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is broken connection violation. */ function hasBrokenConnectionViolation(transactionID?: string): boolean { - const violations = getTransactionViolations(transactionID, allTransactionViolations); + const violations = getTransactionViolations(transactionID); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -928,7 +931,10 @@ function isOnHoldByTransactionID(transactionID: string): boolean { /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transactionID: string, violation: TransactionViolation): boolean { +function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation): boolean { + if (!transactionID) { + return false; + } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; } diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index ea01e8746b58..5d0c846b40d6 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import type {ListRenderItemInfo} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ScrollView from '@components/ScrollView'; From cd59a56006c08747160b281cbb23f717a081cf10 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:18:29 +0530 Subject: [PATCH 03/63] fix eslint. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 9 +++++++-- src/components/MoneyRequestHeader.tsx | 17 ++++++++++------- .../MoneyRequestPreviewContent.tsx | 2 +- src/libs/TransactionUtils/index.ts | 5 ++++- src/libs/actions/Transaction.ts | 5 ++++- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 12573c042418..6567a4269275 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -39,13 +39,18 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn return translate('violations.brokenConnection530Error'); } - if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID ?? '')) { + if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID)) { return ( <> {`${translate('violations.adminBrokenConnectionError')}`} Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id ?? '-1'))} + onPress={() => { + if (!policy?.id) { + return; + } + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id)); + }} >{`${translate('workspace.common.companyCards')}`} . diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 13c9880f65b3..97ff01a57463 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -16,6 +16,7 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as TransactionActions from '@userActions/Transaction'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -50,10 +51,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const route = useRoute(); - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? '-1'}`); + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ - ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1 + ReportActionsUtils.isMoneyRequestAction(parentReportAction) + ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID + : CONST.DEFAULT_NUMBER_ID }`, ); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); @@ -63,21 +66,21 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const {translate} = useLocalize(); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); const isOnHold = TransactionUtils.isOnHold(transaction); - const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? ''); + const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); + const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', parentReport, policy); + const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, parentReport, policy); const shouldShowMarkAsCashButton = - hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID ?? ''))); + hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { - TransactionActions.markAsCash(transaction?.transactionID ?? '-1', reportID ?? ''); + TransactionActions.markAsCash(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index f586855a0d2a..79af844528a5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -254,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', allViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 30e4e507a12b..2bf77d10e2fe 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -894,7 +894,10 @@ function getRecentTransactions(transactions: Record, size = 2): * @param transactionID - the transaction to check * @param checkDismissed - whether to check if the violation has already been dismissed as well */ -function isDuplicate(transactionID: string, checkDismissed = false): boolean { +function isDuplicate(transactionID: string | undefined, checkDismissed = false): boolean { + if (!transactionID) { + return false; + } const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index c8a007458242..8fd955ce2515 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -461,7 +461,10 @@ function clearError(transactionID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } -function markAsCash(transactionID: string, transactionThreadReportID: string) { +function markAsCash(transactionID: string | undefined, transactionThreadReportID: string | undefined) { + if (!transactionID || !transactionThreadReportID) { + return; + } const optimisticReportAction = buildOptimisticDismissedViolationReportAction({ reason: 'manual', violationName: CONST.VIOLATIONS.RTER, From 5682ae0e9ce762f741d0981f2b912d3ec1bdb268 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:34:36 +0530 Subject: [PATCH 04/63] fix eslint. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 2 +- src/components/MoneyRequestHeader.tsx | 4 ++-- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 8 ++++---- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils/index.ts | 5 ++++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 6567a4269275..9ea72ff539bc 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -13,7 +13,7 @@ import TextLink from './TextLink'; type BrokenConnectionDescriptionProps = { /** Transaction id of the corresponding report */ - transactionID: string; + transactionID?: string; /** Current report */ report: OnyxEntry; diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 97ff01a57463..89eac418d2c5 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -107,14 +107,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre icon: getStatusIcon(Expensicons.Hourglass), description: ( ), }; } - if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID))) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } if (isScanning) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 79af844528a5..5cebd1ab5976 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -118,9 +118,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', allViolations, true); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); @@ -254,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 19e3e8feca19..f7961c97249a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -71,7 +71,7 @@ const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS.MODIFIED_AMOUNT, CONST.VIOLATIONS.MODIFIED_DATE]; const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID ?? undefined; }; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 2bf77d10e2fe..9503d2a51664 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -751,7 +751,7 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): * Get all transaction violations of the transaction with given tranactionID. */ function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { - if (transactionID) { + if (!transactionID) { return null; } return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; @@ -945,6 +945,9 @@ function isViolationDismissed(transactionID: string | undefined, violation: Tran * Checks if any violations for the provided transaction are of type 'violation' */ function hasViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && From 10160650998f736eebeb4eedb1e7c8e6367ccb40 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:01:21 +0530 Subject: [PATCH 05/63] fix eslint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 20 +++++-- .../ReportActionItem/MoneyRequestView.tsx | 56 +++++++++---------- src/libs/actions/IOU.ts | 7 ++- src/libs/actions/Transaction.ts | 5 +- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 89eac418d2c5..885d91364917 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -157,11 +157,15 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre shouldShowReportAvatarWithDisplay shouldEnableDetailPageNavigation shouldShowPinButton={false} - report={{ - ...report, - reportID: reportID ?? '', - ownerAccountID: parentReport?.ownerAccountID, - }} + report={ + reportID + ? { + ...report, + reportID: reportID ?? '', + ownerAccountID: parentReport?.ownerAccountID, + } + : undefined + } policy={policy} shouldShowBackButton={shouldUseNarrowLayout} shouldDisplaySearchRouter={shouldDisplaySearchRouter} @@ -181,6 +185,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre text={translate('iou.reviewDuplicates')} style={[styles.p0, styles.ml2]} onPress={() => { + if (!reportID) { + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); }} /> @@ -203,6 +210,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre text={translate('iou.reviewDuplicates')} style={[styles.w100, styles.pr0]} onPress={() => { + if (!reportID) { + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); }} /> diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f7961c97249a..a4e29dbf9073 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -81,32 +81,32 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); - const parentReportID = report?.parentReportID ?? '-1'; - const policyID = report?.policyID ?? '-1'; - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`); + const parentReportID = report?.parentReportID; + const policyID = report?.policyID; + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, { selector: (chatReportValue) => chatReportValue && {reportID: chatReportValue.reportID, errorFields: chatReportValue.errorFields}, }); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID ?? CONST.DEFAULT_NUMBER_ID}`); + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID ?? CONST.DEFAULT_NUMBER_ID}`); const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${updatedTransaction?.reportID}`); - const targetPolicyID = updatedTransaction?.reportID ? transactionReport?.policyID : policyID; + const targetPolicyID = updatedTransaction?.reportID ? transactionReport?.policyID : policyID ?? CONST.DEFAULT_NUMBER_ID; const [policyTagList] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${targetPolicyID}`); - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, { + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`, { canEvict: false, }); const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; - return originalMessage?.IOUTransactionID ?? '-1'; + return originalMessage?.IOUTransactionID; }, [parentReportAction]); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`); - const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`); + const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`); const { created: transactionDate, @@ -227,7 +227,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals if (newBillable === TransactionUtils.getBillable(transaction)) { return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '-1', report?.reportID ?? '-1', newBillable, policy, policyTagList, policyCategories); + IOU.updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); }, [transaction, report, policy, policyTagList, policyCategories], ); @@ -318,17 +318,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute( - CONST.IOU.ACTION.EDIT, - iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', - Navigation.getReportRHPActiveRoute(), - ), - ) - } + ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID, Navigation.getReportRHPActiveRoute()), + ); + }} /> @@ -338,17 +335,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDistanceRate} shouldShowRightIcon={canEditDistanceRate} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('customUnitRateID') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('customUnitRateID')} /> @@ -455,7 +455,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals errors={errors} errorRowStyles={[styles.mh4]} onClose={() => { - if (!transaction?.transactionID && linkedTransactionID === '-1') { + if (!transaction?.transactionID && !linkedTransactionID) { return; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d9e39efb61f5..f2b4da2f972a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3394,13 +3394,16 @@ function updateMoneyRequestDate( /** Updates the billable field of an expense */ function updateMoneyRequestBillable( - transactionID: string, - transactionThreadReportID: string, + transactionID: string | undefined, + transactionThreadReportID: string | undefined, value: boolean, policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, ) { + if (!transactionID || !transactionThreadReportID) { + return; + } const transactionChanges: TransactionChanges = { billable: value, }; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 8fd955ce2515..311b8dfea7dc 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -457,7 +457,10 @@ function abandonReviewDuplicateTransactions() { Onyx.set(ONYXKEYS.REVIEW_DUPLICATES, null); } -function clearError(transactionID: string) { +function clearError(transactionID?: string) { + if (!transactionID) { + return; + } Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } From a2d0da6534a383184151a747f596a7e943de4c9a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:26:58 +0530 Subject: [PATCH 06/63] fix ESLint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 6 +-- .../ReportActionItem/MoneyRequestView.tsx | 41 +++++++++++-------- src/libs/actions/ReportActions.ts | 5 ++- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 885d91364917..edc9ab1e6250 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -161,7 +161,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre reportID ? { ...report, - reportID: reportID ?? '', + reportID, ownerAccountID: parentReport?.ownerAccountID, } : undefined @@ -188,7 +188,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre if (!reportID) { return; } - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID, Navigation.getReportRHPActiveRoute())); }} /> )} @@ -213,7 +213,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre if (!reportID) { return; } - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID, Navigation.getReportRHPActiveRoute())); }} /> diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a4e29dbf9073..148418ccdcf5 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -418,18 +418,21 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( CONST.IOU.ACTION.EDIT, iouType, orderWeight, - transaction?.transactionID ?? '', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={tagError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={tagError} /> @@ -470,7 +473,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } } Transaction.clearError(transaction?.transactionID ?? linkedTransactionID); - ReportActions.clearAllRelatedReportActionErrors(report?.reportID ?? '-1', parentReportAction); + ReportActions.clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); }} > {hasReceipt && ( @@ -496,17 +499,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} /> )} @@ -521,18 +527,21 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals titleStyle={styles.textHeadlineH2} interactive={canEditAmount} shouldShowRightIcon={canEditAmount} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, '', Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('amount') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('amount')} /> diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 89517a753c26..95d9d3d17b28 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -81,7 +81,10 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k ignore: `undefined` means we want to check both parent and children report actions ignore: `parent` or `child` means we want to ignore checking parent or child report actions because they've been previously checked */ -function clearAllRelatedReportActionErrors(reportID: string, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { +function clearAllRelatedReportActionErrors(reportID: string | undefined, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { + if (!reportID) { + return; + } const errorKeys = keys ?? Object.keys(reportAction?.errors ?? {}); if (!reportAction || errorKeys.length === 0) { return; From ee414e3ef8057cea36dd84c3ac458a1c0d76bbc6 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:32:37 +0530 Subject: [PATCH 07/63] fix ESLint. Signed-off-by: krishna2323 --- .../ReportActionItem/MoneyRequestView.tsx | 100 +++++++++++------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 148418ccdcf5..f2cec506ddc8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -554,17 +554,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('comment')} @@ -581,17 +584,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditMerchant} shouldShowRightIcon={canEditMerchant} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} wrapperStyle={[styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('merchant') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('merchant')} @@ -606,17 +612,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDate} shouldShowRightIcon={canEditDate} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DATE.getRoute( - CONST.IOU.ACTION.EDIT, - iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1' ?? '-1', - Navigation.getReportRHPActiveRoute(), - ), - ) - } + ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID, Navigation.getReportRHPActiveRoute()), + ); + }} brickRoadIndicator={getErrorForField('date') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('date')} /> @@ -629,17 +632,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('category')} /> @@ -659,22 +665,25 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals {shouldShowTax && ( + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('tax')} /> @@ -688,17 +697,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditTaxFields} shouldShowRightIcon={canEditTaxFields} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} /> )} @@ -707,11 +719,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals title={translate('travel.viewTripDetails')} icon={Expensicons.Suitcase} onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } const reservations = transaction?.receipt?.reservationList?.length ?? 0; if (reservations > 1) { - Navigation.navigate(ROUTES.TRAVEL_TRIP_SUMMARY.getRoute(report?.reportID ?? '-1', transaction?.transactionID ?? '-1', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRAVEL_TRIP_SUMMARY.getRoute(report?.reportID, transaction?.transactionID, Navigation.getReportRHPActiveRoute())); } - Navigation.navigate(ROUTES.TRAVEL_TRIP_DETAILS.getRoute(report?.reportID ?? '-1', transaction?.transactionID ?? '-1', 0, Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRAVEL_TRIP_DETAILS.getRoute(report?.reportID, transaction?.transactionID, 0, Navigation.getReportRHPActiveRoute())); }} /> )} @@ -726,9 +741,12 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }`} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1')) - } + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID)); + }} interactive shouldRenderAsHTML /> From 4978349cc0cff2889334d5a7819b703c3c3ea63b Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 9 Jan 2025 17:04:07 +0700 Subject: [PATCH 08/63] fix: navigate to parent report on delete track expense --- src/libs/Navigation/Navigation.ts | 25 ++++++++++++++++++++++++- src/libs/ReportUtils.ts | 10 ++++++++-- src/pages/ReportDetailsPage.tsx | 2 +- src/pages/home/ReportScreen.tsx | 3 ++- tests/actions/IOUTest.ts | 11 +++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index eeb6db21447e..62efc1c50329 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -11,7 +11,7 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; import ROUTES, {HYBRID_APP_ROUTES} from '@src/ROUTES'; -import {PROTECTED_SCREENS} from '@src/SCREENS'; +import SCREENS, {PROTECTED_SCREENS} from '@src/SCREENS'; import type {Screen} from '@src/SCREENS'; import type {Report} from '@src/types/onyx'; import originalCloseRHPFlow from './closeRHPFlow'; @@ -428,6 +428,13 @@ function getTopMostCentralPaneRouteFromRootState() { return getTopmostCentralPaneRoute(navigationRef.getRootState() as State); } +function getPreviousTrackReport(reportID?: string) { + if (!reportID) { + return null; + } + return navigationRef.getRootState().routes.find((r) => r.name === SCREENS.REPORT && !!r.params && 'reportID' in r.params && r.params.reportID === reportID); +} + function removeScreenFromNavigationState(screen: Screen) { isNavigationReady().then(() => { navigationRef.dispatch((state) => { @@ -442,6 +449,20 @@ function removeScreenFromNavigationState(screen: Screen) { }); } +function removeScreenByKey(key: string) { + isNavigationReady().then(() => { + navigationRef.dispatch((state) => { + const routes = state.routes?.filter((item) => item.key !== key); + + return CommonActions.reset({ + ...state, + routes, + index: routes.length < state.routes.length ? state.index - 1 : state.index, + }); + }); + }); +} + export default { setShouldPopAllStateOnUP, navigate, @@ -467,6 +488,8 @@ export default { setNavigationActionToMicrotaskQueue, getTopMostCentralPaneRouteFromRootState, removeScreenFromNavigationState, + getPreviousTrackReport, + removeScreenByKey, }; export {navigationRef}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a5ae8b1d102..21737fda5a2d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4312,7 +4312,7 @@ function goBackToDetailsPage(report: OnyxEntry, backTo?: string) { } } -function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) { +function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean, reportID?: string) { if (!backRoute) { return; } @@ -4322,7 +4322,13 @@ function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP return; } if (isFromRHP) { - Navigation.dismissModal(); + if (reportID) { + const trackReport = Navigation.getPreviousTrackReport(reportID); + if (trackReport?.key) { + Navigation.removeScreenByKey(trackReport.key); + } + } + Navigation.isNavigationReady().then(() => Navigation.dismissModal()); } Navigation.isNavigationReady().then(() => { Navigation.goBack(backRoute); diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 166b12b27751..17609a24e3c2 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -904,7 +904,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta Navigation.dismissModal(); } else { Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true); + ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); } }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, isTransactionDeleted]); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index ef3137a8c7d2..191aaa6ada5d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -736,7 +736,8 @@ function ReportScreen({route, navigation}: ReportScreenProps) { !isSingleExpenseReport && !isSingleInvoiceReport && !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && - !ReportActionsUtils.isDeletedAction(mostRecentReportAction); + !ReportActionsUtils.isDeletedAction(mostRecentReportAction) && + (!deleteTransactionNavigateBackUrl || !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); const lastRoute = usePrevious(route); const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index dc07c16c8d7f..3b51081ca96d 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -43,8 +43,19 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ dismissModal: jest.fn(), dismissModalWithReport: jest.fn(), goBack: jest.fn(), + removeScreenByKey: jest.fn(), + isNavigationReady: jest.fn(() => Promise.resolve()), + getPreviousTrackReport: jest.fn(), })); +jest.mock('@src/libs/Navigation/navigationRef', () => ({ + getRootState: () => ({ + routes: [], + }), +})); + +jest.mock('@react-navigation/native'); + jest.mock('@src/libs/Navigation/isSearchTopmostCentralPane', () => jest.fn()); const CARLOS_EMAIL = 'cmartins@expensifail.com'; From 18f9c6f48f721cd468ee68d1fac4a8cedbf2c6f9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 9 Jan 2025 21:02:51 +0530 Subject: [PATCH 09/63] fix ESlint issues. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 5 ++--- src/libs/ReportActionsUtils.ts | 8 ++++++-- src/libs/actions/ReportActions.ts | 4 ++-- src/libs/actions/Transaction.ts | 12 ++++++------ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index ce217266ea0a..769690e63ce8 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -188,9 +188,8 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = - numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1')); - const shouldShowBrokenConnectionViolation = - numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID ?? '-1', iouReport, policy); + numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID)); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions.at(0)) : null; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c1f4057199ee..6705584b0314 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -924,7 +924,8 @@ function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): NonNu * Find the transaction associated with this reportAction, if one exists. */ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry, reportID?: string): string | null { - const reportAction = typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionOrID] : reportActionOrID; + const reportAction = + typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID ?? CONST.DEFAULT_NUMBER_ID}`]?.[reportActionOrID] : reportActionOrID; if (!reportAction || !isMoneyRequestAction(reportAction)) { return null; } @@ -1599,7 +1600,10 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { +function getIOUActionForReportID(reportID?: string, transactionID?: string): OnyxEntry { + if (!reportID || !transactionID) { + return; + } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); const action = Object.values(reportActions ?? {})?.find((reportAction) => { diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 95d9d3d17b28..93752c3f02ab 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -41,7 +41,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k // If there's a linked transaction, delete that too // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID || '-1'); + const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); @@ -104,7 +104,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const childActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.childReportID}`] ?? {}; Object.values(childActions).forEach((action) => { const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err)); - clearAllRelatedReportActionErrors(reportAction.childReportID ?? '-1', action, 'parent', childErrorKeys); + clearAllRelatedReportActionErrors(reportAction.childReportID, action, 'parent', childErrorKeys); }); } } diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 311b8dfea7dc..9bed660dcfbf 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -355,7 +355,7 @@ function updateWaypoints(transactionID: string, waypoints: WaypointCollection, i function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmissedPersonalDetails: PersonalDetails) { const currentTransactionViolations = transactionIDs.map((id) => ({transactionID: id, violations: allTransactionViolation?.[id] ?? []})); const currentTransactions = transactionIDs.map((id) => allTransactions?.[id]); - const transactionsReportActions = currentTransactions.map((transaction) => ReportActionsUtils.getIOUActionForReportID(transaction.reportID ?? '', transaction.transactionID ?? '')); + const transactionsReportActions = currentTransactions.map((transaction) => ReportActionsUtils.getIOUActionForReportID(transaction.reportID, transaction.transactionID)); const optimisticDissmidedViolationReportActions = transactionsReportActions.map(() => { return buildOptimisticDismissedViolationReportAction({reason: 'manual', violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}); }); @@ -365,7 +365,7 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const optimisticReportActions: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: optimisticDissmidedViolationReportActions.at(index) as ReportAction, }, @@ -413,9 +413,9 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const failureReportActions: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: null, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, }, })); @@ -425,9 +425,9 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: null, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, }, })); // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions From 40c5559ee7ad694427846a20e94afb4a88e87062 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 9 Jan 2025 21:09:50 +0530 Subject: [PATCH 10/63] fix ESlint issues. Signed-off-by: krishna2323 --- src/libs/actions/Transaction.ts | 2 +- src/pages/TransactionDuplicate/Review.tsx | 6 +++--- src/pages/iou/request/step/IOURequestStepAttendees.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9bed660dcfbf..075eb4bc8e60 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -367,7 +367,7 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: optimisticDissmidedViolationReportActions.at(index) as ReportAction, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: optimisticDissmidedViolationReportActions.at(index) as ReportAction, }, })); const optimisticDataTransactionViolations: OnyxUpdate[] = currentTransactionViolations.map((transactionViolations) => ({ diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index e7f1ec355d57..5f010c4133ac 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -28,15 +28,15 @@ function TransactionDuplicateReview() { const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID ?? '-1') ?? '-1'; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); + const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], ); - const transactionIDs = [transactionID, ...duplicateTransactionIDs]; + const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : [...duplicateTransactionIDs]; const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 105cedd62dae..6672306f5221 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -39,7 +39,7 @@ function IOURequestStepAttendees({ policyCategories, }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; - const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || -1}`); + const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); From b32cbee063c9a61cefbc815bd16c07fcb7e97e3b Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 13 Jan 2025 17:36:04 +0700 Subject: [PATCH 11/63] fix: update removeScreenByKey --- src/libs/Navigation/Navigation.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 62efc1c50329..20b8cc31fff0 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -450,15 +450,14 @@ function removeScreenFromNavigationState(screen: Screen) { } function removeScreenByKey(key: string) { - isNavigationReady().then(() => { - navigationRef.dispatch((state) => { - const routes = state.routes?.filter((item) => item.key !== key); - - return CommonActions.reset({ - ...state, - routes, - index: routes.length < state.routes.length ? state.index - 1 : state.index, - }); + const state = navigationRef.getRootState(); + const routes = state.routes.filter((item) => item.key !== key); + + navigationRef.current?.dispatch(() => { + return CommonActions.reset({ + ...state, + routes, + index: routes.length < state.routes.length ? state.index - 1 : state.index, }); }); } From 3db3c2c0a07d44d6f5bce25783fcd8d000a97d65 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 15 Jan 2025 15:54:09 +0700 Subject: [PATCH 12/63] fix: lint --- src/pages/ReportDetailsPage.tsx | 312 +++++++++++++++++++------------- src/pages/home/ReportScreen.tsx | 141 +++++++++------ 2 files changed, 281 insertions(+), 172 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 41cff0f32122..f18a46a4f405 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -31,20 +31,100 @@ import useNetwork from '@hooks/useNetwork'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActions from '@libs/actions/Report'; +import { + cancelPayment as cancelPaymentIOU, + deleteMoneyRequest, + deleteTrackExpense, + getNavigationUrlAfterTrackExpenseDelete, + getNavigationUrlOnMoneyRequestDelete, + unapproveExpenseReport, +} from '@libs/actions/IOU'; +import {checkIfActionIsAllowed} from '@libs/actions/Session'; +import {canActionTask as canActionTaskActions, canModifyTask as canModifyTaskActions, deleteTask, reopenTask} from '@libs/actions/Task'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import {getConnectedIntegration, isPolicyAdmin, isPolicyEmployee, isSubmitAndClose, shouldShowPolicy} from '@libs/PolicyUtils'; +import { + getOneTransactionThreadReportID, + getOriginalMessage, + getReportAction, + getTrackExpenseActionableWhisper, + isDeletedAction, + isMoneyRequestAction, + isTrackExpenseAction, +} from '@libs/ReportActionsUtils'; +import { + canDeleteTransaction, + canEditReportDescription as canEditReportDescriptionReportUtils, + canHoldUnholdReportAction as canHoldUnholdReportActionReportUtils, + canJoinChat, + canLeaveChat, + canWriteInReport, + createDraftTransactionAndNavigateToParticipantSelector, + getAvailableReportFields, + getChatRoomSubtitle, + getDisplayNamesWithTooltips, + getIcons, + getOriginalReportID, + getParentNavigationSubtitle, + getParticipantsAccountIDsForDisplay, + getParticipantsList, + getReportDescription, + getReportFieldKey, + getReportName, + isAdminOwnerApproverOrReportOwner, + isArchivedNonExpenseReport as isArchivedNonExpenseReportReportUtils, + isCanceledTaskReport as isCanceledTaskReportReportUtils, + isChatRoom as isChatRoomReportUtils, + isChatThread as isChatThreadReportUtils, + isClosedReport, + isCompletedTaskReport, + isConciergeChatReport, + isDefaultRoom as isDefaultRoomReportUtils, + isExpenseReport as isExpenseReportReportUtils, + isExported, + isGroupChat as isGroupChatReportUtils, + isHiddenForCurrentUser, + isInvoiceReport as isInvoiceReportReportUtils, + isInvoiceRoom as isInvoiceRoomReportUtils, + isMoneyRequestReport as isMoneyRequestReportReportUtils, + isMoneyRequest as isMoneyRequestReportUtils, + isPayer as isPayerReportUtils, + isPolicyExpenseChat as isPolicyExpenseChatReportUtils, + isPublicRoom, + isReportApproved, + isReportFieldDisabled, + isReportFieldOfTypeTitle, + isReportManager, + isRootGroupChat as isRootGroupChatReportUtils, + isSelfDM as isSelfDMReportUtils, + isSettled as isSettledReportUtils, + isSystemChat as isSystemChatReportUtils, + isTaskReport as isTaskReportReportUtils, + isThread as isThreadReportUtils, + isTrackExpenseReport as isTrackExpenseReportReportUtils, + isUserCreatedPolicyRoom as isUserCreatedPolicyRoomReportUtils, + navigateBackOnDeleteTransaction, + navigateToPrivateNotes, + shouldDisableRename as shouldDisableRenameReportUtils, + shouldUseFullTitleToDisplay as shouldUseFullTitleToDisplayReportUtils, +} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import {getAllReportTransactions} from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; -import * as Report from '@userActions/Report'; -import * as Session from '@userActions/Session'; -import * as Task from '@userActions/Task'; +import { + clearAvatarErrors, + clearPolicyRoomNameErrors, + clearReportFieldKeyErrors, + exportReportToCSV, + getReportPrivateNote, + hasErrorInPrivateNotes, + leaveGroupChat, + leaveRoom, + setDeleteTransactionNavigateBackUrl, + updateGroupChatAvatar, +} from '@userActions/Report'; import ConfirmModal from '@src/components/ConfirmModal'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -99,10 +179,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - const transactionThreadReportID = useMemo( - () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), - [report.reportID, reportActions, isOffline], - ); + const transactionThreadReportID = useMemo(() => getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), [report.reportID, reportActions, isOffline]); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`); const [isDebugModeEnabled] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.isDebugModeEnabled}); @@ -117,34 +194,34 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`], [policies, report?.policyID]); - const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); - const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]); - const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]); - const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]); - const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]); - const isDefaultRoom = useMemo(() => ReportUtils.isDefaultRoom(report), [report]); - const isChatThread = useMemo(() => ReportUtils.isChatThread(report), [report]); - const isArchivedRoom = useMemo(() => ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs), [report, reportNameValuePairs]); - const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(report), [report]); - const isMoneyRequest = useMemo(() => ReportUtils.isMoneyRequest(report), [report]); - const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(report), [report]); - const isInvoiceRoom = useMemo(() => ReportUtils.isInvoiceRoom(report), [report]); - const isTaskReport = useMemo(() => ReportUtils.isTaskReport(report), [report]); - const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]); - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); - const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]); + const isUserPolicyAdmin = useMemo(() => isPolicyAdmin(policy), [policy]); + const isUserPolicyEmployee = useMemo(() => isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); + const isPolicyExpenseChat = useMemo(() => isPolicyExpenseChatReportUtils(report), [report]); + const shouldUseFullTitle = useMemo(() => shouldUseFullTitleToDisplayReportUtils(report), [report]); + const isChatRoom = useMemo(() => isChatRoomReportUtils(report), [report]); + const isUserCreatedPolicyRoom = useMemo(() => isUserCreatedPolicyRoomReportUtils(report), [report]); + const isDefaultRoom = useMemo(() => isDefaultRoomReportUtils(report), [report]); + const isChatThread = useMemo(() => isChatThreadReportUtils(report), [report]); + const isArchivedRoom = useMemo(() => isArchivedNonExpenseReportReportUtils(report, reportNameValuePairs), [report, reportNameValuePairs]); + const isMoneyRequestReport = useMemo(() => isMoneyRequestReportReportUtils(report), [report]); + const isMoneyRequest = useMemo(() => isMoneyRequestReportUtils(report), [report]); + const isInvoiceReport = useMemo(() => isInvoiceReportReportUtils(report), [report]); + const isInvoiceRoom = useMemo(() => isInvoiceRoomReportUtils(report), [report]); + const isTaskReport = useMemo(() => isTaskReportReportUtils(report), [report]); + const isSelfDM = useMemo(() => isSelfDMReportUtils(report), [report]); + const isTrackExpenseReport = isTrackExpenseReportReportUtils(report); + const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const isCanceledTaskReport = isCanceledTaskReportReportUtils(report, parentReportAction); + const canEditReportDescription = useMemo(() => canEditReportDescriptionReportUtils(report, policy), [report, policy]); const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== ''); const isExpenseReport = isMoneyRequestReport || isInvoiceReport || isMoneyRequest; const isSingleTransactionView = isMoneyRequest || isTrackExpenseReport; - const isSelfDMTrackExpenseReport = isTrackExpenseReport && ReportUtils.isSelfDM(parentReport); - const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report), [report]); - const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); + const isSelfDMTrackExpenseReport = isTrackExpenseReport && isSelfDMReportUtils(parentReport); + const shouldDisableRename = useMemo(() => shouldDisableRenameReportUtils(report), [report]); + const parentNavigationSubtitleData = getParentNavigationSubtitle(report); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx const chatRoomSubtitle = useMemo(() => { - const subtitle = ReportUtils.getChatRoomSubtitle(report); + const subtitle = getChatRoomSubtitle(report); if (subtitle) { return subtitle; @@ -152,15 +229,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return ''; }, [report]); - const isSystemChat = useMemo(() => ReportUtils.isSystemChat(report), [report]); - const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]); - const isRootGroupChat = useMemo(() => ReportUtils.isRootGroupChat(report), [report]); - const isThread = useMemo(() => ReportUtils.isThread(report), [report]); - const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isPolicyAdmin); + const isSystemChat = useMemo(() => isSystemChatReportUtils(report), [report]); + const isGroupChat = useMemo(() => isGroupChatReportUtils(report), [report]); + const isRootGroupChat = useMemo(() => isRootGroupChatReportUtils(report), [report]); + const isThread = useMemo(() => isThreadReportUtils(report), [report]); + const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isUserPolicyAdmin); const participants = useMemo(() => { - return ReportUtils.getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); + return getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); }, [report, personalDetails, shouldOpenRoomMembersPage]); - const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); + const connectedIntegration = getConnectedIntegration(policy); const transactionIDList = useMemo(() => { if (!isMoneyRequestReport) { @@ -206,7 +283,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); + const isDeletedParentAction = isDeletedAction(requestParentReportAction); const moneyRequestReport: OnyxEntry = useMemo(() => { if (caseID === CASES.MONEY_REQUEST) { @@ -217,21 +294,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const moneyRequestAction = transactionThreadReportID ? requestParentReportAction : parentReportAction; - const canModifyTask = Task.canModifyTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); - const canActionTask = Task.canActionTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canModifyTask = canModifyTaskActions(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canActionTask = canActionTaskActions(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); const shouldShowTaskDeleteButton = - isTaskReport && - !isCanceledTaskReport && - ReportUtils.canWriteInReport(report) && - report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && - !ReportUtils.isClosedReport(report) && - canModifyTask && - canActionTask; - const canDeleteRequest = isActionOwner && (ReportUtils.canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; + isTaskReport && !isCanceledTaskReport && canWriteInReport(report) && report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && !isClosedReport(report) && canModifyTask && canActionTask; + const canDeleteRequest = isActionOwner && (canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; const shouldShowDeleteButton = shouldShowTaskDeleteButton || canDeleteRequest; - const canUnapproveRequest = - ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); + const canUnapproveRequest = isExpenseReportReportUtils(report) && (isReportManager(report) || isUserPolicyAdmin) && isReportApproved(report) && !isSubmitAndClose(policy); useEffect(() => { if (canDeleteRequest) { @@ -247,23 +317,23 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - Report.getReportPrivateNote(report?.reportID); + getReportPrivateNote(report?.reportID); }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]); const leaveChat = useCallback(() => { Navigation.dismissModal(); Navigation.isNavigationReady().then(() => { if (isRootGroupChat) { - Report.leaveGroupChat(report.reportID); + leaveGroupChat(report.reportID); return; } - const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyEmployee; - Report.leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); + const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isUserPolicyEmployee; + leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); }); - }, [isPolicyEmployee, isPolicyExpenseChat, isRootGroupChat, report.reportID, report.visibility]); + }, [isUserPolicyEmployee, isPolicyExpenseChat, isRootGroupChat, report.reportID, report.visibility]); const [moneyRequestReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`); - const isMoneyRequestExported = ReportUtils.isExported(moneyRequestReportActions); + const isMoneyRequestExported = isExported(moneyRequestReportActions); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); @@ -275,16 +345,16 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }, [isMoneyRequestExported, moneyRequestReport, isDelegateAccessRestricted]); - const shouldShowLeaveButton = ReportUtils.canLeaveChat(report, policy); - const shouldShowGoToWorkspace = PolicyUtils.shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; + const shouldShowLeaveButton = canLeaveChat(report, policy); + const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; - const reportName = ReportUtils.getReportName(report); + const reportName = getReportName(report); const additionalRoomDetails = - (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || ReportUtils.isExpenseReport(report) || isPolicyExpenseChat || isInvoiceRoom + (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || isExpenseReportReportUtils(report) || isPolicyExpenseChat || isInvoiceRoom ? chatRoomSubtitle : `${translate('threads.in')} ${chatRoomSubtitle}`; @@ -297,24 +367,24 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta roomDescription = translate('newRoomPage.roomName'); } - const shouldShowNotificationPref = !isMoneyRequestReport && !ReportUtils.isHiddenForCurrentUser(report); + const shouldShowNotificationPref = !isMoneyRequestReport && !isHiddenForCurrentUser(report); const shouldShowWriteCapability = !isMoneyRequestReport; const shouldShowMenuItem = shouldShowNotificationPref || shouldShowWriteCapability || (!!report?.visibility && report.chatType !== CONST.REPORT.CHAT_TYPE.INVOICE); - const isPayer = ReportUtils.isPayer(session, moneyRequestReport); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isPayer = isPayerReportUtils(session, moneyRequestReport); + const isSettled = isSettledReportUtils(moneyRequestReport?.reportID); - const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport); + const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && isExpenseReportReportUtils(moneyRequestReport); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`); - const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; + const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; const cancelPayment = useCallback(() => { if (!chatReport) { return; } - IOU.cancelPayment(moneyRequestReport, chatReport); + cancelPaymentIOU(moneyRequestReport, chatReport); setIsConfirmModalVisible(false); }, [moneyRequestReport, chatReport]); @@ -335,10 +405,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // - The report is a user created room and the room and the current user is a workspace member i.e. non-workspace members should not see this option. if ( (isGroupChat || - (isDefaultRoom && isChatThread && isPolicyEmployee) || + (isDefaultRoom && isChatThread && isUserPolicyEmployee) || (!isUserCreatedPolicyRoom && participants.length) || - (isUserCreatedPolicyRoom && (isPolicyEmployee || (isChatThread && !ReportUtils.isPublicRoom(report))))) && - !ReportUtils.isConciergeChatReport(report) && + (isUserCreatedPolicyRoom && (isUserPolicyEmployee || (isChatThread && !isPublicRoom(report))))) && + !isConciergeChatReport(report) && !isSystemChat ) { items.push({ @@ -356,7 +426,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta } }, }); - } else if ((isUserCreatedPolicyRoom && (!participants.length || !isPolicyEmployee)) || ((isDefaultRoom || isPolicyExpenseChat) && isChatThread && !isPolicyEmployee)) { + } else if ((isUserCreatedPolicyRoom && (!participants.length || !isUserPolicyEmployee)) || ((isDefaultRoom || isPolicyExpenseChat) && isChatThread && !isUserPolicyEmployee)) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.INVITE, translationKey: 'common.invite', @@ -383,8 +453,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta } if (isTrackExpenseReport && !isDeletedParentAction) { - const actionReportID = ReportUtils.getOriginalReportID(report.reportID, parentReportAction); - const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); + const actionReportID = getOriginalReportID(report.reportID, parentReportAction); + const whisperAction = getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); const actionableWhisperReportActionID = whisperAction?.reportActionID; items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS, @@ -393,7 +463,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); }, }); items.push({ @@ -403,7 +473,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); }, }); items.push({ @@ -413,7 +483,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); }, }); } @@ -426,22 +496,22 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Pencil, isAnonymousAction: false, shouldShowRightIcon: true, - action: () => ReportUtils.navigateToPrivateNotes(report, session, backTo), - brickRoadIndicator: Report.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + action: () => navigateToPrivateNotes(report, session, backTo), + brickRoadIndicator: hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }); } // Show actions related to Task Reports if (isTaskReport && !isCanceledTaskReport) { - if (ReportUtils.isCompletedTaskReport(report) && canModifyTask && canActionTask) { + if (isCompletedTaskReport(report) && canModifyTask && canActionTask) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MARK_AS_INCOMPLETE, icon: Expensicons.Checkmark, translationKey: 'task.markAsIncomplete', isAnonymousAction: false, - action: Session.checkIfActionIsAllowed(() => { + action: checkIfActionIsAllowed(() => { Navigation.dismissModal(); - Task.reopenTask(report); + reopenTask(report); }), }); } @@ -469,14 +539,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - ReportActions.exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { + exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { setDownloadErrorModalVisible(true); }); }, }); } - if (policy && connectedIntegration && isPolicyAdmin && !isSingleTransactionView && isExpenseReport) { + if (policy && connectedIntegration && isUserPolicyAdmin && !isSingleTransactionView && isExpenseReport) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.EXPORT, translationKey: 'common.export', @@ -525,7 +595,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Exit, isAnonymousAction: true, action: () => { - if (ReportUtils.getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { + if (getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { setIsLastMemberLeavingGroupModalVisible(true); return; } @@ -553,7 +623,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isGroupChat, isDefaultRoom, isChatThread, - isPolicyEmployee, + isUserPolicyEmployee, isUserCreatedPolicyRoom, participants.length, report, @@ -570,7 +640,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta shouldShowLeaveButton, policy, connectedIntegration, - isPolicyAdmin, + isUserPolicyAdmin, isSingleTransactionView, isExpenseReport, canUnapproveRequest, @@ -595,10 +665,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const displayNamesWithTooltips = useMemo(() => { const hasMultipleParticipants = participants.length > 1; - return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); + return getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); }, [participants, personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); + const icons = useMemo(() => getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); const chatRoomSubtitleText = chatRoomSubtitle ? ( Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID))} onImageRemoved={() => { // Calling this without a file will remove the avatar - Report.updateGroupChatAvatar(report.reportID); + updateGroupChatAvatar(report.reportID); }} - onImageSelected={(file) => Report.updateGroupChatAvatar(report.reportID, file)} + onImageSelected={(file) => updateGroupChatAvatar(report.reportID, file)} editIcon={Expensicons.Camera} editIconStyle={styles.smallEditIconAccount} pendingAction={report.pendingFields?.avatar ?? undefined} errors={report.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} - onErrorClose={() => Report.clearAvatarErrors(report.reportID)} + onErrorClose={() => clearAvatarErrors(report.reportID)} shouldUseStyleUtilityForAnchorPosition style={[styles.w100, styles.mb3]} /> @@ -664,12 +734,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta ); }, [report, icons, isMoneyRequestReport, isInvoiceReport, isGroupChat, isThread, styles]); - const canHoldUnholdReportAction = ReportUtils.canHoldUnholdReportAction(moneyRequestAction); + const canHoldUnholdReportAction = canHoldUnholdReportActionReportUtils(moneyRequestAction); const shouldShowHoldAction = caseID !== CASES.DEFAULT && (canHoldUnholdReportAction.canHoldRequest || canHoldUnholdReportAction.canUnholdRequest) && - !ReportUtils.isArchivedNonExpenseReport(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); - const canJoin = ReportUtils.canJoinChat(report, parentReportAction, policy); + !isArchivedNonExpenseReportReportUtils(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); + const canJoin = canJoinChat(report, parentReportAction, policy); const promotedActions = useMemo(() => { const result: PromotedAction[] = []; @@ -725,7 +795,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta shouldUseFullTitle={shouldUseFullTitle} /> - {isPolicyAdmin ? ( + {isUserPolicyAdmin ? ( Report.clearPolicyRoomNameErrors(report?.reportID)} + onClose={() => clearPolicyRoomNameErrors(report?.reportID)} > ((): OnyxTypes.PolicyReportField | undefined => { - const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); - return fields.find((reportField) => ReportUtils.isReportFieldOfTypeTitle(reportField)); + const fields = getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); + return fields.find((reportField) => isReportFieldOfTypeTitle(reportField)); }, [report, policy?.fieldList]); - const fieldKey = ReportUtils.getReportFieldKey(titleField?.fieldID); - const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, titleField, policy); + const fieldKey = getReportFieldKey(titleField?.fieldID); + const isFieldDisabled = isReportFieldDisabled(report, titleField, policy); - const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && ReportUtils.isAdminOwnerApproverOrReportOwner(report, policy); + const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && isAdminOwnerApproverOrReportOwner(report, policy); const nameSectionFurtherDetailsContent = ( { if (report.errorFields?.reportName) { - Report.clearPolicyRoomNameErrors(report.reportID); + clearPolicyRoomNameErrors(report.reportID); } - Report.clearReportFieldKeyErrors(report.reportID, fieldKey); + clearReportFieldKeyErrors(report.reportID, fieldKey); }} > @@ -841,7 +911,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { - Task.deleteTask(report); + deleteTask(report); return; } @@ -849,12 +919,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); @@ -884,21 +954,21 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // Only proceed with navigation logic if transaction was actually deleted if (!isEmptyObject(requestParentReportAction)) { - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - urlToNavigateBack = IOU.getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } } if (!urlToNavigateBack) { Navigation.dismissModal(); } else { - Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); + setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); } - }, [iouTransactionID, requestParentReportAction, isSingleTransactionView, isTransactionDeleted, moneyRequestReport?.reportID]); + }, [iouTransactionID, requestParentReportAction, isSingleTransactionView, isTransactionDeleted, moneyRequestReport?.reportID, report.reportID]); const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); @@ -925,7 +995,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta { setIsUnapproveModalVisible(false); Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }} cancelText={translate('common.cancel')} onCancel={() => setIsUnapproveModalVisible(false)} diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 0b616a6d18e5..4b488b0ff4b8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -28,19 +28,59 @@ import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; +import {setShouldShowComposeInput} from '@libs/actions/Composer'; +import { + clearDeleteTransactionNavigateBackUrl, + navigateToConciergeChat, + openReport, + readNewestAction, + subscribeToReportLeavingEvents, + unsubscribeFromLeavingRoomReportChannel, + updateLastVisitTime, +} from '@libs/actions/Report'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import {getDisplayNameOrDefault, isPersonalDetailsEmpty} from '@libs/PersonalDetailsUtils'; +import { + getCombinedReportActions, + getOneTransactionThreadReportID, + isActionOfType, + isCreatedAction, + isDeletedAction, + isDeletedParentAction as isDeletedParentActionReportActionUtils, + isMoneyRequestAction, + isWhisperAction, + shouldReportActionBeVisible, +} from '@libs/ReportActionsUtils'; +import { + canAccessReport, + canEditReportAction, + canUserPerformWriteAction, + findLastAccessedReport, + getParticipantsAccountIDsForDisplay, + getReportIDFromLink, + getReportOfflinePendingActionAndErrors, + isChatThread, + isConciergeChatReport, + isExpenseReport, + isGroupChat, + isHiddenForCurrentUser, + isInvoiceReport, + isMoneyRequest, + isMoneyRequestReport, + isMoneyRequestReportPendingDeletion, + isOneTransactionThread, + isPolicyExpenseChat, + isTaskReport, + isTrackExpenseReport, + isValidReportIDFromPath, +} from '@libs/ReportUtils'; import shouldFetchReport from '@libs/shouldFetchReport'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {isNumeric} from '@libs/ValidationUtils'; import type {AuthScreensParamList} from '@navigation/types'; -import * as ComposerActions from '@userActions/Composer'; -import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -133,7 +173,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const wasLoadingApp = usePrevious(isLoadingApp); const finishedLoadingApp = wasLoadingApp && !isLoadingApp; - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); + const isDeletedParentAction = isDeletedParentActionReportActionUtils(parentReportAction); const prevIsDeletedParentAction = usePrevious(isDeletedParentAction); const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); @@ -143,14 +183,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // Don't update if there is a reportID in the params already if (route.params.reportID) { const reportActionID = route?.params?.reportActionID; - const isValidReportActionID = ValidationUtils.isNumeric(reportActionID); + const isValidReportActionID = isNumeric(reportActionID); if (reportActionID && !isValidReportActionID) { navigation.setParams({reportActionID: ''}); } return; } - const lastAccessedReportID = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, !!route.params.openOnAdminRoom, activeWorkspaceID)?.reportID; + const lastAccessedReportID = findLastAccessedReport(!canUseDefaultRooms, !!route.params.openOnAdminRoom, activeWorkspaceID)?.reportID; // It's possible that reports aren't fully loaded yet // in that case the reportID is undefined @@ -165,10 +205,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const chatWithAccountManagerText = useMemo(() => { if (accountManagerReportID) { - const participants = ReportUtils.getParticipantsAccountIDsForDisplay(accountManagerReport, false, true); - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([participants?.at(0) ?? -1], personalDetails); + const participants = getParticipantsAccountIDsForDisplay(accountManagerReport, false, true); + const participantPersonalDetails = getPersonalDetailsForAccountIDs([participants?.at(0) ?? -1], personalDetails); const participantPersonalDetail = Object.values(participantPersonalDetails).at(0); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(participantPersonalDetail); + const displayName = getDisplayNameOrDefault(participantPersonalDetail); const login = participantPersonalDetail?.login; if (displayName && login) { return translate('common.chatWithAccountManager', {accountManagerDisplayName: `${displayName} (${login})`}); @@ -250,7 +290,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const shouldAdjustScrollView = useMemo(() => isComposerFocus && !modal?.willAlertModalBecomeVisible, [isComposerFocus, modal]); const viewportOffsetTop = useViewportOffsetTop(shouldAdjustScrollView); - const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); + const {reportPendingAction, reportErrors} = getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const indexOfLinkedMessage = useMemo( @@ -259,7 +299,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ); const isPendingActionExist = !!reportActions.at(0)?.pendingAction; - const doesCreatedActionExists = useCallback(() => !!sortedAllReportActions?.findLast((action) => ReportActionsUtils.isCreatedAction(action)), [sortedAllReportActions]); + const doesCreatedActionExists = useCallback(() => !!sortedAllReportActions?.findLast((action) => isCreatedAction(action)), [sortedAllReportActions]); const isLinkedMessageAvailable = useMemo(() => indexOfLinkedMessage > -1, [indexOfLinkedMessage]); // The linked report actions should have at least 15 messages (counting as 1 page) above them to fill the screen. @@ -269,13 +309,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If there's a non-404 error for the report we should show it instead of blocking the screen const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); - const shouldHideReport = !hasHelpfulErrors && !ReportUtils.canAccessReport(report, policies, betas); + const shouldHideReport = !hasHelpfulErrors && !canAccessReport(report, policies, betas); - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID ?? '', reportActions ?? [], isOffline); + const transactionThreadReportID = getOneTransactionThreadReportID(reportID ?? '', reportActions ?? [], isOffline); const [transactionThreadReportActions = {}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`); - const combinedReportActions = ReportActionsUtils.getCombinedReportActions(reportActions, transactionThreadReportID ?? null, Object.values(transactionThreadReportActions)); - const lastReportAction = [...combinedReportActions, parentReportAction].find((action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)); - const isSingleTransactionView = ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report); + const combinedReportActions = getCombinedReportActions(reportActions, transactionThreadReportID ?? null, Object.values(transactionThreadReportActions)); + const lastReportAction = [...combinedReportActions, parentReportAction].find((action) => canEditReportAction(action) && !isMoneyRequestAction(action)); + const isSingleTransactionView = isMoneyRequest(report) || isTrackExpenseReport(report); const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`]; const isTopMostReportId = currentReportIDValue?.currentReportID === reportIDFromRoute; const didSubscribeToReportLeavingEvents = useRef(false); @@ -319,13 +359,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } useEffect(() => { - if (!transactionThreadReportID || !route?.params?.reportActionID || !ReportUtils.isOneTransactionThread(linkedAction?.childReportID ?? '-1', reportID ?? '', linkedAction)) { + if (!transactionThreadReportID || !route?.params?.reportActionID || !isOneTransactionThread(linkedAction?.childReportID ?? '-1', reportID ?? '', linkedAction)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(route?.params?.reportID)); }, [transactionThreadReportID, route?.params?.reportActionID, route?.params?.reportID, linkedAction, reportID]); - if (ReportUtils.isMoneyRequestReport(report) || ReportUtils.isInvoiceReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { headerView = ( = CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); const isLinkedActionDeleted = useMemo( - () => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, ReportUtils.canUserPerformWriteAction(report)), + () => !!linkedAction && !shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, canUserPerformWriteAction(report)), [linkedAction, report], ); const prevIsLinkedActionDeleted = usePrevious(linkedAction ? isLinkedActionDeleted : undefined); const isLinkedActionInaccessibleWhisper = useMemo( - () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), + () => !!linkedAction && isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), [currentUserAccountID, linkedAction], ); const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); @@ -368,12 +408,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // Clear the URL after all interactions are processed to ensure all updates are completed before hiding the skeleton InteractionManager.runAfterInteractions(() => { requestAnimationFrame(() => { - Report.clearDeleteTransactionNavigateBackUrl(); + clearDeleteTransactionNavigateBackUrl(); }); }); }, [isFocused, deleteTransactionNavigateBackUrl]); - const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()); + const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || isPersonalDetailsEmpty()); const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || @@ -382,7 +422,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { isLoadingReportOnyx || !isCurrentReportLoadedFromOnyx || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || + (deleteTransactionNavigateBackUrl && getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || (!reportMetadata.isOptimisticReport && isLoading); const isLinkedActionBecomesDeleted = prevIsLinkedActionDeleted !== undefined && !prevIsLinkedActionDeleted && isLinkedActionDeleted; @@ -426,7 +466,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { if (shouldHideReport) { return true; } - return !!currentReportIDFormRoute && !ReportUtils.isValidReportIDFromPath(currentReportIDFormRoute); + return !!currentReportIDFormRoute && !isValidReportIDFromPath(currentReportIDFormRoute); }, [ shouldShowNotFoundLinkedAction, isLoadingApp, @@ -440,14 +480,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ]); const fetchReport = useCallback(() => { - Report.openReport(reportIDFromRoute, reportActionIDFromRoute); + openReport(reportIDFromRoute, reportActionIDFromRoute); }, [reportIDFromRoute, reportActionIDFromRoute]); useEffect(() => { if (!reportID || !isFocused) { return; } - Report.updateLastVisitTime(reportID); + updateLastVisitTime(reportID); }, [reportID, isFocused]); useEffect(() => { @@ -466,7 +506,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const fetchReportIfNeeded = useCallback(() => { // Report ID will be empty when the reports collection is empty. // This could happen when we are loading the collection for the first time after logging in. - if (!ReportUtils.isValidReportIDFromPath(reportIDFromRoute)) { + if (!isValidReportIDFromPath(reportIDFromRoute)) { return; } @@ -518,7 +558,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { useEffect(() => { const interactionTask = InteractionManager.runAfterInteractions(() => { - ComposerActions.setShouldShowComposeInput(true); + setShouldShowComposeInput(true); }); return () => { interactionTask.cancel(); @@ -526,7 +566,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return; } - Report.unsubscribeFromLeavingRoomReportChannel(reportID ?? ''); + unsubscribeFromLeavingRoomReportChannel(reportID ?? ''); }; // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render. @@ -556,10 +596,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If a user has chosen to leave a thread, and then returns to it (e.g. with the back button), we need to call `openReport` again in order to allow the user to rejoin and to receive real-time updates useEffect(() => { - if (!shouldUseNarrowLayout || !isFocused || prevIsFocused || !ReportUtils.isChatThread(report) || !ReportUtils.isHiddenForCurrentUser(report) || isSingleTransactionView) { + if (!shouldUseNarrowLayout || !isFocused || prevIsFocused || !isChatThread(report) || !isHiddenForCurrentUser(report) || isSingleTransactionView) { return; } - Report.openReport(reportID ?? ''); + openReport(reportID ?? ''); // We don't want to run this useEffect every time `report` is changed // Excluding shouldUseNarrowLayout from the dependency list to prevent re-triggering on screen resize events. @@ -577,8 +617,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const prevOnyxReportID = prevReport?.reportID; const wasReportRemoved = !!prevOnyxReportID && prevOnyxReportID === reportIDFromRoute && !onyxReportID; const isRemovalExpectedForReportType = - isEmpty(report) && - (ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport) || ReportUtils.isGroupChat(prevReport)); + isEmpty(report) && (isMoneyRequest(prevReport) || isMoneyRequestReport(prevReport) || isPolicyExpenseChat(prevReport) || isGroupChat(prevReport)); const didReportClose = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const isTopLevelPolicyRoomWithNoStatus = !report?.statusNum && !prevReport?.parentReportID && prevReport?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM; const isClosedTopLevelPolicyRoom = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && isTopLevelPolicyRoomWithNoStatus; @@ -603,14 +642,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } if (prevReport?.parentReportID) { // Prevent navigation to the IOU/Expense Report if it is pending deletion. - if (ReportUtils.isMoneyRequestReportPendingDeletion(prevReport.parentReportID)) { + if (isMoneyRequestReportPendingDeletion(prevReport.parentReportID)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(prevReport.parentReportID)); return; } - Report.navigateToConciergeChat(); + navigateToConciergeChat(); return; } @@ -623,7 +662,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } fetchReportIfNeeded(); - ComposerActions.setShouldShowComposeInput(true); + setShouldShowComposeInput(true); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ route, @@ -645,7 +684,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ]); useEffect(() => { - if (!ReportUtils.isValidReportIDFromPath(reportIDFromRoute)) { + if (!isValidReportIDFromPath(reportIDFromRoute)) { return; } // Ensures the optimistic report is created successfully @@ -660,7 +699,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { let interactionTask: ReturnType | null = null; if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { interactionTask = InteractionManager.runAfterInteractions(() => { - Report.subscribeToReportLeavingEvents(reportIDFromRoute); + subscribeToReportLeavingEvents(reportIDFromRoute); didSubscribeToReportLeavingEvents.current = true; }); } @@ -719,24 +758,24 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }, [isLinkedActionInaccessibleWhisper]); useEffect(() => { - if (!!report?.lastReadTime || !ReportUtils.isTaskReport(report)) { + if (!!report?.lastReadTime || !isTaskReport(report)) { return; } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. - Report.readNewestAction(report?.reportID ?? ''); + readNewestAction(report?.reportID ?? ''); }, [report]); const mostRecentReportAction = reportActions.at(0); const isMostRecentReportIOU = mostRecentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; const isSingleIOUReportAction = reportActions.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU).length === 1; - const isSingleExpenseReport = ReportUtils.isExpenseReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; - const isSingleInvoiceReport = ReportUtils.isInvoiceReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; + const isSingleExpenseReport = isExpenseReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; + const isSingleInvoiceReport = isInvoiceReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; const shouldShowMostRecentReportAction = !!mostRecentReportAction && !isSingleExpenseReport && !isSingleInvoiceReport && - !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && - !ReportActionsUtils.isDeletedAction(mostRecentReportAction) && - (!deleteTransactionNavigateBackUrl || !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); + !isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && + !isDeletedAction(mostRecentReportAction) && + (!deleteTransactionNavigateBackUrl || !isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); const lastRoute = usePrevious(route); const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute); @@ -778,7 +817,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { > {headerView} - {!!accountManagerReportID && ReportUtils.isConciergeChatReport(report) && isBannerVisible && ( + {!!accountManagerReportID && isConciergeChatReport(report) && isBannerVisible && ( )} - + Date: Wed, 15 Jan 2025 19:50:39 +0530 Subject: [PATCH 13/63] fix ESlint. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 14 ++-- src/components/MoneyRequestHeader.tsx | 40 ++++++---- .../MoneyRequestPreviewContent.tsx | 79 ++++++++++--------- 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 2b0b5a3dba8d..14265030bf75 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -2,9 +2,9 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminUtil} from '@libs/PolicyUtils'; +import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -25,11 +25,11 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); - const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); + const isPolicyAdmin = isPolicyAdminUtil(policy); if (!brokenConnection530Error && !brokenConnectionError) { return ''; @@ -39,7 +39,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn return translate('violations.brokenConnection530Error'); } - if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID)) { + if (isPolicyAdmin && !isCurrentUserSubmitter(report?.reportID)) { return ( <> {`${translate('violations.adminBrokenConnectionError')}`} @@ -57,7 +57,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn ); } - if (ReportUtils.isReportApproved(report) || ReportUtils.isReportManuallyReimbursed(report) || (ReportUtils.isProcessingReport(report) && !PolicyUtils.isInstantSubmitEnabled(policy))) { + if (isReportApproved(report) || isReportManuallyReimbursed(report) || (isProcessingReport(report) && !isInstantSubmitEnabled(policy))) { return translate('violations.memberBrokenConnectionError'); } diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index edc9ab1e6250..4a24dbaa1134 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -9,10 +9,21 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {isPolicyAdmin} from '@libs/PolicyUtils'; +import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {isCurrentUserSubmitter} from '@libs/ReportUtils'; +import { + allHavePendingRTERViolation, + getTransactionViolations, + hasPendingRTERViolation, + hasReceipt, + isDuplicate as isDuplicateUtil, + isExpensifyCardTransaction, + isOnHold as isOnHoldUtil, + isPending, + isReceiptBeingScanned, + shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, +} from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as TransactionActions from '@userActions/Transaction'; @@ -54,9 +65,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ - ReportActionsUtils.isMoneyRequestAction(parentReportAction) - ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID - : CONST.DEFAULT_NUMBER_ID + isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID }`, ); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); @@ -65,25 +74,24 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const theme = useTheme(); const {translate} = useLocalize(); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); - const isOnHold = TransactionUtils.isOnHold(transaction); - const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID); + const isOnHold = isOnHoldUtil(transaction); + const isDuplicate = isDuplicateUtil(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, parentReport, policy); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationUtil(transaction?.transactionID, parentReport, policy); - const shouldShowMarkAsCashButton = - hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID))); + const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { TransactionActions.markAsCash(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); - const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + const isScanning = hasReceipt(transaction) && isReceiptBeingScanned(transaction); const getStatusIcon: (src: IconAsset) => ReactNode = (src) => ( avatar.id); if (isPolicyExpenseChat && isBillSplit) { - sortedParticipantAvatars.push(ReportUtils.getWorkspaceIcon(chatReport)); + sortedParticipantAvatars.push(getWorkspaceIcon(chatReport)); } // Pay button should only be visible to the manager of the report. @@ -109,7 +116,7 @@ function MoneyRequestPreviewContent({ merchant, tag, category, - } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); + } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); @@ -119,14 +126,14 @@ function MoneyRequestPreviewContent({ const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); const isCardTransaction = TransactionUtils.isCardTransaction(transaction); - const isSettled = ReportUtils.isSettled(iouReport?.reportID); - const isApproved = ReportUtils.isReportApproved(iouReport); + const isSettled = isSettledUtil(iouReport?.reportID); + const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; @@ -153,8 +160,8 @@ function MoneyRequestPreviewContent({ const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && !!transaction?.comment?.hold; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const reviewingTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; + const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const reviewingTransactionID = isMoneyRequestActionUtil(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; /* Show the merchant for IOUs and expenses only if: @@ -174,7 +181,7 @@ function MoneyRequestPreviewContent({ merchantOrDescription = description || ''; } - const receiptImages = [{...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}]; + const receiptImages = [{...getThumbnailAndImageURIs(transaction), transaction}]; const getSettledMessage = (): string => { if (isCardTransaction) { @@ -232,9 +239,9 @@ function MoneyRequestPreviewContent({ } return message; } - } else if (hasNoticeTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { + } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID)) { message += ` • ${translate('violations.reviewRequired')}`; - } else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold) { + } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID) && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`; } else if (iouReport?.isCancelledIOU) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.canceled')}`; @@ -271,12 +278,12 @@ function MoneyRequestPreviewContent({ return translate('iou.fieldPending'); } - return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); + return convertToDisplayString(requestAmount, requestCurrency); }; const getDisplayDeleteAmountText = (): string => { - const iouOriginalMessage: OnyxEntry = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action) ?? undefined : undefined; - return CurrencyUtils.convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); + const iouOriginalMessage: OnyxEntry = isMoneyRequestActionUtil(action) ? getOriginalMessage(action) ?? undefined : undefined; + return convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); }; const displayAmount = isDeleted ? getDisplayDeleteAmountText() : getDisplayAmountText(); @@ -288,7 +295,7 @@ function MoneyRequestPreviewContent({ () => shouldShowSplitShare ? transaction?.comment?.splits?.find((split) => split.accountID === sessionAccountID)?.amount ?? - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency ?? '', action.actorAccountID === sessionAccountID) + calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency ?? '', action.actorAccountID === sessionAccountID) : 0, [shouldShowSplitShare, isPolicyExpenseChat, action.actorAccountID, participantAccountIDs.length, transaction?.comment?.splits, requestAmount, requestCurrency, sessionAccountID], ); @@ -350,7 +357,7 @@ function MoneyRequestPreviewContent({ size={1} /> )} - {isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( + {isEmptyObject(transaction) && !isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( ) : ( @@ -384,7 +391,7 @@ function MoneyRequestPreviewContent({ > {displayAmount} - {ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( + {isSettledUtil(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( {!!splitShare && ( - {translate('iou.yourSplit', {amount: CurrencyUtils.convertToDisplayString(splitShare, requestCurrency)})} + {translate('iou.yourSplit', {amount: convertToDisplayString(splitShare, requestCurrency)})} )} @@ -471,7 +478,7 @@ function MoneyRequestPreviewContent({ numberOfLines={1} style={[styles.textMicroSupporting, styles.pre, styles.flexShrink1]} > - {PolicyUtils.getCleanedTagName(tag)} + {getCleanedTagName(tag)} )} @@ -493,17 +500,17 @@ function MoneyRequestPreviewContent({ return ( DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressIn={() => canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={showContextMenu} shouldUseHapticsOnLongPress accessibilityLabel={isBillSplit ? translate('iou.split') : showCashOrCard} - accessibilityHint={CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} + accessibilityHint={convertToDisplayString(requestAmount, requestCurrency)} style={[ styles.moneyRequestPreviewBox, containerStyles, shouldDisableOnPress && styles.cursorDefault, - (isSettled || ReportUtils.isReportApproved(iouReport)) && isSettlementOrApprovalPartial && styles.offlineFeedback.pending, + (isSettled || isReportApproved(iouReport)) && isSettlementOrApprovalPartial && styles.offlineFeedback.pending, ]} > {childContainer} From 825c4bea39e8c6a588695d49d7f535b452161627 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 15 Jan 2025 20:33:37 +0530 Subject: [PATCH 14/63] fix ESlint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 8 +- .../MoneyRequestPreviewContent.tsx | 73 +++++--- .../ReportActionItem/MoneyRequestView.tsx | 169 ++++++++++-------- 3 files changed, 149 insertions(+), 101 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 4a24dbaa1134..45fb3134b0db 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,6 +8,8 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {dismissHoldUseExplanation} from '@libs/actions/IOU'; +import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -25,8 +27,6 @@ import { shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import * as IOU from '@userActions/IOU'; -import * as TransactionActions from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -88,7 +88,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { - TransactionActions.markAsCash(transaction?.transactionID, reportID); + markAsCashUtil(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); const isScanning = hasReceipt(transaction) && isReceiptBeingScanned(transaction); @@ -154,7 +154,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [isSmallScreenWidth, shouldShowHoldMenu]); const handleHoldRequestClose = () => { - IOU.dismissHoldUseExplanation(); + dismissHoldUseExplanation(); }; return ( diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 6363676892e4..d59f56ec53a8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -23,6 +23,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {clearWalletTermsError} from '@libs/actions/PaymentMethods'; +import {clearIOUError} from '@libs/actions/Report'; +import {abandonReviewDuplicateTransactions, setReviewDuplicatesKey} from '@libs/actions/Transaction'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -45,12 +48,28 @@ import { } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import { + compareDuplicateTransactionFields, + getTransactionViolations, + hasMissingSmartscanFields, + hasNoticeTypeViolation, + hasPendingUI, + hasReceipt as hasReceiptUtil, + hasViolation, + hasWarningTypeViolation, + isAmountMissing as isAmountMissingUtil, + isCardTransaction as isCardTransactionUtil, + isDistanceRequest as isDistanceRequestUtil, + isFetchingWaypointsFromServer as isFetchingWaypointsFromServerUtil, + isMerchantMissing as isMerchantMissingUtil, + isOnHold as isOnHoldUtil, + isPending, + isReceiptBeingScanned, + removeSettledAndApprovedTransactions, + shouldShowBrokenConnectionViolation, +} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as Report from '@userActions/Report'; -import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -92,7 +111,7 @@ function MoneyRequestPreviewContent({ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const transactionViolations = TransactionUtils.getTransactionViolations(transaction?.transactionID); + const transactionViolations = getTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; @@ -120,18 +139,18 @@ function MoneyRequestPreviewContent({ const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); - const isOnHold = TransactionUtils.isOnHold(transaction); + const hasReceipt = hasReceiptUtil(transaction); + const isScanning = hasReceipt && isReceiptBeingScanned(transaction); + const isOnHold = isOnHoldUtil(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); - const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); - const isCardTransaction = TransactionUtils.isCardTransaction(transaction); + const hasViolations = hasViolation(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolation(transaction?.transactionID, allViolations, true); + const hasFieldErrors = hasMissingSmartscanFields(transaction); + const isDistanceRequest = isDistanceRequestUtil(transaction); + const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerUtil(transaction); + const isCardTransaction = isCardTransactionUtil(transaction); const isSettled = isSettledUtil(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -147,7 +166,7 @@ function MoneyRequestPreviewContent({ ); // Remove settled transactions from duplicates - const duplicates = useMemo(() => TransactionUtils.removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]); + const duplicates = useMemo(() => removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]); // When there are no settled transactions in duplicates, show the "Keep this one" button const shouldShowKeepButton = !!(allDuplicates.length && duplicates.length && allDuplicates.length === duplicates.length); @@ -214,7 +233,7 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = TransactionUtils.getTransactionViolations(transaction.transactionID); + const violations = getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } @@ -228,8 +247,8 @@ function MoneyRequestPreviewContent({ return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } if (hasFieldErrors) { - const isMerchantMissing = TransactionUtils.isMerchantMissing(transaction); - const isAmountMissing = TransactionUtils.isAmountMissing(transaction); + const isMerchantMissing = isMerchantMissingUtil(transaction); + const isAmountMissing = isAmountMissingUtil(transaction); if (isAmountMissing && isMerchantMissing) { message += ` ${CONST.DOT_SEPARATOR} ${translate('violations.reviewRequired')}`; } else if (isAmountMissing) { @@ -255,13 +274,13 @@ function MoneyRequestPreviewContent({ if (isScanning) { return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; } - if (TransactionUtils.isPending(transaction)) { + if (isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { + if (shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID))) { + if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; @@ -305,9 +324,9 @@ function MoneyRequestPreviewContent({ // Clear the draft before selecting a different expense to prevent merging fields from the previous expense // (e.g., category, tag, tax) that may be not enabled/available in the new expense's policy. - Transaction.abandonReviewDuplicateTransactions(); - const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); - Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); + abandonReviewDuplicateTransactions(); + const comparisonResult = compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); + setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID, backTo)); @@ -335,8 +354,8 @@ function MoneyRequestPreviewContent({ { - PaymentMethods.clearWalletTermsError(); - Report.clearIOUError(chatReportID); + clearWalletTermsError(); + clearIOUError(chatReportID); }} errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f2cec506ddc8..513f33d1a78c 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -18,26 +18,56 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useViolations from '@hooks/useViolations'; import type {ViolationField} from '@hooks/useViolations'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {cleanUpMoneyRequest, updateMoneyRequestBillable} from '@libs/actions/IOU'; +import {navigateToConciergeChatAndDeleteReport} from '@libs/actions/Report'; +import {clearAllRelatedReportActionErrors} from '@libs/actions/ReportActions'; +import {clearError} from '@libs/actions/Transaction'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; -import * as ReceiptUtils from '@libs/ReceiptUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {hasEnabledOptions} from '@libs/OptionsListUtils'; +import {getTagLists, hasDependentTags, isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; +import {getOriginalMessage, isMoneyRequestAction, isPayAction} from '@libs/ReportActionsUtils'; +import { + canEditFieldOfMoneyRequest, + canEditMoneyRequest, + canUserPerformWriteAction as canUserPerformWriteActionUtil, + getAddWorkspaceRoomOrChatReportErrors, + getTransactionDetails, + getTripIDFromTransactionParentReportID, + isInvoiceReport, + isMoneyRequestReport, + isPaidGroupPolicy, + isReportApproved, + isReportInGroupPolicy, + isSettled as isSettledUtil, + isTrackExpenseReport, +} from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; -import * as TagsOptionsListUtils from '@libs/TagsOptionsListUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; +import { + didReceiptScanSucceed as didReceiptScanSucceedUtil, + getBillable, + getCardName, + getDescription, + getDistanceInMeters, + getTagForDisplay, + getTaxName, + getTransactionViolations, + hasMissingSmartscanFields, + hasReceipt as hasReceiptUtil, + hasReservationList, + hasRoute as hasRouteUtil, + isCardTransaction as isCardTransactionUtil, + isDistanceRequest as isDistanceRequestUtil, + isReceiptBeingScanned as isReceiptBeingScannedUtil, + shouldShowAttendees as shouldShowAttendeesUtil, +} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; -import * as IOU from '@userActions/IOU'; -import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import * as Report from '@src/libs/actions/Report'; -import * as ReportActions from '@src/libs/actions/ReportActions'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -72,7 +102,7 @@ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS. const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; - const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; + const originalMessage = parentReportAction && isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID ?? undefined; }; @@ -95,13 +125,13 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`, { canEvict: false, }); - const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); + const transactionViolations = getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; - const isTrackExpense = ReportUtils.isTrackExpenseReport(report); + const isTrackExpense = isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { - const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; + const originalMessage = parentReportAction && isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID; }, [parentReportAction]); @@ -122,74 +152,74 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals originalAmount: transactionOriginalAmount, originalCurrency: transactionOriginalCurrency, postedDate: transactionPostedDate, - } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); + } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : ''; - const formattedPerAttendeeAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; - const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); - const isCardTransaction = TransactionUtils.isCardTransaction(transaction); - const cardProgramName = TransactionUtils.getCardName(transaction); + const isDistanceRequest = isDistanceRequestUtil(transaction); + const formattedTransactionAmount = transactionAmount ? convertToDisplayString(transactionAmount, transactionCurrency) : ''; + const formattedPerAttendeeAmount = transactionAmount ? convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; + const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); + const isCardTransaction = isCardTransactionUtil(transaction); + const cardProgramName = getCardName(transaction); const shouldShowCard = isCardTransaction && cardProgramName; - const isApproved = ReportUtils.isReportApproved(moneyRequestReport); - const isInvoice = ReportUtils.isInvoiceReport(moneyRequestReport); - const isPaidReport = ReportActionsUtils.isPayAction(parentReportAction); + const isApproved = isReportApproved(moneyRequestReport); + const isInvoice = isInvoiceReport(moneyRequestReport); + const isPaidReport = isPayAction(parentReportAction); const taxRates = policy?.taxRates; const formattedTaxAmount = updatedTransaction?.taxAmount - ? CurrencyUtils.convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency) - : CurrencyUtils.convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency); + ? convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency) + : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency); const taxRatesDescription = taxRates?.name; - const taxRateTitle = updatedTransaction ? TransactionUtils.getTaxName(policy, updatedTransaction) : TransactionUtils.getTaxName(policy, transaction); + const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isSettled = isSettledUtil(moneyRequestReport?.reportID); const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU; // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... - const canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(report) && !readonly; - const canEdit = ReportActionsUtils.isMoneyRequestAction(parentReportAction) && ReportUtils.canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; + const canUserPerformWriteAction = !!canUserPerformWriteActionUtil(report) && !readonly; + const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; const canEditTaxFields = canEdit && !isDistanceRequest; - const canEditAmount = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); - const canEditMerchant = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); - const canEditDate = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); - const canEditReceipt = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const hasReceipt = TransactionUtils.hasReceipt(updatedTransaction ?? transaction); - const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(updatedTransaction ?? transaction); - const didReceiptScanSucceed = hasReceipt && TransactionUtils.didReceiptScanSucceed(transaction); - const canEditDistance = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); - const canEditDistanceRate = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); + const canEditAmount = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); + const canEditMerchant = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); + const canEditDate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); + const canEditReceipt = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const hasReceipt = hasReceiptUtil(updatedTransaction ?? transaction); + const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedUtil(updatedTransaction ?? transaction); + const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedUtil(transaction); + const canEditDistance = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); + const canEditDistanceRate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); const isAdmin = policy?.role === 'admin'; - const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; + const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isRequestor = currentUserPersonalDetails.accountID === parentReportAction?.actorAccountID; // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat - const isPolicyExpenseChat = ReportUtils.isReportInGroupPolicy(report); + const isPolicyExpenseChat = isReportInGroupPolicy(report); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTagList), [policyTagList]); + const policyTagLists = useMemo(() => getTagLists(policyTagList), [policyTagList]); const iouType = isTrackExpense ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT; // Flags for showing categories and tags // transactionCategory can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(policyCategories ?? {})); + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || hasEnabledOptions(policyCategories ?? {})); // transactionTag can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowTag = isPolicyExpenseChat && (transactionTag || TagsOptionsListUtils.hasEnabledTags(policyTagLists)); + const shouldShowTag = isPolicyExpenseChat && (transactionTag || hasEnabledTags(policyTagLists)); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable); - const shouldShowAttendees = useMemo(() => TransactionUtils.shouldShowAttendees(iouType, policy), [iouType, policy]); + const shouldShowAttendees = useMemo(() => shouldShowAttendeesUtil(iouType, policy), [iouType, policy]); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); - const tripID = ReportUtils.getTripIDFromTransactionParentReportID(parentReport?.parentReportID); - const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && !!tripID; + const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID); + const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID; - const {getViolationsForField} = useViolations(transactionViolations ?? [], isReceiptBeingScanned || !ReportUtils.isPaidGroupPolicy(report)); + const {getViolationsForField} = useViolations(transactionViolations ?? [], isReceiptBeingScanned || !isPaidGroupPolicy(report)); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean => getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0, @@ -199,15 +229,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const hasRoute = TransactionUtils.hasRoute(transactionBackup ?? transaction, isDistanceRequest); + const hasRoute = hasRouteUtil(transactionBackup ?? transaction, isDistanceRequest); const {unit, rate} = DistanceRequestUtils.getRate({transaction, policy}); - const distance = TransactionUtils.getDistanceInMeters(transactionBackup ?? transaction, unit); + const distance = getDistanceInMeters(transactionBackup ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; const rateToDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : ''; - if (TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { + if (hasReceiptUtil(transaction) && isReceiptBeingScannedUtil(transaction)) { merchantTitle = translate('iou.receiptStatusTitle'); amountTitle = translate('iou.receiptStatusTitle'); } @@ -216,7 +246,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals if (!updatedTransaction) { return undefined; } - return TransactionUtils.getDescription(updatedTransaction ?? null); + return getDescription(updatedTransaction ?? null); }, [updatedTransaction]); const isEmptyUpdatedMerchant = updatedTransaction?.modifiedMerchant === '' || updatedTransaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const updatedMerchantTitle = isEmptyUpdatedMerchant ? '' : updatedTransaction?.modifiedMerchant ?? merchantTitle; @@ -224,10 +254,10 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const saveBillable = useCallback( (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal - if (newBillable === TransactionUtils.getBillable(transaction)) { + if (newBillable === getBillable(transaction)) { return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); + updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); }, [transaction, report, policy, policyTagList, policyCategories], ); @@ -256,9 +286,9 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } let receiptURIs; - const hasErrors = TransactionUtils.hasMissingSmartscanFields(transaction); + const hasErrors = hasMissingSmartscanFields(transaction); if (hasReceipt) { - receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(updatedTransaction ?? transaction); + receiptURIs = getThumbnailAndImageURIs(updatedTransaction ?? transaction); } const pendingAction = transaction?.pendingAction; // Need to return undefined when we have pendingAction to avoid the duplicate pending action @@ -358,7 +388,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const isReceiptAllowed = !isPaidReport && !isInvoice; const shouldShowReceiptEmptyState = - isReceiptAllowed && !hasReceipt && !isApproved && !isSettled && (canEditReceipt || isAdmin || isApprover || isRequestor) && (canEditReceipt || ReportUtils.isPaidGroupPolicy(report)); + isReceiptAllowed && !hasReceipt && !isApproved && !isSettled && (canEditReceipt || isAdmin || isApprover || isRequestor) && (canEditReceipt || isPaidGroupPolicy(report)); const [receiptImageViolations, receiptViolations] = useMemo(() => { const imageViolations = []; @@ -382,8 +412,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals // Whether to show receipt audit result (e.g.`Verified`, `Issue Found`) and messages (e.g. `Receipt not verified. Please confirm accuracy.`) // `!!(receiptViolations.length || didReceiptScanSucceed)` is for not showing `Verified` when `receiptViolations` is empty and `didReceiptScanSucceed` is false. - const shouldShowAuditMessage = - !isReceiptBeingScanned && (hasReceipt || receiptRequiredViolation) && !!(receiptViolations.length || didReceiptScanSucceed) && ReportUtils.isPaidGroupPolicy(report); + const shouldShowAuditMessage = !isReceiptBeingScanned && (hasReceipt || receiptRequiredViolation) && !!(receiptViolations.length || didReceiptScanSucceed) && isPaidGroupPolicy(report); const shouldShowReceiptAudit = isReceiptAllowed && (shouldShowReceiptEmptyState || hasReceipt); const errors = { @@ -392,8 +421,8 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }; const tagList = policyTagLists.map(({name, orderWeight, tags}, index) => { - const tagForDisplay = TransactionUtils.getTagForDisplay(updatedTransaction ?? transaction, index); - const shouldShow = !!tagForDisplay || OptionsListUtils.hasEnabledOptions(tags); + const tagForDisplay = getTagForDisplay(updatedTransaction ?? transaction, index); + const shouldShow = !!tagForDisplay || hasEnabledOptions(tags); if (!shouldShow) { return null; } @@ -404,7 +433,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals tagListIndex: index, tagListName: name, }, - PolicyUtils.hasDependentTags(policy, policyTagList), + hasDependentTags(policy, policyTagList), tagForDisplay, ); return ( @@ -463,17 +492,17 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } if (transaction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - if (chatReport?.reportID && ReportUtils.getAddWorkspaceRoomOrChatReportErrors(chatReport)) { - Report.navigateToConciergeChatAndDeleteReport(chatReport.reportID, true, true); + if (chatReport?.reportID && getAddWorkspaceRoomOrChatReportErrors(chatReport)) { + navigateToConciergeChatAndDeleteReport(chatReport.reportID, true, true); return; } if (parentReportAction) { - IOU.cleanUpMoneyRequest(transaction?.transactionID ?? linkedTransactionID, parentReportAction, true); + cleanUpMoneyRequest(transaction?.transactionID ?? linkedTransactionID, parentReportAction, true); return; } } - Transaction.clearError(transaction?.transactionID ?? linkedTransactionID); - ReportActions.clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); + clearError(transaction?.transactionID ?? linkedTransactionID); + clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); }} > {hasReceipt && ( From 486acb530cb149d4d059afa24c05b368a0c24675 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:22:53 +0530 Subject: [PATCH 15/63] revert unnecessary changes. Signed-off-by: krishna2323 --- src/libs/actions/Transaction.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9baeadff4232..5eb426b0435f 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -433,13 +433,18 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss failureData.push(...failureDataTransaction); failureData.push(...failureReportActions); - const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, - value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, - }, - })); + const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => { + const optimisticDissmidedViolationReportAction = optimisticDissmidedViolationReportActions.at(index); + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID}`, + value: optimisticDissmidedViolationReportAction + ? { + [optimisticDissmidedViolationReportAction.reportActionID]: null, + } + : undefined, + }; + }); // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions // should be correctly linked with their parent report but the BE is sometimes linking report actions to different From 0a0cbc31a67f77e336fb1cda75a201f23be3a70e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:43:29 +0530 Subject: [PATCH 16/63] fix ESLint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 2 -- .../ReportActionItem/ReportPreview.tsx | 2 +- src/libs/actions/ReportActions.ts | 18 ++++++++--------- src/libs/actions/Transaction.ts | 1 - .../DebugTransactionViolations.tsx | 4 ++-- .../DebugTransactionViolationCreatePage.tsx | 8 ++++---- .../DebugTransactionViolationPage.tsx | 8 ++++---- src/pages/TransactionDuplicate/Review.tsx | 20 +++++++++---------- 8 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 0929a9ad8751..372245070380 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,7 +8,6 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {dismissHoldUseExplanation} from '@libs/actions/IOU'; import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; @@ -27,7 +26,6 @@ import { shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 8d1383cd89db..796fbd60a9df 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -237,7 +237,7 @@ function ReportPreview({ hasActionsWithErrors(iouReportID); const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); - const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID, transactionViolations)); + const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID)); const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(allTransactions.at(0)?.transactionID, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? getDescription(allTransactions.at(0)) : null; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 93752c3f02ab..1c3da0d97a85 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,12 +1,12 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import * as ReportActionUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; +import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; -import * as Report from './Report'; +import {deleteReport} from './Report'; type IgnoreDirection = 'parent' | 'child'; @@ -27,7 +27,7 @@ Onyx.connect({ }); function clearReportActionErrors(reportID: string, reportAction: ReportAction, keys?: string[]) { - const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); + const originalReportID = getOriginalReportID(reportID, reportAction); if (!reportAction?.reportActionID) { return; @@ -41,16 +41,16 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k // If there's a linked transaction, delete that too // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID); + const linkedTransactionID = getLinkedTransactionID(reportAction.reportActionID, originalReportID); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); } // Delete the failed task report too - const taskReportID = ReportActionUtils.getReportActionMessage(reportAction)?.taskReportID; - if (taskReportID && ReportActionUtils.isCreatedTaskReportAction(reportAction)) { - Report.deleteReport(taskReportID); + const taskReportID = getReportActionMessage(reportAction)?.taskReportID; + if (taskReportID && isCreatedTaskReportAction(reportAction)) { + deleteReport(taskReportID); } return; } @@ -94,7 +94,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.parentReportID && report?.parentReportActionID && ignore !== 'parent') { - const parentReportAction = ReportActionUtils.getReportAction(report.parentReportID, report.parentReportActionID); + const parentReportAction = getReportAction(report.parentReportID, report.parentReportActionID); const parentErrorKeys = Object.keys(parentReportAction?.errors ?? {}).filter((err) => errorKeys.includes(err)); clearAllRelatedReportActionErrors(report.parentReportID, parentReportAction, 'child', parentErrorKeys); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 5eb426b0435f..29bd11fd2247 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -445,7 +445,6 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss : undefined, }; }); - // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions // should be correctly linked with their parent report but the BE is sometimes linking report actions to different // parent reports than the one we set optimistically, resulting in duplicate report actions. Therefore, we send the BE diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index 5d0c846b40d6..5190d6c76e50 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -6,7 +6,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import ROUTES from '@src/ROUTES'; import type {TransactionViolation} from '@src/types/onyx'; @@ -16,7 +16,7 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 8d9df99a7dfa..452925da8b86 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -9,11 +9,11 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -62,7 +62,7 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); @@ -95,7 +95,7 @@ function DebugTransactionViolationCreatePage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 46e09df43dfc..1b26b0c5f72a 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -6,13 +6,13 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Debug from '@libs/actions/Debug'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import type {DebugTabNavigatorRoutes} from '@libs/Navigation/DebugTabNavigator'; import DebugTabNavigator from '@libs/Navigation/DebugTabNavigator'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -29,7 +29,7 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); const styles = useThemeStyles(); @@ -84,7 +84,7 @@ function DebugTransactionViolationPage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 5f010c4133ac..883b7e2d8031 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -10,13 +10,13 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {dismissDuplicateTransactionViolation} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as Transaction from '@userActions/Transaction'; +import {getLinkedTransactionID, getReportAction} from '@libs/ReportActionsUtils'; +import {isReportApproved, isSettled} from '@libs/ReportUtils'; +import {getTransaction, getTransactionViolations} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -28,9 +28,9 @@ function TransactionDuplicateReview() { const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const transactionID = getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; + const transactionViolations = getTransactionViolations(transactionID); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], @@ -38,14 +38,14 @@ function TransactionDuplicateReview() { ); const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : [...duplicateTransactionIDs]; - const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); + const transactions = transactionIDs.map((item) => getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); const keepAll = () => { - Transaction.dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); + dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); Navigation.goBack(); }; - const hasSettledOrApprovedTransaction = transactions.some((transaction) => ReportUtils.isSettled(transaction?.reportID) || ReportUtils.isReportApproved(transaction?.reportID)); + const hasSettledOrApprovedTransaction = transactions.some((transaction) => isSettled(transaction?.reportID) || isReportApproved(transaction?.reportID)); return ( From 55f73fa15a46123b85a10de9b022ae28db466051 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:49:08 +0530 Subject: [PATCH 17/63] fix ESLint. Signed-off-by: krishna2323 --- .../iou/request/step/IOURequestStepAttendees.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 6672306f5221..2e64e55fb8a1 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -4,10 +4,10 @@ import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getAttendees, getTransactionViolations} from '@libs/TransactionUtils'; import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; -import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -40,19 +40,19 @@ function IOURequestStepAttendees({ }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); - const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); + const [attendees, setAttendees] = useState(() => getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const violations = TransactionUtils.getTransactionViolations(transactionID); + const violations = getTransactionViolations(transactionID); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { return; } if (!lodashIsEqual(previousAttendees, attendees)) { - IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); + setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); + updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); } } From a5194c88a9373051e93197c3fb77b665e084257a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 02:48:20 +0530 Subject: [PATCH 18/63] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 9c6193dfaa9d..e6cdacc81aa8 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -901,14 +901,17 @@ function isDuplicate(transactionID: string | undefined, checkDismissed = false): if (!transactionID) { return false; } - const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( + const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); + + const hasDuplicatedViolation = !!duplicateViolation; if (!checkDismissed) { - return hasDuplicatedViolation; + return !!duplicateViolation; } - const didDismissedViolation = - allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.duplicatedTransaction?.[currentUserEmail] === `${currentUserAccountID}`; + + const didDismissedViolation = isViolationDismissed(transactionID, duplicateViolation); + return hasDuplicatedViolation && !didDismissedViolation; } @@ -937,8 +940,8 @@ function isOnHoldByTransactionID(transactionID: string | undefined | null): bool /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation): boolean { - if (!transactionID) { +function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation | undefined): boolean { + if (!transactionID || !violation) { return false; } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; From f2a9d1989c8663e2773c5de1a71c3f87634d1aeb Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 16 Jan 2025 16:02:23 +0700 Subject: [PATCH 19/63] fix: Nothing happens when clicking on tracking options --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/IOU.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 57951ece5c21..d6d5d5a46226 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8437,7 +8437,7 @@ function createDraftTransactionAndNavigateToParticipantSelector( actionName: IOUAction, reportActionID: string | undefined, ): void { - if (!transactionID || !reportID || !reportActionID) { + if (!transactionID || !reportID) { return; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ac396709b07..11282037f8cf 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -225,7 +225,7 @@ type CategorizeTrackedExpenseReportInformation = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID: string | undefined; linkedTrackedExpenseReportAction: OnyxTypes.ReportAction; linkedTrackedExpenseReportID: string; transactionThreadReportID: string; @@ -4240,7 +4240,7 @@ function trackExpense( switch (action) { case CONST.IOU.ACTION.CATEGORIZE: { - if (!linkedTrackedExpenseReportAction || !actionableWhisperReportActionID || !linkedTrackedExpenseReportID) { + if (!linkedTrackedExpenseReportAction || !linkedTrackedExpenseReportID) { return; } const transactionParams: CategorizeTrackedExpenseTransactionParams = { @@ -4283,7 +4283,7 @@ function trackExpense( break; } case CONST.IOU.ACTION.SHARE: { - if (!linkedTrackedExpenseReportAction || !actionableWhisperReportActionID || !linkedTrackedExpenseReportID) { + if (!linkedTrackedExpenseReportAction || !linkedTrackedExpenseReportID) { return; } shareTrackedExpense( From 332efdf319463b535c92acd4c56b3881d9b06c4b Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 04:45:06 +0530 Subject: [PATCH 20/63] fix merge conflicts. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 4 +- src/components/MoneyRequestHeader.tsx | 8 +-- .../MoneyRequestPreviewContent.tsx | 54 +++++++++---------- .../ReportActionItem/ReportPreview.tsx | 3 +- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 14265030bf75..90f47feb69ad 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminUtil} from '@libs/PolicyUtils'; +import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; import {getTransactionViolations} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; @@ -29,7 +29,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); - const isPolicyAdmin = isPolicyAdminUtil(policy); + const isPolicyAdmin = isPolicyAdminPolicyUtils(policy); if (!brokenConnection530Error && !brokenConnectionError) { return ''; diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 372245070380..68dbd297a177 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -23,7 +23,7 @@ import { isOnHold as isOnHoldUtil, isPending, isReceiptBeingScanned, - shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, + shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -77,10 +77,10 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; + const transactionIDList = transaction ? [transaction.transactionID] : []; + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationUtil(transaction?.transactionID, parentReport, policy); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transaction?.transactionID, parentReport, policy); const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index d59f56ec53a8..4147e3e73e7b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -36,13 +36,13 @@ import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/type import {getAvatarsForAccountIDs} from '@libs/OptionsListUtils'; import {getCleanedTagName, getPolicy} from '@libs/PolicyUtils'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; -import {getOriginalMessage, getReportAction, isMessageDeleted, isMoneyRequestAction as isMoneyRequestActionUtil} from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, isMessageDeleted, isMoneyRequestAction as isMoneyRequestActionReportActionUtils} from '@libs/ReportActionsUtils'; import { getTransactionDetails, getWorkspaceIcon, isPaidGroupPolicy, isPaidGroupPolicyExpenseReport, - isPolicyExpenseChat as isPolicyExpenseChatUtil, + isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isReportApproved, isSettled as isSettledUtil, } from '@libs/ReportUtils'; @@ -52,17 +52,17 @@ import { compareDuplicateTransactionFields, getTransactionViolations, hasMissingSmartscanFields, - hasNoticeTypeViolation, + hasNoticeTypeViolation as hasNoticeTypeViolationTransactionUtils, hasPendingUI, - hasReceipt as hasReceiptUtil, - hasViolation, - hasWarningTypeViolation, - isAmountMissing as isAmountMissingUtil, - isCardTransaction as isCardTransactionUtil, - isDistanceRequest as isDistanceRequestUtil, - isFetchingWaypointsFromServer as isFetchingWaypointsFromServerUtil, - isMerchantMissing as isMerchantMissingUtil, - isOnHold as isOnHoldUtil, + hasReceipt as hasReceiptTransactionUtils, + hasViolation as hasViolationTransactionUtils, + hasWarningTypeViolation as hasWarningTypeViolationTransactionUtils, + isAmountMissing as isAmountMissingTransactionUtils, + isCardTransaction as isCardTransactionTransactionUtils, + isDistanceRequest as isDistanceRequestTransactionUtils, + isFetchingWaypointsFromServer as isFetchingWaypointsFromServerTransactionUtils, + isMerchantMissing as isMerchantMissingTransactionUtils, + isOnHold as isOnHoldTransactionUtils, isPending, isReceiptBeingScanned, removeSettledAndApprovedTransactions, @@ -106,7 +106,7 @@ function MoneyRequestPreviewContent({ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || CONST.DEFAULT_NUMBER_ID}`); const policy = getPolicy(iouReport?.policyID); - const isMoneyRequestAction = isMoneyRequestActionUtil(action); + const isMoneyRequestAction = isMoneyRequestActionReportActionUtils(action); const transactionID = isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : undefined; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); @@ -116,9 +116,9 @@ function MoneyRequestPreviewContent({ const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; const ownerAccountID = iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; - const isPolicyExpenseChat = isPolicyExpenseChatUtil(chatReport); + const isPolicyExpenseChat = isPolicyExpenseChatReportUtils(chatReport); - const participantAccountIDs = isMoneyRequestActionUtil(action) && isBillSplit ? getOriginalMessage(action)?.participantAccountIDs ?? [] : [managerID, ownerAccountID]; + const participantAccountIDs = isMoneyRequestActionReportActionUtils(action) && isBillSplit ? getOriginalMessage(action)?.participantAccountIDs ?? [] : [managerID, ownerAccountID]; const participantAvatars = getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}); const sortedParticipantAvatars = lodashSortBy(participantAvatars, (avatar) => avatar.id); if (isPolicyExpenseChat && isBillSplit) { @@ -139,18 +139,18 @@ function MoneyRequestPreviewContent({ const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const hasReceipt = hasReceiptUtil(transaction); + const hasReceipt = hasReceiptTransactionUtils(transaction); const isScanning = hasReceipt && isReceiptBeingScanned(transaction); - const isOnHold = isOnHoldUtil(transaction); + const isOnHold = isOnHoldTransactionUtils(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = hasWarningTypeViolation(transaction?.transactionID, allViolations, true); + const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true); const hasFieldErrors = hasMissingSmartscanFields(transaction); - const isDistanceRequest = isDistanceRequestUtil(transaction); - const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerUtil(transaction); - const isCardTransaction = isCardTransactionUtil(transaction); + const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); + const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerTransactionUtils(transaction); + const isCardTransaction = isCardTransactionTransactionUtils(transaction); const isSettled = isSettledUtil(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -180,7 +180,7 @@ function MoneyRequestPreviewContent({ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const reviewingTransactionID = isMoneyRequestActionUtil(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; + const reviewingTransactionID = isMoneyRequestActionReportActionUtils(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; /* Show the merchant for IOUs and expenses only if: @@ -247,8 +247,8 @@ function MoneyRequestPreviewContent({ return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } if (hasFieldErrors) { - const isMerchantMissing = isMerchantMissingUtil(transaction); - const isAmountMissing = isAmountMissingUtil(transaction); + const isMerchantMissing = isMerchantMissingTransactionUtils(transaction); + const isAmountMissing = isAmountMissingTransactionUtils(transaction); if (isAmountMissing && isMerchantMissing) { message += ` ${CONST.DOT_SEPARATOR} ${translate('violations.reviewRequired')}`; } else if (isAmountMissing) { @@ -301,7 +301,7 @@ function MoneyRequestPreviewContent({ }; const getDisplayDeleteAmountText = (): string => { - const iouOriginalMessage: OnyxEntry = isMoneyRequestActionUtil(action) ? getOriginalMessage(action) ?? undefined : undefined; + const iouOriginalMessage: OnyxEntry = isMoneyRequestActionReportActionUtils(action) ? getOriginalMessage(action) ?? undefined : undefined; return convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); }; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 796fbd60a9df..6c9219ec05c5 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -238,7 +238,8 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID)); - const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(allTransactions.at(0)?.transactionID, iouReport, policy); + const transactionIDList = [allTransactions.at(0)?.transactionID].filter((transactionID): transactionID is string => transactionID !== undefined); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? getDescription(allTransactions.at(0)) : null; From 1ef4be56b9114ae44ba7a011db5f14bf7a57322e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 04:49:07 +0530 Subject: [PATCH 21/63] minor import fix. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 4147e3e73e7b..510d1bec903c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -44,7 +44,7 @@ import { isPaidGroupPolicyExpenseReport, isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isReportApproved, - isSettled as isSettledUtil, + isSettled as isSettledReportUtils, } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; @@ -151,7 +151,7 @@ function MoneyRequestPreviewContent({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerTransactionUtils(transaction); const isCardTransaction = isCardTransactionTransactionUtils(transaction); - const isSettled = isSettledUtil(iouReport?.reportID); + const isSettled = isSettledReportUtils(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; @@ -258,9 +258,9 @@ function MoneyRequestPreviewContent({ } return message; } - } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID)) { + } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledReportUtils(iouReport?.reportID)) { message += ` • ${translate('violations.reviewRequired')}`; - } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID) && !isPartialHold) { + } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledReportUtils(iouReport?.reportID) && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`; } else if (iouReport?.isCancelledIOU) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.canceled')}`; @@ -410,7 +410,7 @@ function MoneyRequestPreviewContent({ > {displayAmount} - {isSettledUtil(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( + {isSettledReportUtils(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( Date: Fri, 17 Jan 2025 05:03:23 +0530 Subject: [PATCH 22/63] remove duplicate imports. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b89bb61506cf..b3b43aaa6e80 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -23,9 +23,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {clearWalletTermsError} from '@libs/actions/PaymentMethods'; -import {clearIOUError} from '@libs/actions/Report'; -import {abandonReviewDuplicateTransactions, setReviewDuplicatesKey} from '@libs/actions/Transaction'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; From 05747b2d83c33053cd718e831d8e672f6032b106 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 05:07:30 +0530 Subject: [PATCH 23/63] minor fix. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index f3a5a3db51b1..ed5ecf41078a 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -13,8 +13,7 @@ import TextLink from './TextLink'; type BrokenConnectionDescriptionProps = { /** Transaction id of the corresponding report */ - transactionID: string | undefined;https://github.com/Expensify/App/pull/54455/conflicts - + transactionID: string | undefined; /** Current report */ report: OnyxEntry; From d87095684e03a9a912187543f93a3b6ceedde167 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 05:22:32 +0530 Subject: [PATCH 24/63] fix typescript issue. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- src/libs/SearchUIUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 9 ++------- src/libs/actions/IOU.ts | 11 +++-------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 7882308044fc..7a1b08b99378 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -242,7 +242,7 @@ function ReportPreview({ const isArchived = isArchivedReport(iouReport); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const shouldShowSubmitButton = canSubmitReport(iouReport, policy, transactionIDList, transactionViolations); + const shouldShowSubmitButton = canSubmitReport(iouReport, policy, transactionIDList); const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 58dcf4932b71..f8441c274940 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -341,7 +341,7 @@ function getAction(data: OnyxTypes.SearchResults['data'], key: string): SearchTr // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead const transactionIDList = allReportTransactions.map((reportTransaction) => reportTransaction.transactionID); - if (canSubmitReport(report, policy, transactionIDList, allViolations) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, transactionIDList) && isAllowedToApproveExpenseReport) { return CONST.SEARCH.ACTION_TYPES.SUBMIT; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 93aa6b920961..8846de0f067c 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -791,13 +791,8 @@ function hasBrokenConnectionViolation(transactionID?: string): boolean { /** * Check if user should see broken connection violation warning. */ -function shouldShowBrokenConnectionViolation( - transactionIDList: string[] | undefined, - report: OnyxEntry | SearchReport, - policy: OnyxEntry | SearchPolicy, - allViolations?: OnyxCollection, -): boolean { - const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, allViolations)) ?? []; +function shouldShowBrokenConnectionViolation(transactionIDList: string[] | undefined, report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy): boolean { + const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID)) ?? []; return ( transactionsWithBrokenConnectionViolation.length > 0 && transactionsWithBrokenConnectionViolation?.some((value) => value === true) && diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e757ea4c0128..212f7af0d8d3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7446,19 +7446,14 @@ function canIOUBePaid( ); } -function canSubmitReport( - report: OnyxEntry | SearchReport, - policy: OnyxEntry | SearchPolicy, - transactionIDList: string[], - allViolations?: OnyxCollection, -) { +function canSubmitReport(report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, transactionIDList: string[]) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isArchived = isArchivedReport(report); const {reimbursableSpend} = getMoneyRequestSpendBreakdown(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations); - const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy, allViolations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList); + const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy); return ( isOpenExpenseReport && From 89ecbf545feb74b14caa39171a5a3cd6efc9756f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 17 Jan 2025 11:32:00 +0700 Subject: [PATCH 25/63] fix lint --- src/libs/API/parameters/CategorizeTrackedExpenseParams.ts | 2 +- src/libs/API/parameters/ShareTrackedExpenseParams.ts | 2 +- src/libs/actions/IOU.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts index 78eb0adecc5e..149124cd7cae 100644 --- a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts +++ b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts @@ -11,7 +11,7 @@ type CategorizeTrackedExpenseParams = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID?: string; modifiedExpenseReportActionID: string; reportPreviewReportActionID: string; category?: string; diff --git a/src/libs/API/parameters/ShareTrackedExpenseParams.ts b/src/libs/API/parameters/ShareTrackedExpenseParams.ts index cee4bc40d9ac..96f5345885fe 100644 --- a/src/libs/API/parameters/ShareTrackedExpenseParams.ts +++ b/src/libs/API/parameters/ShareTrackedExpenseParams.ts @@ -11,7 +11,7 @@ type ShareTrackedExpenseParams = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID?: string; modifiedExpenseReportActionID: string; reportPreviewReportActionID: string; category?: string; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 11282037f8cf..20f25feb82fb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3723,7 +3723,7 @@ function updateMoneyRequestDistanceRate( const getConvertTrackedExpenseInformation = ( transactionID: string, - actionableWhisperReportActionID: string, + actionableWhisperReportActionID: string | undefined, moneyRequestReportID: string, linkedTrackedExpenseReportAction: OnyxTypes.ReportAction, linkedTrackedExpenseReportID: string, @@ -3893,7 +3893,7 @@ function shareTrackedExpense( moneyRequestPreviewReportActionID: string, moneyRequestReportID: string, moneyRequestCreatedReportActionID: string, - actionableWhisperReportActionID: string, + actionableWhisperReportActionID: string | undefined, linkedTrackedExpenseReportAction: OnyxTypes.ReportAction, linkedTrackedExpenseReportID: string, transactionThreadReportID: string, From 277488056debb1454cac1fbcdb349317effda20c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 09:40:10 +0530 Subject: [PATCH 26/63] minor updates. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 7 +++---- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/ReportActionsUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 8 +++++++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b3b43aaa6e80..017c6b6b15f5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -233,14 +233,13 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } - const firstViolation = violations?.at(0); + const firstViolation = transactionViolations?.at(0); if (firstViolation) { const violationMessage = ViolationsUtils.getViolationTranslation(firstViolation, translate); - const violationsCount = violations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; + const violationsCount = transactionViolations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; @@ -280,7 +279,7 @@ function MoneyRequestPreviewContent({ if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID))) { + if (hasPendingUI(transaction, transactionViolations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 513f33d1a78c..10eb80a09881 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -127,7 +127,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }); const transactionViolations = getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; + const parentReportAction = report?.parentReportActionID ? parentReportActions?.[report?.parentReportActionID] : undefined; const isTrackExpense = isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c105908396ca..cb9d0106926f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1603,7 +1603,7 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { if (!reportID || !transactionID) { - return; + return undefined; } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 8846de0f067c..bb0b487215c3 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -760,7 +760,7 @@ function getTransactionViolations(transactionID: string | undefined): Transactio if (!transactionID) { return null; } - return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null; } /** @@ -971,6 +971,9 @@ function hasViolation(transactionID: string | undefined, transactionViolations: * Checks if any violations for the provided transaction are of type 'notice' */ function hasNoticeTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && @@ -983,6 +986,9 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi * Checks if any violations for the provided transaction are of type 'warning' */ function hasWarningTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( From 078b3fc38efb51db9f9e4430d6ac9ed307b1b14d Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 09:49:54 +0530 Subject: [PATCH 27/63] fix merge conflicts. Signed-off-by: krishna2323 --- .../ReportActionItem/MoneyRequestView.tsx | 38 +++++++++---------- src/libs/TransactionUtils/index.ts | 12 ++++++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 10eb80a09881..4299ba2605ed 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -31,7 +31,7 @@ import {getOriginalMessage, isMoneyRequestAction, isPayAction} from '@libs/Repor import { canEditFieldOfMoneyRequest, canEditMoneyRequest, - canUserPerformWriteAction as canUserPerformWriteActionUtil, + canUserPerformWriteAction as canUserPerformWriteActionReportUtils, getAddWorkspaceRoomOrChatReportErrors, getTransactionDetails, getTripIDFromTransactionParentReportID, @@ -40,13 +40,13 @@ import { isPaidGroupPolicy, isReportApproved, isReportInGroupPolicy, - isSettled as isSettledUtil, + isSettled as isSettledReportUtils, isTrackExpenseReport, } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; import { - didReceiptScanSucceed as didReceiptScanSucceedUtil, + didReceiptScanSucceed as didReceiptScanSucceedTransactionUtils, getBillable, getCardName, getDescription, @@ -55,13 +55,13 @@ import { getTaxName, getTransactionViolations, hasMissingSmartscanFields, - hasReceipt as hasReceiptUtil, + hasReceipt as hasReceiptTransactionUtils, hasReservationList, - hasRoute as hasRouteUtil, - isCardTransaction as isCardTransactionUtil, - isDistanceRequest as isDistanceRequestUtil, - isReceiptBeingScanned as isReceiptBeingScannedUtil, - shouldShowAttendees as shouldShowAttendeesUtil, + hasRoute as hasRouteTransactionUtils, + isCardTransaction as isCardTransactionTransactionUtils, + isDistanceRequest as isDistanceRequestTransactionUtils, + isReceiptBeingScanned as isReceiptBeingScannedTransactionUtils, + shouldShowAttendees as shouldShowAttendeesTransactionUtils, } from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; @@ -154,11 +154,11 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals postedDate: transactionPostedDate, } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const isDistanceRequest = isDistanceRequestUtil(transaction); + const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const formattedTransactionAmount = transactionAmount ? convertToDisplayString(transactionAmount, transactionCurrency) : ''; const formattedPerAttendeeAmount = transactionAmount ? convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); - const isCardTransaction = isCardTransactionUtil(transaction); + const isCardTransaction = isCardTransactionTransactionUtils(transaction); const cardProgramName = getCardName(transaction); const shouldShowCard = isCardTransaction && cardProgramName; const isApproved = isReportApproved(moneyRequestReport); @@ -172,12 +172,12 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const taxRatesDescription = taxRates?.name; const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction); - const isSettled = isSettledUtil(moneyRequestReport?.reportID); + const isSettled = isSettledReportUtils(moneyRequestReport?.reportID); const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU; // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... - const canUserPerformWriteAction = !!canUserPerformWriteActionUtil(report) && !readonly; + const canUserPerformWriteAction = !!canUserPerformWriteActionReportUtils(report) && !readonly; const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; const canEditTaxFields = canEdit && !isDistanceRequest; @@ -185,9 +185,9 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const canEditMerchant = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); const canEditDate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const hasReceipt = hasReceiptUtil(updatedTransaction ?? transaction); - const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedUtil(updatedTransaction ?? transaction); - const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedUtil(transaction); + const hasReceipt = hasReceiptTransactionUtils(updatedTransaction ?? transaction); + const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedTransactionUtils(updatedTransaction ?? transaction); + const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedTransactionUtils(transaction); const canEditDistance = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); const canEditDistanceRate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); @@ -213,7 +213,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const shouldShowTag = isPolicyExpenseChat && (transactionTag || hasEnabledTags(policyTagLists)); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable); - const shouldShowAttendees = useMemo(() => shouldShowAttendeesUtil(iouType, policy), [iouType, policy]); + const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID); @@ -229,7 +229,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const hasRoute = hasRouteUtil(transactionBackup ?? transaction, isDistanceRequest); + const hasRoute = hasRouteTransactionUtils(transactionBackup ?? transaction, isDistanceRequest); const {unit, rate} = DistanceRequestUtils.getRate({transaction, policy}); const distance = getDistanceInMeters(transactionBackup ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; @@ -237,7 +237,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : ''; - if (hasReceiptUtil(transaction) && isReceiptBeingScannedUtil(transaction)) { + if (hasReceiptTransactionUtils(transaction) && isReceiptBeingScannedTransactionUtils(transaction)) { merchantTitle = translate('iou.receiptStatusTitle'); amountTitle = translate('iou.receiptStatusTitle'); } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index bb0b487215c3..484f972299d2 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -959,6 +959,10 @@ function hasViolation(transactionID: string | undefined, transactionViolations: if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && @@ -974,6 +978,10 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && @@ -989,6 +997,10 @@ function hasWarningTypeViolation(transactionID: string | undefined, transactionV if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( From a39a2f5d08fb8e0239aa8073fdfef449bc9c0d6e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 10:02:51 +0530 Subject: [PATCH 28/63] minor import names update. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 224e342be056..193edaa829d0 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -18,9 +18,9 @@ import { getTransactionViolations, hasPendingRTERViolation, hasReceipt, - isDuplicate as isDuplicateUtil, + isDuplicate as isDuplicateTransactionUtils, isExpensifyCardTransaction, - isOnHold as isOnHoldUtil, + isOnHold as isOnHoldTransactionUtils, isPending, isReceiptBeingScanned, shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, @@ -71,8 +71,8 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const isOnHold = isOnHoldUtil(transaction); - const isDuplicate = isDuplicateUtil(transaction?.transactionID); + const isOnHold = isOnHoldTransactionUtils(transaction); + const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; From 93d33a2b50e74c753181a9cb97da87a90c0a54a2 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 24 Jan 2025 09:11:57 +0530 Subject: [PATCH 29/63] add unit tests for violation util functions. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 1 + src/types/onyx/Transaction.ts | 2 +- tests/unit/ViolationUtilsTest.ts | 120 +++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 9a83dc53c67e..ec9752fdf9a7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1484,6 +1484,7 @@ export { getFormattedPostedDate, getCategoryTaxCodeAndAmount, isPerDiemRequest, + isViolationDismissed, }; export type {TransactionChanges}; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 968b9a4dea4b..a573bce74e27 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -82,7 +82,7 @@ type Comment = { splits?: Split[]; /** Violations that were dismissed */ - dismissedViolations?: Record>; + dismissedViolations?: Partial>>; }; /** Model of transaction custom unit */ diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 659240cc0c30..35d6a8034131 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -1,9 +1,12 @@ import {beforeEach} from '@jest/globals'; import Onyx from 'react-native-onyx'; +import {getTransactionViolations, hasWarningTypeViolation, isViolationDismissed} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyTagLists, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction'; +import type {TransactionViolationsCollectionDataSet} from '@src/types/onyx/TransactionViolation'; const categoryOutOfPolicyViolation = { name: CONST.VIOLATIONS.CATEGORY_OUT_OF_POLICY, @@ -30,6 +33,16 @@ const tagOutOfPolicyViolation = { type: CONST.VIOLATION_TYPES.VIOLATION, }; +const smartScanFailedViolation = { + name: CONST.VIOLATIONS.SMARTSCAN_FAILED, + type: CONST.VIOLATION_TYPES.WARNING, +}; + +const duplicatedTransactionViolation = { + name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION, + type: CONST.VIOLATION_TYPES.WARNING, +}; + describe('getViolationsOnyxData', () => { let transaction: Transaction; let transactionViolations: TransactionViolation[]; @@ -349,3 +362,110 @@ describe('getViolationsOnyxData', () => { }); }); }); + +const getFakeTransaction = (transactionID: string, comment?: Transaction['comment']) => ({ + transactionID, + attendees: [{email: 'text@expensify.com'}], + reportID: '1234', + amount: 100, + comment: comment ?? {}, + created: '2023-07-24 13:46:20', + merchant: 'United Airlines', + currency: 'USD', +}); + +const CARLOS_EMAIL = 'cmartins@expensifail.com'; +const CARLOS_ACCOUNT_ID = 1; + +describe('getViolations', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + initialKeyStates: { + [ONYXKEYS.SESSION]: { + email: CARLOS_EMAIL, + accountID: CARLOS_ACCOUNT_ID, + }, + }, + }); + }); + + afterEach(() => Onyx.clear()); + + it('should check if violation is dismissed or not', async () => { + const transaction1 = getFakeTransaction('123', { + dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, + }); + + const transactionCollectionDataSet: TransactionCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + }; + + await Onyx.multiSet({...transactionCollectionDataSet}); + + const isSmartScanDismissed = isViolationDismissed(transaction1.transactionID, smartScanFailedViolation); + const isDuplicateViolationDismissed = isViolationDismissed(transaction1.transactionID, duplicatedTransactionViolation); + + expect(isSmartScanDismissed).toBeTruthy(); + expect(isDuplicateViolationDismissed).toBeFalsy(); + }); + + it('should return filtered out dismissed violations', async () => { + const transaction1 = getFakeTransaction('123', { + dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, + }); + + const transactionCollectionDataSet: TransactionCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + }; + + const transactionViolationsCollection: TransactionViolationsCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], + }; + + await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection}); + + const filteredViolations = getTransactionViolations(transaction1.transactionID); + expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); + }); + + it('should return filtered out dismissed violations', async () => { + const transaction1 = getFakeTransaction('123', { + dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, + }); + + const transactionCollectionDataSet: TransactionCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + }; + + const transactionViolationsCollection: TransactionViolationsCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], + }; + + await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection}); + + // Should filter out the smartScanFailedViolation + const filteredViolations = getTransactionViolations(transaction1.transactionID); + expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); + }); + + it('checks if transaction has notice type violation after filtering dismissed violations', async () => { + const transaction1 = getFakeTransaction('123', { + dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, + }); + + const transactionCollectionDataSet: TransactionCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + }; + + const transactionViolationsCollection = { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], + }; + + await Onyx.multiSet({...transactionCollectionDataSet}); + + // Should filter out the smartScanFailedViolation and return true, duplicatedTransactionViolation is a warning type violation but it's not considered in hasWarningTypeViolation + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction1.transactionID, transactionViolationsCollection); + expect(hasWarningTypeViolationRes).toBeFalsy(); + }); +}); From 8aa6ef02d31ac52ac45fcdb9d66890c53d56f99e Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:37:44 +1300 Subject: [PATCH 30/63] Bump react-native-live-markdown to 0.1.223 --- ios/Podfile.lock | 8 ++++---- package-lock.json | 9 ++++----- package.json | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c674513b9f73..f23b5cdb4e85 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2491,7 +2491,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.210): + - RNLiveMarkdown (0.1.223): - DoubleConversion - glog - hermes-engine @@ -2511,10 +2511,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.210) + - RNLiveMarkdown/newarch (= 0.1.223) - RNReanimated/worklets - Yoga - - RNLiveMarkdown/newarch (0.1.210): + - RNLiveMarkdown/newarch (0.1.223): - DoubleConversion - glog - hermes-engine @@ -3383,7 +3383,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 364e6862a112045bb5c5d35601f0bdb0304af979 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 19826569be35bada5c0f21a0c48b5bc780051501 + RNLiveMarkdown: 5c76c659b125006ff525a095b65184ecb72392f3 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: d184c8d3213acf4c97ec71fbbb6f9d4954552d80 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 diff --git a/package-lock.json b/package-lock.json index 82299434a3b1..23670af1fbe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@dotlottie/react-player": "^1.6.3", "@expensify/react-native-background-task": "file:./modules/background-task", - "@expensify/react-native-live-markdown": "0.1.210", + "@expensify/react-native-live-markdown": "0.1.223", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -3641,10 +3641,9 @@ "link": true }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.210", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.210.tgz", - "integrity": "sha512-CW9DY2yN/QJrqkD6+74s+kWQ9bhWQwd2jT+x5RCgyy5N2SdcoE8G8DGQQvmo6q94KcRkHIr/HsTVOyzACQ/nrw==", - "hasInstallScript": true, + "version": "0.1.223", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.223.tgz", + "integrity": "sha512-rE5cQ9lBDP2tqtR4Tta3PNx2i5K83sdht1meYMvmLPqFVy7C9A743wzZe6oudVnhSDem8MbU4NMJStadp9xn6Q==", "license": "MIT", "workspaces": [ "./example", diff --git a/package.json b/package.json index 393f670098fa..cc58de81b21e 100644 --- a/package.json +++ b/package.json @@ -76,8 +76,8 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.210", "@expensify/react-native-background-task": "file:./modules/background-task", + "@expensify/react-native-live-markdown": "0.1.223", "@expo/metro-runtime": "^4.0.0", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", From 156a3aac5cb7f86979193651bebcba13ba6a745e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 28 Jan 2025 14:09:42 +0530 Subject: [PATCH 31/63] minor NAB fixes. Signed-off-by: krishna2323 --- tests/unit/ViolationUtilsTest.ts | 45 +++++++++----------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 35d6a8034131..b161ea8fe40c 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -393,79 +393,60 @@ describe('getViolations', () => { afterEach(() => Onyx.clear()); it('should check if violation is dismissed or not', async () => { - const transaction1 = getFakeTransaction('123', { + const transaction = getFakeTransaction('123', { dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, }); const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, }; await Onyx.multiSet({...transactionCollectionDataSet}); - const isSmartScanDismissed = isViolationDismissed(transaction1.transactionID, smartScanFailedViolation); - const isDuplicateViolationDismissed = isViolationDismissed(transaction1.transactionID, duplicatedTransactionViolation); + const isSmartScanDismissed = isViolationDismissed(transaction.transactionID, smartScanFailedViolation); + const isDuplicateViolationDismissed = isViolationDismissed(transaction.transactionID, duplicatedTransactionViolation); expect(isSmartScanDismissed).toBeTruthy(); expect(isDuplicateViolationDismissed).toBeFalsy(); }); it('should return filtered out dismissed violations', async () => { - const transaction1 = getFakeTransaction('123', { + const transaction = getFakeTransaction('123', { dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, }); const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, }; const transactionViolationsCollection: TransactionViolationsCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], - }; - - await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection}); - - const filteredViolations = getTransactionViolations(transaction1.transactionID); - expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); - }); - - it('should return filtered out dismissed violations', async () => { - const transaction1 = getFakeTransaction('123', { - dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, - }); - - const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, - }; - - const transactionViolationsCollection: TransactionViolationsCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], }; await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection}); // Should filter out the smartScanFailedViolation - const filteredViolations = getTransactionViolations(transaction1.transactionID); + const filteredViolations = getTransactionViolations(transaction.transactionID); expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); }); - it('checks if transaction has notice type violation after filtering dismissed violations', async () => { - const transaction1 = getFakeTransaction('123', { + it('checks if transaction has warning type violation after filtering dismissed violations', async () => { + const transaction = getFakeTransaction('123', { dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, }); const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction1.transactionID}`]: transaction1, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, }; const transactionViolationsCollection = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction1.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], }; await Onyx.multiSet({...transactionCollectionDataSet}); // Should filter out the smartScanFailedViolation and return true, duplicatedTransactionViolation is a warning type violation but it's not considered in hasWarningTypeViolation - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction1.transactionID, transactionViolationsCollection); + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction.transactionID, transactionViolationsCollection); expect(hasWarningTypeViolationRes).toBeFalsy(); }); }); From ed89588fa58ddf83aaa0c97450265ac0bfd9efa0 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 28 Jan 2025 14:59:45 +0530 Subject: [PATCH 32/63] fix ESLint. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 173279801a34..652e41126de1 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1399,7 +1399,7 @@ function getAllSortedTransactions(iouReportID?: string): Array Date: Wed, 29 Jan 2025 16:00:28 +0530 Subject: [PATCH 33/63] minor fix. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- src/libs/TransactionUtils/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index baecdbf4e495..c9965cd51dcb 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -237,7 +237,7 @@ function ReportPreview({ const lastThreeTransactions = transactions?.slice(-3) ?? []; const lastTransaction = transactions?.at(0); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); - const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, getTransactionViolations(lastTransaction?.transactionID, transactionViolations)); + const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, getTransactionViolations(lastTransaction?.transactionID)); const transactionIDList = [lastTransaction?.transactionID].filter((transactionID): transactionID is string => transactionID !== undefined); const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 8e4970ceec56..ec9752fdf9a7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1484,7 +1484,7 @@ export { getFormattedPostedDate, getCategoryTaxCodeAndAmount, isPerDiemRequest, - isViolationDismissed + isViolationDismissed, }; export type {TransactionChanges}; From e2a084dbbf2f42af2f767accb1651b22525a2184 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 6 Dec 2024 11:52:00 +0100 Subject: [PATCH 34/63] poc: masked input --- ios/Podfile.lock | 41 ++++++++++++++++--- package-lock.json | 18 ++++++++ package.json | 1 + .../SearchFiltersAmountPage.tsx | 9 +++- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b0e84d3d033f..c830f5b97339 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -166,6 +166,7 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (9.1.0) + - ForkInputMask (7.3.3) - FullStory (1.52.0) - fullstory_react-native (1.7.2): - DoubleConversion @@ -1604,6 +1605,28 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-advanced-input-mask (1.1.4): + - DoubleConversion + - ForkInputMask (~> 7.3.2) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-airship (19.2.1): - AirshipFrameworkProxy (= 7.1.2) - DoubleConversion @@ -2880,6 +2903,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-advanced-input-mask (from `../node_modules/react-native-advanced-input-mask`) - "react-native-airship (from `../node_modules/@ua/react-native-airship`)" - react-native-app-logs (from `../node_modules/react-native-app-logs`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) @@ -2968,6 +2992,7 @@ SPEC REPOS: - FirebaseInstallations - FirebasePerformance - FirebaseRemoteConfig + - ForkInputMask - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -3093,6 +3118,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-advanced-input-mask: + :path: "../node_modules/react-native-advanced-input-mask" react-native-airship: :path: "../node_modules/@ua/react-native-airship" react-native-app-logs: @@ -3248,8 +3275,8 @@ SPEC CHECKSUMS: AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 - boost: d7090b1a93a9798c029277a8288114f2948f471c - DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 + boost: 26992d1adf73c1c7676360643e687aee6dda994b + DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 EXAV: 9773c9799767c9925547b05e41a26a0240bb8ef2 EXImageLoader: 759063a65ab016b836f73972d3bb25404888713d expensify-react-native-background-task: 6f797cf470b627912c246514b1631a205794775d @@ -3269,7 +3296,8 @@ SPEC CHECKSUMS: FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b - fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be + fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 + ForkInputMask: 55e3fbab504b22da98483e9f9a6514b98fdd2f3c FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d fullstory_react-native: 63a803cca04b0447a71daa73e4df3f7b56e1919d glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a @@ -3294,7 +3322,7 @@ SPEC CHECKSUMS: onfido-react-native-sdk: 4ccfdeb10f9ccb4a5799d2555cdbc2a068a42c0d Plaid: c32f22ffce5ec67c9e6147eaf6c4d7d5f8086d89 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 + RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 RCTDeprecation: 2c5e1000b04ab70b53956aa498bf7442c3c6e497 RCTRequired: 5f785a001cf68a551c5f5040fb4c415672dbb481 RCTTypeSafety: 6b98db8965005d32449605c0d005ecb4fee8a0f7 @@ -3323,6 +3351,7 @@ SPEC CHECKSUMS: React-logger: 26155dc23db5c9038794db915f80bd2044512c2e React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c + react-native-advanced-input-mask: 5ef08be0877500034332486f29b60fed8b9db670 react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc react-native-app-logs: ee32b6e80bf8d1b883dfc5ac96efa7c1bd9a06a5 react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 @@ -3400,8 +3429,8 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e VisionCamera: c95a8ad535f527562be1fb05fb2fd324578e769c - Yoga: 3deb2471faa9916c8a82dda2a22d3fba2620ad37 + Yoga: f6dc1b6029519815d5516a1241821c6a9074af6d PODFILE CHECKSUM: 6fc95cc1e80a55665a376c27ca23105e1eda8c64 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/package-lock.json b/package-lock.json index f8b051c85f97..eea7dcb1f018 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", + "react-native-advanced-input-mask": "^1.1.4", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", @@ -32027,6 +32028,23 @@ } } }, + "node_modules/react-native-advanced-input-mask": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.1.4.tgz", + "integrity": "sha512-paDcVx4796tjoT6jzuMBJaQE9Pgs7J5RWsrwvgyqn+DjzmRzDZBOoYPRhTmmZ7ufUyptH+9Nw1zmals0SBcQ3Q==", + "license": "MIT", + "workspaces": [ + "example", + "WebExample" + ], + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-android-location-enabler": { "version": "2.0.1", "license": "MIT", diff --git a/package.json b/package.json index 04fc9e7027d1..00cea75c64d8 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", + "react-native-advanced-input-mask": "^1.1.4", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx index 6473da9fc9f5..9260aa99a871 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx @@ -18,6 +18,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; +import TextInputMask from "react-native-text-input-mask"; +import { MaskedTextInput } from 'react-native-advanced-input-mask'; + function SearchFiltersAmountPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -73,7 +76,11 @@ function SearchFiltersAmountPage() { Date: Wed, 15 Jan 2025 20:03:52 +0100 Subject: [PATCH 35/63] feat: continue implementation --- src/components/AmountWithoutCurrencyInput.tsx | 46 +++++++++++++++++++ src/components/RNMaskedTextInput.tsx | 39 ++++++++++++++++ .../BaseTextInput/implementations.ts | 11 +++++ .../TextInput/BaseTextInput/index.native.tsx | 11 +++-- .../TextInput/BaseTextInput/index.tsx | 11 +++-- .../TextInput/BaseTextInput/types.ts | 12 +++-- .../PrivateNotes/PrivateNotesEditPage.tsx | 2 +- src/pages/RoomDescriptionPage.tsx | 2 +- .../SearchFiltersAmountPage.tsx | 12 ++--- .../step/IOURequestStepDescription.tsx | 2 +- src/pages/tasks/NewTaskDescriptionPage.tsx | 2 +- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 2 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 2 +- .../WorkspaceProfileDescriptionPage.tsx | 2 +- 15 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 src/components/AmountWithoutCurrencyInput.tsx create mode 100644 src/components/RNMaskedTextInput.tsx create mode 100644 src/components/TextInput/BaseTextInput/implementations.ts diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx new file mode 100644 index 000000000000..44e4c3d24693 --- /dev/null +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import type {ForwardedRef} from 'react'; +import CONST from '@src/CONST'; +import TextInput from './TextInput'; +import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; + +type AmountFormProps = { + /** Amount supplied by the FormProvider */ + value?: string; + + /** Callback to update the amount in the FormProvider */ + onInputChange?: (value: string) => void; + + /** Should we allow negative number as valid input */ + shouldAllowNegative?: boolean; +} & Partial; + +function AmountWithoutCurrencyForm( + {value: amount, onInputChange, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, + ref: ForwardedRef, +) { + return ( + + ); +} + +AmountWithoutCurrencyForm.displayName = 'AmountWithoutCurrencyForm'; + +export default React.forwardRef(AmountWithoutCurrencyForm); diff --git a/src/components/RNMaskedTextInput.tsx b/src/components/RNMaskedTextInput.tsx new file mode 100644 index 000000000000..22a69d2c7fbd --- /dev/null +++ b/src/components/RNMaskedTextInput.tsx @@ -0,0 +1,39 @@ +import type {ForwardedRef} from 'react'; +import React from 'react'; +import type {TextInput} from 'react-native'; +import type {MaskedTextInputProps} from 'react-native-advanced-input-mask'; +import {MaskedTextInput} from 'react-native-advanced-input-mask'; +import Animated from 'react-native-reanimated'; +import useTheme from '@hooks/useTheme'; + +// Convert the underlying TextInput into an Animated component so that we can take an animated ref and pass it to a worklet +const AnimatedTextInput = Animated.createAnimatedComponent(MaskedTextInput); + +type AnimatedTextInputRef = typeof AnimatedTextInput & TextInput & HTMLInputElement; + +function RNMaskedTextInputWithRef(props: MaskedTextInputProps, ref: ForwardedRef) { + const theme = useTheme(); + + return ( + { + if (typeof ref !== 'function') { + return; + } + ref(refHandle as AnimatedTextInputRef); + }} + // eslint-disable-next-line + {...props} + /> + ); +} + +RNMaskedTextInputWithRef.displayName = 'RNMaskedTextInputWithRef'; + +export default React.forwardRef(RNMaskedTextInputWithRef); +export type {AnimatedTextInputRef}; diff --git a/src/components/TextInput/BaseTextInput/implementations.ts b/src/components/TextInput/BaseTextInput/implementations.ts new file mode 100644 index 000000000000..7c638bf4e265 --- /dev/null +++ b/src/components/TextInput/BaseTextInput/implementations.ts @@ -0,0 +1,11 @@ +import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; +import RNMaskedTextInput from '@components/RNMaskedTextInput'; +import RNTextInput from '@components/RNTextInput'; + +const InputComponentMap = new Map([ + ['default', RNTextInput], + ['mask', RNMaskedTextInput], + ['markdown', RNMarkdownTextInput], +]); + +export default InputComponentMap; diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 8d1800ce3b65..98c074c41713 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -10,9 +10,7 @@ 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'; import TextInputClearButton from '@components/TextInput/TextInputClearButton'; @@ -26,6 +24,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import isInputAutoFilled from '@libs/isInputAutoFilled'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import InputComponentMap from './implementations'; import type {BaseTextInputProps, BaseTextInputRef} from './types'; function BaseTextInput( @@ -62,7 +61,7 @@ function BaseTextInput( prefixCharacter = '', suffixCharacter = '', inputID, - isMarkdownEnabled = false, + type = 'default', excludedMarkdownStyles = [], shouldShowClearButton = false, prefixContainerStyle = [], @@ -71,11 +70,13 @@ function BaseTextInput( suffixStyle = [], contentWidth, loadingSpinnerStyle, + uncontrolled, ...props }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = isMarkdownEnabled ? RNMarkdownTextInput : RNTextInput; + const InputComponent = InputComponentMap.get(type); + const isMarkdownEnabled = type === 'markdown'; const isAutoGrowHeightMarkdown = isMarkdownEnabled && autoGrowHeight; const inputProps = {shouldSaveDraft: false, shouldUseDefaultValue: false, ...props}; @@ -379,7 +380,7 @@ function BaseTextInput( showSoftInputOnFocus={!disableKeyboard} keyboardType={inputProps.keyboardType} inputMode={!disableKeyboard ? inputProps.inputMode : CONST.INPUT_MODE.NONE} - value={value} + value={uncontrolled ? undefined : value} selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 4581338bb38e..43c01f3a14ca 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -10,9 +10,7 @@ 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'; @@ -29,6 +27,7 @@ import {scrollToRight} from '@libs/InputUtils'; import isInputAutoFilled from '@libs/isInputAutoFilled'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import InputComponentMap from './implementations'; import type {BaseTextInputProps, BaseTextInputRef} from './types'; function BaseTextInput( @@ -65,7 +64,7 @@ function BaseTextInput( prefixCharacter = '', suffixCharacter = '', inputID, - isMarkdownEnabled = false, + type = 'default', excludedMarkdownStyles = [], shouldShowClearButton = false, shouldUseDisabledStyles = true, @@ -76,11 +75,13 @@ function BaseTextInput( contentWidth, loadingSpinnerStyle, placeholderTextColor, + uncontrolled = false, ...inputProps }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = isMarkdownEnabled ? RNMarkdownTextInput : RNTextInput; + const InputComponent = InputComponentMap.get(type); + const isMarkdownEnabled = type === 'markdown'; const isAutoGrowHeightMarkdown = isMarkdownEnabled && autoGrowHeight; const theme = useTheme(); @@ -383,7 +384,7 @@ function BaseTextInput( onPressOut={inputProps.onPress} showSoftInputOnFocus={!disableKeyboard} inputMode={inputProps.inputMode} - value={value} + value={uncontrolled ? undefined : value} selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index eef2d471a0d7..20bddd14ab8c 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -3,6 +3,7 @@ import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewSt import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type IconAsset from '@src/types/utils/IconAsset'; +type InputType = 'markdown' | 'mask' | 'default'; type CustomBaseTextInputProps = { /** Input label */ label?: string; @@ -116,9 +117,6 @@ type CustomBaseTextInputProps = { /** Type of autocomplete */ autoCompleteType?: string; - /** Should live markdown be enabled. Changes RNTextInput component to RNMarkdownTextInput */ - isMarkdownEnabled?: boolean; - /** List of markdowns that won't be styled as a markdown */ excludedMarkdownStyles?: Array; @@ -145,10 +143,16 @@ type CustomBaseTextInputProps = { /** The width of inner content */ contentWidth?: number; + + /** The type (internal implementation) of input. Cab one of: `default`, `mask`, `markdown` */ + type?: InputType; + + /** Whether the input should be enforced to be uncontrolled. Default is `false` */ + uncontrolled?: boolean; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; type BaseTextInputProps = CustomBaseTextInputProps & TextInputProps; -export type {BaseTextInputProps, BaseTextInputRef, CustomBaseTextInputProps}; +export type {BaseTextInputProps, BaseTextInputRef, CustomBaseTextInputProps, InputType}; diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index deab122e3006..83543557f36f 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -159,7 +159,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr } privateNotesInput.current = el; }} - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx index 69faef68f766..210f529e566f 100644 --- a/src/pages/RoomDescriptionPage.tsx +++ b/src/pages/RoomDescriptionPage.tsx @@ -118,7 +118,7 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { value={description} onChangeText={handleReportDescriptionChange} autoCapitalize="none" - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx index 9260aa99a871..a813c6333359 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AmountWithoutCurrencyForm from '@components/AmountWithoutCurrencyForm'; +import AmountWithoutCurrencyInput from '@components/AmountWithoutCurrencyInput'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormOnyxValues} from '@components/Form/types'; @@ -18,9 +19,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; -import TextInputMask from "react-native-text-input-mask"; -import { MaskedTextInput } from 'react-native-advanced-input-mask'; - function SearchFiltersAmountPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -76,17 +74,15 @@ function SearchFiltersAmountPage() { console.log('paste', e.nativeEvent)} name={INPUT_IDS.LESS_THAN} defaultValue={lessThanFormattedAmount} label={translate('search.filters.amount.lessThan')} accessibilityLabel={translate('search.filters.amount.lessThan')} role={CONST.ROLE.PRESENTATION} + uncontrolled /> diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index f4a8ee827ff1..4d151d17ba28 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -164,7 +164,7 @@ function IOURequestStepDescription({ autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} shouldSubmitForm - isMarkdownEnabled + type="markdown" excludedMarkdownStyles={!isReportInGroupPolicy ? ['mentionReport'] : []} ref={inputCallbackRef} /> diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index 4b2c08c95fb1..b4bd5851a747 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -91,7 +91,7 @@ function NewTaskDescriptionPage({task, route}: NewTaskDescriptionPageProps) { autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} shouldSubmitForm - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index ffb199c6108d..4ec1964f4316 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -136,7 +136,7 @@ function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) { defaultValue={Parser.htmlToMarkdown(Parser.replace(taskDescription))} value={taskDescription} onValueChange={setTaskDescription} - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index 86a7cbc54a23..80ba61c2368c 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -132,7 +132,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} shouldSubmitForm - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index c64c53306a1f..6fe676dd619b 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -289,7 +289,7 @@ function WorkspaceNewRoomPage() { maxLength={CONST.REPORT_DESCRIPTION.MAX_LENGTH} autoCapitalize="none" shouldInterceptSwipe - isMarkdownEnabled + type="markdown" /> diff --git a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx index f6a9e3649b22..9e5ba5bd12f2 100644 --- a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx +++ b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx @@ -108,7 +108,7 @@ function WorkspaceProfileDescriptionPage({policy}: Props) { autoFocus onChangeText={setDescription} autoGrowHeight - isMarkdownEnabled + type="markdown" ref={(el: BaseTextInputRef | null): void => { if (!isInputInitializedRef.current) { updateMultilineInputRange(el); From 0323f42686e87e94771ab409ac8e9374be103cd1 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 17 Jan 2025 11:59:47 +0100 Subject: [PATCH 36/63] fix: ts/lint --- src/components/AmountWithoutCurrencyInput.tsx | 1 - src/components/TextInput/BaseTextInput/implementations.ts | 7 +++++-- src/components/TextInput/BaseTextInput/index.native.tsx | 3 ++- src/components/TextInput/BaseTextInput/index.tsx | 3 ++- src/components/TextInput/BaseTextInput/types.ts | 8 +++++++- .../SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx | 1 - 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx index 44e4c3d24693..7a8e1deca420 100644 --- a/src/components/AmountWithoutCurrencyInput.tsx +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -31,7 +31,6 @@ function AmountWithoutCurrencyForm( keyboardType={!shouldAllowNegative ? CONST.KEYBOARD_TYPE.DECIMAL_PAD : undefined} type="mask" mask="[09999999].[99]" - customNotations={[{character: '.', characterSet: '.', isOptional: true}, {character: ',', characterSet: ',', isOptional: true}]} // On android autoCapitalize="words" is necessary when keyboardType="decimal-pad" or inputMode="decimal" to prevent input lag. // See https://github.com/Expensify/App/issues/51868 for more information autoCapitalize="words" diff --git a/src/components/TextInput/BaseTextInput/implementations.ts b/src/components/TextInput/BaseTextInput/implementations.ts index 7c638bf4e265..80b91ace7cff 100644 --- a/src/components/TextInput/BaseTextInput/implementations.ts +++ b/src/components/TextInput/BaseTextInput/implementations.ts @@ -1,10 +1,13 @@ import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import RNMaskedTextInput from '@components/RNMaskedTextInput'; import RNTextInput from '@components/RNTextInput'; +import type {BaseTextInputProps, InputType} from './types'; -const InputComponentMap = new Map([ +type InputComponentType = React.ComponentType; + +const InputComponentMap = new Map([ ['default', RNTextInput], - ['mask', RNMaskedTextInput], + ['mask', RNMaskedTextInput as InputComponentType], ['markdown', RNMarkdownTextInput], ]); diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 98c074c41713..af537a981cee 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -11,6 +11,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import type {AnimatedMarkdownTextInputRef} 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'; import TextInputClearButton from '@components/TextInput/TextInputClearButton'; @@ -75,7 +76,7 @@ function BaseTextInput( }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = InputComponentMap.get(type); + const InputComponent = InputComponentMap.get(type) ?? RNTextInput; const isMarkdownEnabled = type === 'markdown'; const isAutoGrowHeightMarkdown = isMarkdownEnabled && autoGrowHeight; diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 43c01f3a14ca..dff9b951cec7 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -11,6 +11,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import type {AnimatedMarkdownTextInputRef} 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'; @@ -80,7 +81,7 @@ function BaseTextInput( }: BaseTextInputProps, ref: ForwardedRef, ) { - const InputComponent = InputComponentMap.get(type); + const InputComponent = InputComponentMap.get(type) ?? RNTextInput; const isMarkdownEnabled = type === 'markdown'; const isAutoGrowHeightMarkdown = isMarkdownEnabled && autoGrowHeight; diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 20bddd14ab8c..9a07b7773463 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -1,5 +1,6 @@ import type {MarkdownStyle} from '@expensify/react-native-live-markdown'; import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; +import type {MaskedTextInputOwnProps} from 'react-native-advanced-input-mask/lib/typescript/src/types'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -120,6 +121,8 @@ type CustomBaseTextInputProps = { /** List of markdowns that won't be styled as a markdown */ excludedMarkdownStyles?: Array; + markdownStyle?: MarkdownStyle; + /** Whether the clear button should be displayed */ shouldShowClearButton?: boolean; @@ -144,9 +147,12 @@ type CustomBaseTextInputProps = { /** The width of inner content */ contentWidth?: number; - /** The type (internal implementation) of input. Cab one of: `default`, `mask`, `markdown` */ + /** The type (internal implementation) of input. Can be one of: `default`, `mask`, `markdown` */ type?: InputType; + /** The mask of the masked input */ + mask?: MaskedTextInputOwnProps['mask']; + /** Whether the input should be enforced to be uncontrolled. Default is `false` */ uncontrolled?: boolean; }; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx index a813c6333359..2f06c4299d78 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx @@ -76,7 +76,6 @@ function SearchFiltersAmountPage() { console.log('paste', e.nativeEvent)} name={INPUT_IDS.LESS_THAN} defaultValue={lessThanFormattedAmount} label={translate('search.filters.amount.lessThan')} From c856778ac58251c2ab70b5b36a4ed4398faa8ada Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 20 Jan 2025 15:19:12 +0100 Subject: [PATCH 37/63] fix: changes before review --- src/components/AmountWithoutCurrencyInput.tsx | 8 ++++---- .../SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx index 7a8e1deca420..a85a7ec05e9b 100644 --- a/src/components/AmountWithoutCurrencyInput.tsx +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -15,7 +15,7 @@ type AmountFormProps = { shouldAllowNegative?: boolean; } & Partial; -function AmountWithoutCurrencyForm( +function AmountWithoutCurrencyInput( {value: amount, onInputChange, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, ref: ForwardedRef, ) { @@ -30,7 +30,7 @@ function AmountWithoutCurrencyForm( ref={ref} keyboardType={!shouldAllowNegative ? CONST.KEYBOARD_TYPE.DECIMAL_PAD : undefined} type="mask" - mask="[09999999].[99]" + mask="[09999999].[09]" // On android autoCapitalize="words" is necessary when keyboardType="decimal-pad" or inputMode="decimal" to prevent input lag. // See https://github.com/Expensify/App/issues/51868 for more information autoCapitalize="words" @@ -40,6 +40,6 @@ function AmountWithoutCurrencyForm( ); } -AmountWithoutCurrencyForm.displayName = 'AmountWithoutCurrencyForm'; +AmountWithoutCurrencyInput.displayName = 'AmountWithoutCurrencyForm'; -export default React.forwardRef(AmountWithoutCurrencyForm); +export default React.forwardRef(AmountWithoutCurrencyInput); diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx index 2f06c4299d78..913387703053 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx @@ -1,7 +1,6 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import AmountWithoutCurrencyForm from '@components/AmountWithoutCurrencyForm'; import AmountWithoutCurrencyInput from '@components/AmountWithoutCurrencyInput'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -62,7 +61,7 @@ function SearchFiltersAmountPage() { > From 975d7f600b7fa6d8e2bf864352ecb105cb0eb1d9 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 20 Jan 2025 15:30:01 +0100 Subject: [PATCH 38/63] fix: resolve merge conflicts --- ios/Podfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c830f5b97339..eea6bb15a18b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3275,8 +3275,8 @@ SPEC CHECKSUMS: AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 - boost: 26992d1adf73c1c7676360643e687aee6dda994b - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + boost: d7090b1a93a9798c029277a8288114f2948f471c + DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 EXAV: 9773c9799767c9925547b05e41a26a0240bb8ef2 EXImageLoader: 759063a65ab016b836f73972d3bb25404888713d expensify-react-native-background-task: 6f797cf470b627912c246514b1631a205794775d @@ -3296,7 +3296,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b - fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 + fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be ForkInputMask: 55e3fbab504b22da98483e9f9a6514b98fdd2f3c FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d fullstory_react-native: 63a803cca04b0447a71daa73e4df3f7b56e1919d @@ -3322,7 +3322,7 @@ SPEC CHECKSUMS: onfido-react-native-sdk: 4ccfdeb10f9ccb4a5799d2555cdbc2a068a42c0d Plaid: c32f22ffce5ec67c9e6147eaf6c4d7d5f8086d89 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 RCTDeprecation: 2c5e1000b04ab70b53956aa498bf7442c3c6e497 RCTRequired: 5f785a001cf68a551c5f5040fb4c415672dbb481 RCTTypeSafety: 6b98db8965005d32449605c0d005ecb4fee8a0f7 @@ -3429,8 +3429,8 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e VisionCamera: c95a8ad535f527562be1fb05fb2fd324578e769c - Yoga: f6dc1b6029519815d5516a1241821c6a9074af6d + Yoga: 3deb2471faa9916c8a82dda2a22d3fba2620ad37 PODFILE CHECKSUM: 6fc95cc1e80a55665a376c27ca23105e1eda8c64 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 From 065fd5f2db61fe6543f08bf07981e0adfcd1ea48 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 20 Jan 2025 15:45:49 +0100 Subject: [PATCH 39/63] fix: eslint --- .../PrivateNotes/PrivateNotesEditPage.tsx | 18 +++++++++--------- src/pages/RoomDescriptionPage.tsx | 10 +++++----- .../request/step/IOURequestStepDescription.tsx | 18 +++++++++--------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index 83543557f36f..42e83dbff6b0 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -18,12 +18,12 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {PrivateNotesNavigatorParamList} from '@libs/Navigation/types'; import Parser from '@libs/Parser'; -import * as ReportUtils from '@libs/ReportUtils'; +import {goBackFromPrivateNotes, navigateToDetailsPage} from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import type {WithReportAndPrivateNotesOrNotFoundProps} from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import variables from '@styles/variables'; -import * as ReportActions from '@userActions/Report'; +import {clearPrivateNotesError, getDraftPrivateNote, handleUserDeletedLinksInHtml, savePrivateNotesDraft, updatePrivateNotes} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -46,7 +46,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr // We need to edit the note in markdown format, but display it in HTML format const [privateNote, setPrivateNote] = useState( - () => ReportActions.getDraftPrivateNote(report.reportID).trim() || Parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), + () => getDraftPrivateNote(report.reportID).trim() || Parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim(), ); /** @@ -56,7 +56,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr const debouncedSavePrivateNote = useMemo( () => lodashDebounce((text: string) => { - ReportActions.savePrivateNotesDraft(report.reportID, text); + savePrivateNotesDraft(report.reportID, text); }, 1000), [report.reportID], ); @@ -85,8 +85,8 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? ''; let editedNote = ''; if (privateNote.trim() !== originalNote.trim()) { - editedNote = ReportActions.handleUserDeletedLinksInHtml(privateNote.trim(), Parser.htmlToMarkdown(originalNote).trim()); - ReportActions.updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); + editedNote = handleUserDeletedLinksInHtml(privateNote.trim(), Parser.htmlToMarkdown(originalNote).trim()); + updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); } // We want to delete saved private note draft after saving the note @@ -94,7 +94,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr Keyboard.dismiss(); if (!Object.values({...report.privateNotes, [route.params.accountID]: {note: editedNote}}).some((item) => item.note)) { - ReportUtils.navigateToDetailsPage(report, backTo); + navigateToDetailsPage(report, backTo); } else { Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID, backTo))); } @@ -108,7 +108,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr > ReportUtils.goBackFromPrivateNotes(report, accountID, backTo)} + onBackButtonPress={() => goBackFromPrivateNotes(report, accountID, backTo)} shouldShowBackButton onCloseButtonPress={() => Navigation.dismissModal()} /> @@ -130,7 +130,7 @@ function PrivateNotesEditPage({route, report, accountID}: PrivateNotesEditPagePr errors={{ ...(report?.privateNotes?.[Number(route.params.accountID)]?.errors ?? ''), }} - onClose={() => ReportActions.clearPrivateNotesError(report.reportID, Number(route.params.accountID))} + onClose={() => clearPrivateNotesError(report.reportID, Number(route.params.accountID))} style={[styles.mb3]} > >(); const backTo = route.params.backTo; const styles = useThemeStyles(); - const [description, setDescription] = useState(() => Parser.htmlToMarkdown(ReportUtils.getReportDescription(report))); + const [description, setDescription] = useState(() => Parser.htmlToMarkdown(getReportDescription(report))); const reportDescriptionInputRef = useRef(null); const focusTimeoutRef = useRef | null>(null); const {translate} = useLocalize(); @@ -58,7 +58,7 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { const previousValue = report?.description ?? ''; const newValue = description.trim(); - Report.updateDescription(report.reportID, previousValue, newValue); + updateDescription(report.reportID, previousValue, newValue); goBack(); }, [report.reportID, report.description, description, goBack]); @@ -76,7 +76,7 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { }, []), ); - const canEdit = ReportUtils.canEditReportDescription(report, policy); + const canEdit = canEditReportDescription(report, policy); return ( CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage( + addErrorMessage( errors, 'moneyRequestComment', translate('common.error.characterLimitExceedCounter', {length: values.moneyRequestComment.length, limit: CONST.DESCRIPTION_LIMIT}), @@ -116,7 +116,7 @@ function IOURequestStepDescription({ navigateBack(); return; } - const isTransactionDraft = IOUUtils.shouldUseTransactionDraft(action); + const isTransactionDraft = shouldUseTransactionDraft(action); IOU.setMoneyRequestDescription(transaction?.transactionID, newComment, isTransactionDraft); @@ -129,9 +129,9 @@ function IOURequestStepDescription({ const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; - const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); + const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !isMoneyRequestAction(reportAction) || !canEditMoneyRequest(reportAction)); const isReportInGroupPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE; return ( From c0e7f99a056f9206dfad1bd83af2c6b216446d38 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 22 Jan 2025 12:22:06 +0100 Subject: [PATCH 40/63] fix: do not allow to enter forbidden characters --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- src/components/AmountWithoutCurrencyInput.tsx | 1 + src/components/TextInput/BaseTextInput/types.ts | 3 +++ 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index eea6bb15a18b..6c45241fff61 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1605,7 +1605,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-advanced-input-mask (1.1.4): + - react-native-advanced-input-mask (1.2.0): - DoubleConversion - ForkInputMask (~> 7.3.2) - glog @@ -3351,7 +3351,7 @@ SPEC CHECKSUMS: React-logger: 26155dc23db5c9038794db915f80bd2044512c2e React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c - react-native-advanced-input-mask: 5ef08be0877500034332486f29b60fed8b9db670 + react-native-advanced-input-mask: 133eb22a94337d3cd99c5e831a4ee306db5ae6e5 react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc react-native-app-logs: ee32b6e80bf8d1b883dfc5ac96efa7c1bd9a06a5 react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 diff --git a/package-lock.json b/package-lock.json index eea7dcb1f018..3a7f790713e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", - "react-native-advanced-input-mask": "^1.1.4", + "react-native-advanced-input-mask": "^1.2.0", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", @@ -32029,9 +32029,9 @@ } }, "node_modules/react-native-advanced-input-mask": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.1.4.tgz", - "integrity": "sha512-paDcVx4796tjoT6jzuMBJaQE9Pgs7J5RWsrwvgyqn+DjzmRzDZBOoYPRhTmmZ7ufUyptH+9Nw1zmals0SBcQ3Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.2.0.tgz", + "integrity": "sha512-Pk7Yau14Zm5PZhJgvDDYOZb224MxzgMv86hiXIZja7QOXaLlWUwm+Ko1s4mW20p1RKIPba7QsRfr9CzXuLDrLg==", "license": "MIT", "workspaces": [ "example", diff --git a/package.json b/package.json index 00cea75c64d8..afad6cdb1168 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", - "react-native-advanced-input-mask": "^1.1.4", + "react-native-advanced-input-mask": "^1.2.0", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx index a85a7ec05e9b..f68f4b102c61 100644 --- a/src/components/AmountWithoutCurrencyInput.tsx +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -31,6 +31,7 @@ function AmountWithoutCurrencyInput( keyboardType={!shouldAllowNegative ? CONST.KEYBOARD_TYPE.DECIMAL_PAD : undefined} type="mask" mask="[09999999].[09]" + allowedKeys="0123456789.," // On android autoCapitalize="words" is necessary when keyboardType="decimal-pad" or inputMode="decimal" to prevent input lag. // See https://github.com/Expensify/App/issues/51868 for more information autoCapitalize="words" diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 9a07b7773463..e01a9a8ce7af 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -153,6 +153,9 @@ type CustomBaseTextInputProps = { /** The mask of the masked input */ mask?: MaskedTextInputOwnProps['mask']; + /** A set of permitted characters for the input */ + allowedKeys?: MaskedTextInputOwnProps['allowedKeys']; + /** Whether the input should be enforced to be uncontrolled. Default is `false` */ uncontrolled?: boolean; }; From f1dea18f63c97d8a578148404d6e2c8ab35c40da Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 23 Jan 2025 11:46:46 +0100 Subject: [PATCH 41/63] fix: eslint --- .../step/IOURequestStepDescription.tsx | 8 ++--- src/pages/tasks/NewTaskDescriptionPage.tsx | 12 ++++---- src/pages/tasks/NewTaskDetailsPage.tsx | 29 +++++++------------ src/pages/tasks/TaskDescriptionPage.tsx | 4 +-- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index 4e8f55f2b420..7006b66ed3b9 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -18,7 +18,7 @@ import {isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {canEditMoneyRequest} from '@libs/ReportUtils'; import {areRequiredFieldsEmpty} from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import * as IOU from '@userActions/IOU'; +import {setDraftSplitTransaction, setMoneyRequestDescription, updateMoneyRequestDescription} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -112,16 +112,16 @@ function IOURequestStepDescription({ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { - IOU.setDraftSplitTransaction(transaction?.transactionID, {comment: newComment}); + setDraftSplitTransaction(transaction?.transactionID, {comment: newComment}); navigateBack(); return; } const isTransactionDraft = shouldUseTransactionDraft(action); - IOU.setMoneyRequestDescription(transaction?.transactionID, newComment, isTransactionDraft); + setMoneyRequestDescription(transaction?.transactionID, newComment, isTransactionDraft); if (action === CONST.IOU.ACTION.EDIT) { - IOU.updateMoneyRequestDescription(transaction?.transactionID, reportID, newComment, policy, policyTags, policyCategories); + updateMoneyRequestDescription(transaction?.transactionID, reportID, newComment, policy, policyTags, policyCategories); } navigateBack(); diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index b4bd5851a747..e70b6ea15d20 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -11,15 +11,15 @@ import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {addErrorMessage} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {NewTaskNavigatorParamList} from '@libs/Navigation/types'; import Parser from '@libs/Parser'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getCommentLength} from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import variables from '@styles/variables'; -import * as TaskActions from '@userActions/Task'; +import {setDescriptionValue} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -41,15 +41,15 @@ function NewTaskDescriptionPage({task, route}: NewTaskDescriptionPageProps) { const goBack = () => Navigation.goBack(ROUTES.NEW_TASK.getRoute(route.params?.backTo)); const onSubmit = (values: FormOnyxValues) => { - TaskActions.setDescriptionValue(values.taskDescription); + setDescriptionValue(values.taskDescription); goBack(); }; const validate = (values: FormOnyxValues): FormInputErrors => { const errors = {}; - const taskDescriptionLength = ReportUtils.getCommentLength(values.taskDescription); + const taskDescriptionLength = getCommentLength(values.taskDescription); if (taskDescriptionLength > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskDescription', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); + addErrorMessage(errors, 'taskDescription', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); } return errors; diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 4ec1964f4316..65f78ac94e49 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -11,15 +11,15 @@ import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {addErrorMessage} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {NewTaskNavigatorParamList} from '@libs/Navigation/types'; import Parser from '@libs/Parser'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getCommentLength} from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import variables from '@styles/variables'; -import * as TaskActions from '@userActions/Task'; +import {createTaskAndNavigate, dismissModalAndClearOutTaskInfo, setDetailsValue, setShareDestinationValue} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -56,13 +56,13 @@ function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) { if (!values.taskTitle) { // We error if the user doesn't enter a task name - ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('newTaskPage.pleaseEnterTaskName')); + addErrorMessage(errors, 'taskTitle', translate('newTaskPage.pleaseEnterTaskName')); } else if (values.taskTitle.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); + addErrorMessage(errors, 'taskTitle', translate('common.error.characterLimitExceedCounter', {length: values.taskTitle.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } - const taskDescriptionLength = ReportUtils.getCommentLength(values.taskDescription); + const taskDescriptionLength = getCommentLength(values.taskDescription); if (taskDescriptionLength > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'taskDescription', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); + addErrorMessage(errors, 'taskDescription', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); } return errors; @@ -71,19 +71,12 @@ function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) { // On submit, we want to call the assignTask function and wait to validate // the response const onSubmit = (values: FormOnyxValues) => { - TaskActions.setDetailsValue(values.taskTitle, values.taskDescription); + setDetailsValue(values.taskTitle, values.taskDescription); if (skipConfirmation) { - TaskActions.setShareDestinationValue(task?.parentReportID ?? '-1'); + setShareDestinationValue(task?.parentReportID ?? '-1'); playSound(SOUNDS.DONE); - TaskActions.createTaskAndNavigate( - task?.parentReportID ?? '-1', - values.taskTitle, - values.taskDescription ?? '', - task?.assignee ?? '', - task.assigneeAccountID, - task.assigneeChatReport, - ); + createTaskAndNavigate(task?.parentReportID ?? '-1', values.taskTitle, values.taskDescription ?? '', task?.assignee ?? '', task.assigneeAccountID, task.assigneeChatReport); } else { Navigation.navigate(ROUTES.NEW_TASK.getRoute(backTo)); } @@ -98,7 +91,7 @@ function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) { TaskActions.dismissModalAndClearOutTaskInfo(backTo)} + onBackButtonPress={() => dismissModalAndClearOutTaskInfo(backTo)} /> CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); + addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); } return errors; From 04b9271bf17e2ed3e1410bef577d560c86952687 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 23 Jan 2025 12:08:51 +0100 Subject: [PATCH 42/63] fix: eslint --- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 18 +++---- src/pages/workspace/WorkspaceNewRoomPage.tsx | 54 +++++++++----------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 65f78ac94e49..f225727f10c3 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -74,7 +74,7 @@ function NewTaskDetailsPage({task, route}: NewTaskDetailsPageProps) { setDetailsValue(values.taskTitle, values.taskDescription); if (skipConfirmation) { - setShareDestinationValue(task?.parentReportID ?? '-1'); + setShareDestinationValue(task?.parentReportID); playSound(SOUNDS.DONE); createTaskAndNavigate(task?.parentReportID ?? '-1', values.taskTitle, values.taskDescription ?? '', task?.assignee ?? '', task.assigneeAccountID, task.assigneeChatReport); } else { diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index b90a37a59db2..9fdf1757ca96 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -18,12 +18,12 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types'; import Parser from '@libs/Parser'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getCommentLength, getParsedComment, isOpenTaskReport, isTaskReport} from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound'; import variables from '@styles/variables'; -import * as Task from '@userActions/Task'; +import {canModifyTask, editTask} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -40,8 +40,8 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { const errors = {}; - const parsedDescription = ReportUtils.getParsedComment(values?.description); - const taskDescriptionLength = ReportUtils.getCommentLength(parsedDescription); + const parsedDescription = getParsedComment(values?.description); + const taskDescriptionLength = getCommentLength(parsedDescription); if (values?.description && taskDescriptionLength > CONST.DESCRIPTION_LIMIT) { addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: taskDescriptionLength, limit: CONST.DESCRIPTION_LIMIT})); } @@ -56,7 +56,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti if (values.description !== Parser.htmlToMarkdown(report?.description ?? '') && !isEmptyObject(report)) { // Set the description of the report in the store and then call EditTask API // to update the description of the report on the server - Task.editTask(report, {description: values.description}); + editTask(report, {description: values.description}); } Navigation.dismissModal(report?.reportID); @@ -64,7 +64,7 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti [report], ); - if (!ReportUtils.isTaskReport(report)) { + if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { Navigation.dismissModal(report?.reportID); }); @@ -72,9 +72,9 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti const inputRef = useRef(null); const focusTimeoutRef = useRef(null); - const isOpen = ReportUtils.isOpenTaskReport(report); - const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); - const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen); + const isOpen = isOpenTaskReport(report); + const canActuallyModifyTask = canModifyTask(report, currentUserPersonalDetails.accountID); + const isTaskNonEditable = isTaskReport(report) && (!canActuallyModifyTask || !isOpen); useFocusEffect( useCallback(() => { diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 6fe676dd619b..fd3cc90682f9 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -24,14 +24,14 @@ import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {addErrorMessage} from '@libs/ErrorUtils'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {getActivePolicies} from '@libs/PolicyUtils'; +import {buildOptimisticChatReport, getCommentLength, getParsedComment, isPolicyAdmin} from '@libs/ReportUtils'; +import {isExistingRoomName, isReservedRoomName, isValidRoomName} from '@libs/ValidationUtils'; import variables from '@styles/variables'; -import * as Report from '@userActions/Report'; +import {addPolicyReport, clearNewRoomFormError} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -64,7 +64,7 @@ function WorkspaceNewRoomPage() { const workspaceOptions = useMemo( () => - PolicyUtils.getActivePolicies(policies, session?.email) + getActivePolicies(policies, session?.email) ?.filter((policy) => policy.type !== CONST.POLICY.TYPE.PERSONAL) .map((policy) => ({ label: policy.name, @@ -79,12 +79,12 @@ function WorkspaceNewRoomPage() { } return ''; }); - const isPolicyAdmin = useMemo(() => { + const isAdminPolicy = useMemo(() => { if (!policyID) { return false; } - return ReportUtils.isPolicyAdmin(policyID, policies); + return isPolicyAdmin(policyID, policies); }, [policyID, policies]); const [newRoomReportID, setNewRoomReportID] = useState(); @@ -93,8 +93,8 @@ function WorkspaceNewRoomPage() { */ const submit = (values: FormOnyxValues) => { const participants = [session?.accountID ?? -1]; - const parsedDescription = ReportUtils.getParsedComment(values.reportDescription ?? '', {policyID}); - const policyReport = ReportUtils.buildOptimisticChatReport( + const parsedDescription = getParsedComment(values.reportDescription ?? '', {policyID}); + const policyReport = buildOptimisticChatReport( participants, values.roomName, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, @@ -110,11 +110,11 @@ function WorkspaceNewRoomPage() { parsedDescription, ); setNewRoomReportID(policyReport.reportID); - Report.addPolicyReport(policyReport); + addPolicyReport(policyReport); }; useEffect(() => { - Report.clearNewRoomFormError(); + clearNewRoomFormError(); }, []); useEffect(() => { @@ -140,12 +140,12 @@ function WorkspaceNewRoomPage() { }, [isLoading, errorFields]); useEffect(() => { - if (isPolicyAdmin) { + if (isAdminPolicy) { return; } setWriteCapability(CONST.REPORT.WRITE_CAPABILITIES.ALL); - }, [isPolicyAdmin]); + }, [isAdminPolicy]); /** * @param values - form input values passed by the Form component @@ -157,27 +157,23 @@ function WorkspaceNewRoomPage() { if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { // We error if the user doesn't enter a room name or left blank - ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.pleaseEnterRoomName')); - } else if (values.roomName !== CONST.POLICY.ROOM_PREFIX && !ValidationUtils.isValidRoomName(values.roomName)) { + addErrorMessage(errors, 'roomName', translate('newRoomPage.pleaseEnterRoomName')); + } else if (values.roomName !== CONST.POLICY.ROOM_PREFIX && !isValidRoomName(values.roomName)) { // We error if the room name has invalid characters - ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameInvalidError')); - } else if (ValidationUtils.isReservedRoomName(values.roomName)) { + addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameInvalidError')); + } else if (isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameReservedError', {reservedName: values.roomName})); - } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, values.policyID ?? '-1')) { + addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameReservedError', {reservedName: values.roomName})); + } else if (isExistingRoomName(values.roomName, reports, values.policyID ?? '-1')) { // Certain names are reserved for default rooms and should not be used for policy rooms. - ErrorUtils.addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); + addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); } else if (values.roomName.length > CONST.TITLE_CHARACTER_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'roomName', translate('common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); + addErrorMessage(errors, 'roomName', translate('common.error.characterLimitExceedCounter', {length: values.roomName.length, limit: CONST.TITLE_CHARACTER_LIMIT})); } - const descriptionLength = ReportUtils.getCommentLength(values.reportDescription, {policyID}); + const descriptionLength = getCommentLength(values.reportDescription, {policyID}); if (descriptionLength > CONST.REPORT_DESCRIPTION.MAX_LENGTH) { - ErrorUtils.addErrorMessage( - errors, - 'reportDescription', - translate('common.error.characterLimitExceedCounter', {length: descriptionLength, limit: CONST.REPORT_DESCRIPTION.MAX_LENGTH}), - ); + addErrorMessage(errors, 'reportDescription', translate('common.error.characterLimitExceedCounter', {length: descriptionLength, limit: CONST.REPORT_DESCRIPTION.MAX_LENGTH})); } if (!values.policyID) { @@ -302,7 +298,7 @@ function WorkspaceNewRoomPage() { onValueChange={(value) => setPolicyID(value as typeof policyID)} /> - {isPolicyAdmin && ( + {isAdminPolicy && ( Date: Thu, 23 Jan 2025 12:26:18 +0100 Subject: [PATCH 43/63] fix: eslint --- src/pages/workspace/WorkspaceNewRoomPage.tsx | 4 ++-- src/pages/workspace/WorkspaceProfileDescriptionPage.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index fd3cc90682f9..0ce996d1a2e6 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -92,7 +92,7 @@ function WorkspaceNewRoomPage() { * @param values - form input values passed by the Form component */ const submit = (values: FormOnyxValues) => { - const participants = [session?.accountID ?? -1]; + const participants = [session?.accountID ?? CONST.DEFAULT_NUMBER_ID]; const parsedDescription = getParsedComment(values.reportDescription ?? '', {policyID}); const policyReport = buildOptimisticChatReport( participants, @@ -164,7 +164,7 @@ function WorkspaceNewRoomPage() { } else if (isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. addErrorMessage(errors, 'roomName', translate('newRoomPage.roomNameReservedError', {reservedName: values.roomName})); - } else if (isExistingRoomName(values.roomName, reports, values.policyID ?? '-1')) { + } else if (isExistingRoomName(values.roomName, reports, values.policyID)) { // Certain names are reserved for default rooms and should not be used for policy rooms. addErrorMessage(errors, 'roomName', translate('newRoomPage.roomAlreadyExistsError')); } else if (values.roomName.length > CONST.TITLE_CHARACTER_LIMIT) { diff --git a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx index 9e5ba5bd12f2..2c5d87df2a25 100644 --- a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx +++ b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx @@ -9,12 +9,12 @@ import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {addErrorMessage} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import Parser from '@libs/Parser'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import variables from '@styles/variables'; -import * as Policy from '@userActions/Policy/Policy'; +import {updateWorkspaceDescription} from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper'; @@ -49,7 +49,7 @@ function WorkspaceProfileDescriptionPage({policy}: Props) { const errors = {}; if (values.description.length > CONST.DESCRIPTION_LIMIT) { - ErrorUtils.addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); + addErrorMessage(errors, 'description', translate('common.error.characterLimitExceedCounter', {length: values.description.length, limit: CONST.DESCRIPTION_LIMIT})); } return errors; @@ -63,7 +63,7 @@ function WorkspaceProfileDescriptionPage({policy}: Props) { return; } - Policy.updateWorkspaceDescription(policy.id, values.description.trim(), policy.description ?? ''); + updateWorkspaceDescription(policy.id, values.description.trim(), policy.description ?? ''); Keyboard.dismiss(); Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack()); }, From 3a2e53441092b2b6f11f600d4fed9c9e6b28836b Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 23 Jan 2025 13:30:13 +0100 Subject: [PATCH 44/63] fix: not firing onChange handler --- src/components/AmountWithoutCurrencyInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx index f68f4b102c61..4d54258dbef0 100644 --- a/src/components/AmountWithoutCurrencyInput.tsx +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -16,7 +16,7 @@ type AmountFormProps = { } & Partial; function AmountWithoutCurrencyInput( - {value: amount, onInputChange, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, + {value: amount, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, ref: ForwardedRef, ) { return ( From c2bbcc5f7d75a9744f96229f3874101bbb426477 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 24 Jan 2025 16:13:10 +0100 Subject: [PATCH 45/63] fix: review comments --- src/components/TextInput/BaseTextInput/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index e01a9a8ce7af..ced78a5bb4e8 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -121,6 +121,7 @@ type CustomBaseTextInputProps = { /** List of markdowns that won't be styled as a markdown */ excludedMarkdownStyles?: Array; + /** A set of styles for markdown elements (such as link, h1, emoji etc.) */ markdownStyle?: MarkdownStyle; /** Whether the clear button should be displayed */ From 298f8175c5d12fc2a43cfb60d3d6fb83f72523c4 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 29 Jan 2025 18:11:59 +0100 Subject: [PATCH 46/63] fix: display default value --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- src/components/Form/FormProvider.tsx | 3 ++- src/components/Form/types.ts | 1 + 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6c45241fff61..3e2e603c95cc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1605,7 +1605,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-advanced-input-mask (1.2.0): + - react-native-advanced-input-mask (1.2.1): - DoubleConversion - ForkInputMask (~> 7.3.2) - glog @@ -3351,7 +3351,7 @@ SPEC CHECKSUMS: React-logger: 26155dc23db5c9038794db915f80bd2044512c2e React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c - react-native-advanced-input-mask: 133eb22a94337d3cd99c5e831a4ee306db5ae6e5 + react-native-advanced-input-mask: 22e3bd2a0f38fada50b475c98bf39d39053097a3 react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc react-native-app-logs: ee32b6e80bf8d1b883dfc5ac96efa7c1bd9a06a5 react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 diff --git a/package-lock.json b/package-lock.json index 3a7f790713e6..6f294fdcad54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", - "react-native-advanced-input-mask": "^1.2.0", + "react-native-advanced-input-mask": "1.2.1", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", @@ -32029,9 +32029,9 @@ } }, "node_modules/react-native-advanced-input-mask": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.2.0.tgz", - "integrity": "sha512-Pk7Yau14Zm5PZhJgvDDYOZb224MxzgMv86hiXIZja7QOXaLlWUwm+Ko1s4mW20p1RKIPba7QsRfr9CzXuLDrLg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-advanced-input-mask/-/react-native-advanced-input-mask-1.2.1.tgz", + "integrity": "sha512-qXK6l8f5zOLrWxhrtA2od4R2UsV8OEcvFlZlX5VTp3sB/JlHW/iJd15m8Rgn/mcJFfvnKlHmVVHJefDrUOJFvA==", "license": "MIT", "workspaces": [ "example", diff --git a/package.json b/package.json index afad6cdb1168..3b821385219f 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "react-fast-pdf": "^1.0.22", "react-map-gl": "^7.1.3", "react-native": "0.76.3", - "react-native-advanced-input-mask": "^1.2.0", + "react-native-advanced-input-mask": "1.2.1", "react-native-android-location-enabler": "^2.0.1", "react-native-app-logs": "0.3.1", "react-native-blob-util": "0.19.4", diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 0b84f0034035..bf3746b61776 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -326,7 +326,8 @@ function FormProvider( value: inputValues[inputID], // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. - defaultValue: undefined, + // If it's uncontrolled, then we set the `defaultValue` prop to actual value + defaultValue: inputProps.uncontrolled ? inputProps.defaultValue : undefined, onTouched: (event) => { if (!inputProps.shouldSetTouchedOnBlurOnly) { setTimeout(() => { diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index d6bcc28e09bf..02cc4e899b32 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -118,6 +118,7 @@ type InputComponentBaseProps = Input autoGrowHeight?: boolean; blurOnSubmit?: boolean; shouldSubmitForm?: boolean; + uncontrolled?: boolean; }; type FormOnyxValues = Omit; From e79c90bc01126c9347dddba23a44ac06f33bac3c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 30 Jan 2025 14:19:20 +0530 Subject: [PATCH 47/63] fix TS issues. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- src/libs/SearchUIUtils.ts | 2 +- src/libs/actions/IOU.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index d180eacd1c61..6dd6144c7203 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -250,7 +250,7 @@ function ReportPreview({ const isArchived = isArchivedReportWithID(iouReport?.reportID); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const filteredTransactions = transactions?.filter((transaction) => transaction) ?? []; - const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, transactionViolations); + const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions); const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 4721f7c76451..dcd859cb4c10 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -363,7 +363,7 @@ function getAction(data: OnyxTypes.SearchResults['data'], key: string): SearchTr } // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead - if (canSubmitReport(report, policy, allReportTransactions, allViolations) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, allReportTransactions) && isAllowedToApproveExpenseReport) { return CONST.SEARCH.ACTION_TYPES.SUBMIT; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e595bd181aee..a0f2814ea4a0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7918,15 +7918,14 @@ function canSubmitReport( report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, transactions: OnyxTypes.Transaction[] | SearchTransaction[], - allViolations?: OnyxCollection, ) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isArchived = isArchivedReportWithID(report?.reportID); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const transactionIDList = transactions.map((transaction) => transaction.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations); - const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy, allViolations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList); + const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy); const hasOnlyPendingCardOrScanFailTransactions = transactions.length > 0 && From f21052ff23b29094fcfe4c47eccd391da8fa23d1 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 30 Jan 2025 16:33:20 +0300 Subject: [PATCH 48/63] fix canAddOrDeleteTransactions logic --- src/libs/ReportUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5e50f69e28fa..028c2087c79e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2007,14 +2007,12 @@ function getChildReportNotificationPreference(reportAction: OnyxInputOrEntry): boolean { + if (moneyRequestReport?.reportID == '4070865872455813') debugger; if (!isMoneyRequestReport(moneyRequestReport) || isArchivedReportWithID(moneyRequestReport?.reportID)) { return false; } const policy = getPolicy(moneyRequestReport?.policyID); - if (isInstantSubmitEnabled(policy) && isSubmitAndClose(policy) && hasOnlyNonReimbursableTransactions(moneyRequestReport?.reportID)) { - return false; - } if (isInstantSubmitEnabled(policy) && isSubmitAndClose(policy) && !arePaymentsEnabled(policy)) { return false; @@ -2047,6 +2045,11 @@ function canAddTransaction(moneyRequestReport: OnyxEntry): boolean { return false; } + const policy = getPolicy(moneyRequestReport?.policyID); + if (isInstantSubmitEnabled(policy) && isSubmitAndClose(policy) && hasOnlyNonReimbursableTransactions(moneyRequestReport?.reportID)) { + return false; + } + return canAddOrDeleteTransactions(moneyRequestReport); } From cc23003a09835e0af5e3ffd50dad645b06703dd6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 30 Jan 2025 17:29:52 +0300 Subject: [PATCH 49/63] minor fix --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 028c2087c79e..ae5fc41c063d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2007,7 +2007,6 @@ function getChildReportNotificationPreference(reportAction: OnyxInputOrEntry): boolean { - if (moneyRequestReport?.reportID == '4070865872455813') debugger; if (!isMoneyRequestReport(moneyRequestReport) || isArchivedReportWithID(moneyRequestReport?.reportID)) { return false; } From a482d4e995274aca921549a26c1224077dbe6f5e Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 31 Jan 2025 11:23:18 +0700 Subject: [PATCH 50/63] refactor: change functions names --- src/libs/Navigation/Navigation.ts | 8 ++++---- src/libs/ReportUtils.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 85dfcdded31f..f68eaf5d57b0 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -428,7 +428,7 @@ function getTopMostCentralPaneRouteFromRootState() { return getTopmostCentralPaneRoute(navigationRef.getRootState() as State); } -function getPreviousTrackReport(reportID?: string) { +function getReportRouteByID(reportID?: string) { if (!reportID) { return null; } @@ -449,7 +449,7 @@ function removeScreenFromNavigationState(screen: Screen) { }); } -function removeScreenByKey(key: string) { +function removeScreenFromNavigationStateByKey(key: string) { const state = navigationRef.getRootState(); const routes = state.routes.filter((item) => item.key !== key); @@ -487,8 +487,8 @@ export default { setNavigationActionToMicrotaskQueue, getTopMostCentralPaneRouteFromRootState, removeScreenFromNavigationState, - getPreviousTrackReport, - removeScreenByKey, + getReportRouteByID, + removeScreenFromNavigationStateByKey, }; export {navigationRef}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 35fcd639b2fb..8747ce8fd80f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4437,7 +4437,7 @@ function goBackToDetailsPage(report: OnyxEntry, backTo?: string) { } } -function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean, reportID?: string) { +function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean, reportIDToRemove?: string) { if (!backRoute) { return; } @@ -4447,10 +4447,11 @@ function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP return; } if (isFromRHP) { - if (reportID) { - const trackReport = Navigation.getPreviousTrackReport(reportID); - if (trackReport?.key) { - Navigation.removeScreenByKey(trackReport.key); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportIDToRemove}`]; + if (report && isTrackExpenseReport(report)) { + const trackReportRoute = Navigation.getReportRouteByID(reportIDToRemove); + if (trackReportRoute?.key) { + Navigation.removeScreenFromNavigationStateByKey(trackReportRoute.key); } } Navigation.isNavigationReady().then(() => Navigation.dismissModal()); From e10d4f21126df41f3fee08d846677356ffa4fb6b Mon Sep 17 00:00:00 2001 From: jayeshmangwani Date: Fri, 31 Jan 2025 12:06:45 +0530 Subject: [PATCH 51/63] updated spanish copy to remove duplicate plan name --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 70c2b16975db..a4a1f291988e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4523,7 +4523,7 @@ const translations = { benefit3: 'Flujos de aprobación de varios niveles', benefit4: 'Controles de seguridad mejorados', toUpgrade: 'Para mejorar, haz clic', - selectWorkspace: 'selecciona un espacio de trabajo y cambia el tipo de plan a Controlar.', + selectWorkspace: 'selecciona un espacio de trabajo y cambia el tipo de plan a', }, }, }, @@ -4543,7 +4543,7 @@ const translations = { headsUp: '¡Atención!', multiWorkspaceNote: 'Tendrás que bajar de categoría todos tus espacios de trabajo antes de tu primer pago mensual para comenzar una suscripción con la tasa del plan Recopilar. Haz clic', - selectStep: '> selecciona cada espacio de trabajo > cambia el tipo de plan a Recopilar.', + selectStep: '> selecciona cada espacio de trabajo > cambia el tipo de plan a', }, }, completed: { From fbe9c896738f2a7a123e46199a1823d0063667d4 Mon Sep 17 00:00:00 2001 From: jayeshmangwani Date: Fri, 31 Jan 2025 15:32:34 +0530 Subject: [PATCH 52/63] updated spanish copies for upgrade/downgrade modal note --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a4a1f291988e..6cd0b4473eec 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4522,7 +4522,7 @@ const translations = { benefit2: 'Reglas inteligentes de gastos', benefit3: 'Flujos de aprobación de varios niveles', benefit4: 'Controles de seguridad mejorados', - toUpgrade: 'Para mejorar, haz clic', + toUpgrade: 'Para mejorar, haz clic en', selectWorkspace: 'selecciona un espacio de trabajo y cambia el tipo de plan a', }, }, @@ -4542,7 +4542,7 @@ const translations = { benefit4: 'Controles de seguridad mejorados', headsUp: '¡Atención!', multiWorkspaceNote: - 'Tendrás que bajar de categoría todos tus espacios de trabajo antes de tu primer pago mensual para comenzar una suscripción con la tasa del plan Recopilar. Haz clic', + 'Tendrás que bajar de categoría todos tus espacios de trabajo antes de tu primer pago mensual para comenzar una suscripción con la tasa del plan Recopilar. Haz clic en', selectStep: '> selecciona cada espacio de trabajo > cambia el tipo de plan a', }, }, From 1b908ba26f83c13a1cd8ec37040a9972242d8207 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 1 Feb 2025 00:10:50 +1300 Subject: [PATCH 53/63] Fix storybook --- src/stories/Composer.stories.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index a92dc0e789a0..1c2e06199798 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -5,7 +5,7 @@ import React, {useState} from 'react'; import {Image, View} from 'react-native'; import type {FileObject} from '@components/AttachmentModal'; import Composer from '@components/Composer'; -import type {ComposerProps} from '@components/Composer/types'; +import type {ComposerProps, CustomSelectionChangeEvent, TextSelection} from '@components/Composer/types'; import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; @@ -33,6 +33,7 @@ function Default(props: ComposerProps) { const [pastedFile, setPastedFile] = useState(null); const [comment, setComment] = useState(props.defaultValue); const renderedHTML = parser.replace(comment ?? ''); + const [selection, setSelection] = useState(() => ({start: props.defaultValue?.length ?? 0, end: props.defaultValue?.length ?? 0, positionX: 0, positionY: 0})); return ( @@ -41,8 +42,13 @@ function Default(props: ComposerProps) { // eslint-disable-next-line react/jsx-props-no-spreading {...props} multiline + value={comment} onChangeText={setComment} onPasteFile={setPastedFile} + selection={selection} + onSelectionChange={(e: CustomSelectionChangeEvent) => { + setSelection(e.nativeEvent.selection); + }} style={[defaultStyles.textInputCompose, defaultStyles.w100, defaultStyles.verticalAlignTop]} /> From 5b95e0aa789ad0359ea6fa9b6dee635c9417ac28 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 1 Feb 2025 00:25:51 +1300 Subject: [PATCH 54/63] Fix storybook --- src/stories/Composer.stories.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index 1c2e06199798..efb8e05614e2 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -28,12 +28,17 @@ const story: Meta = { const parser = new ExpensiMark(); +const DEFAULT_VALUE = `Composer can do the following: + + * It can contain MD e.g. *bold* _italic_ + * Supports Pasted Images via Ctrl+V`; + function Default(props: ComposerProps) { const StyleUtils = useStyleUtils(); const [pastedFile, setPastedFile] = useState(null); - const [comment, setComment] = useState(props.defaultValue); + const [comment, setComment] = useState(DEFAULT_VALUE); const renderedHTML = parser.replace(comment ?? ''); - const [selection, setSelection] = useState(() => ({start: props.defaultValue?.length ?? 0, end: props.defaultValue?.length ?? 0, positionX: 0, positionY: 0})); + const [selection, setSelection] = useState(() => ({start: DEFAULT_VALUE.length, end: DEFAULT_VALUE.length, positionX: 0, positionY: 0})); return ( @@ -79,10 +84,6 @@ Default.args = { autoFocus: true, placeholder: 'Compose Text Here', placeholderTextColor: defaultTheme.placeholderText, - defaultValue: `Composer can do the following: - - * It can contain MD e.g. *bold* _italic_ - * Supports Pasted Images via Ctrl+V`, isDisabled: false, maxLines: 16, }; From a7474ea99ac6053233e4d725473c9a937617a6f9 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Fri, 31 Jan 2025 13:38:22 +0100 Subject: [PATCH 55/63] Install desktop node-modules only when building desktop app --- .github/actions/composite/setupNode/action.yml | 7 ++++++- .github/workflows/deploy.yml | 2 ++ .github/workflows/testBuild.yml | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml index cfa3f9fc191e..51fed0a6a26d 100644 --- a/.github/actions/composite/setupNode/action.yml +++ b/.github/actions/composite/setupNode/action.yml @@ -6,6 +6,10 @@ inputs: description: "Indicates if node is set up for hybrid app" required: false default: 'false' + IS_DESKTOP_BUILD: + description: "Indicates if node is set up for desktop app" + required: false + default: 'false' outputs: cache-hit: @@ -41,6 +45,7 @@ runs: key: ${{ runner.os }}-node-modules-${{ hashFiles('Mobile-Expensify/package-lock.json', 'Mobile-Expensify/patches/**') }} - id: cache-desktop-node-modules + if: inputs.IS_DESKTOP_BUILD == 'true' uses: actions/cache@v4 with: path: desktop/node_modules @@ -60,7 +65,7 @@ runs: command: npm ci - name: Install node packages for desktop submodule - if: steps.cache-desktop-node-modules.outputs.cache-hit != 'true' + if: inputs.IS_DESKTOP_BUILD == 'true' && steps.cache-desktop-node-modules.outputs.cache-hit != 'true' uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 with: timeout_minutes: 30 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0c6ffd4306f7..e7ee3ac4d973 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -301,6 +301,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Decrypt Developer ID Certificate run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 869db3d04be7..1bd4282b2830 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -247,6 +247,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Decrypt Developer ID Certificate run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg From e935679cf73d6cc720aaa27d72fdfdf0e3ead482 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 31 Jan 2025 20:26:24 +0100 Subject: [PATCH 56/63] fix: ts --- src/components/Search/SearchAutocompleteInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchAutocompleteInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx index ae18442745de..2fea54f5d0ce 100644 --- a/src/components/Search/SearchAutocompleteInput.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -138,7 +138,7 @@ function SearchAutocompleteInput( isLoading={!!isSearchingForReports} ref={ref} onKeyPress={handleKeyPress(onSubmit)} - isMarkdownEnabled + type="markdown" multiline={false} parser={(input: string) => { 'worklet'; From 147f52a6dbe4c9d94878526955bff7d6b7bfeb8c Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 3 Feb 2025 11:30:16 +0100 Subject: [PATCH 57/63] Update adhoc flow to use mobile-expensify PR link --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- .github/workflows/testBuildHybrid.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2dfd9348d961..b8de634a489f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,9 +23,9 @@ PROPOSAL: diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index d958e0958083..6a8a0d5884bf 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -81,7 +81,7 @@ jobs: }); const body = pullRequest.data.body; - const regex = /MOBILE-EXPENSIFY:(?\d+)/; + const regex = /MOBILE-EXPENSIFY:\s*https:\/\/github.com\/Expensify\/Mobile-Expensify\/pull\/(?\d+)/; const found = body.match(regex)?.groups?.prNumber || ""; return found.trim(); From 32bf0c8686909284f0645d616447fcf0c5130fe9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 3 Feb 2025 18:53:43 +0530 Subject: [PATCH 58/63] minor update. Signed-off-by: krishna2323 --- src/libs/ReportActionsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2e4c376b8ad5..b959479d8e63 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1616,7 +1616,7 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { if (!reportID || !transactionID) { - return; + return undefined; } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); From 94e80472e3e753c948f5350c6434e6f6760cb515 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:32:17 -0800 Subject: [PATCH 59/63] Delete docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md https://github.com/Expensify/Expensify/issues/460782 --- .../Assign-Company-Cards.md | 70 ------------------- 1 file changed, 70 deletions(-) delete mode 100644 docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md diff --git a/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md b/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md deleted file mode 100644 index 54bd12ce5c49..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Assign Company Cards -description: How to assign company cards to employees in Expensify once they have been connected or imported ---- - -After connecting or importing your company cards to Expensify, you can assign each card to its respective cardholder. - -# Assign new cards - -If you're assigning cards via CSV upload for the first time, - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain. -3. Click the card dropdown menu and select the desired feed from the list. -![Click the dropdown located right below the Imported Cards title near the top of the page. Then select a card from the list.](https://help.expensify.com/assets/images/csv-03.png){:width="100%"} - -{:start="4"} -4. Click **Assign New Cards**. - -![Under the Company Cards tab on the left, you'll use the dropdown menu to select a card and beneath that, you'll click Assign New Cards]({{site.url}}/assets/images/CompanyCards_Assign.png){:width="100%"} - -{:start="5"} -5. Enter the employee's email address and/or select it from the dropdown list. *Note: Employees must have an email address under this domain in order to assign a card to them.* -![Below the Assign a Card header, enter or select the employee's email address]({{site.url}}/assets/images/CompanyCards_EmailAssign.png){:width="100%"} - -{:start="6"} -6. Enter the last four digits of the card number and/or select it from the dropdown list. - - If no transactions have been posted on the card, the card number will not appear in the list and you'll need to enter the full card number into the field. Then press ENTER on your keyboard. The field may clear itself after you press ENTER, but you can disregard this and continue to the next step. -7. (Optional) Set the transaction start date. Any transactions that were posted before this date will not be imported into Expensify. If you do not make a selection, it will default to the earliest available transactions from the card. *Note: Expensify can only import data for the time period released by the bank. Most banks only provide a certain amount of historical data, averaging 30-90 days into the past. It's not possible to override the start date the bank has provided via this tool.* -8. Click **Assign**. - -Once assigned, you will see each cardholder associated with their card and the start date listed. The transactions will now be imported to the cardholder's account, where they can add receipts, code the expenses, and submit them for review and approval. - -![Expensify domain assigned cards](https://help.expensify.com/assets/images/ExpensifyHelp_AssignedCard.png){:width="100%"} - -# Upload new expenses for existing assigned cards - -To add new expenses to an existing uploaded and assigned card, - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain name. -3. Click **Manage/Import CSV**. -![Click Manage/Import CSV located in the top right between the Issue Virtual Card button and the Import Card button.](https://help.expensify.com/assets/images/csv-02.png){:width="100%"} - -{:start="4"} -4. Select the saved layout from the drop-down list. -5. Click **Upload CSV**. -6. Click **Update All Cards** to retrieve the new expenses for the assigned cards. - -# Unassign company cards - -{% include info.html %} -Unassigning a company card will delete any unsubmitted (Open or Unreported) expenses in the cardholder's account. -{% include end-info.html %} - -To unassign a specific card, click the Actions button to the right of the card and click **Unassign**. - -![Click the Actions button to the right of the card and select Unassign.]({{site.url}}/assets/images/CompanyCards_Unassign.png){:width="100%"} - -To completely remove the card connection, unassign every card from the list and then refresh the page. - -*Note: If expenses are Processing and then rejected, they will also be deleted when they're returned to an Open state, as the card they're linked to no longer exists.* - -{% include faq-begin.md %} - -**My Commercial Card Feed is set up. Why is a specific card not coming up when I try to assign it to an employee?** - -Cards will appear in the dropdown when they are activated and have at least one posted transaction. If the card is activated and has been used for a while and you're still not seeing it, reach out to your Account Manager or message concierge@expensify.com for further assistance. - -{% include faq-end.md %} From 157cc80c4294e7eea7b452c3dc9dc70760b21d43 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:33:48 -0800 Subject: [PATCH 60/63] Delete docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md https://github.com/Expensify/Expensify/issues/460782 --- .../Configure-Company-Card-Settings.md | 122 ------------------ 1 file changed, 122 deletions(-) delete mode 100644 docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md diff --git a/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md deleted file mode 100644 index 75580b94f1ad..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: Configure Company Card Settings -description: How to customize your company card settings ---- - -Once you’ve imported your company cards via [commercial card feed](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds), [direct bank feed](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections), or [CSV import](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import), the next step is to configure the card settings. - -{% include info.html %} -You must be a Domain Admin to complete this process. -{% include end-info.html %} - -# Configure company card settings - -1. Hover over **Settings** and click **Domains**. -2. Select the desired domain. -3. Click the **Settings** tab located at the top of the Company Cards tab. -![Near the top right, click the Settings tab that is located between the Card List and Reconciliation tabs.](https://help.expensify.com/assets/images/compcard-01.png){:width="100%"} -5. Set the following preferences, then click **Save**. - -## Preferred Workspace - -Setting a preferred Workspace for a company card feed ensures that the imported transactions are added to a report for that Workspace. This is useful when members are on multiple Workspaces and need to ensure their company card expenses are reported to a particular Workspace. - -## Reimbursable preference - -You can control how your employees' company card expenses are flagged for reimbursement: - -- **Force Yes**: All expenses will be marked as reimbursable. Employees cannot change this setting. -- **Force No**: All expenses will be marked as non-reimbursable. Employees cannot change this setting. -- **Do Not Force**: Expenses will default to either reimbursable or non-reimbursable (your choice), but employees can adjust if necessary. - -## Liability type - -Choose the liability type that suits your needs: - -- **Corporate Liability**: Users cannot delete company card expenses. -- **Personal Liability**: Users are allowed to delete company card expenses. - -If you update the settings on an existing company card feed, the changes will apply to expenses imported after the date that the setting is saved. The update will not affect previously imported expenses. - -# Use Scheduled Submit with company cards - -With Scheduled Submit, employees no longer have to create their expenses, add them to a report, and submit them manually. All they need to do is SmartScan their receipts and Concierge will take care of the rest using a variety of schedules that you can set according to your preferences. - -{% include info.html %} -Concierge won't automatically submit expenses on reports that have expense violations. These expenses will be moved to a new report for the current reporting period. -{% include end-info.html %} - -To enable Scheduled Submit, - -1. Hover over **Settings** and click **Workspaces**. -2. Select the desired Workspace. -3. Click the **Reports** tab on the left. -4. Enable the Scheduled Submit toggle. -5. Select the report submission frequency. -6. Select the date that reports will be submitted. - -# Connect company cards to an accounting integration - -If you're using a connected accounting system such as NetSuite, Xero, Intacct, Quickbooks Desktop, or QuickBooks Online, you can also connect the card to export to a specific credit card GL account. First, connect the card itself, and once completed, follow the steps below: - -1. Hover over **Settings** and click **Domains** -2. Select the desired domain. -3. Click **Edit Exports** near the top right and select the general ledger (GL) account you want to export expenses to. -![Find the desired card in the table. In that same row, click Edit Exports.](https://help.expensify.com/assets/images/cardfeeds-02.png){:width="100%"} - -Once the account is set, exported expenses will be mapped to the selected account when exported by a Domain Admin. - -# Export company card expenses to a connected accounting integration - -## Pooled GL account - -To export credit card expenses to a pooled GL account, - -1. Hover over **Settings** and click **Workspaces**. -2. Select the desired Workspace. -3. Click the **Connections** tab on the left. -4. Under Accounting Integrations, click **Configure** next to the desired accounting integration. -5. For Non-reimbursable export, select **Credit Card / Charge Card / Bank Transaction**. -6. Review the Export Settings page for exporting Expense Reports to NetSuite. -7. Select the Vendor/liability account you want to export all non-reimbursable expenses to. - -## Individual GL account - -1. Hover over **Settings** and click **Domains**. -2. Select the desired Domain. -3. Click the **Edit Exports** to the right of the desired card. Then select the general ledger (GL) account you want to export expenses to. -![Find the desired card in the table. In that same row, click Edit Exports.](https://help.expensify.com/assets/images/cardfeeds-02.png){:width="100%"} - -Once the account is set, exported expenses will be mapped to the selected account. - -# Identify company card transactions - -When you link your credit cards to Expensify, the transactions will appear in each user's account on the Expenses page as soon as they're posted. Transactions from centrally managed cards have a locked card icon next to them to indicate that they’re company card expenses. - -# Import historical transactions - -Once a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days of historical transactions to your account (based on your bank's discretion). Any historical expenses beyond that date range can be imported using the [CSV import](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import). - -# Use eReceipts - -Expensify eReceipts are digital substitutes for paper receipts, eliminating the need to keep physical receipts or use SmartScan for receipts. For Expensify Card transactions, eReceipts are automatically generated for all amounts in these categories: Airlines, Commuter expenses, Gas, Groceries, Mail, Meals, Car rental, Taxis, and Utilities. For other card programs, eReceipts are generated for USD purchases of $75 or less. - -{% include info.html %} -To ensure seamless automatic importation, it is key that you maintain your transactions in US Dollars. eReceipts can also be directly imported from your bank account. CSV/OFX imported files of bank transactions do not support eReceipts. eReceipts are not generated for lodging expenses. Due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses. -{% include end-info.html %} - -{% include faq-begin.md %} - -**What plan/subscription is required in order to manage corporate cards?** - -A Group Workspace is required. - -**When do my company card transactions import to Expensify?** - -Credit card transactions are imported to Expensify once they’re posted to the bank account. This usually takes 1-3 business days between the point of purchase and when the transactions populate in your account. - -**Scheduled Submit is disabled. Why are reports still being submitted automatically?** - -If Scheduled Submit is disabled at the Group Workspace level or set to a manual frequency but expense reports are still being automatically submitted, Scheduled Submit is most likely enabled on the user’s Individual Workspace settings. - -{% include faq-end.md %} From b2adf91f076ecfd28df76138ee925e42effe96a2 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:34:36 -0800 Subject: [PATCH 61/63] Update redirects.csv https://github.com/Expensify/Expensify/issues/460782 --- docs/redirects.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 9ccef010ec96..ab7abe782458 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -624,3 +624,5 @@ https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the https://help.expensify.com/articles/expensify-classic/settings/Change-or-add-email-address,https://help.expensify.com/articles/expensify-classic/settings/Managing-Primary-and-Secondary-Logins-in-Expensify https://help.expensify.com/articles/expensify-classic/domains/SAML-SSO,https://help.expensify.com/articles/expensify-classic/domains/Managing-Single-Sign-On-(SSO)-in-Expensify https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Direct-Bank-Connections,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Connect-Company-Cards +https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Assign-Company-Cards,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards +https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Configure-Company-Card-Settings,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Manage-Company-Cards From b125307567978063e5f7b8372092d68cfedc8769 Mon Sep 17 00:00:00 2001 From: staszekscp Date: Mon, 3 Feb 2025 19:35:09 +0100 Subject: [PATCH 62/63] Update README.md and add building grunt to --- README.md | 1 + scripts/run-build.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 3b55f54bead2..de5c746a964d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ * [Running The Tests](#running-the-tests) * [Debugging](#debugging) * [App Structure and Conventions](#app-structure-and-conventions) +* [HybridApp](#HybridApp) * [Philosophy](#Philosophy) * [Security](#Security) * [Internationalization](#Internationalization) diff --git a/scripts/run-build.sh b/scripts/run-build.sh index 70e0dcf7c586..0abbd4530adf 100755 --- a/scripts/run-build.sh +++ b/scripts/run-build.sh @@ -38,6 +38,9 @@ NEW_DOT_FLAG="${STANDALONE_NEW_DOT:-false}" SCHEME="Expensify Dev" APP_ID="org.me.mobiexpensifyg.dev" + # Build Yapl JS + cd Mobile-Expensify && npm run grunt:build:shared && cd .. + echo -e "\n${GREEN}Starting a HybridApp build!${NC}" PROJECT_ROOT_PATH="Mobile-Expensify/" export CUSTOM_APK_NAME="Expensify-debug.apk" From 253746eafb7618eca185405eb2b5b8815c131191 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 3 Feb 2025 11:08:02 -0800 Subject: [PATCH 63/63] Add share extension provisioning profile --- ...pp_AdHoc_Share_Extension.mobileprovision.gpg | Bin 0 -> 11124 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ios/NewApp_AdHoc_Share_Extension.mobileprovision.gpg diff --git a/ios/NewApp_AdHoc_Share_Extension.mobileprovision.gpg b/ios/NewApp_AdHoc_Share_Extension.mobileprovision.gpg new file mode 100644 index 0000000000000000000000000000000000000000..c9b3eb213f797c03d25f69b8f97a700b5d0ed71f GIT binary patch literal 11124 zcmV-)D~r^O4Fm}T0zd8~+$_i&H}%r(0k;6;Q#9-xv(W-+`2{Q}UoF`iotiN7J12+k)f zo8&)Wz(O@V|HYsugRGR}@80-0xIlkWTa&JbJsH!c#*tg#wVNlWFLpIRSCwXLdsGF= zm#KP7=%FD8BM$88zp$!fsH)9|fCy17<^PC#d>RQp#F2D(P#X&kcQ+et0WisbW{5HB zJJ@^#HTa%=Tfwsv{ihHrgD|@&9EYaiSZ-{TBy${=j{C?I&bhvF%|5n$T%}ZP5yFEW zRwsmNTrFXLis@b}vMZ{v=hb?_Cv^KJan+8H-_`Q_QfpzUFV$`l!4V3dpY!2XRh}LJ z`zr%y3R*KxLGTLPbnaIuaypmp?_8|zPuCv_QcT|~c5BJPrc4jP2XE&n9C0C-Fi8NVY1E&Y%Bu68G*>VY15>Jx+Uooo zl&Buj#C6FMNt;%<`h(rKWnl~TQIR#vLDbCap6vK);$YRh*B%BCBQM*n+UcG}p^Sh3 zlOp4dCj8qKUM>LOEJc>dQAbInby55j>gB;+>}oL(tro_S?%WV;u0JwgIW61YkxT*} zkcq-1kL)!#irnK3! znfRq}jWY-etn_Wn2huNHjPniTPzM6ZEKB0xT$1I96Kz%5Q6EOH?<@VPAmmh9W|_wL zXuVT_)R)rYNujUYOQ?n5yDl5T*`o@e&(Rrg;&G6KE%9Bqr&e8HA0&K?sUm0R0CPQQ z8K@o^1otqi1g2UP$b8-C*)h%9;jBo>;t;XMU~QbLga#>}#)Sw60D~vz7n@N1vS=3v zWrs7M#sgKv_m9HhFVWII~E$>>!PP&+4echvpU}{S_cE5B)Ea!Slm0RB8vp0s02$ zeJQ8>S!?@&raWzc(R7WJ@L55Z*q-t#{S-bGCY@HRTd>Oi93v{jnLycY913g6ib()7 zbUw_-WME-#AA|4o`bB}t>PDK3$5;bU#vGMe7wrg@Zp2cTGD&r! zKH?v1XG2+wxb0ixFE}>u7N7I0^@x=^q*AfnJ{sw!&h{$pwP(jBxfv>eFn7h>D8gD{ zAe}0~6nsiDR>1gVA43PB$T;zKXz^ZaNy?1gk-FZ7N}(j%;w(7MR2bqSKo{<1y#OMy zkp>g{rpMNq)kTW2)wn@4 z!~UWefD;;(RS7P`?=iTqi9%;`n7!CD%ACO$<@T&+#0nfJJ}Wby{;mk*NzwA z3oc@5g9vQ7&auxu43y-(Sdh?^+9f4L%xSu|RZF=O789wJA!@CJaj49w zRp89|LimVvjlMgEDnTchgQw1#DnUp*l(BB493elhh)AhtHRXoh!G05>(XycgQ7Q6h zH`))Bd5_T{?C0jpPFr%9`o;|SBjGBMLc(P8b~qA&E8Rq}?#_6Z8@j3>=XD9&ks^&W zc@g@>K*mOJb%V&6K_DU-DzxGy zWL9}k7}}B=v6WH2bpA5)vyu7Y_259F_aP;sm8xZCqhg6<4=)39%sGE{iHzG)u&92N z9_ZB1-7C+lsukkm!iTRmPRLt)2ox1<#HgOzH`sR5A@_IVlBDq^*or=Zg9=wQ`3COhJJC$}L<^5n zLM>Z>hlQYsS~0FK^TLDlS!r$jp5PzH7P<36M64(YFXFdRFr?lXN_gJnQ+N4EZjl9N zZH(&k!DR5o~=HWC#pR0{Hcs|h*Xy z0{<)gXCEYWP|k{FPA0?%aZsS7p%q&naEfQsm+9JDaD5!O-i2#jy74|?Y|9G4eVx|Z z$QArThME86jLJ6b_ZWm!H4J_vW4qBk4*;>VLu!phO$*33uA1b~6pKqTmpX8zk>ygK zu^__~_0nA%tU?+U)_-a@dzR=!5ykmvCwD*Q1_PX9ts2D`N?hI{Uo5%o_XeGl_E=Qa z4=Cw#Yz4OqGUMi$i88)Z$-v=CWf2eL6j6vZ?D0w26;8H=h@j$*>b?y>s)cMHKYkLC znE?N^MA54w1vjHZV-AoZPLc_BvHxzJa7X7K2L)ICck}~j=}W;kf)hMr@`}6nOg0g> zR47*%jd8*y9u~lgK`Qgz1FpB$SFFpr?n<1;2E)yXm&d2ycmcbj7b+WlDJ0O{Hu-S=ks!fWhIakHa`-Fi)4} z?QAwS@gCfk>o(P|J?v~b9D$&@EEAao~$58C#Zg_h2uA$S@mo6tmE13%i) zW%SdJkY{Xb*uocuy16b4veIZie5DM0+3lUnG{s zZ}+9w@j+#$0|`H(EvtKy_T~-FA?``odvn>%6ST=ypYPiN z0-JIg3#%QQd;=N;y#~c_K8akd%X=O7UZ*(J)IVvrGdDw%>sYq7O+|=>pd}9ZVLbbj z&NJx$glY9(YOKLE(+Zw?#q4DC^PpO$L3+Cl0jy+LzDSw?mKdrl_$XPe#l~Ynn38WH zuk|^{+?Sot0M8zeQvJ4E%##6p72n-{EK2LiajmxQpRE>V+@$e%POLwqO6<>){1PnD zwdit-#F*?R^UP8`#FFfNsW2`-gvjeKt@E4aWg??ZHg4S7#M8M)T2FU)fs*WFeTCIk z+>J9!lUXzDNNLMz*V4h^Z?wEhE~iICidt=5lWl@&lNOq)qx%ONhJL0-7GQ@0AhhSZ zd;~|)_r6$i8LV7DA1^^{JM0<8XIk6oJBV2G-4nN@MdzMp=1r%4_@LT7o|Adf;Eh8) z-CO$b|9Qf7t2LMZE+%8iZ&{&WcYGW97F>g#4C5!VZRv$iMoC&{S2>aCW7j{?GTy+D zaJEXMGbYA0%eGb5XT*-p17(_{R1m*@lkGAk)l>EelYaa({>6~OMInDe6<(H##N6A| zI3Nn_LfpcXP?CQAbJjh+As?Oq-&VIVyV3vO6`{#_%Zk_-(nT?<=2DO^-s1r9v7!lH zI=Kl6U{$Fbt`jaun40dHbxME0Ytr$-i-$*Hh^7acJ=Dh$^ZMkFRI+t%U8LE5HPLa- z`I&$3U|R5NzNYW$PyL{u2mp2nPw$jHP5j$cb9H-~HRcrAvKjGhRz}>cTqxp$w?|7l z@2Eoi#izsu)q-aPh;jgLO#Jwp=p>XChX8 zTe6aTKD7{%t0Ep~ZsE~j&w!2qOpK@P&);v6#hK>kUk1kn%IbpFwJe$s;PC#fSVrt~ zLG-P$*4T_R11!QhP_ApJMQA1TsQ>nVBidtS4j@=v`Nt2?zUKCTNdP^6f+-};6T!5Q zpy>SJbHyLJeIjIz%+&X6M%AA{MA<@<-y!23x$kRNV0P2vi;HJ9@|@MjjyzfdL_7+K9@ba%(=6#p} z2dpb3%HgBGh_SV=`@CqyOQD2lJvM>3h5A)P@4o2j#AFA&Ao0}<@GB3D6JmRf8@*AL zX{F}y-m^@cm<95`=pah2a#w*bsH^567ynyEzp$O#`HPV2u5G`W8SIAMjoHeTb+l&L zTflV8OPnn0%rtzT@;frCH%g4-@R)}$%jgFuRMoRsjM+J3_jauS6Xozr@3O!nnNZ{# z*aF2Etr3zRl?ZeKe+7*$~)IJT`N37!Pr2j${`gzJ8r8|VI|9M!Ur8>5S)jR zyHMIo{4oT>tqYx$+(*_ABgO@7mb(PH2+$7NZbAkh7xj$rp)iU2r^Nj8*ca1CCP<4E zcVf$T5sC{<8j!2Ce%DRu`ts7yb5_t@&Wlm$K>?B9b)c&O%Kj?miA`tWGuaISSUmj#L{dndT-1EsYJg(6N-@Nk`6Ud6tK-?K5Lx+BKKzj z?JlgLV<0uV3kU&~jC=2u=u8i}iyU~>a-`#+TTF!7wJJXSVp0SDUyEg^$>FJ2bi5$? zrbKdz%o5FZN9*cOHZs!gHsZWY9+|p!X%R#j^Uc|?UAZ%1Y!pTf()f-=1V|ZA>=RBf z=)F#k`384tfFf)nw;fiy-rJ{Gz_v%6R9kk5`~OKMxL}& z@CI_(GxvpG?Pn)!$#B_u6^OUZTxVI=FS3%Qi~uutwpE<~vF92&m+}!B#|~5vr>D>> zcE|$9{>dHdDH0{l8vs{1_BxAX6N}vxRG>vak=V(0vuE4g_O*y-;E*piE1}xyhO! zN<Qvzl06i zwW@doz&*qIDo8Up#(7*)mDOnY(lU4&H`94?&mR%FN!SGzHo<;;dw5JfU60V}EO$8V zPc}3v1>g{$e)1(JcFR)p`P>t-)OQ{U7Oty?%Xmi8w~ z=$+O|Bgd^+>k`fWJm=lhHND;4)W_MdCT1Npmi#2k2Wb4jcJaIcxGLGKj!N2<&`&-A z>dOHzVwV+hXru^z#t_jTAopo9+l6DJ;dwRf726M=B9Jq3RIAT1@^jOzqfuKv44O$M|@7OH8> zS^dTj02xsAfRZD2VSRwy*+E@g*dtDx1@#*H{$WE7DfWip3KI-CikK(urTK27+6fdA zotze{Ia9GJ|JGI|Y6RV-*F5NI60vFM)AJ)PAPQVMiJ>aut4U`rHgQYN%FS1>Ck1r_ z2uC(V-Lpwf%^tcJa&3NG0LJ(YAHqJaQXC2pU@Rq7-U?+H@^7bwjTL_K)A{IfDR@RSI<)eW9Zt3~T;Ccu((Ur1_tp117c$)fFF>d`FH zQ76ac`VL9pmbWWNBfUy_=3l33aGaMRx`D+XF{yI)hz%C?*j6d_QUA(;=rr9t+|^&d zhioahdws~LI(j`b+x2CrOb+nPXf402M{Csma#k#WZ5hYaT!_&%078B{)oFuyI;KMI z950svkN7A)N!9beii*yffbv&b+0 zp`1_1Pu*ol25Suip;ey5?yG8Ftjb^{nI}>*0+iG0wndq8AUGvqu223ZjrEga*ZDHT2(nP@n<2wdjlLmL4Ey|Tk?U0tNzx7{ znxK=EyHMEP0+gKuc4|}Mgzrr3l{YvUhn}3r&VMWu;5^kT2Oiq+ z9nzCZK`iMk2iaVd+5F*80X-qWm5E0`mypk6aN?rmWvTaKM&#j^b^!ymb;8&PQ5SAj z`hLJt5%NMgmHiFmJ!?n%q&Gp>8pf-mCTP!?4cY-27S3LarK9xmBeEswMnR~g;6K+r z%<(n7j5rQ_LaQtIs#xzAIL?)BeiO(43>QNAT**dVj$PF= z-iKme-_kl?eC?bz0N!wj7qR`P8}6xd4yN)VgH#;HJy4Wv&-m<175~~D;p7?cT7m=i z;~R*{g8wElqdsmf%gIJr+Gq9@qBF6o!5vXesiKP!>tDL)HZ|9vaQLTyU|rfr-0qQq z)TC>L;M2yL0(<-;3Wy+LVTxTCjOEa=l>fdji~r`Amb`hzFXQ64tf5=XIMkoH8><~o zIYn|zKb{=pAwMyDu?-;m3(F)u*ET0C0nf6Q@kw1{bNRWT{mlc9|1{+8&Ah^&?JHZ@9p)wznrK-V5z5?eTssup!dy6?xF z>?m~`ua6T@ipP`m0xEBB&a-vmFd@SeFNwkIRgrNT)1UvRFo%2WSh zdK>H_ZgLozdUT_k$TV2<%oaFbTLDsnbT$tR{;Wn+eQo9nxBz-hfr+ z=a=OX#)cB{H}p-7sNQ=`Jt0AM*E`MYt}x9_uvowc7V=Sir8&)R*_8*siyz&~Ib545O9z5x0@YMmC)wX**l2lsIoP?|k`E zrnuz6#z&KT-n14@1q)3*N8A6$-${rm3o?S_vJJOO!;yir+^8NL48PuNnxuJzs-v5< zPpOSoPqk{s##HAvafa5z^=YLp81SelZJbV#Umx6LeSO4wBfyq1+4a4aCkBC1Dm?v{ z#(pb)Xqkx-P12m9f8a0*4Ig{0psEo0KPu>bmxJ7%eU~ogk64q@0}Hvx6PCfJD!DuF zB3lYw{16?9YjZ*c?ag&ODaaq~lV%}-f9%HwvYWEkA}zZgS($QTNJ zkzV6XcX`KpjAwjf0oA;c&v>Yq$1DW=ezZ^JZtc=tx+`$` zzX!OUG4TeyyRY*PTdDvHv|1ixw5$j`((!m0w2Q{~w;_N=PzetK2%y)|v3?Zo_4^P{sBU=>g;Ex-J$nyVaZD_rI6Orpau!`%gI=2Iby=mvZg z1~4WLP1y)7=HY%BKlXZO%lHdF0%At-6J8-xD*Kh>spSk=_VET40xBNF1nal0ntD$1 zjt}cn#Y}mPG5?UNa-`7}pQ#xJu_db}r(>Ca^X-9TPRX%J(d^88ibXxABuHlT7&GHj z>xk|bEW@Fm@*tmoJjLv6jAJ7{!Y>R!1*&yr`HYk1X~K1^`a}}DCYx~HSc-e$@v}FN zP4hPB%G|aLW)tNd`eA$q`zw|-T_%CMgx{^~A9Y-92EqJbY(j=~vuvmdBm!wljT#nD z#2@zAE(57pnk2vUNMsMhbI;GN>X#n@nU@+<4}#fWX;J@#?C=RU9BB%~15c0OuyEz$ zVx^D&tEv5QHCRCBReCpiFH^W3e%9*14b`J%1(2t=aA%+N*-eS3Yo(ifNk@%DeO#{PT^TeQ={Rlg%T$7<{W$)AH+0?9`Ant@}2S=E~w59-(<` z0L<;1(3*Ur!_E-u4!Xwi6^BG9?m4HjgI4!r*v16>f!xi%XFr=$>1ICwTc*e#xT{wv z4ij`?_Kp$BgQOfTs7?1MJ9T`cDoxy~GZ=1$k;)_(6=-O{IcBq>n+29WE==H3#{|&l zTL!`G)+psrwSu1tD1B4Eo^Vw1m-rNa5NQ$aN&F55_8B%=8{KN9ouDm7icWv^(I zSb^64!XMbQDH{q_WYp8X+H>PwS}PgqYF>(|C}XLdZ4QM#0O>8jqrP0XnC31VIHjx$ z>!(eA*W`jy{hJGs+13Lh3WK?H z-t+rmB$#?T=XwCsFIlLkeP3k?SjrOU@@YcumOb+rl=wS)Dc>w#H;#FEon*X$ruLmR z|5PtEC#K0BuKk-bGp7Hs%3>2;nwy3m``j4~tXU&lqYbByqXrvsD6oYA%i!UqHQa^c z_(*2;D(#27S|?23ngiytm9WSXf*judSXZ}7XzC%-{Mh>-cZJ!E2CC6Jlna1h&nYF^ zr))o#*ju)Fv7_Jx4mhL5s#_981zG_GP?O4jP^=+4R zX?YqR37k3pDb0{+1K)bXcuUjxTz)sM)ML)Avb&O*76I$y@#08enLkbag-~~CoPeeB z<8WlMYC2gnjo)wrBNrSdZ*-dF@aE>pqC%v0sVclfzG!U>aczZNzN7rDl`bgMP5y!m zQ-~kaidk<+57C@c^WxKSXP-}_)d?V$-rv zkprloNI)&(9HuYf-?_f_xy$1_Y%tu@=x%HuwH=I2=@+n!g;8}UTVHb_6qhkcHzq*_ zA;0@vPwimBb^>5>!T&U(cJ?Y#!Ub6cRs)$XEU?cHm`i^<=t&E@8NjAekx;rfA=3ZM~#>kZce}u)`_8$cAP>2wL8jhoK zLjO@346$niJ1@lix04!~k@A!0b^GR~9BGDFvt8bx7Lx(^JSN{e)OMSxq`eFzO<9u8 zi8f=|IPeoSTP{<>_INq+jO(?1{f@jI$dk6(q`e2PD9SrG!#zWl>xh~h$P^pNJ>6?? zeg8I4(5BXi8A?zUiY}-#Y+c_K$)~jh^HC0h=tcli%c!p5lrgm!f%;YJn0j6=tPaoNM$fGQh-}4`i4s=#Tx)-zAvttli9&NXs`Ts`%ju zwpb>2QRIVX^S!I$9=o0YV#9oeOARn`hsnh(Fq@GftsFv{oe;auYBa8SL0IKC`M$mP z1BSAmF0LH5#~f=N>df$~<%mwx?!d9|bNWt5z{+6383fRNIhD2>O}ANDp?$PKi2=UEm@p6y?@@ zS%sGo&bT=mw+{FB5%(+zKR$Pi{6X(SD`VlyQ3Qkg%`c&322Hdw2Gb4|_CRi0rDHL} z9VsE@L*fxEcP4rxFx_{7ooDk@dn662zQ{_$M6vg&r*xuMciC0W)t>i><4N=3fTA}y zz&7>{)I^bz0Gp85T&GV~aNKcY%PnS>^0amfEV{?)K2uc3sedEc1d^_#z^7hatmC*) zdJ%vgEM@94`=)XLD3V(Qs+cd9bm?~JmPskkOz)hPhFz32IZZhn&=QbR__AE2ETWem z2|ZF0oujv!U)bhUD#4$u0%5Eh2+64VvKlnDq_g?m?{QQ)TqAsbPh5H2=cAVlUHf>V zmX?)MGnhToGNHl8ve1Y=P7c0Hg8W zM|yz;``(xjrzr4`+|fV$2bPphg-S+Db>>++WFF$X#!@uMDLz`^a(D215ali%=k-CS zcw2nx3DH08nYOg}H%L)JxHxQka`zbx+OYbu9Y9&%iv0HH>(@o6T=(uMog>Lijy?|; zV74`3{~HmxdT%FMvZ(r`vqg|o@rW?9!ItvR?}Hl=EyqM0k#_@Ut7(?awMUk8yZE2k z80cCbMaDvZ5Tu?lOT5EFEw0tnpG%Mp3_#8cy+C#M!c$>G<7EFDcC2gS|0|e7Zttj9 ze+i>7X0%|Lib;3hmj}dCGV9;5@ z0~N8X&?QCxfhR0GnZVKA6G)kZBgJIHqTmchGC~SJP{l@K^UKg5VC-%%@wMIPGVX2W zn_gbxyXV&GC@&`~%6`C~@5_Mi41+UTP$*%sR&1~OGx(mBD<3vXn2O*CcZV=$GVzl3 zqwT#N1p$=;171i(|IcYO*Gw>;3%Xh>&)7AsO4#92w(XFAa(uymHx zYblmT@!@pj<)~cY;idS=p8OkKQEpGK&P|sANu)+5@YgI}v(2qcfQPbM97v|FsQ?jV zqS^7*!q0l@FuZhP0o*f~A=;u7nH=qsgwFM-U2QDz_ z$9&6mY;CB*c9vT)>r*@XD#cwNeScvR=hYQp@z{<8w)`QC GduK@g=(H^W literal 0 HcmV?d00001