From 913a3d95b71cbb341946f38f04e1fc2d0cef715e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 21 Aug 2024 22:13:58 +0530 Subject: [PATCH 001/352] demo skip dupe tax step. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 25 ++++++++++++------- .../TransactionDuplicate/ReviewTaxCode.tsx | 9 ++++--- .../step/IOURequestStepConfirmation.tsx | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8597654576fc..02a3764bf419 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -31,6 +31,8 @@ import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import {getPolicy} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -279,15 +281,20 @@ function MoneyRequestPreviewContent({ const navigateToReviewFields = () => { const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); - if ('merchant' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); - } else if ('category' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); - } else if ('tag' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); - } else if ('description' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); - } else if ('taxCode' in comparisonResult.change) { + + const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); + const policy = PolicyUtils.getPolicy(report2?.policyID); + const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; + // if ('merchant' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('category' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('tag' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('description' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); + // } + if ('taxCode' in comparisonResult.change && hasValidTaxes) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index aa598bff8fcd..acb45b2c8a63 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,13 +20,13 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const transaction = TransactionUtils.getTransaction(reviewDuplicates?.transactionID ?? ''); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(reviewDuplicates?.transactionID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); - const transaction = TransactionUtils.getTransaction(transactionID); const options = useMemo( () => compareResult.change.taxCode?.map((taxID) => @@ -39,6 +39,7 @@ function ReviewTaxRate() { ), [compareResult.change.taxCode, policy, transaction, translate], ); + const getTaxAmount = useCallback( (taxID: string) => { const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxID); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index b33ce6f56600..5090c1dfb2d6 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; From 0429b7f0e996e11f6649f218908d38add53c8e1f Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 28 Aug 2024 09:02:26 +0530 Subject: [PATCH 002/352] minor update. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index d901e3eb7c3a..9f2f52d7ac35 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -294,6 +294,10 @@ function MoneyRequestPreviewContent({ // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); // } if ('taxCode' in comparisonResult.change && hasValidTaxes) { + if (!hasValidTaxes) { + Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); From 890eba70df851f1613896c3df84e137538dead09 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 9 Sep 2024 10:46:29 +0700 Subject: [PATCH 003/352] Feature: add loading indicator when ReconnectApp is running --- src/pages/home/ReportScreen.tsx | 3 +++ .../sidebar/SidebarScreen/BaseSidebarScreen.tsx | 4 ++++ src/styles/index.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 4eaf3633af38..be4cf15787a5 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -13,6 +13,7 @@ import DragAndDropProvider from '@components/DragAndDrop/Provider'; import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ProgressBar from '@components/ProgressBar'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import TaskHeaderActionButton from '@components/TaskHeaderActionButton'; @@ -128,6 +129,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [workspaceTooltip] = useOnyx(ONYXKEYS.NVP_WORKSPACE_TOOLTIP); const wasLoadingApp = usePrevious(isLoadingApp); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); const prevIsDeletedParentAction = usePrevious(isDeletedParentAction); @@ -756,6 +758,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro needsOffscreenAlphaCompositing > {headerView} + {shouldUseNarrowLayout && } {ReportUtils.isTaskReport(report) && shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index edc8dfb3cb3a..4626429fb53c 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -1,6 +1,7 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import ProgressBar from '@components/ProgressBar'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useLocalize from '@hooks/useLocalize'; @@ -29,6 +30,8 @@ function BaseSidebarScreen() { const {translate} = useLocalize(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); + useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED); @@ -57,6 +60,7 @@ function BaseSidebarScreen() { breadcrumbLabel={translate('common.inbox')} activeWorkspaceID={activeWorkspaceID} /> + top: 80, left: 12, }, + progressBarWrapper: { + height: 2, + width: '100%', + backgroundColor: '#1A3D32', + borderRadius: 5, + overflow: 'hidden', + }, + progressBar: { + height: '100%', + backgroundColor: '#03D47C', + borderRadius: 5, + width: '100%', + }, } satisfies Styles); type ThemeStyles = ReturnType; From 7a77276bd2414483144892228d98584faac03b9d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 9 Sep 2024 10:55:23 +0700 Subject: [PATCH 004/352] fix ts check --- src/components/ProgressBar.tsx | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/components/ProgressBar.tsx diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx new file mode 100644 index 000000000000..d37d34887c03 --- /dev/null +++ b/src/components/ProgressBar.tsx @@ -0,0 +1,78 @@ +import React, {useEffect} from 'react'; +import Animated, {cancelAnimation, Easing, runOnJS, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function ProgressBar({shouldShow}: {shouldShow: boolean}) { + const left = useSharedValue(0); + const width = useSharedValue(0); + const opacity = useSharedValue(0); + const isVisible = useSharedValue(false); + const styles = useThemeStyles(); + + useEffect(() => { + if (shouldShow) { + // eslint-disable-next-line react-compiler/react-compiler + isVisible.value = true; + left.value = 0; + width.value = 0; + opacity.value = withTiming(1, {duration: 300}); + left.value = withDelay( + 300, // 0.3s delay + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(0, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, + ), + ); + + width.value = withDelay( + 300, // 0.3s delay + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(100, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, + ), + ); + } else if (isVisible.value) { + opacity.value = withTiming(0, {duration: 300}, () => { + runOnJS(() => { + isVisible.value = false; + cancelAnimation(left); + cancelAnimation(width); + }); + }); + } + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, [shouldShow]); + + const animatedIndicatorStyle = useAnimatedStyle(() => { + return { + left: `${left.value}%`, + width: `${width.value}%`, + }; + }); + + const animatedContainerStyle = useAnimatedStyle(() => { + return { + opacity: opacity.value, + }; + }); + + return isVisible.value ? ( + + + + ) : null; +} + +ProgressBar.displayName = 'ProgressBar'; + +export default ProgressBar; From 0265bc6133f6e93382e98105646ba484831fd173 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 9 Sep 2024 16:53:46 +0700 Subject: [PATCH 005/352] fix style of progress bar --- src/styles/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 8a742934739c..825f17701740 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5202,13 +5202,13 @@ const styles = (theme: ThemeColors) => progressBarWrapper: { height: 2, width: '100%', - backgroundColor: '#1A3D32', + backgroundColor: theme.buttonDefaultBG, borderRadius: 5, overflow: 'hidden', }, progressBar: { height: '100%', - backgroundColor: '#03D47C', + backgroundColor: theme.success, borderRadius: 5, width: '100%', }, From af9b5b8a2f9406d1bf39b01727b3efeb65036bfe Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 9 Sep 2024 17:26:43 +0700 Subject: [PATCH 006/352] fix style progress bar --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 825f17701740..b086d4cfd9b3 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5202,7 +5202,7 @@ const styles = (theme: ThemeColors) => progressBarWrapper: { height: 2, width: '100%', - backgroundColor: theme.buttonDefaultBG, + backgroundColor: theme.border, borderRadius: 5, overflow: 'hidden', }, From c177829d889eb4a1998d524d17c4b453ff82d5bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 10 Sep 2024 22:05:34 +0700 Subject: [PATCH 007/352] add new const and fix style progress bar --- src/CONST.ts | 3 +++ src/components/ProgressBar.tsx | 17 +++++++++-------- src/styles/index.ts | 1 - 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d0695b1e285f..a6578118e7c8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -180,6 +180,9 @@ const CONST = { ANIMATED_HIGHLIGHT_END_DURATION: 2000, ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, + ANIMATED_PROGRESS_BAR_DELAY: 300, + ANIMATED_PROGRESS_BAR_OPACITY_DURATION: 300, + ANIMATED_PROGRESS_BAR_DURATION: 750, ANIMATION_IN_TIMING: 100, ANIMATION_DIRECTION: { IN: 'in', diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index d37d34887c03..63d3d1142d30 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -1,6 +1,7 @@ import React, {useEffect} from 'react'; import Animated, {cancelAnimation, Easing, runOnJS, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming} from 'react-native-reanimated'; import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; function ProgressBar({shouldShow}: {shouldShow: boolean}) { const left = useSharedValue(0); @@ -15,14 +16,14 @@ function ProgressBar({shouldShow}: {shouldShow: boolean}) { isVisible.value = true; left.value = 0; width.value = 0; - opacity.value = withTiming(1, {duration: 300}); + opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}); left.value = withDelay( - 300, // 0.3s delay + CONST.ANIMATED_PROGRESS_BAR_DELAY, withRepeat( withSequence( withTiming(0, {duration: 0}), - withTiming(0, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(100, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), ), -1, false, @@ -30,19 +31,19 @@ function ProgressBar({shouldShow}: {shouldShow: boolean}) { ); width.value = withDelay( - 300, // 0.3s delay + CONST.ANIMATED_PROGRESS_BAR_DELAY, withRepeat( withSequence( withTiming(0, {duration: 0}), - withTiming(100, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(0, {duration: 750, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), ), -1, false, ), ); } else if (isVisible.value) { - opacity.value = withTiming(0, {duration: 300}, () => { + opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { runOnJS(() => { isVisible.value = false; cancelAnimation(left); diff --git a/src/styles/index.ts b/src/styles/index.ts index b086d4cfd9b3..9221f480ab8e 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5209,7 +5209,6 @@ const styles = (theme: ThemeColors) => progressBar: { height: '100%', backgroundColor: theme.success, - borderRadius: 5, width: '100%', }, } satisfies Styles); From 90508101c480f9b346da03d06c291394d28532b4 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 11 Sep 2024 11:38:15 +0530 Subject: [PATCH 008/352] revert changes in ReviewTaxRate page. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 28 ++++++++++--------- .../TransactionDuplicate/ReviewTaxCode.tsx | 9 +++--- .../step/IOURequestStepConfirmation.tsx | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ecd5b4e73548..89a1c8dc412b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -291,20 +291,22 @@ function MoneyRequestPreviewContent({ const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); const policy = PolicyUtils.getPolicy(report2?.policyID); const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; - // if ('merchant' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('category' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('tag' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('description' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); - // } + + if (!hasValidTaxes) { + Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); + return; + } + + if ('merchant' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); + } else if ('category' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); + } else if ('tag' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); + } else if ('description' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); + } if ('taxCode' in comparisonResult.change && hasValidTaxes) { - if (!hasValidTaxes) { - Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); - return; - } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index acb45b2c8a63..aa598bff8fcd 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,13 +20,13 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const transaction = TransactionUtils.getTransaction(reviewDuplicates?.transactionID ?? ''); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(reviewDuplicates?.transactionID ?? ''); + const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); + const transaction = TransactionUtils.getTransaction(transactionID); const options = useMemo( () => compareResult.change.taxCode?.map((taxID) => @@ -39,7 +39,6 @@ function ReviewTaxRate() { ), [compareResult.change.taxCode, policy, transaction, translate], ); - const getTaxAmount = useCallback( (taxID: string) => { const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxID); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 3f8f17d57d0f..6c1457abef62 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; From b2b979b31d4008e3b1580621c4f4542b8eef56ea Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 02:54:28 +0530 Subject: [PATCH 009/352] revert changes. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index bf3c48e9e5b7..51f9c1c521a4 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -288,15 +288,6 @@ function MoneyRequestPreviewContent({ const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); - const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); - const policy = PolicyUtils.getPolicy(report2?.policyID); - const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; - - if (!hasValidTaxes) { - Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); - return; - } - if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); } else if ('category' in comparisonResult.change) { @@ -306,7 +297,7 @@ function MoneyRequestPreviewContent({ } else if ('description' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); } - if ('taxCode' in comparisonResult.change && hasValidTaxes) { + if ('taxCode' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); From 61ce0021e927f584d9aa4a8b1ed1afa11aa24d05 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 03:27:01 +0530 Subject: [PATCH 010/352] remove tax code from changes if policy doesn't include that. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 6 ++---- src/libs/TransactionUtils/index.ts | 11 +++++++++++ src/pages/TransactionDuplicate/ReviewBillable.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewCategory.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewDescription.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewMerchant.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewReimbursable.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewTag.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 3 ++- src/types/onyx/ReviewDuplicates.ts | 3 +++ 10 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 51f9c1c521a4..8e1cfb4a29e8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -31,8 +31,6 @@ import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import {getPolicy} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -285,8 +283,8 @@ function MoneyRequestPreviewContent({ ); const navigateToReviewFields = () => { - const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); - Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); + const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID ?? ''); + Transaction.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)); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 63c2f9aa9862..a734995a60ea 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1020,6 +1020,17 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia } else { processChanges(fieldName, transactions, keys); } + } else if (fieldName === 'taxCode') { + const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const policy = PolicyUtils.getPolicy(report?.policyID); + const differentValues = getDifferentValues(transactions, keys); + const hasValidTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name).length; + + if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) || !hasValidTaxes) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } else { + processChanges(fieldName, transactions, keys); + } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { diff --git a/src/pages/TransactionDuplicate/ReviewBillable.tsx b/src/pages/TransactionDuplicate/ReviewBillable.tsx index 4cb2b130aea1..cd9fa8e1ae92 100644 --- a/src/pages/TransactionDuplicate/ReviewBillable.tsx +++ b/src/pages/TransactionDuplicate/ReviewBillable.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewBillable() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'billable', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx index 12a5968e2c43..04dbae092318 100644 --- a/src/pages/TransactionDuplicate/ReviewCategory.tsx +++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewCategory() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'category', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index e6229afe48ac..fee570273e05 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewDescription() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'description', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx index 80ae43a0d338..5a94161a72d5 100644 --- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx +++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewMerchant() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'merchant', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx index fbf7e43a2013..43e3a063e106 100644 --- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx +++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewReimbursable() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'reimbursable', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx index abab4d3e03f3..46c32b5f12ad 100644 --- a/src/pages/TransactionDuplicate/ReviewTag.tsx +++ b/src/pages/TransactionDuplicate/ReviewTag.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -17,7 +19,8 @@ function ReviewTag() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'tag', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index aa598bff8fcd..e52368f35005 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -23,7 +23,8 @@ function ReviewTaxRate() { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); const transaction = TransactionUtils.getTransaction(transactionID); diff --git a/src/types/onyx/ReviewDuplicates.ts b/src/types/onyx/ReviewDuplicates.ts index 0682ed0a7f7c..6c5ccbd93481 100644 --- a/src/types/onyx/ReviewDuplicates.ts +++ b/src/types/onyx/ReviewDuplicates.ts @@ -8,6 +8,9 @@ type ReviewDuplicates = { /** ID of transaction we want to keep */ transactionID: string; + /** ID of the transaction report we want to keep */ + reportID: string; + /** Merchant which user want to keep */ merchant: string; From 5fff73f2a953260d6e6719ea78d92ead9e4bfd98 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 03:30:54 +0530 Subject: [PATCH 011/352] minor update. 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 a734995a60ea..1d5fe405ee70 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -943,7 +943,7 @@ function removeSettledAndApprovedTransactions(transactionIDs: string[]) { * 6. It returns the 'keep' and 'change' objects. */ -function compareDuplicateTransactionFields(transactionID: string): {keep: Partial; change: FieldsToChange} { +function compareDuplicateTransactionFields(transactionID: string, reportID: string): {keep: Partial; change: FieldsToChange} { const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const duplicates = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? []; const transactions = removeSettledAndApprovedTransactions([transactionID, ...duplicates]).map((item) => getTransaction(item)); From 9801ae89d3f0bf9ff300a2a4ed976b6ee9e2edad Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 17 Sep 2024 02:40:32 +0530 Subject: [PATCH 012/352] only add taxCode in changes if availble in policy. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1d5fe405ee70..9fb78eea1555 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1024,12 +1024,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); - const hasValidTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name).length; + const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); - if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) || !hasValidTaxes) { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; - } else { - processChanges(fieldName, transactions, keys); + if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { + change[fieldName] = validTaxes; } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; From 32117b20050e7bce79e80d93ff9cb0cf0a87d153 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 30 Sep 2024 16:02:17 +0700 Subject: [PATCH 013/352] fix style progress bar --- src/components/ProgressBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index 63d3d1142d30..885fe337c002 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -67,11 +67,11 @@ function ProgressBar({shouldShow}: {shouldShow: boolean}) { }; }); - return isVisible.value ? ( + return ( - + {isVisible.value ? : null} - ) : null; + ); } ProgressBar.displayName = 'ProgressBar'; From b2b14cdc1033a168022dedca43baaa6b07518258 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 15:37:21 +0530 Subject: [PATCH 014/352] feat: skip category review when category isn't present in policy. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 13 ++++++++++++- src/libs/actions/Policy/Category.ts | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 71f103999c91..401e29976f71 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,6 +3,7 @@ import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import {getPolicyCategories} from '@libs/actions/Policy/Category'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1007,6 +1008,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const keys = fieldsToCompare[fieldName]; const firstTransaction = transactions.at(0); const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; + const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); @@ -1023,7 +1025,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri processChanges(fieldName, transactions, keys); } } else if (fieldName === 'taxCode') { - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); @@ -1031,6 +1032,16 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { change[fieldName] = validTaxes; } + } else if (fieldName === 'category') { + const differentValues = getDifferentValues(transactions, keys); + const policyCategories = getPolicyCategories(report?.policyID ?? '-1'); + const availableCategories = Object.values(policyCategories) + .filter((category) => differentValues.includes(category.name)) + .map((e) => e.name); + + if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && availableCategories.length > 1) { + change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index e237ed80e293..2d4299cc224f 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1331,6 +1331,10 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } +function getPolicyCategories(policyID: string) { + return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; +} + export { openPolicyCategoriesPage, buildOptimisticPolicyRecentlyUsedCategories, @@ -1354,4 +1358,5 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, + getPolicyCategories, }; From 57bd4005b5c9982cb5e982ef4755da3b60002dda Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 16:07:14 +0530 Subject: [PATCH 015/352] minor fix. Signed-off-by: krishna2323 --- src/pages/TransactionDuplicate/Confirmation.tsx | 2 +- src/pages/TransactionDuplicate/ReviewBillable.tsx | 2 +- src/pages/TransactionDuplicate/ReviewCategory.tsx | 2 +- src/pages/TransactionDuplicate/ReviewDescription.tsx | 2 +- src/pages/TransactionDuplicate/ReviewMerchant.tsx | 2 +- src/pages/TransactionDuplicate/ReviewReimbursable.tsx | 2 +- src/pages/TransactionDuplicate/ReviewTag.tsx | 2 +- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 15217e215ad4..a8d358184d77 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -38,7 +38,7 @@ function Confirmation() { const [reviewDuplicates, reviewDuplicatesResult] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); const transaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo); const [report, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`); diff --git a/src/pages/TransactionDuplicate/ReviewBillable.tsx b/src/pages/TransactionDuplicate/ReviewBillable.tsx index 9b9900741c2c..166c61209a42 100644 --- a/src/pages/TransactionDuplicate/ReviewBillable.tsx +++ b/src/pages/TransactionDuplicate/ReviewBillable.tsx @@ -19,7 +19,7 @@ function ReviewBillable() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx index 5ded413d9f3f..b28cb6863137 100644 --- a/src/pages/TransactionDuplicate/ReviewCategory.tsx +++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx @@ -19,7 +19,7 @@ function ReviewCategory() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index e8ef70f9a70a..d3c379517cf1 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -19,7 +19,7 @@ function ReviewDescription() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx index 586857f7946f..d49a67d7d911 100644 --- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx +++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx @@ -19,7 +19,7 @@ function ReviewMerchant() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx index dbddce9d208f..361b92c2af5a 100644 --- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx +++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx @@ -19,7 +19,7 @@ function ReviewReimbursable() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx index db1e02db4809..16138865cfd0 100644 --- a/src/pages/TransactionDuplicate/ReviewTag.tsx +++ b/src/pages/TransactionDuplicate/ReviewTag.tsx @@ -21,7 +21,7 @@ function ReviewTag() { const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index ea75dc87c192..90e8c26656c9 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -24,7 +24,7 @@ function ReviewTaxRate() { const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), From 2124c249c02483ddda1aa3fa46a54a084f93b804 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 16:09:18 +0530 Subject: [PATCH 016/352] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 401e29976f71..3070eaa092a7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1039,7 +1039,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name)) .map((e) => e.name); - if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && availableCategories.length > 1) { + if ( + !areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && + (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes(''))) + ) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { From 1709905f776da09c567cc76349763a429de55406 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 22:42:35 +0530 Subject: [PATCH 017/352] migrate from withOnyx HOC to the useOnyx hook. --- src/components/MapView/MapView.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index cbe604661c05..a72788fcc62f 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -3,7 +3,7 @@ import type {MapState} from '@rnmapbox/maps'; import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps'; import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import useTheme from '@hooks/useTheme'; @@ -21,11 +21,12 @@ import Direction from './Direction'; import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps} from './types'; import utils from './utils'; const MapView = forwardRef( - ({accessToken, style, mapPadding, userLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { + ({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { + const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -298,8 +299,4 @@ const MapView = forwardRef( }, ); -export default withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, -})(memo(MapView)); +export default memo(MapView); From 4bef7003eb8a3655663e3a63f428462db50efd1e Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:02:04 +0530 Subject: [PATCH 018/352] remove userLocation from Props types and migrate from withOnyx HOC to useOnyx hook --- src/components/MapView/MapViewImpl.website.tsx | 13 +++++-------- src/components/MapView/types.ts | 8 ++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/components/MapView/MapViewImpl.website.tsx b/src/components/MapView/MapViewImpl.website.tsx index 7df277671043..5958b2939396 100644 --- a/src/components/MapView/MapViewImpl.website.tsx +++ b/src/components/MapView/MapViewImpl.website.tsx @@ -9,7 +9,7 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, import type {MapRef, ViewState} from 'react-map-gl'; import Map, {Marker} from 'react-map-gl'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import usePrevious from '@hooks/usePrevious'; @@ -29,7 +29,7 @@ import './mapbox.css'; import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps} from './types'; import utils from './utils'; const MapViewImpl = forwardRef( @@ -40,13 +40,14 @@ const MapViewImpl = forwardRef( waypoints, mapPadding, accessToken, - userLocation, directionCoordinates, initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}, interactive = true, }, ref, ) => { + const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); + const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -295,8 +296,4 @@ const MapViewImpl = forwardRef( }, ); -export default withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, -})(MapViewImpl); +export default MapViewImpl; diff --git a/src/components/MapView/types.ts b/src/components/MapView/types.ts index a0494a9ac499..77b1b9eb82c2 100644 --- a/src/components/MapView/types.ts +++ b/src/components/MapView/types.ts @@ -2,10 +2,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import type * as OnyxTypes from '@src/types/onyx'; import type {MapViewProps} from './MapViewTypes'; -type MapViewOnyxProps = { - userLocation: OnyxEntry; -}; +type ComponentProps = MapViewProps; -type ComponentProps = MapViewProps & MapViewOnyxProps; - -export type {MapViewOnyxProps, ComponentProps}; +export type {ComponentProps}; From e7f3b6e14bf2044b274d44dfb24125f7ff2d0ca5 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:19:45 +0530 Subject: [PATCH 019/352] Address TypeScript errors --- src/components/MapView/MapView.website.tsx | 5 ++--- src/components/MapView/MapViewImpl.website.tsx | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 3a28943b575a..52cdaee902a3 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -4,11 +4,10 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; -import type {ComponentProps} from './types'; -const MapView = forwardRef((props, ref) => { +const MapView = forwardRef((props, ref) => { const {isOffline} = useNetwork(); const {translate} = useLocalize(); const styles = useThemeStyles(); diff --git a/src/components/MapView/MapViewImpl.website.tsx b/src/components/MapView/MapViewImpl.website.tsx index 5958b2939396..5f68b041602e 100644 --- a/src/components/MapView/MapViewImpl.website.tsx +++ b/src/components/MapView/MapViewImpl.website.tsx @@ -26,13 +26,12 @@ import getCurrentPosition from '@src/libs/getCurrentPosition'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; import './mapbox.css'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps} from './types'; import utils from './utils'; -const MapViewImpl = forwardRef( +const MapViewImpl = forwardRef( ( { style, From 3cbc0df8fb4018812d46e92fd52c81b33f86a4ab Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:25:09 +0530 Subject: [PATCH 020/352] deleted extra types file from Mapview --- src/components/MapView/MapView.tsx | 5 ++--- src/components/MapView/types.ts | 7 ------- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 src/components/MapView/types.ts diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index a72788fcc62f..a611c3d62727 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -18,13 +18,12 @@ import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps} from './types'; import utils from './utils'; -const MapView = forwardRef( +const MapView = forwardRef( ({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); diff --git a/src/components/MapView/types.ts b/src/components/MapView/types.ts deleted file mode 100644 index 77b1b9eb82c2..000000000000 --- a/src/components/MapView/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {OnyxEntry} from 'react-native-onyx'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {MapViewProps} from './MapViewTypes'; - -type ComponentProps = MapViewProps; - -export type {ComponentProps}; From 801c3678937740e61db937a5cc32ae33d236fb79 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:39:53 +0530 Subject: [PATCH 021/352] Remove unused @ts-expect-error directive from MapView --- src/components/MapView/MapView.website.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 52cdaee902a3..b89bfa19e98e 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -50,7 +50,6 @@ const MapView = forwardRef((props, ref) => { } > Date: Thu, 10 Oct 2024 00:38:39 +0530 Subject: [PATCH 022/352] minor fix. Signed-off-by: krishna2323 --- src/libs/actions/Policy/Category.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index a49a5e5c733f..78b0f2dec9e2 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1346,10 +1346,6 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } -function getPolicyCategories(policyID: string) { - return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; -} - export { getPolicyCategories, openPolicyCategoriesPage, @@ -1374,5 +1370,4 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, - getPolicyCategories, }; From d8cecc998c28b73e41c027f6b395cf9b41065663 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 10 Oct 2024 01:05:06 +0530 Subject: [PATCH 023/352] fix category dupe step skip. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 +++--- src/libs/actions/Policy/Category.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 3070eaa092a7..cd2c6544b199 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,7 +3,7 @@ import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {getPolicyCategories} from '@libs/actions/Policy/Category'; +import {getPolicyCategoriesData} from '@libs/actions/Policy/Category'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1034,9 +1034,9 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); - const policyCategories = getPolicyCategories(report?.policyID ?? '-1'); + const policyCategories = getPolicyCategoriesData(report?.policyID ?? '-1'); const availableCategories = Object.values(policyCategories) - .filter((category) => differentValues.includes(category.name)) + .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); if ( diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 78b0f2dec9e2..41771ac5aa0e 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1346,6 +1346,10 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } +function getPolicyCategoriesData(policyID: string) { + return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; +} + export { getPolicyCategories, openPolicyCategoriesPage, @@ -1370,4 +1374,5 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, + getPolicyCategoriesData, }; From 6d07d46ae17c867d108cab7cdcd77649d76cb8d9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 14 Oct 2024 03:41:22 +0530 Subject: [PATCH 024/352] feat: skip tags review if no valid tags are available. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 35 +++++++++++++++++++++++++----- src/libs/actions/Policy/Tag.ts | 5 +++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index a627a7e61fd9..0e308af2692e 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -4,6 +4,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import {getPolicyCategoriesData} from '@libs/actions/Policy/Category'; +import {getPolicyTagsData} from '@libs/actions/Policy/Tag'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1014,6 +1015,8 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const areAllFieldsEqualForKey = areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')); + if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => getDescription(item) === ''); @@ -1033,8 +1036,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); - if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { + if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1043,13 +1048,31 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); - if ( - !areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && - (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes(''))) - ) { + if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } + } else if (fieldName === 'tag') { + const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); + const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); + if (isMultiLevelTags) { + if (areAllFieldsEqualForKey) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } else { + processChanges(fieldName, transactions, keys); + } + } else { + const differentValues = getDifferentValues(transactions, keys); + const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); + const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); + if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { + change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } } - } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { processChanges(fieldName, transactions, keys); diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 7708921f57b5..772e748ad4f2 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -1040,6 +1040,10 @@ function downloadTagsCSV(policyID: string, onDownloadFailed: () => void) { fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_TAGS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST, onDownloadFailed); } +function getPolicyTagsData(policyID: string) { + return allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; +} + export { buildOptimisticPolicyRecentlyUsedTags, setPolicyRequiresTag, @@ -1058,6 +1062,7 @@ export { setPolicyTagApprover, importPolicyTags, downloadTagsCSV, + getPolicyTagsData, }; export type {NewCustomUnit}; From bd352700e4209128f32a17a382ec81af53367425 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 14 Oct 2024 16:36:45 +0700 Subject: [PATCH 025/352] build optimistic change field action --- src/libs/ReportUtils.ts | 58 +++++++++++++++++++++++++++++++++++++- src/libs/actions/Report.ts | 27 ++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2623fab86a05..a53c3d32d4a1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -53,7 +53,7 @@ import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/ import type {Status} from '@src/types/onyx/PersonalDetails'; import type {ConnectionName} from '@src/types/onyx/Policy'; import type {NotificationPreference, Participants, PendingChatMember, Participant as ReportParticipant} from '@src/types/onyx/Report'; -import type {Message, ReportActions} from '@src/types/onyx/ReportAction'; +import type {Message, OldDotReportAction, ReportActions} from '@src/types/onyx/ReportAction'; import type {Comment, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -258,6 +258,11 @@ type OptimisticCancelPaymentReportAction = Pick< 'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' >; +type OptimisticChangeFieldAction = Pick< + OldDotReportAction & ReportAction, + 'actionName' | 'actorAccountID' | 'originalMessage' | 'person' | 'reportActionID' | 'created' | 'pendingAction' | 'message' +>; + type OptimisticEditedTaskReportAction = Pick< ReportAction, 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person' | 'delegateAccountID' @@ -2539,6 +2544,56 @@ function getReimbursementDeQueuedActionMessage( return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount: formattedAmount}); } +/** + * Builds an optimistic REIMBURSEMENT_DEQUEUED report action with a randomly generated reportActionID. + * + */ +function buildOptimisticChangeFieldAction(reportField: PolicyReportField, previousReportField: PolicyReportField): OptimisticChangeFieldAction { + return { + actionName: CONST.REPORT.ACTIONS.TYPE.CHANGE_FIELD, + actorAccountID: currentUserAccountID, + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'You', + }, + { + type: 'TEXT', + style: 'normal', + text: ` modified field '${reportField.name}'.`, + }, + { + type: 'TEXT', + style: 'normal', + text: ` New value is '${reportField.value}'`, + }, + { + type: 'TEXT', + style: 'normal', + text: ` (previously '${previousReportField.value}').`, + }, + ], + originalMessage: { + fieldName: reportField.name, + newType: reportField.type, + newValue: reportField.value, + oldType: previousReportField.type, + oldValue: previousReportField.value, + }, + person: [ + { + style: 'strong', + text: getCurrentUserDisplayNameOrEmail(), + type: 'TEXT', + }, + ], + reportActionID: NumberUtils.rand64(), + created: DateUtils.getDBTime(), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }; +} + /** * Builds an optimistic REIMBURSEMENT_DEQUEUED report action with a randomly generated reportActionID. * @@ -8470,6 +8525,7 @@ export { hasMissingInvoiceBankAccount, reasonForReportToBeInOptionList, getReasonAndReportActionThatRequiresAttention, + buildOptimisticChangeFieldAction, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 13b14d380758..57d5d7e8e287 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1909,6 +1909,8 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre const fieldViolation = ReportUtils.getFieldViolation(reportViolations, reportField); const recentlyUsedValues = allRecentlyUsedReportFields?.[fieldKey] ?? []; + const optimisticChangeFieldAction = ReportUtils.buildOptimisticChangeFieldAction(reportField, previousReportField); + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1922,6 +1924,13 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre }, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticChangeFieldAction.reportActionID]: optimisticChangeFieldAction, + }, + }, ]; if (fieldViolation) { @@ -1962,6 +1971,15 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre }, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticChangeFieldAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericUpdateReportFieldFailureMessage'), + }, + }, + }, ]; if (reportField.type === 'dropdown') { @@ -1987,6 +2005,15 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre }, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticChangeFieldAction.reportActionID]: { + pendingAction: null, + }, + }, + }, ]; const parameters = { From aa64f7e326e644586087632e2ee4b757640e6801 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 14 Oct 2024 18:53:43 +0530 Subject: [PATCH 026/352] minor fix. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0e308af2692e..0a3c18ec7a89 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1038,8 +1038,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1050,8 +1048,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); @@ -1068,8 +1064,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } } else if (areAllFieldsEqualForKey) { From 3fb2bea0fc36361b555dfbe410c4dc26f8f9c8a9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 14 Oct 2024 21:42:23 +0700 Subject: [PATCH 027/352] add parameter --- src/libs/actions/Report.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 57d5d7e8e287..f69c3fce4fb5 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2019,6 +2019,7 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre const parameters = { reportID, reportFields: JSON.stringify({[fieldKey]: reportField}), + reportFieldsActionIDs: JSON.stringify({[fieldKey]: optimisticChangeFieldAction.reportActionID}), }; API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData}); From 55d5d6905246c7d0120e813ce8bb8eb0a7e1c91a Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 15 Oct 2024 09:16:16 +0700 Subject: [PATCH 028/352] fix: incorrect lhn message when partially pay held report --- src/libs/ReportUtils.ts | 12 +++++++++++- src/libs/actions/IOU.ts | 28 +++++++++++++++++++--------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2623fab86a05..08962d8597c4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -446,6 +446,7 @@ type OptimisticIOUReport = Pick< | 'parentReportID' | 'lastVisibleActionCreated' | 'fieldList' + | 'parentReportActionID' >; type DisplayNameWithTooltips = Array>; @@ -4410,7 +4411,15 @@ function buildOptimisticTaskCommentReportAction( * @param isSendingMoney - If we pay someone the IOU should be created as settled */ -function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number, total: number, chatReportID: string, currency: string, isSendingMoney = false): OptimisticIOUReport { +function buildOptimisticIOUReport( + payeeAccountID: number, + payerAccountID: number, + total: number, + chatReportID: string, + currency: string, + isSendingMoney = false, + parentReportActionID?: string, +): OptimisticIOUReport { const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency); const personalDetails = getPersonalDetailsForAccountID(payerAccountID); const payerEmail = 'login' in personalDetails ? personalDetails.login : ''; @@ -4440,6 +4449,7 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number parentReportID: chatReportID, lastVisibleActionCreated: DateUtils.getDBTime(), fieldList: policy?.fieldList, + parentReportActionID, }; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index de896e6f72f5..dc0bf138b43e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6472,15 +6472,25 @@ function getReportFromHoldRequestsOnyxData( const firstHoldTransaction = holdTransactions.at(0); const newParentReportActionID = rand64(); - const optimisticExpenseReport = ReportUtils.buildOptimisticExpenseReport( - chatReport.reportID, - chatReport.policyID ?? iouReport?.policyID ?? '', - recipient.accountID ?? 1, - holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), - getCurrency(firstHoldTransaction), - false, - newParentReportActionID, - ); + const optimisticExpenseReport = ReportUtils.isExpenseReport(iouReport) + ? ReportUtils.buildOptimisticExpenseReport( + chatReport.reportID, + chatReport.policyID ?? iouReport?.policyID ?? '', + recipient.accountID ?? 1, + holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), + getCurrency(firstHoldTransaction), + false, + newParentReportActionID, + ) + : ReportUtils.buildOptimisticIOUReport( + iouReport?.ownerAccountID ?? -1, + iouReport?.managerID ?? -1, + holdTransactions.reduce((acc, transaction) => acc + transaction.amount, 0) * (ReportUtils.isIOUReport(iouReport) ? 1 : -1), + chatReport.reportID, + getCurrency(firstHoldTransaction), + false, + newParentReportActionID, + ); const optimisticExpenseReportPreview = ReportUtils.buildOptimisticReportPreview( chatReport, optimisticExpenseReport, From 58663255a5c7d8eaf92eb41dd27b93998426bda7 Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 16 Oct 2024 23:49:06 +0500 Subject: [PATCH 029/352] hide expensify from new chat page --- src/libs/OptionsListUtils.ts | 2 +- src/pages/NewChatPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fbf2f3b94c7c..1ede98338f87 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1994,7 +1994,7 @@ function getOptions( allPersonalDetailsOptions = lodashOrderBy(allPersonalDetailsOptions, [(personalDetail) => personalDetail.text?.toLowerCase()], 'asc'); } - const optionsToExclude: Option[] = []; + const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}]; // If we're including selected options from the search results, we only want to exclude them if the search input is empty // This is because on certain pages, we show the selected options at the top when the search input is empty diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index c406f7f3058c..494e099933fe 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -229,7 +229,7 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { const itemRightSideComponent = useCallback( (item: ListItem & OptionsListUtils.Option, isFocused?: boolean) => { - if (!!item.isSelfDM || (item.accountID && CONST.NON_ADDABLE_ACCOUNT_IDS.includes(item.accountID))) { + if (!!item.isSelfDM || (item.login && excludedGroupEmails.includes(item.login))) { return null; } /** From 039f0d6982b91199c18400bf4bafccfa3a0a6390 Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 17 Oct 2024 00:12:55 +0500 Subject: [PATCH 030/352] fix type error --- src/pages/NewChatPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 494e099933fe..bd3e30a48cf4 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -38,7 +38,7 @@ type NewChatPageProps = { isGroupChat?: boolean; }; -const excludedGroupEmails = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); +const excludedGroupEmails: Array = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); function useOptions({isGroupChat}: NewChatPageProps) { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); From 0fd9a41a1e274fbf11b4d7c0b8aef6f9ade3d4aa Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 17 Oct 2024 00:28:04 +0500 Subject: [PATCH 031/352] fix lint error --- src/pages/NewChatPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index bd3e30a48cf4..5b44659babab 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -38,7 +38,7 @@ type NewChatPageProps = { isGroupChat?: boolean; }; -const excludedGroupEmails: Array = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); +const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); function useOptions({isGroupChat}: NewChatPageProps) { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); From 45d852fd4d6a968b82b3e8ef0b87dc87f2136263 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 17 Oct 2024 20:01:02 +0530 Subject: [PATCH 032/352] clear review duplicates data when starting a new flow. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 92444062ced9..5021a3e65c56 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -289,6 +289,7 @@ function MoneyRequestPreviewContent({ const navigateToReviewFields = () => { const backTo = route.params.backTo; + Transaction.abandonReviewDuplicateTransactions(); const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID ?? ''); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? '', reportID: transaction?.reportID}); From 1d3dee84a0e4a16646cd83cd8c5968d5ad9c92d9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 18 Oct 2024 18:16:58 +0530 Subject: [PATCH 033/352] category/tag review shows when feature is disabled. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 15 ++++++++++----- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0a3c18ec7a89..919ce85c5c1d 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1014,9 +1014,9 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const firstTransaction = transactions.at(0); const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const policy = PolicyUtils.getPolicy(report?.policyID); const areAllFieldsEqualForKey = areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')); - if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => getDescription(item) === ''); @@ -1032,12 +1032,13 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri processChanges(fieldName, transactions, keys); } } else if (fieldName === 'taxCode') { - const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1046,14 +1047,16 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); - if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { + if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); if (isMultiLevelTags) { - if (areAllFieldsEqualForKey) { + if (areAllFieldsEqualForKey || !policy?.areTagsEnabled) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { processChanges(fieldName, transactions, keys); @@ -1062,8 +1065,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const differentValues = getDifferentValues(transactions, keys); const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); - if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { + if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } } else if (areAllFieldsEqualForKey) { diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index 90e8c26656c9..857a93429f00 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,10 +20,10 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID ?? route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( From 3620918bf11759e18a7d23ae49faa4eacf1a211f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 19:16:12 +0530 Subject: [PATCH 034/352] add selftourviewed command --- src/libs/API/types.ts | 2 ++ src/libs/actions/Welcome/index.ts | 17 ++++++++++++++++- src/types/onyx/Onboarding.ts | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 929e709559b7..0bfd77c4dcd0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -429,6 +429,7 @@ const WRITE_COMMANDS = { SET_CARD_EXPORT_ACCOUNT: 'SetCardExportAccount', SET_MISSING_PERSONAL_DETAILS_AND_SHIP_EXPENSIFY_CARD: 'SetMissingPersonalDetailsAndShipExpensifyCard', SET_INVOICING_TRANSFER_BANK_ACCOUNT: 'SetInvoicingTransferBankAccount', + SELF_TOUR_VIEWED: 'SelfTourViewed', } as const; type WriteCommand = ValueOf; @@ -868,6 +869,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_XERO_SYNC_REIMBURSEMENT_ACCOUNT_ID]: Parameters.UpdateXeroGenericTypeParams; [WRITE_COMMANDS.SET_INVOICING_TRANSFER_BANK_ACCOUNT]: Parameters.SetInvoicingTransferBankAccountParams; + [WRITE_COMMANDS.SELF_TOUR_VIEWED]: null; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index d504c5550331..19a570ab610f 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -2,7 +2,7 @@ import {NativeModules} from 'react-native'; import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; +import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import Log from '@libs/Log'; import type {OnboardingCompanySizeType, OnboardingPurposeType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -183,6 +183,20 @@ function resetAllChecks() { OnboardingFlow.clearInitialPath(); } +function setSelfTourViewed() { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_ONBOARDING, + value: { + selfTourViewed: true, + }, + }, + ]; + + API.write(WRITE_COMMANDS.SELF_TOUR_VIEWED, null, {optimisticData}); +} + export { onServerDataReady, isOnboardingFlowCompleted, @@ -195,4 +209,5 @@ export { completeHybridAppOnboarding, setOnboardingErrorMessage, setOnboardingCompanySize, + setSelfTourViewed, }; diff --git a/src/types/onyx/Onboarding.ts b/src/types/onyx/Onboarding.ts index 4b6a52f25cb4..2cf8eccba1c2 100644 --- a/src/types/onyx/Onboarding.ts +++ b/src/types/onyx/Onboarding.ts @@ -11,6 +11,9 @@ type Onboarding = { /** A string that informs which qualifier the user selected during sign up */ signupQualifier: ValueOf; + + /** A Boolean that tells whether the user has seen navattic tour */ + selfTourViewed?: boolean; }; export default Onboarding; From 31ff69bb79ad057aafd7825c5598bb156c7f49b0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:00:06 +0530 Subject: [PATCH 035/352] add util to get tour url --- src/CONST.ts | 6 ++++-- src/libs/TourUtils.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/libs/TourUtils.ts diff --git a/src/CONST.ts b/src/CONST.ts index 84003710938a..ff9f960c756e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -748,8 +748,10 @@ const CONST = { // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', NAVATTIC: { - ADMIN_TOUR: 'https://expensify.navattic.com/kh204a7', - EMPLOYEE_TOUR: 'https://expensify.navattic.com/35609gb', + ADMIN_TOUR_PRODUCTION: 'https://expensify.navattic.com/kh204a7', + ADMIN_TOUR_STAGING: 'https://expensify.navattic.com/stagingAdmin', + EMPLOYEE_TOUR_PRODUCTION: 'https://expensify.navattic.com/35609gb', + EMPLOYEE_TOUR_STAGING: 'https://expensify.navattic.com/stagingEmployee', }, OLDDOT_URLS: { diff --git a/src/libs/TourUtils.ts b/src/libs/TourUtils.ts new file mode 100644 index 000000000000..a88ee47cc563 --- /dev/null +++ b/src/libs/TourUtils.ts @@ -0,0 +1,14 @@ +import type {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; +import type {OnboardingPurposeType} from '@src/CONST'; + +function getNavatticURL(environment: ValueOf, introSelected?: OnboardingPurposeType) { + const adminTourURL = environment === CONST.ENVIRONMENT.PRODUCTION ? CONST.NAVATTIC.ADMIN_TOUR_PRODUCTION : CONST.NAVATTIC.ADMIN_TOUR_STAGING; + const employeeTourURL = environment === CONST.ENVIRONMENT.PRODUCTION ? CONST.NAVATTIC.EMPLOYEE_TOUR_PRODUCTION : CONST.NAVATTIC.EMPLOYEE_TOUR_STAGING; + return introSelected === CONST.SELECTABLE_ONBOARDING_CHOICES.MANAGE_TEAM ? adminTourURL : employeeTourURL; +} + +export { + // eslint-disable-next-line import/prefer-default-export + getNavatticURL, +}; From 528395398d39a00243c860a4d1553559de8a5c54 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:02:57 +0530 Subject: [PATCH 036/352] use util to get url --- src/pages/Search/EmptySearchView.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index c148f1b56d62..8bcb3ee7b46d 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -10,12 +10,14 @@ import MenuItem from '@components/MenuItem'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import * as ReportUtils from '@libs/ReportUtils'; +import {getNavatticURL} from '@libs/TourUtils'; import * as TripsResevationUtils from '@libs/TripReservationUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; @@ -93,7 +95,8 @@ function EmptySearchView({type}: EmptySearchViewProps) { }, [styles, translate, ctaErrorMessage]); const [onboardingPurpose] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: (introSelected) => introSelected?.choice}); - const navatticLink = onboardingPurpose === CONST.SELECTABLE_ONBOARDING_CHOICES.MANAGE_TEAM ? CONST.NAVATTIC.ADMIN_TOUR : CONST.NAVATTIC.EMPLOYEE_TOUR; + const {environment} = useEnvironment(); + const navatticURL = getNavatticURL(environment, onboardingPurpose); const content = useMemo(() => { switch (type) { @@ -120,7 +123,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { title: translate('search.searchResults.emptyExpenseResults.title'), subtitle: translate('search.searchResults.emptyExpenseResults.subtitle'), buttons: [ - {buttonText: translate('emptySearchView.takeATour'), buttonAction: () => Link.openExternalLink(navatticLink)}, + {buttonText: translate('emptySearchView.takeATour'), buttonAction: () => Link.openExternalLink(navatticURL)}, { buttonText: translate('iou.createExpense'), buttonAction: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.CREATE, ReportUtils.generateReportID())), @@ -140,7 +143,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { headerContentStyles: styles.emptyStateFolderWebStyles, }; } - }, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage, navatticLink]); + }, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage, navatticURL]); return ( Date: Sun, 20 Oct 2024 20:27:57 +0530 Subject: [PATCH 037/352] fix staging url --- src/CONST.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ff9f960c756e..01466d8baf86 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -749,9 +749,9 @@ const CONST = { DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', NAVATTIC: { ADMIN_TOUR_PRODUCTION: 'https://expensify.navattic.com/kh204a7', - ADMIN_TOUR_STAGING: 'https://expensify.navattic.com/stagingAdmin', + ADMIN_TOUR_STAGING: 'https://expensify.navattic.com/3i300k18', EMPLOYEE_TOUR_PRODUCTION: 'https://expensify.navattic.com/35609gb', - EMPLOYEE_TOUR_STAGING: 'https://expensify.navattic.com/stagingEmployee', + EMPLOYEE_TOUR_STAGING: 'https://expensify.navattic.com/cf15002s', }, OLDDOT_URLS: { From c1081d02784a0020a5dde6b865f582afcbd98b84 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:28:05 +0530 Subject: [PATCH 038/352] add tour icon --- assets/images/tour.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 assets/images/tour.svg diff --git a/assets/images/tour.svg b/assets/images/tour.svg new file mode 100644 index 000000000000..926cfcd8de69 --- /dev/null +++ b/assets/images/tour.svg @@ -0,0 +1,6 @@ + + + + + + From cd6558b6a095cd842500ad22f7466e9219acc7f8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:28:12 +0530 Subject: [PATCH 039/352] add tour icon --- src/components/Icon/Expensicons.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index cd9c97105ff0..621bc5e43bea 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -182,6 +182,7 @@ import Task from '@assets/images/task.svg'; import Thread from '@assets/images/thread.svg'; import ThreeDots from '@assets/images/three-dots.svg'; import ThumbsUp from '@assets/images/thumbs-up.svg'; +import Tour from '@assets/images/tour.svg'; import Transfer from '@assets/images/transfer.svg'; import Trashcan from '@assets/images/trashcan.svg'; import Unlock from '@assets/images/unlock.svg'; @@ -404,4 +405,5 @@ export { Bookmark, Star, QBDSquare, + Tour, }; From 38820777379e9e84eb42231e4e88bdbdc1f2364d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:29:12 +0530 Subject: [PATCH 040/352] add lang --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5798f7fe48e9..e68e89296222 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5132,6 +5132,10 @@ const translations = { emptySearchView: { takeATour: 'Take a tour', }, + tour: { + takeATwoMinuteTour: 'Take a 2-minute tour', + exploreExpensify: 'Explore everything Expensify has to offer', + }, }; export default translations satisfies TranslationDeepObject; diff --git a/src/languages/es.ts b/src/languages/es.ts index 84c03d5d9bf3..9a24b1bf9386 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5649,6 +5649,10 @@ const translations = { emptySearchView: { takeATour: 'Haz un tour', }, + tour: { + takeATwoMinuteTour: 'Haz un tour de 2 minutos', + exploreExpensify: 'Explora todo lo que Expensify tiene para ofrecer', + }, }; export default translations satisfies TranslationDeepObject; From 469c0520a80fd3f6a97961c0b99b86f2591be2ab Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:29:25 +0530 Subject: [PATCH 041/352] add onboarding selftourviewed selector --- src/libs/onboardingSelectors.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libs/onboardingSelectors.ts b/src/libs/onboardingSelectors.ts index efa67d2aed48..f4b4759867bf 100644 --- a/src/libs/onboardingSelectors.ts +++ b/src/libs/onboardingSelectors.ts @@ -35,4 +35,19 @@ function hasCompletedHybridAppOnboardingFlowSelector(tryNewDotData: OnyxValue): boolean | undefined { + if (Array.isArray(onboarding)) { + return true; + } + + return onboarding?.selfTourViewed; +} + +export {hasCompletedGuidedSetupFlowSelector, hasCompletedHybridAppOnboardingFlowSelector, hasSeenTourSelector}; From 63af4e7b7f01d664e132860d71a303aeb1705f54 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 20 Oct 2024 20:30:03 +0530 Subject: [PATCH 042/352] add tour to FAB --- .../FloatingActionButtonAndPopover.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index a49b474b185e..764095a49193 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -11,6 +11,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PopoverMenu from '@components/PopoverMenu'; import Text from '@components/Text'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; @@ -23,14 +24,18 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; +import {hasSeenTourSelector} from '@libs/onboardingSelectors'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; +import {getNavatticURL} from '@libs/TourUtils'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; +import * as Link from '@userActions/Link'; import * as Policy from '@userActions/Policy/Policy'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; +import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -189,6 +194,12 @@ function FloatingActionButtonAndPopover( const {canUseSpotnanaTravel, canUseCombinedTrackSubmit} = usePermissions(); const canSendInvoice = useMemo(() => PolicyUtils.canSendInvoice(allPolicies as OnyxCollection, session?.email), [allPolicies, session?.email]); + const {environment} = useEnvironment(); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const navatticURL = getNavatticURL(environment, introSelected?.choice); + const [hasSeenTour = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasSeenTourSelector, + }); const quickActionAvatars = useMemo(() => { if (quickActionReport) { @@ -454,7 +465,24 @@ function FloatingActionButtonAndPopover( }, ] : []), - ...(!isLoading && !Policy.hasActiveChatEnabledPolicies(allPolicies) + ...(!hasSeenTour + ? [ + { + icon: Expensicons.Tour, + displayInDefaultIconColor: true, + contentFit: 'contain' as ImageContentFit, + iconWidth: 46, + iconHeight: 40, + text: translate('tour.takeATwoMinuteTour'), + description: translate('tour.exploreExpensify'), + onSelected: () => { + Welcome.setSelfTourViewed(); + Link.openExternalLink(navatticURL); + }, + }, + ] + : []), + ...(!isLoading && !Policy.hasActiveChatEnabledPolicies(allPolicies) && hasSeenTour ? [ { displayInDefaultIconColor: true, From 7e9bf7c70414d2fdefba7a8f51d584c99fad69ce Mon Sep 17 00:00:00 2001 From: c3024 Date: Mon, 21 Oct 2024 11:35:46 +0530 Subject: [PATCH 043/352] add self guided tours to tasks --- src/CONST.ts | 17 ++++++++++++++++- src/libs/actions/Report.ts | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 01466d8baf86..4bb90fca7aab 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -83,6 +83,13 @@ const signupQualifiers = { SMB: 'smb', } as const; +const selfGuidedTourTask: OnboardingTaskType = { + type: 'viewTour', + autoCompleted: false, + title: 'Take a 2-minute tour', + description: ({navatticURL}) => `[Take a self-guided product tour](${navatticURL}) and learn about everything Expensify has to offer.`, +}; + const onboardingEmployerOrSubmitMessage: OnboardingMessageType = { message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.', video: { @@ -93,6 +100,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessageType = { height: 960, }, tasks: [ + selfGuidedTourTask, { type: 'submitExpense', autoCompleted: false, @@ -149,11 +157,15 @@ const onboardingCompanySize = { type OnboardingInviteType = ValueOf; +type Description = + | string + | ((params: Partial<{adminsRoomLink: string; workspaceCategoriesLink: string; workspaceMoreFeaturesLink: string; workspaceMembersLink: string; navatticURL: string}>) => string); + type OnboardingTaskType = { type: string; autoCompleted: boolean; title: string; - description: string | ((params: Partial<{adminsRoomLink: string; workspaceCategoriesLink: string; workspaceMoreFeaturesLink: string; workspaceMembersLink: string}>) => string); + description: Description; }; type OnboardingMessageType = { @@ -4652,6 +4664,7 @@ const CONST = { '\n' + '*Your new workspace is ready! It’ll keep all of your spend (and chats) in one place.*', }, + selfGuidedTourTask, { type: 'meetGuide', autoCompleted: false, @@ -4748,6 +4761,7 @@ const CONST = { height: 960, }, tasks: [ + selfGuidedTourTask, { type: 'trackExpense', autoCompleted: false, @@ -4776,6 +4790,7 @@ const CONST = { height: 960, }, tasks: [ + selfGuidedTourTask, { type: 'startChat', autoCompleted: false, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7071c96f8612..bd237908a96e 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -58,6 +58,8 @@ import DateUtils from '@libs/DateUtils'; import {prepareDraftComment} from '@libs/DraftCommentUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as Environment from '@libs/Environment/Environment'; +import getEnvironment from '@libs/Environment/getEnvironment'; +import type EnvironmentType from '@libs/Environment/getEnvironment/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import fileDownload from '@libs/fileDownload'; import HttpUtils from '@libs/HttpUtils'; @@ -83,6 +85,7 @@ import type {OptimisticAddCommentReportAction} from '@libs/ReportUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation'; +import {getNavatticURL} from '@libs/TourUtils'; import Visibility from '@libs/Visibility'; import CONFIG from '@src/CONFIG'; import type {OnboardingAccountingType, OnboardingCompanySizeType, OnboardingPurposeType} from '@src/CONST'; @@ -102,6 +105,7 @@ import type { ReportActionReactions, ReportUserIsTyping, } from '@src/types/onyx'; +import type IntroSelected from '@src/types/onyx/IntroSelected'; import type {Decision} from '@src/types/onyx/OriginalMessage'; import type {ConnectionName} from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; @@ -269,9 +273,21 @@ Onyx.connect({ callback: (value) => (allReportDraftComments = value), }); +let introSelected: IntroSelected | undefined = {}; + +Onyx.connect({ + key: ONYXKEYS.NVP_INTRO_SELECTED, + callback: (val) => (introSelected = val), +}); + let environmentURL: string; Environment.getEnvironmentURL().then((url: string) => (environmentURL = url)); +let environment: EnvironmentType; +getEnvironment().then((env) => { + environment = env; +}); + registerPaginationConfig({ initialCommand: WRITE_COMMANDS.OPEN_REPORT, previousCommand: READ_COMMANDS.GET_OLDER_ACTIONS, @@ -3359,7 +3375,6 @@ function completeOnboarding( reportComment: videoComment.commentText, }; } - const tasksData = data.tasks.map((task, index) => { const taskDescription = typeof task.description === 'function' @@ -3368,6 +3383,7 @@ function completeOnboarding( workspaceCategoriesLink: `${environmentURL}/${ROUTES.WORKSPACE_CATEGORIES.getRoute(onboardingPolicyID ?? '-1')}`, workspaceMembersLink: `${environmentURL}/${ROUTES.WORKSPACE_MEMBERS.getRoute(onboardingPolicyID ?? '-1')}`, workspaceMoreFeaturesLink: `${environmentURL}/${ROUTES.WORKSPACE_MORE_FEATURES.getRoute(onboardingPolicyID ?? '-1')}`, + navatticURL: getNavatticURL(environment, introSelected?.choice), }) : task.description; const currentTask = ReportUtils.buildOptimisticTaskReport( From ec3e59831d6353275512f404810d14fd7612c67e Mon Sep 17 00:00:00 2001 From: flaviadefaria <80457174+flaviadefaria@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:36:44 +0200 Subject: [PATCH 044/352] Update Connect-To-QuickBooks-Desktop.md Adding a note before the FAQ sections so that users are reminded to store the connection password --- .../quickbooks-desktop/Connect-To-QuickBooks-Desktop.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md index bda84eb0a49f..30785330a9ad 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md @@ -52,6 +52,10 @@ For this step, it is key to ensure that the correct company file is open in Quic ![The Web Connector pop-up, where you will need to click "Yes"](https://help.expensify.com/assets/images/QBO_desktop_07.png){:width="100%"} +{% include info.html %} +Be sure to securely save this password in a trusted password manager. You'll need it for future configuration updates or troubleshooting. Having it easily accessible will help avoid delays and ensure a smoother workflow. +{% include end-info.html %} + # FAQ ## What are the hardware and software requirements for the QuickBooks Desktop connector? From 28940c2602ead14d427cb3a726287edf2828f8a9 Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 22 Oct 2024 19:05:34 +0700 Subject: [PATCH 045/352] fix: app crash when opening split view --- src/components/MoneyRequestConfirmationList.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index a46b9d62dfde..df12ec986aa9 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -159,7 +159,7 @@ function MoneyRequestConfirmationList({ payeePersonalDetails: payeePersonalDetailsProp, isReadOnly = false, bankAccountRoute = '', - policyID = '', + policyID, reportID = '', receiptPath = '', iouComment, @@ -174,14 +174,14 @@ function MoneyRequestConfirmationList({ shouldPlaySound = true, isConfirmed, }: MoneyRequestConfirmationListProps) { - const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); - const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); - const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`); - const [defaultMileageRate] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, { + const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID ?? '-1'}`); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID ?? '-1'}`); + const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID ?? '-1'}`); + const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID ?? '-1'}`); + const [defaultMileageRate] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID ?? '-1'}`, { selector: (selectedPolicy) => DistanceRequestUtils.getDefaultMileageRate(selectedPolicy), }); - const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`); + const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID ?? '-1'}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST); const policy = policyReal ?? policyDraft; From 80fd4e774bb51f3aec476f6da5ae2a8f65bb8340 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 23 Oct 2024 10:50:28 +0200 Subject: [PATCH 046/352] add recentSearchHash --- src/components/Search/types.ts | 2 ++ src/libs/SearchQueryUtils.ts | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 4f96090be9d0..43ebbed24943 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -76,6 +76,8 @@ type SearchQueryAST = { type SearchQueryJSON = { inputQuery: SearchQueryString; hash: number; + /** Hash used for putting queries in recent searches list. It ignores sortOrder and sortBy, because we want to treat queries differing only in sort params as the same query */ + recentSearchHash: number; flatFilters: QueryFilters; } & SearchQueryAST; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 32c2eff72007..7c7395ef1a9b 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -208,15 +208,13 @@ function findIDFromDisplayValue(filterName: ValueOf Date: Wed, 23 Oct 2024 11:40:38 +0200 Subject: [PATCH 047/352] Revert "Revert "[HybridApp] Allow classic experience users to use NewDot travel page"" This reverts commit 3d342ee74e418637451323a17cc54170aafbe659. --- src/ONYXKEYS.ts | 4 ++ src/components/InitialURLContextProvider.tsx | 7 +-- src/components/ScreenWrapper.tsx | 13 ++++- src/hooks/useOnboardingFlow.ts | 8 +-- src/libs/TripReservationUtils.ts | 25 +++++++-- src/libs/actions/Link.ts | 6 ++- src/libs/actions/Session/index.ts | 53 +++++++++++++------- tests/perf-test/ReportScreen.perf-test.tsx | 4 +- tests/perf-test/SearchRouter.perf-test.tsx | 2 + 9 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d083a46d7760..14c0dc4abc50 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -441,6 +441,9 @@ const ONYXKEYS = { /** Stores recently used currencies */ RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies', + /** States whether we transitioned from OldDot to show only certain group of screens. It should be undefined on pure NewDot. */ + IS_SINGLE_NEW_DOT_ENTRY: 'isSingleNewDotEntry', + /** Company cards custom names */ NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames', @@ -1004,6 +1007,7 @@ type OnyxValuesMapping = { [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; [ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet; [ONYXKEYS.LAST_ROUTE]: string; + [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined; [ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean; [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx index 85ad54ca6c94..f026f2de53f9 100644 --- a/src/components/InitialURLContextProvider.tsx +++ b/src/components/InitialURLContextProvider.tsx @@ -31,9 +31,10 @@ function InitialURLContextProvider({children, url}: InitialURLContextProviderPro useEffect(() => { if (url) { - const route = signInAfterTransitionFromOldDot(url); - setInitialURL(route); - setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + signInAfterTransitionFromOldDot(url).then((route) => { + setInitialURL(route); + setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + }); return; } Linking.getInitialURL().then((initURL) => { diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 152a14fc3eb7..967d366aaa7c 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -1,9 +1,9 @@ -import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {UNSTABLE_usePreventRemove, useIsFocused, useNavigation, useRoute} from '@react-navigation/native'; import type {StackNavigationProp} from '@react-navigation/stack'; import type {ForwardedRef, ReactNode} from 'react'; import React, {createContext, forwardRef, useEffect, useMemo, useRef, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import {Keyboard, PanResponder, View} from 'react-native'; +import {Keyboard, NativeModules, PanResponder, View} from 'react-native'; import {PickerAvoidingView} from 'react-native-picker-select'; import type {EdgeInsets} from 'react-native-safe-area-context'; import useEnvironment from '@hooks/useEnvironment'; @@ -164,6 +164,15 @@ function ScreenWrapper( isKeyboardShownRef.current = keyboardState?.isKeyboardShown ?? false; + const route = useRoute(); + const shouldReturnToOldDot = useMemo(() => { + return !!route?.params && 'singleNewDotEntry' in route.params && route.params.singleNewDotEntry === 'true'; + }, [route]); + + UNSTABLE_usePreventRemove(shouldReturnToOldDot, () => { + NativeModules.HybridAppModule?.closeReactNativeApp(false, false); + }); + const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponderCapture: (_e, gestureState) => gestureState.numberActiveTouches === CONST.TEST_TOOL.NUMBER_OF_TAPS, diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts index 5ccd3bab9378..9e97c552a6e0 100644 --- a/src/hooks/useOnboardingFlow.ts +++ b/src/hooks/useOnboardingFlow.ts @@ -21,14 +21,16 @@ function useOnboardingFlowRouter() { selector: hasCompletedHybridAppOnboardingFlowSelector, }); + const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY); + useEffect(() => { - if (isLoadingOnyxValue(isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata)) { + if (isLoadingOnyxValue(isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata, isSingleNewDotEntryMetadata)) { return; } if (NativeModules.HybridAppModule) { // When user is transitioning from OldDot to NewDot, we usually show the explanation modal - if (isHybridAppOnboardingCompleted === false) { + if (isHybridAppOnboardingCompleted === false && !isSingleNewDotEntry) { Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT); } @@ -43,7 +45,7 @@ function useOnboardingFlowRouter() { if (!NativeModules.HybridAppModule && isOnboardingCompleted === false) { OnboardingFlow.startOnboardingFlow(); } - }, [isOnboardingCompleted, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata]); + }, [isOnboardingCompleted, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata, isSingleNewDotEntryMetadata, isSingleNewDotEntry]); return {isOnboardingCompleted, isHybridAppOnboardingCompleted}; } diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts index b7f754f9cac6..f2ce5113af81 100644 --- a/src/libs/TripReservationUtils.ts +++ b/src/libs/TripReservationUtils.ts @@ -1,5 +1,6 @@ import {Str} from 'expensify-common'; import type {Dispatch, SetStateAction} from 'react'; +import {NativeModules} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; @@ -13,6 +14,7 @@ import type Transaction from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import * as Link from './actions/Link'; +import Log from './Log'; import Navigation from './Navigation/Navigation'; import * as PolicyUtils from './PolicyUtils'; @@ -40,6 +42,14 @@ Onyx.connect({ }, }); +let isSingleNewDotEntry: boolean | undefined; +Onyx.connect({ + key: ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY, + callback: (val) => { + isSingleNewDotEntry = val; + }, +}); + function getTripReservationIcon(reservationType: ReservationType): IconAsset { switch (reservationType) { case CONST.RESERVATION_TYPE.FLIGHT: @@ -91,8 +101,17 @@ function bookATrip(translate: LocaleContextProps['translate'], setCtaErrorMessag if (ctaErrorMessage) { setCtaErrorMessage(''); } - Link.openTravelDotLink(activePolicyID)?.catch(() => { - setCtaErrorMessage(translate('travel.errorMessage')); - }); + Link.openTravelDotLink(activePolicyID) + ?.then(() => { + if (!NativeModules.HybridAppModule || !isSingleNewDotEntry) { + return; + } + + Log.info('[HybridApp] Returning to OldDot after opening TravelDot'); + NativeModules.HybridAppModule.closeReactNativeApp(false, false); + }) + ?.catch(() => { + setCtaErrorMessage(translate('travel.errorMessage')); + }); } export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon, bookATrip}; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 13fcea0df85d..4cda676d89e8 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -111,7 +111,7 @@ function openTravelDotLink(policyID: OnyxEntry, postLoginPath?: string) policyID, }; - return new Promise((_, reject) => { + return new Promise((resolve, reject) => { const error = new Error('Failed to generate spotnana token.'); asyncOpenURL( @@ -122,7 +122,9 @@ function openTravelDotLink(policyID: OnyxEntry, postLoginPath?: string) reject(error); throw error; } - return buildTravelDotURL(response.spotnanaToken, postLoginPath); + const travelURL = buildTravelDotURL(response.spotnanaToken, postLoginPath); + resolve(undefined); + return travelURL; }) .catch(() => { reject(error); diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 37488442525d..d75c5064f93a 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -483,28 +483,43 @@ function signUpUser() { function signInAfterTransitionFromOldDot(transitionURL: string) { const [route, queryParams] = transitionURL.split('?'); - const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding} = Object.fromEntries( - queryParams.split('&').map((param) => { - const [key, value] = param.split('='); - return [key, value]; - }), - ); - - const setSessionDataAndOpenApp = () => { - Onyx.multiSet({ - [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, - [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, - [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, - }).then(App.openApp); + const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding, isSingleNewDotEntry, primaryLogin} = + Object.fromEntries( + queryParams.split('&').map((param) => { + const [key, value] = param.split('='); + return [key, value]; + }), + ); + + const clearOnyxForNewAccount = () => { + if (clearOnyxOnStart !== 'true') { + return Promise.resolve(); + } + + return Onyx.clear(KEYS_TO_PRESERVE); }; - if (clearOnyxOnStart === 'true') { - Onyx.clear(KEYS_TO_PRESERVE).then(setSessionDataAndOpenApp); - } else { - setSessionDataAndOpenApp(); - } + const setSessionDataAndOpenApp = new Promise((resolve) => { + clearOnyxForNewAccount() + .then(() => + Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, + [ONYXKEYS.ACCOUNT]: {primaryLogin}, + [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, + [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: isSingleNewDotEntry === 'true', + [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, + }), + ) + .then(App.openApp) + .catch((error) => { + Log.hmmm('[HybridApp] Initialization of HybridApp has failed. Forcing transition', {error}); + }) + .finally(() => { + resolve(`${route}?singleNewDotEntry=${isSingleNewDotEntry}` as Route); + }); + }); - return route as Route; + return setSessionDataAndOpenApp; } /** diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index 550b6adabc36..b43004fcc82b 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -118,6 +118,8 @@ jest.mock('@react-navigation/native', () => { useFocusEffect: jest.fn(), useIsFocused: () => true, useRoute: () => jest.fn(), + // eslint-disable-next-line @typescript-eslint/naming-convention + UNSTABLE_usePreventRemove: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), addListener: () => jest.fn(), @@ -231,7 +233,6 @@ test('[ReportScreen] should render ReportScreen', async () => { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measureRenders( { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measureRenders( { useFocusEffect: jest.fn(), useIsFocused: () => true, useRoute: () => jest.fn(), + // eslint-disable-next-line @typescript-eslint/naming-convention + UNSTABLE_usePreventRemove: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), addListener: () => jest.fn(), From 0bc8694afda1c3e7411695c59764593803386e60 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 23 Oct 2024 16:36:50 +0530 Subject: [PATCH 048/352] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index cf10aa35025f..d25c56451dba 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1085,7 +1085,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { @@ -1097,7 +1097,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { @@ -1115,7 +1115,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } From a42aadcd277284a09fafa5638b73ae5d9b9276d3 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 23 Oct 2024 13:14:42 +0200 Subject: [PATCH 049/352] Fix bug when user cannot return from Travel page --- src/libs/Navigation/NavigationRoot.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index bb005fc6b763..c23c3783b3bf 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -108,7 +108,8 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh } // If there is no lastVisitedPath, we can do early return. We won't modify the default behavior. - if (!lastVisitedPath) { + // The same applies to HybridApp, as we always define the route to which we want to transition. + if (!lastVisitedPath || NativeModules.HybridAppModule) { return undefined; } From 5e98efc6a8f25ddc9f4113902881e829782b8a2a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 23 Oct 2024 13:16:33 +0200 Subject: [PATCH 050/352] integrate press selection handler for web TextInput --- src/components/AmountForm.tsx | 4 +- src/components/MoneyRequestAmountInput.tsx | 8 ++-- .../BaseTextInputWithCurrencySymbol.tsx | 4 +- .../index.android.tsx | 4 +- .../TextInputWithCurrencySymbol/index.tsx | 8 +++- .../TextInputWithCurrencySymbol/index.web.tsx | 42 +++++++++++++++++++ .../TextInputWithCurrencySymbol/types.ts | 12 ++++-- 7 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 src/components/TextInputWithCurrencySymbol/index.web.tsx diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 4848577bdea0..aa2ff1387de4 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -16,7 +16,7 @@ import TextInput from './TextInput'; import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; -import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; +import type BaseTextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; type AmountFormProps = { /** Amount supplied by the FormProvider */ @@ -51,7 +51,7 @@ type AmountFormProps = { /** Number of decimals to display */ fixedDecimals?: number; -} & Pick & +} & Pick & Pick; /** diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 702e6c384b58..f83914c0ee51 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; -import type {NativeSyntheticEvent, StyleProp, TextInputSelectionChangeEventData, TextStyle, ViewStyle} from 'react-native'; +import type {NativeSyntheticEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import {useMouseContext} from '@hooks/useMouseContext'; import * as Browser from '@libs/Browser'; @@ -304,7 +304,7 @@ function MoneyRequestAmountInput( }} selectedCurrencyCode={currency} selection={selection} - onSelectionChange={(e: NativeSyntheticEvent) => { + onSelectionChange={(selectionStart, selectionEnd) => { if (shouldIgnoreSelectionWhenUpdatedManually && willSelectionBeUpdatedManually.current) { willSelectionBeUpdatedManually.current = false; return; @@ -313,8 +313,8 @@ function MoneyRequestAmountInput( return; } const maxSelection = formattedAmount.length; - const start = Math.min(e.nativeEvent.selection.start, maxSelection); - const end = Math.min(e.nativeEvent.selection.end, maxSelection); + const start = Math.min(selectionStart, maxSelection); + const end = Math.min(selectionEnd, maxSelection); setSelection({start, end}); }} onKeyPress={textInputKeyPress} diff --git a/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx index 3e7c5f0bc414..4c30b048c1af 100644 --- a/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx +++ b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx @@ -7,7 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; -import type TextInputWithCurrencySymbolProps from './types'; +import type BaseTextInputWithCurrencySymbolProps from './types'; function BaseTextInputWithCurrencySymbol( { @@ -24,7 +24,7 @@ function BaseTextInputWithCurrencySymbol( extraSymbol, style, ...rest - }: TextInputWithCurrencySymbolProps, + }: BaseTextInputWithCurrencySymbolProps, ref: React.ForwardedRef, ) { const {fromLocaleDigit} = useLocalize(); diff --git a/src/components/TextInputWithCurrencySymbol/index.android.tsx b/src/components/TextInputWithCurrencySymbol/index.android.tsx index cbd822d07cf8..f7794c5822ff 100644 --- a/src/components/TextInputWithCurrencySymbol/index.android.tsx +++ b/src/components/TextInputWithCurrencySymbol/index.android.tsx @@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react'; import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import BaseTextInputWithCurrencySymbol from './BaseTextInputWithCurrencySymbol'; -import type TextInputWithCurrencySymbolProps from './types'; +import type {TextInputWithCurrencySymbolProps} from './types'; function TextInputWithCurrencySymbol({onSelectionChange = () => {}, ...props}: TextInputWithCurrencySymbolProps, ref: React.ForwardedRef) { const [skipNextSelectionChange, setSkipNextSelectionChange] = useState(false); @@ -21,7 +21,7 @@ function TextInputWithCurrencySymbol({onSelectionChange = () => {}, ...props}: T setSkipNextSelectionChange(false); return; } - onSelectionChange(event); + onSelectionChange(event.nativeEvent.selection.start, event.nativeEvent.selection.end); }} /> ); diff --git a/src/components/TextInputWithCurrencySymbol/index.tsx b/src/components/TextInputWithCurrencySymbol/index.tsx index b6230061bb6c..9e8333d9db23 100644 --- a/src/components/TextInputWithCurrencySymbol/index.tsx +++ b/src/components/TextInputWithCurrencySymbol/index.tsx @@ -1,14 +1,18 @@ import React from 'react'; +import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import BaseTextInputWithCurrencySymbol from './BaseTextInputWithCurrencySymbol'; -import type TextInputWithCurrencySymbolProps from './types'; +import type {TextInputWithCurrencySymbolProps} from './types'; -function TextInputWithCurrencySymbol(props: TextInputWithCurrencySymbolProps, ref: React.ForwardedRef) { +function TextInputWithCurrencySymbol({onSelectionChange = () => {}, ...props}: TextInputWithCurrencySymbolProps, ref: React.ForwardedRef) { return ( ) => { + onSelectionChange(event.nativeEvent.selection.start, event.nativeEvent.selection.end); + }} /> ); } diff --git a/src/components/TextInputWithCurrencySymbol/index.web.tsx b/src/components/TextInputWithCurrencySymbol/index.web.tsx new file mode 100644 index 000000000000..f86b764f79cc --- /dev/null +++ b/src/components/TextInputWithCurrencySymbol/index.web.tsx @@ -0,0 +1,42 @@ +import React, {useRef} from 'react'; +import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; +import BaseTextInputWithCurrencySymbol from './BaseTextInputWithCurrencySymbol'; +import type {TextInputWithCurrencySymbolProps} from './types'; + +function TextInputWithCurrencySymbol({onSelectionChange = () => {}, ...props}: TextInputWithCurrencySymbolProps, ref: React.ForwardedRef) { + const textInputRef = useRef(null); + + return ( + { + textInputRef.current = element as HTMLFormElement; + + if (!ref) { + return; + } + + if (typeof ref === 'function') { + ref(element as HTMLFormElement); + return; + } + + // eslint-disable-next-line no-param-reassign + ref.current = element as HTMLFormElement; + }} + onSelectionChange={(event: NativeSyntheticEvent) => { + onSelectionChange(event.nativeEvent.selection.start, event.nativeEvent.selection.end); + }} + onPress={() => { + const selectionStart = (textInputRef.current?.selectionStart as number) ?? 0; + const selectionEnd = (textInputRef.current?.selectionEnd as number) ?? 0; + onSelectionChange(selectionStart, selectionEnd); + }} + /> + ); +} + +TextInputWithCurrencySymbol.displayName = 'TextInputWithCurrencySymbol'; + +export default React.forwardRef(TextInputWithCurrencySymbol); diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index 619ed0fd84e6..401af75b16cd 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -2,7 +2,7 @@ import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInput import type {TextSelection} from '@components/Composer/types'; import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types'; -type TextInputWithCurrencySymbolProps = { +type BaseTextInputWithCurrencySymbolProps = { /** Formatted amount in local currency */ formattedAmount: string; @@ -77,6 +77,12 @@ type TextInputWithCurrencySymbolProps = { /** Hide the focus styles on TextInput */ hideFocusedState?: boolean; -} & Pick; +} & Pick; -export default TextInputWithCurrencySymbolProps; +type TextInputWithCurrencySymbolProps = Omit & { + onSelectionChange?: (start: number, end: number) => void; +}; + +export type {TextInputWithCurrencySymbolProps}; + +export default BaseTextInputWithCurrencySymbolProps; From 045b59aa67e4d8aea9d7518b6ce2cdcda1b8511c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 23 Oct 2024 13:18:29 +0200 Subject: [PATCH 051/352] integrate changes to AmountForm --- src/components/AmountForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index aa2ff1387de4..a230dfa1af8d 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; +import type {NativeSyntheticEvent} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -290,11 +290,11 @@ function AmountForm( }} selectedCurrencyCode={currency} selection={selection} - onSelectionChange={(e: NativeSyntheticEvent) => { + onSelectionChange={(start, end) => { if (!shouldUpdateSelection) { return; } - setSelection(e.nativeEvent.selection); + setSelection({start, end}); }} onKeyPress={textInputKeyPress} isCurrencyPressable={isCurrencyPressable} From 1a6950b7c7909d251c5257ed2661fcc66285c803 Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 24 Oct 2024 04:52:37 +0500 Subject: [PATCH 052/352] remove is group chat --- src/pages/NewChatPage.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 97712a0fa9d4..c28290e353e7 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -34,13 +34,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; -type NewChatPageProps = { - isGroupChat?: boolean; -}; - const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); -function useOptions({isGroupChat}: NewChatPageProps) { +function useOptions() { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedOptions, setSelectedOptions] = useState>([]); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -57,22 +53,20 @@ function useOptions({isGroupChat}: NewChatPageProps) { personalDetails: listOptions.personalDetails ?? [], betas: betas ?? [], selectedOptions, - excludeLogins: isGroupChat ? excludedGroupEmails : [], maxRecentReportsToShow: 0, includeSelfDM: true, }); return filteredOptions; - }, [betas, isGroupChat, listOptions.personalDetails, listOptions.reports, selectedOptions]); + }, [betas, listOptions.personalDetails, listOptions.reports, selectedOptions]); const options = useMemo(() => { const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, { selectedOptions, - excludeLogins: isGroupChat ? excludedGroupEmails : [], maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); return filteredOptions; - }, [debouncedSearchTerm, defaultOptions, isGroupChat, selectedOptions]); + }, [debouncedSearchTerm, defaultOptions, selectedOptions]); const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]); const headerMessage = useMemo(() => { return OptionsListUtils.getHeaderMessage( @@ -129,7 +123,7 @@ function useOptions({isGroupChat}: NewChatPageProps) { }; } -function NewChatPage({isGroupChat}: NewChatPageProps) { +function NewChatPage() { const {translate} = useLocalize(); const {isOffline} = useNetwork(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only @@ -142,9 +136,7 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { const selectionListRef = useRef(null); const {headerMessage, searchTerm, debouncedSearchTerm, setSearchTerm, selectedOptions, setSelectedOptions, recentReports, personalDetails, userToInvite, areOptionsInitialized} = - useOptions({ - isGroupChat, - }); + useOptions(); const [sections, firstKeyForList] = useMemo(() => { const sectionsList: OptionsListUtils.CategorySection[] = []; From a3b151358cbc0fd1167bba702c23c34e111483e4 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:54:29 +0100 Subject: [PATCH 053/352] fix(debug mode): undefined reportID when viewing cause for RBR in LHN --- src/libs/WorkspacesSettingsUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index a27d518fe727..1c78515f76e8 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -125,8 +125,14 @@ function getChatTabBrickRoadReport(policyID?: string): OnyxEntry { return undefined; } + // There are optimistic reports without a reportID, so we need to extract it from the report key + const normalizedAllReports = Object.entries(allReports).map(([key, report]) => ({ + ...report, + reportID: report?.reportID ?? key.replace(ONYXKEYS.COLLECTION.REPORT, ''), + })); + // If policyID is undefined, then all reports are checked whether they contain any brick road - const policyReports = policyID ? Object.values(allReports).filter((report) => report?.policyID === policyID) : Object.values(allReports); + const policyReports = policyID ? normalizedAllReports.filter((report) => report?.policyID === policyID) : normalizedAllReports; let reportWithGBR: OnyxEntry; From 4b2d8ad39fc8f1099bc659de398a1137c8c1a82f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:55:00 +0100 Subject: [PATCH 054/352] fix(debug mode): missing reportID in optimistic report --- src/libs/actions/Report.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4af2357fc572..a7f0dd10d621 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -767,10 +767,13 @@ function openReport( return; } - const optimisticReport = reportActionsExist(reportID) - ? {} + const optimisticReport: Report = reportActionsExist(reportID) + ? { + reportID, + } : { reportName: ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.reportName ?? CONST.REPORT.DEFAULT_REPORT_NAME, + reportID, }; const optimisticData: OnyxUpdate[] = [ From 8982be0306992b014e1433af355811a104efa94a Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:55:47 +0100 Subject: [PATCH 055/352] feat(debug mode): redirect user to debug report page when viewing cause for RBR in LHN --- .../createCustomBottomTabNavigator/DebugTabView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx index 3e5803b797dc..95ca6d5f35f7 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx @@ -134,7 +134,7 @@ function DebugTabView({selectedTab = '', chatTabBrickRoad, activeWorkspaceID}: D const report = getChatTabBrickRoadReport(activeWorkspaceID); if (report) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); + Navigation.navigate(ROUTES.DEBUG_REPORT.getRoute(report.reportID)); } } if (selectedTab === SCREENS.SETTINGS.ROOT) { From 39b1dbf2db920b31247bb0e9f2837c62e8799884 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:56:22 +0100 Subject: [PATCH 056/352] feat(debug mode): add button to view report from debug report page --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/Debug/Report/DebugReportPage.tsx | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4050993a23f2..0eef59b22d7e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5123,6 +5123,7 @@ const translations = { RBR: 'RBR', true: 'true', false: 'false', + viewReport: 'View Report', reasonVisibleInLHN: { hasDraftComment: 'Has draft comment', hasGBR: 'Has GBR', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1db6616571e2..f083d22f045b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5639,6 +5639,7 @@ const translations = { RBR: 'RBR', true: 'verdadero', false: 'falso', + viewReport: 'Ver Informe', reasonVisibleInLHN: { hasDraftComment: 'Tiene comentario en borrador', hasGBR: 'Tiene GBR', diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index fe26fed0c9c0..47d9def1f379 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; import Text from '@components/Text'; @@ -157,6 +158,13 @@ function DebugReportPage({ )} ))} +