From 7d69e6634b3ac8b61303f1c3ecf06a768a81f4be Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sun, 6 Oct 2024 01:29:38 +0700 Subject: [PATCH 001/128] Change behavior, navigate before transaction delete to resolve [delete..] briefly appeared --- src/libs/actions/IOU.ts | 50 +++++++++++++++++++++++ src/libs/actions/Task.ts | 1 + src/pages/ReportDetailsPage.tsx | 72 +++++++++++++++++++++------------ 3 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8d8e25a3ffb6..4b2c4dcd199e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -73,6 +73,7 @@ import * as Category from './Policy/Category'; import * as Policy from './Policy/Policy'; import * as Tag from './Policy/Tag'; import * as Report from './Report'; +import * as Task from '@userActions/Task'; type IOURequestType = ValueOf; @@ -5684,6 +5685,51 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT }; } +function getUrlToNavigateBackForTask(report: OnyxEntry): string | undefined { + const parentReport = Task.getParentReport(report); + const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID ?? '-1'); + if (shouldDeleteTaskReport) { + return ROUTES.REPORT_WITH_ID.getRoute(parentReport?.reportID ?? ''); + } + return undefined; +} +function getUrlToNavigateBackForMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView: boolean): string | undefined { + const { + shouldDeleteTransactionThread, + shouldDeleteIOUReport, + iouReport, + chatReport, + } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + + let reportIDToNavigateBack: string | undefined; + if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { + reportIDToNavigateBack = iouReport.chatReportID; + } + + if (iouReport?.chatReportID && shouldDeleteIOUReport) { + reportIDToNavigateBack = iouReport.chatReportID; + } + + return reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; +} +function getUrlToNavigateBackForTrackExpense(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView: boolean): string | undefined { + const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; + if (!ReportUtils.isSelfDM(chatReport)) { + return getUrlToNavigateBackForMoneyRequest(transactionID, reportAction, isSingleTransactionView); + } + + const { shouldDeleteTransactionThread } = getDeleteTrackExpenseInformation( + chatReportID, + transactionID, + reportAction, + ); + + if (shouldDeleteTransactionThread) { + return ROUTES.REPORT_WITH_ID.getRoute(chatReportID); + } + + return undefined; +} /** * * @param transactionID - The transactionID of IOU @@ -8476,5 +8522,9 @@ export { updateMoneyRequestTaxRate, mergeDuplicates, resolveDuplicates, + getUrlToNavigateBackForTask, + getUrlToNavigateBackForMoneyRequest, + getUrlToNavigateBackForTrackExpense, + }; export type {GPSPoint as GpsPoint, IOURequestType}; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 2f9ec060c1e8..ae3c2b596a7d 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1207,6 +1207,7 @@ export { canModifyTask, canActionTask, setNewOptimisticAssignee, + getParentReport, }; export type {PolicyValue, Assignee, ShareDestination}; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index e51b8e36704a..03c911305feb 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -718,24 +718,46 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const deleteTransaction = useCallback(() => { setIsDeleteModalVisible(false); - + + let urlToNavigateBack: string | undefined; + if (caseID === CASES.DEFAULT) { - navigateBackToAfterDelete.current = Task.deleteTask(report); + urlToNavigateBack = IOU.getUrlToNavigateBackForTask(report); + if (urlToNavigateBack) { + Navigation.goBack(urlToNavigateBack as Route); + } else { + Navigation.dismissModal(); + } + Task.deleteTask(report); return; } - + if (!requestParentReportAction) { return; } - - if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { - navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + if (isTrackExpense) { + urlToNavigateBack = IOU.getUrlToNavigateBackForTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = IOU.getUrlToNavigateBackForMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } - + + if (!urlToNavigateBack) { + Navigation.dismissModal(); + } else { + ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); + } + + if (isTrackExpense) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } + isTransactionDeleted.current = true; }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView]); + return ( @@ -826,23 +848,23 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { onConfirm={deleteTransaction} onCancel={() => setIsDeleteModalVisible(false)} onModalHide={() => { - // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. - if (!isTransactionDeleted.current) { - if (caseID === CASES.DEFAULT) { - if (navigateBackToAfterDelete.current) { - Navigation.goBack(navigateBackToAfterDelete.current); - } else { - Navigation.dismissModal(); - } - } - return; - } - - if (!navigateBackToAfterDelete.current) { - Navigation.dismissModal(); - } else { - ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); - } + // // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. + // if (!isTransactionDeleted.current) { + // if (caseID === CASES.DEFAULT) { + // if (navigateBackToAfterDelete.current) { + // Navigation.goBack(navigateBackToAfterDelete.current); + // } else { + // Navigation.dismissModal(); + // } + // } + // return; + // } + + // if (!navigateBackToAfterDelete.current) { + // Navigation.dismissModal(); + // } else { + // ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); + // } }} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} From bc8f8edbd608afeb42020a776c2886b55c18fae4 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sun, 6 Oct 2024 23:46:32 +0700 Subject: [PATCH 002/128] fix incorrect reportIDToNavigateBack --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4b2c4dcd199e..9304b3a4c08a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5703,7 +5703,7 @@ function getUrlToNavigateBackForMoneyRequest(transactionID: string, reportAction let reportIDToNavigateBack: string | undefined; if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { - reportIDToNavigateBack = iouReport.chatReportID; + reportIDToNavigateBack = iouReport.reportID; } if (iouReport?.chatReportID && shouldDeleteIOUReport) { From f6781e6e0f0cd6325759f3c6eb64ab8f8790495c Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 00:08:15 +0700 Subject: [PATCH 003/128] actually delete the transaction after ReportDetailsPage unmounted --- src/pages/ReportDetailsPage.tsx | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 03c911305feb..600b14117297 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -715,9 +715,35 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const isTransactionDeleted = useRef(false); // Where to go back after deleting the transaction and its report. It's empty if the transaction report isn't deleted. const navigateBackToAfterDelete = useRef(); + useEffect(() => { + return () => { + if(!isTransactionDeleted.current) + { + return; + } + + if (caseID === CASES.DEFAULT) { + Task.deleteTask(report); + return; + } + + if (!requestParentReportAction) { + return; + } + + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + + if (isTrackExpense) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } + }; + }, [isTransactionDeleted]); const deleteTransaction = useCallback(() => { setIsDeleteModalVisible(false); + isTransactionDeleted.current = true; let urlToNavigateBack: string | undefined; @@ -728,7 +754,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { } else { Navigation.dismissModal(); } - Task.deleteTask(report); return; } @@ -749,13 +774,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); } - if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); - } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); - } - - isTransactionDeleted.current = true; }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView]); return ( From 0bd45cc23e6c040a86e249353ab4da429efbfddb Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 13:46:53 +0700 Subject: [PATCH 004/128] Refactor, remove unnecessary code --- src/pages/ReportDetailsPage.tsx | 75 +++++++++++++-------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 600b14117297..533709ff50d7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -3,7 +3,7 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -713,35 +713,37 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { // A flag to indicate whether the user choose to delete the transaction or not const isTransactionDeleted = useRef(false); - // Where to go back after deleting the transaction and its report. It's empty if the transaction report isn't deleted. - const navigateBackToAfterDelete = useRef(); useEffect(() => { return () => { - if(!isTransactionDeleted.current) - { - return; - } - - if (caseID === CASES.DEFAULT) { - Task.deleteTask(report); - return; - } - - if (!requestParentReportAction) { - return; - } - - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); - - if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); - } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); - } + deleteTransaction(); }; - }, [isTransactionDeleted]); + }, []); const deleteTransaction = useCallback(() => { + if (!isTransactionDeleted.current) { + return; + } + + if (caseID === CASES.DEFAULT) { + Task.deleteTask(report); + return; + } + + if (!requestParentReportAction) { + return; + } + + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + + if (isTrackExpense) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } + }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); + + // Where to go back after deleting the transaction and its report. + const navigateAfterTransactionDeletion = useCallback(() => { setIsDeleteModalVisible(false); isTransactionDeleted.current = true; @@ -774,7 +776,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); } - }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView]); + }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, setIsDeleteModalVisible, isTransactionDeleted]); return ( @@ -863,27 +865,8 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { setIsDeleteModalVisible(false)} - onModalHide={() => { - // // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. - // if (!isTransactionDeleted.current) { - // if (caseID === CASES.DEFAULT) { - // if (navigateBackToAfterDelete.current) { - // Navigation.goBack(navigateBackToAfterDelete.current); - // } else { - // Navigation.dismissModal(); - // } - // } - // return; - // } - - // if (!navigateBackToAfterDelete.current) { - // Navigation.dismissModal(); - // } else { - // ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); - // } - }} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From eb051bb80bce29294ea1552eb62163bd01fda5c1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 14:23:17 +0700 Subject: [PATCH 005/128] Add NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL to show loading transaction during delete process --- src/ONYXKEYS.ts | 4 ++++ src/libs/actions/IOU.ts | 16 ++++++++++++++++ src/libs/actions/Task.ts | 5 +++++ src/pages/ReportDetailsPage.tsx | 2 ++ src/pages/home/ReportScreen.tsx | 4 +++- 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index df1413620c20..ecc3d865c9e4 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -208,6 +208,9 @@ const ONYXKEYS = { /** The NVP containing all information related to educational tooltip in workspace chat */ NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', + /** The NVP contain url to back after deleting transaction */ + NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', + /** Whether to show save search rename tooltip */ SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip', @@ -990,6 +993,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; [ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip; + [ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL]: string | undefined; [ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP]: boolean; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9304b3a4c08a..afaf4b3143b5 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5977,6 +5977,12 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }); } + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, + value: null, + }); + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -6260,6 +6266,15 @@ function getSendMoneyParams( [optimisticCreatedActionForTransactionThread?.reportActionID ?? '-1']: optimisticCreatedActionForTransactionThread, }, }; + const optimisticDeleteTransactionNavigateBackUrl: OnyxUpdate = { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, + value: null, +}; + + + + const successData: OnyxUpdate[] = []; @@ -6430,6 +6445,7 @@ function getSendMoneyParams( optimisticTransactionData, optimisticTransactionThreadData, optimisticTransactionThreadReportActionsData, + optimisticDeleteTransactionNavigateBackUrl, ]; if (!isEmptyObject(optimisticPersonalDetailListData)) { diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index ae3c2b596a7d..b8edbaa8b245 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1005,6 +1005,11 @@ function deleteTask(report: OnyxEntry) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, value: optimisticReportActions as OnyxTypes.ReportActions, }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, + value: null, + }, ]; // Update optimistic data for parent report action if the report is a child report and the task report has no visible child diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 533709ff50d7..3e098e20b27d 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -751,6 +751,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { if (caseID === CASES.DEFAULT) { urlToNavigateBack = IOU.getUrlToNavigateBackForTask(report); + Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); if (urlToNavigateBack) { Navigation.goBack(urlToNavigateBack as Route); } else { @@ -769,6 +770,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { } else { urlToNavigateBack = IOU.getUrlToNavigateBackForMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } + Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); if (!urlToNavigateBack) { Navigation.dismissModal(); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index dd38a0716377..ac7418062de7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -343,13 +343,15 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro [currentUserAccountID, linkedAction], ); + const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); + /** * Using logical OR operator because with nullish coalescing operator, when `isLoadingApp` is false, the right hand side of the operator * is not evaluated. This causes issues where we have `isLoading` set to false and later set to true and then set to false again. * Ideally, `isLoading` should be set initially to true and then set to false. We can achieve this by using logical OR operator. */ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const isLoading = isLoadingApp || !reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty(); + const isLoading = isLoadingApp || !reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty() || (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID); const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || From c2cecdc0da807aed28640c29fa411e37a3da8ab1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 14:33:52 +0700 Subject: [PATCH 006/128] prettier --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index afaf4b3143b5..f8c10e21b006 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6270,7 +6270,7 @@ function getSendMoneyParams( onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, value: null, -}; + }; From 7e8d4f45dc89f0000da7969d918fce0251a1a8a9 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 15:46:26 +0700 Subject: [PATCH 007/128] Refactor --- src/pages/ReportDetailsPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 3e098e20b27d..e82fdf97c1ae 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -715,15 +715,15 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const isTransactionDeleted = useRef(false); useEffect(() => { return () => { + if (!isTransactionDeleted.current) { + return; + } + deleteTransaction(); }; }, []); const deleteTransaction = useCallback(() => { - if (!isTransactionDeleted.current) { - return; - } - if (caseID === CASES.DEFAULT) { Task.deleteTask(report); return; @@ -743,7 +743,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); // Where to go back after deleting the transaction and its report. - const navigateAfterTransactionDeletion = useCallback(() => { + const navigateToTargetUrl = useCallback(() => { setIsDeleteModalVisible(false); isTransactionDeleted.current = true; @@ -867,7 +867,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { setIsDeleteModalVisible(false)} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} From 66e3de8f4bff7c7b1f0990e9efec698dfcea9f7e Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 7 Oct 2024 16:35:27 +0700 Subject: [PATCH 008/128] Partially DeleteTransactionThread to prevent not found page briefly appear --- src/libs/actions/IOU.ts | 46 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f8c10e21b006..ef2cc894c82b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1744,7 +1744,16 @@ function getDeleteTrackExpenseInformation( { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, - value: null, + value: { + reportID: null, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + participants: { + [userAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + }, }, { onyxMethod: Onyx.METHOD.SET, @@ -1784,6 +1793,17 @@ function getDeleteTrackExpenseInformation( }, ]; + if (shouldDeleteTransactionThread && transactionThread) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, + value: Object.keys(transactionThread).reduce>((acc, key) => { + acc[key] = null; + return acc; + }, {}), + }); + } + const failureData: OnyxUpdate[] = []; if (shouldDeleteTransactionFromOnyx) { @@ -5908,9 +5928,18 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor if (shouldDeleteTransactionThread) { optimisticData.push( { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, - value: null, + value: { + reportID: null, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + participants: { + [userAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + }, }, { onyxMethod: Onyx.METHOD.SET, @@ -6007,6 +6036,17 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }, ]; + if (shouldDeleteTransactionThread && transactionThread) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, + value: Object.keys(transactionThread).reduce>((acc, key) => { + acc[key] = null; + return acc; + }, {}), + }); + } + if (shouldDeleteIOUReport) { successData.push({ onyxMethod: Onyx.METHOD.SET, From c7e3f5a133d9359e9c1074086fca073fcbd50e3c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 9 Oct 2024 22:54:21 +0300 Subject: [PATCH 009/128] fix the bottom line of the composer on composer full size mode change --- src/components/Composer/index.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 15087193a593..8a899d4e3562 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -102,6 +102,7 @@ function Composer( const [isRendered, setIsRendered] = useState(false); const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); const [prevScroll, setPrevScroll] = useState(); + const [prevHeight, setPrevHeight] = useState(); const isReportFlatListScrolling = useRef(false); useEffect(() => { @@ -231,10 +232,18 @@ function Composer( } setPrevScroll(textInput.current.scrollTop); }, 100); + const debouncedSetPrevHeight = lodashDebounce(() => { + if (!textInput.current) { + return; + } + setPrevHeight(textInput.current.clientHeight); + }, 100); textInput.current.addEventListener('scroll', debouncedSetPrevScroll); + textInput.current.addEventListener('resize', debouncedSetPrevHeight); return () => { textInput.current?.removeEventListener('scroll', debouncedSetPrevScroll); + textInput.current?.removeEventListener('resize', debouncedSetPrevHeight); }; }, []); @@ -262,11 +271,11 @@ function Composer( }, []); useEffect(() => { - if (!textInput.current || prevScroll === undefined) { + if (!textInput.current || prevScroll === undefined || prevHeight === undefined) { return; } // eslint-disable-next-line react-compiler/react-compiler - textInput.current.scrollTop = prevScroll; + textInput.current.scrollTop = prevScroll + prevHeight - textInput.current.clientHeight; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isComposerFullSize]); From 26926f016bba9f99ca73b511ef3983db6ed77e35 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 9 Oct 2024 23:03:30 +0300 Subject: [PATCH 010/128] change listener --- src/components/Composer/index.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 8a899d4e3562..56a0a06e9c30 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -231,19 +231,12 @@ function Composer( return; } setPrevScroll(textInput.current.scrollTop); - }, 100); - const debouncedSetPrevHeight = lodashDebounce(() => { - if (!textInput.current) { - return; - } setPrevHeight(textInput.current.clientHeight); }, 100); textInput.current.addEventListener('scroll', debouncedSetPrevScroll); - textInput.current.addEventListener('resize', debouncedSetPrevHeight); return () => { textInput.current?.removeEventListener('scroll', debouncedSetPrevScroll); - textInput.current?.removeEventListener('resize', debouncedSetPrevHeight); }; }, []); From 447dc037c2e6f26b82f305160e7bd005559b2a48 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 9 Oct 2024 23:22:39 +0300 Subject: [PATCH 011/128] set prevHeight to content size height --- src/components/Composer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 56a0a06e9c30..21708d85612b 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -231,7 +231,6 @@ function Composer( return; } setPrevScroll(textInput.current.scrollTop); - setPrevHeight(textInput.current.clientHeight); }, 100); textInput.current.addEventListener('scroll', debouncedSetPrevScroll); @@ -395,6 +394,7 @@ function Composer( {...props} onSelectionChange={addCursorPositionToSelectionChange} onContentSizeChange={(e) => { + setPrevHeight(e.nativeEvent.contentSize.height); setTextInputWidth(`${e.nativeEvent.contentSize.width}px`); updateIsFullComposerAvailable({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles); }} From a0617684a9eebdd6f38ecf132d58685a37667748 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 22 Oct 2024 01:49:20 +0530 Subject: [PATCH 012/128] fix height being cut for popover --- src/pages/settings/Security/SecuritySettingsPage.tsx | 11 +++-------- src/styles/variables.ts | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index d1919f9ddf83..924c3c6c468c 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -47,7 +47,7 @@ function SecuritySettingsPage() { const waitForNavigate = useWaitForNavigation(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {canUseNewDotCopilot} = usePermissions(); - const {windowWidth} = useWindowDimensions(); + const {windowWidth, windowHeight} = useWindowDimensions(); const personalDetails = usePersonalDetails(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); @@ -58,8 +58,6 @@ function SecuritySettingsPage() { const [selectedDelegate, setSelectedDelegate] = useState(); const [anchorPosition, setAnchorPosition] = useState({ - anchorPositionHorizontal: 0, - anchorPositionVertical: 0, anchorPositionTop: 0, anchorPositionRight: 0, }); @@ -70,15 +68,12 @@ function SecuritySettingsPage() { } const position = getClickedTargetLocation(delegateButtonRef.current); - setAnchorPosition({ - anchorPositionTop: position.top + position.height - variables.bankAccountActionPopoverTopSpacing, + anchorPositionTop: Math.min(position.top + position.height - variables.bankAccountActionPopoverTopSpacing, windowHeight - variables.delegateAccessLevelModalHeight), // We want the position to be 23px to the right of the left border anchorPositionRight: windowWidth - position.right + variables.bankAccountActionPopoverRightSpacing, - anchorPositionHorizontal: position.x + variables.addBankAccountLeftSpacing, - anchorPositionVertical: position.y, }); - }, [windowWidth]); + }, [windowWidth, windowHeight, delegateButtonRef]); const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false; const delegates = account?.delegatedAccess?.delegates ?? []; diff --git a/src/styles/variables.ts b/src/styles/variables.ts index dc6655791489..ad40c6688eed 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -259,6 +259,7 @@ export default { minimalTopBarOffset: -26, searchHeaderHeight: 80, searchListContentMarginTop: 116, + delegateAccessLevelModalHeight: 168, h20: 20, h28: 28, From fdbb357e1585e7bc98f05b6a5c23a368c461262f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 22 Oct 2024 03:30:25 +0530 Subject: [PATCH 013/128] use popovermenu to set anchor position --- .../Security/SecuritySettingsPage.tsx | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 924c3c6c468c..764412b236ff 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -14,7 +14,8 @@ import MenuItem from '@components/MenuItem'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import {usePersonalDetails} from '@components/OnyxProvider'; -import Popover from '@components/Popover'; +import PopoverMenu from '@components/PopoverMenu'; +import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; @@ -32,7 +33,7 @@ import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; -import variables from '@styles/variables'; +import type {AnchorPosition} from '@styles/index'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -47,7 +48,7 @@ function SecuritySettingsPage() { const waitForNavigate = useWaitForNavigation(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {canUseNewDotCopilot} = usePermissions(); - const {windowWidth, windowHeight} = useWindowDimensions(); + const {windowWidth} = useWindowDimensions(); const personalDetails = usePersonalDetails(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); @@ -57,11 +58,18 @@ function SecuritySettingsPage() { const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); const [selectedDelegate, setSelectedDelegate] = useState(); - const [anchorPosition, setAnchorPosition] = useState({ - anchorPositionTop: 0, - anchorPositionRight: 0, + const [anchorPosition, setAnchorPosition] = useState({ + horizontal: 0, + vertical: 0, }); + const isActingAsDelegate = !!account?.delegatedAccess?.delegate; + const delegates = account?.delegatedAccess?.delegates ?? []; + const delegators = account?.delegatedAccess?.delegators ?? []; + + const hasDelegates = delegates.length > 0; + const hasDelegators = delegators.length > 0; + const setMenuPosition = useCallback(() => { if (!delegateButtonRef.current) { return; @@ -69,18 +77,10 @@ function SecuritySettingsPage() { const position = getClickedTargetLocation(delegateButtonRef.current); setAnchorPosition({ - anchorPositionTop: Math.min(position.top + position.height - variables.bankAccountActionPopoverTopSpacing, windowHeight - variables.delegateAccessLevelModalHeight), - // We want the position to be 23px to the right of the left border - anchorPositionRight: windowWidth - position.right + variables.bankAccountActionPopoverRightSpacing, + horizontal: windowWidth - position.x, + vertical: position.y + position.height, }); - }, [windowWidth, windowHeight, delegateButtonRef]); - const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false; - - const delegates = account?.delegatedAccess?.delegates ?? []; - const delegators = account?.delegatedAccess?.delegators ?? []; - - const hasDelegates = delegates.length > 0; - const hasDelegators = delegators.length > 0; + }, [windowWidth, delegateButtonRef]); const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, delegate: Delegate) => { delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; @@ -172,7 +172,7 @@ function SecuritySettingsPage() { }), // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - [delegates, translate, styles, personalDetails], + [delegates, translate, styles, personalDetails, windowWidth], ); const delegatorMenuItems: MenuItemProps[] = useMemo( @@ -198,6 +198,27 @@ function SecuritySettingsPage() { [delegators, styles, translate, personalDetails], ); + const delegatePopoverMenuItems: PopoverMenuItem[] = [ + { + text: translate('delegate.changeAccessLevel'), + icon: Expensicons.Pencil, + onPress: () => { + Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); + setShouldShowDelegatePopoverMenu(false); + setSelectedDelegate(undefined); + }, + }, + { + text: translate('delegate.removeCopilot'), + icon: Expensicons.Trashcan, + onPress: () => + Modal.close(() => { + setShouldShowDelegatePopoverMenu(false); + setShouldShowRemoveDelegateModal(true); + }), + }, + ]; + return ( )} - } anchorPosition={{ - top: anchorPosition.anchorPositionTop, - right: anchorPosition.anchorPositionRight, + horizontal: anchorPosition.horizontal, + vertical: anchorPosition.vertical, }} + anchorAlignment={{ + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, + }} + menuItems={delegatePopoverMenuItems} onClose={() => { setShouldShowDelegatePopoverMenu(false); }} - > - - { - Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); - setShouldShowDelegatePopoverMenu(false); - setSelectedDelegate(undefined); - }} - wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} - /> - - Modal.close(() => { - setShouldShowDelegatePopoverMenu(false); - setShouldShowRemoveDelegateModal(true); - }) - } - wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} - /> - - + /> Date: Tue, 22 Oct 2024 03:40:17 +0530 Subject: [PATCH 014/128] rm unused --- src/styles/variables.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/variables.ts b/src/styles/variables.ts index ad40c6688eed..dc6655791489 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -259,7 +259,6 @@ export default { minimalTopBarOffset: -26, searchHeaderHeight: 80, searchListContentMarginTop: 116, - delegateAccessLevelModalHeight: 168, h20: 20, h28: 28, From ccb9ca5eca10055b51a4f084ba29e2e76d1a2f17 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 22 Oct 2024 16:02:17 +0300 Subject: [PATCH 015/128] resolve conflict --- src/components/Composer/implementation/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Composer/implementation/index.tsx b/src/components/Composer/implementation/index.tsx index 4431007793cb..6c4a1a4c723c 100755 --- a/src/components/Composer/implementation/index.tsx +++ b/src/components/Composer/implementation/index.tsx @@ -75,6 +75,7 @@ function Composer( const [isRendered, setIsRendered] = useState(false); const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); const [prevScroll, setPrevScroll] = useState(); + const [prevHeight, setPrevHeight] = useState(); const isReportFlatListScrolling = useRef(false); useEffect(() => { @@ -243,11 +244,11 @@ function Composer( }, []); useEffect(() => { - if (!textInput.current || prevScroll === undefined) { + if (!textInput.current || prevScroll === undefined || prevHeight === undefined) { return; } // eslint-disable-next-line react-compiler/react-compiler - textInput.current.scrollTop = prevScroll; + textInput.current.scrollTop = prevScroll + prevHeight - textInput.current.clientHeight; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isComposerFullSize]); @@ -353,6 +354,7 @@ function Composer( {...props} onSelectionChange={addCursorPositionToSelectionChange} onContentSizeChange={(e) => { + setPrevHeight(e.nativeEvent.contentSize.height); updateIsFullComposerAvailable({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles); }} disabled={isDisabled} From 9bce4f03bb5c00cd93be04f841a23d7e54c9dc35 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 23 Oct 2024 21:54:54 +0700 Subject: [PATCH 016/128] decouple urlToNavigateBack functions --- src/libs/actions/IOU.ts | 116 ++++++++++++++++++++++++++++++++++----- src/libs/actions/Task.ts | 53 +++++++++++++++--- 2 files changed, 148 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 497f43f93317..01a86eef2b21 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5713,16 +5713,17 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT // STEP 5: Calculate the url that the user will be navigated back to // This depends on which page they are on and which resources were deleted - let reportIDToNavigateBack: string | undefined; - if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { - reportIDToNavigateBack = iouReport.reportID; - } + // let reportIDToNavigateBack: string | undefined; + // if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { + // reportIDToNavigateBack = iouReport.reportID; + // } - if (iouReport?.chatReportID && shouldDeleteIOUReport) { - reportIDToNavigateBack = iouReport.chatReportID; - } + // if (iouReport?.chatReportID && shouldDeleteIOUReport) { + // reportIDToNavigateBack = iouReport.chatReportID; + // } - const urlToNavigateBack = reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; + // const urlToNavigateBack = reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; + const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); return { shouldDeleteTransactionThread, @@ -5741,6 +5742,84 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT }; } +/** + * Calculate the URL to navigate to after a money request deletion + * @param transactionID - The ID of the money request being deleted + * @param reportAction - The report action associated with the money request + * @param isSingleTransactionView - Whether we're in single transaction view + * @returns The URL to navigate to + */ +function getNavigationUrlAfterMoneyRequestDelete( + transactionID: string, + reportAction: OnyxTypes.ReportAction, + isSingleTransactionView = false +): string | undefined { + // Get all collections we need for navigation + const allReports = ReportConnection.getAllReports(); + const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) + ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID + : '-1'; + const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null; + const transactionThreadID = reportAction.childReportID; + + // Calculate if resources would be deleted + const shouldDeleteTransactionThread = transactionThreadID + ? (reportAction?.childVisibleActionCount ?? 0) === 0 + : false; + + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1'); + const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1').lastMessageText; + const shouldDeleteIOUReport = iouReportLastMessageText.length === 0 + && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) + && (!transactionThreadID || shouldDeleteTransactionThread); + + // Determine which report to navigate back to + if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID); + } + + if (iouReport?.chatReportID && shouldDeleteIOUReport) { + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID); + } + + return undefined; +} + +/** + * Calculate the URL to navigate to after a track expense deletion + * @param chatReportID - The ID of the chat report containing the track expense + * @param transactionID - The ID of the track expense being deleted + * @param reportAction - The report action associated with the track expense + * @param isSingleTransactionView - Whether we're in single transaction view + * @returns The URL to navigate to + */ +function getNavigationUrlAfterTrackExpenseDelete( + chatReportID: string, + transactionID: string, + reportAction: OnyxTypes.ReportAction, + isSingleTransactionView = false +): string | undefined { + const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; + + // If not a self DM, handle it as a regular money request + if (!ReportUtils.isSelfDM(chatReport)) { + return getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + } + + const transactionThreadID = reportAction.childReportID; + const shouldDeleteTransactionThread = transactionThreadID + ? (reportAction?.childVisibleActionCount ?? 0) === 0 + : false; + + // Only navigate if in single transaction view and the thread will be deleted + if (isSingleTransactionView && shouldDeleteTransactionThread && chatReport?.reportID) { + // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. + return ROUTES.REPORT_WITH_ID.getRoute(chatReport.reportID); + } + + return undefined; +} + /** * * @param transactionID - The transactionID of IOU @@ -6115,10 +6194,18 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor } function deleteTrackExpense(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { + const urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete( + chatReportID, + transactionID, + reportAction, + isSingleTransactionView + ); + // STEP 1: Get all collections we're updating const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; if (!ReportUtils.isSelfDM(chatReport)) { - return deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView); + deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView); + return urlToNavigateBack; } const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(transactionID, chatReportID); @@ -6138,10 +6225,11 @@ function deleteTrackExpense(chatReportID: string, transactionID: string, reportA CachedPDFPaths.clearByKey(transactionID); // STEP 7: Navigate the user depending on which page they are on and which resources were deleted - if (isSingleTransactionView && shouldDeleteTransactionThread) { - // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - return ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '-1'); - } + return urlToNavigateBack; + // if (isSingleTransactionView && shouldDeleteTransactionThread) { + // // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. + // return ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '-1'); + // } } /** @@ -8565,5 +8653,7 @@ export { updateLastLocationPermissionPrompt, resolveDuplicates, getIOUReportActionToApproveOrPay, + getNavigationUrlAfterMoneyRequestDelete, + getNavigationUrlAfterTrackExpenseDelete, }; export type {GPSPoint as GpsPoint, IOURequestType}; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index b159e391f949..2031d5a42257 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -955,6 +955,36 @@ function getParentReport(report: OnyxEntry): OnyxEntry): string | undefined { + if (!report) { + return undefined; + } + + const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID ?? '-1'); + if (!shouldDeleteTaskReport) { + return undefined; + } + + // First try to navigate to parent report + const parentReport = getParentReport(report); + if (parentReport?.reportID) { + return ROUTES.REPORT_WITH_ID.getRoute(parentReport.reportID); + } + + // If no parent report, try to navigate to most recent report + const mostRecentReportID = Report.getMostRecentReportID(report); + if (mostRecentReportID) { + return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); + } + + return undefined; +} + /** * Cancels a task by setting the report state to SUBMITTED and status to CLOSED */ @@ -1109,15 +1139,21 @@ function deleteTask(report: OnyxEntry) { API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); Report.notifyNewAction(report.reportID, currentUserAccountID); - if (shouldDeleteTaskReport) { + // if (shouldDeleteTaskReport) { + // Navigation.goBack(); + // if (parentReport?.reportID) { + // return ROUTES.REPORT_WITH_ID.getRoute(parentReport.reportID); + // } + // const mostRecentReportID = Report.getMostRecentReportID(report); + // if (mostRecentReportID) { + // return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); + // } + // } + + const urlToNavigateBack = getNavigationUrlAfterTaskDelete(report); + if (urlToNavigateBack) { Navigation.goBack(); - if (parentReport?.reportID) { - return ROUTES.REPORT_WITH_ID.getRoute(parentReport.reportID); - } - const mostRecentReportID = Report.getMostRecentReportID(report); - if (mostRecentReportID) { - return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); - } + return urlToNavigateBack; } } @@ -1232,6 +1268,7 @@ export { canActionTask, setNewOptimisticAssignee, getParentReport, + getNavigationUrlAfterTaskDelete, }; export type {PolicyValue, Assignee, ShareDestination}; From e2cd64213c51a03b9d0a0836257d82430899e4b4 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 23 Oct 2024 22:06:39 +0700 Subject: [PATCH 017/128] actually delete transaction after reportdetailspage unmounted --- src/pages/ReportDetailsPage.tsx | 107 +++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 7de12eeda892..fcaa696d526b 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -3,7 +3,7 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -225,6 +225,18 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const canUnapproveRequest = ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); + + useEffect(() => { + return () => { + if (!isTransactionDeleted.current) { + return; + } + + deleteTransaction(); + }; + }, []); + + useEffect(() => { if (canDeleteRequest) { return; @@ -759,11 +771,48 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { // Where to go back after deleting the transaction and its report. It's empty if the transaction report isn't deleted. const navigateBackToAfterDelete = useRef(); - const deleteTransaction = useCallback(() => { + + // Where to go back after deleting the transaction and its report. + const navigateToTargetUrl = useCallback(() => { setIsDeleteModalVisible(false); + isTransactionDeleted.current = true; + + let urlToNavigateBack: string | undefined; + + if (caseID === CASES.DEFAULT) { + urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); + Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); + if (urlToNavigateBack) { + Navigation.goBack(urlToNavigateBack as Route); + } else { + Navigation.dismissModal(); + } + return; + } + + if (!requestParentReportAction) { + return; + } + + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + if (isTrackExpense) { + urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } + Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); + + if (!urlToNavigateBack) { + Navigation.dismissModal(); + } else { + ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); + } + + }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, setIsDeleteModalVisible, isTransactionDeleted]); + const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { - navigateBackToAfterDelete.current = Task.deleteTask(report); + Task.deleteTask(report); return; } @@ -771,14 +820,14 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { return; } - if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { - navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + + if (isTrackExpense) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } - - isTransactionDeleted.current = true; - }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView]); + }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); @@ -871,27 +920,27 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { setIsDeleteModalVisible(false)} - onModalHide={() => { - // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. - if (!isTransactionDeleted.current) { - if (caseID === CASES.DEFAULT) { - if (navigateBackToAfterDelete.current) { - Navigation.goBack(navigateBackToAfterDelete.current); - } else { - Navigation.dismissModal(); - } - } - return; - } - - if (!navigateBackToAfterDelete.current) { - Navigation.dismissModal(); - } else { - ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); - } - }} + // onModalHide={() => { + // // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. + // if (!isTransactionDeleted.current) { + // if (caseID === CASES.DEFAULT) { + // if (navigateBackToAfterDelete.current) { + // Navigation.goBack(navigateBackToAfterDelete.current); + // } else { + // Navigation.dismissModal(); + // } + // } + // return; + // } + + // if (!navigateBackToAfterDelete.current) { + // Navigation.dismissModal(); + // } else { + // ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); + // } + // }} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From dca3bcc040cf5545c1feabb88f63289ae65ce4e5 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 23 Oct 2024 18:47:15 +0300 Subject: [PATCH 018/128] scroll to the cursor on composer size change for native --- .../Composer/implementation/index.native.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 9f237dd02424..905fa537ff55 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -59,6 +59,17 @@ function Composer( inputCallbackRef(autoFocus ? textInput.current : null); }, [autoFocus, inputCallbackRef, autoFocusInputRef]); + useEffect(() => { + if (!textInput.current || !selection) { + return; + } + + // We are setting selection twice to trigger a scroll to the cursor on change of composer size. + textInput.current?.setSelection((selection.start || 1) - 1, selection.start); + textInput.current?.setSelection(selection.start, selection.start); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, [isComposerFullSize]); + /** * Set the TextInput Ref * @param {Element} el From 980e8e5dc810f1e2e511228e2dfc817b89a3034c Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 24 Oct 2024 04:40:53 +0700 Subject: [PATCH 019/128] Resolve cyclic dependency --- src/libs/actions/IOU.ts | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 01a86eef2b21..53a0b4f47e9e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5723,7 +5723,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT // } // const urlToNavigateBack = reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; - const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + const urlToNavigateBack = undefined; return { shouldDeleteTransactionThread, @@ -5754,24 +5754,32 @@ function getNavigationUrlAfterMoneyRequestDelete( reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false ): string | undefined { - // Get all collections we need for navigation - const allReports = ReportConnection.getAllReports(); - const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) - ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID - : '-1'; - const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null; - const transactionThreadID = reportAction.childReportID; + // // Get all collections we need for navigation + // const allReports = ReportConnection.getAllReports(); + // const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) + // ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID + // : '-1'; + // const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null; + // const transactionThreadID = reportAction.childReportID; + + // // Calculate if resources would be deleted + // const shouldDeleteTransactionThread = transactionThreadID + // ? (reportAction?.childVisibleActionCount ?? 0) === 0 + // : false; + + // const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1'); + // const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1').lastMessageText; + // const shouldDeleteIOUReport = iouReportLastMessageText.length === 0 + // && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) + // && (!transactionThreadID || shouldDeleteTransactionThread); - // Calculate if resources would be deleted - const shouldDeleteTransactionThread = transactionThreadID - ? (reportAction?.childVisibleActionCount ?? 0) === 0 - : false; + const { + shouldDeleteTransactionThread, + shouldDeleteIOUReport, + iouReport, + chatReport, + } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1'); - const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1').lastMessageText; - const shouldDeleteIOUReport = iouReportLastMessageText.length === 0 - && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) - && (!transactionThreadID || shouldDeleteTransactionThread); // Determine which report to navigate back to if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { From 0288efb572cf441b44066069362f6a1cc5c880f9 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 24 Oct 2024 08:58:00 +0700 Subject: [PATCH 020/128] extract navagateUrl from prepareToCleanUpMoneyRequest --- src/libs/actions/IOU.ts | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 53a0b4f47e9e..64dca4fd8517 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5723,7 +5723,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT // } // const urlToNavigateBack = reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; - const urlToNavigateBack = undefined; + // const urlToNavigateBack = undefined; return { shouldDeleteTransactionThread, @@ -5738,7 +5738,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT transactionViolations, reportPreviewAction, iouReport, - urlToNavigateBack, + // urlToNavigateBack, }; } @@ -5754,30 +5754,10 @@ function getNavigationUrlAfterMoneyRequestDelete( reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false ): string | undefined { - // // Get all collections we need for navigation - // const allReports = ReportConnection.getAllReports(); - // const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) - // ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID - // : '-1'; - // const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null; - // const transactionThreadID = reportAction.childReportID; - - // // Calculate if resources would be deleted - // const shouldDeleteTransactionThread = transactionThreadID - // ? (reportAction?.childVisibleActionCount ?? 0) === 0 - // : false; - - // const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1'); - // const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1').lastMessageText; - // const shouldDeleteIOUReport = iouReportLastMessageText.length === 0 - // && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) - // && (!transactionThreadID || shouldDeleteTransactionThread); - const { shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport, - chatReport, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); @@ -5846,9 +5826,10 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo chatReport, iouReport, reportPreviewAction, - urlToNavigateBack, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + + const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); // build Onyx data // Onyx operations to delete the transaction, update the IOU report action and chat report action @@ -5984,9 +5965,10 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor transactionViolations, iouReport, reportPreviewAction, - urlToNavigateBack, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + // STEP 2: Build Onyx data // The logic mostly resembles the cleanUpMoneyRequest function const optimisticData: OnyxUpdate[] = [ From 5d982cc62be7074302d153b1d575c6ec63946d15 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 24 Oct 2024 09:45:21 +0700 Subject: [PATCH 021/128] Implement NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL --- src/libs/actions/IOU.ts | 10 ++++++++++ src/pages/home/ReportScreen.tsx | 11 +++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c125e0830568..b271baf93886 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1779,6 +1779,11 @@ function getDeleteTrackExpenseInformation( lastMessageHtml: !lastMessageHtml ? lastMessageText : lastMessageHtml, }, }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, + value: null, + } ); const successData: OnyxUpdate[] = [ @@ -6020,6 +6025,11 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: ReportUtils.getOutstandingChildRequest(updatedIOUReport), }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, + value: null, + } ); if (!shouldDeleteIOUReport && updatedReportPreviewAction?.childMoneyRequestCount === 0) { diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index baf3d02d7af3..d17ee485498a 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -342,8 +342,15 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), [currentUserAccountID, linkedAction], ); - - const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()); + const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); + console.log("[wildebug] ~ file: ReportScreen.tsx:346 ~ ReportScreen ~ deleteTransactionNavigateBackUrl:", deleteTransactionNavigateBackUrl) + console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ report?.reportID:", report?.reportID) + console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl):", ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl)) + console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID):", (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID)) + + const isLoading = isLoadingApp || ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()) || (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID)); + // const isLoading = isLoadingApp || ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty())); + console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ isLoading:", isLoading) const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || (!isLinkingToMessage && !isInitialPageReady) || From 44473445ee83fc22b70910ca11eb0a147776d03e Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 24 Oct 2024 10:07:01 +0700 Subject: [PATCH 022/128] change delete money request and track expense to merge instead of fast, to resolve not found page briefly appeared --- src/libs/actions/IOU.ts | 48 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index b271baf93886..45f0a95e424b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1752,9 +1752,18 @@ function getDeleteTrackExpenseInformation( if (shouldDeleteTransactionThread) { optimisticData.push( { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, - value: null, + value: { + reportID: null, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + participants: { + [userAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + }, }, { onyxMethod: Onyx.METHOD.SET, @@ -1799,6 +1808,17 @@ function getDeleteTrackExpenseInformation( }, ]; + if (shouldDeleteTransactionThread && transactionThread) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, + value: Object.keys(transactionThread).reduce>((acc, key) => { + acc[key] = null; + return acc; + }, {}), + }); + } + const failureData: OnyxUpdate[] = []; if (shouldDeleteTransactionFromOnyx) { @@ -5990,9 +6010,18 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor if (shouldDeleteTransactionThread) { optimisticData.push( { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, - value: null, + value: { + reportID: null, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + participants: { + [userAccountID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + }, }, { onyxMethod: Onyx.METHOD.SET, @@ -6088,6 +6117,17 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }, ]; + if (shouldDeleteTransactionThread && transactionThread) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, + value: Object.keys(transactionThread).reduce>((acc, key) => { + acc[key] = null; + return acc; + }, {}), + }); + } + if (shouldDeleteIOUReport) { successData.push({ onyxMethod: Onyx.METHOD.SET, From 71c3d417ef110eee161f8c51e5789d77a1ba5762 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 24 Oct 2024 11:04:06 +0700 Subject: [PATCH 023/128] show skeleton when deleting processed --- src/libs/actions/IOU.ts | 10 ---------- src/libs/actions/Task.ts | 5 ----- src/pages/home/ReportScreen.tsx | 25 +++++++++++++++++-------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 45f0a95e424b..39692bc10fe0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1788,11 +1788,6 @@ function getDeleteTrackExpenseInformation( lastMessageHtml: !lastMessageHtml ? lastMessageText : lastMessageHtml, }, }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, - value: null, - } ); const successData: OnyxUpdate[] = [ @@ -6054,11 +6049,6 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: ReportUtils.getOutstandingChildRequest(updatedIOUReport), }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, - value: null, - } ); if (!shouldDeleteIOUReport && updatedReportPreviewAction?.childMoneyRequestCount === 0) { diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 2031d5a42257..b2fc07bd8685 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1053,11 +1053,6 @@ function deleteTask(report: OnyxEntry) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, value: optimisticReportActions as OnyxTypes.ReportActions, }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, - value: null, - }, ]; // Update optimistic data for parent report action if the report is a child report and the task report has no visible child diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index d17ee485498a..15c5e9b34877 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -55,6 +55,9 @@ import ReportActionsView from './report/ReportActionsView'; import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext'; import {ActionListContext, ReactionListContext} from './ReportScreenContext'; +import Onyx from 'react-native-onyx'; +import lodashDefer from 'lodash/defer'; + type ReportScreenNavigationProps = StackScreenProps; @@ -343,20 +346,26 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro [currentUserAccountID, linkedAction], ); const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); - console.log("[wildebug] ~ file: ReportScreen.tsx:346 ~ ReportScreen ~ deleteTransactionNavigateBackUrl:", deleteTransactionNavigateBackUrl) - console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ report?.reportID:", report?.reportID) - console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl):", ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl)) - console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID):", (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID)) - - const isLoading = isLoadingApp || ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()) || (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID)); - // const isLoading = isLoadingApp || ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty())); - console.log("[wildebug] ~ file: ReportScreen.tsx:348 ~ ReportScreen ~ isLoading:", isLoading) + + useEffect(() => { + if (!isFocused || !deleteTransactionNavigateBackUrl) { + return; + } + // Schedule the code to run after the current call stack is cleared, + // ensuring all updates are processed before hide the skeleton + lodashDefer(() => { + Onyx.merge(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, null); + }); + }, [isFocused]); + + const isLoading = isLoadingApp ?? ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty())); const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || (!isLinkingToMessage && !isInitialPageReady) || isEmptyObject(reportOnyx) || isLoadingReportOnyx || !isCurrentReportLoadedFromOnyx || + (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || isLoading; const isLinkedActionBecomesDeleted = prevIsLinkedActionDeleted !== undefined && !prevIsLinkedActionDeleted && isLinkedActionDeleted; From 54402d18f585bfc1c34e61502c43d910d505e341 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 28 Oct 2024 18:40:39 +0300 Subject: [PATCH 024/128] comment out the set selection code --- .../Composer/implementation/index.native.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 905fa537ff55..23531ceafa83 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -59,16 +59,16 @@ function Composer( inputCallbackRef(autoFocus ? textInput.current : null); }, [autoFocus, inputCallbackRef, autoFocusInputRef]); - useEffect(() => { - if (!textInput.current || !selection) { - return; - } + // useEffect(() => { + // if (!textInput.current || !selection) { + // return; + // } - // We are setting selection twice to trigger a scroll to the cursor on change of composer size. - textInput.current?.setSelection((selection.start || 1) - 1, selection.start); - textInput.current?.setSelection(selection.start, selection.start); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [isComposerFullSize]); + // // We are setting selection twice to trigger a scroll to the cursor on change of composer size. + // textInput.current?.setSelection((selection.start || 1) - 1, selection.start); + // textInput.current?.setSelection(selection.start, selection.start); + // // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + // }, [isComposerFullSize]); /** * Set the TextInput Ref From 4e65339fec300f16e3ff124bc5c4d1c88b7095d7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 28 Oct 2024 23:33:16 +0300 Subject: [PATCH 025/128] revert --- .../Composer/implementation/index.native.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 23531ceafa83..905fa537ff55 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -59,16 +59,16 @@ function Composer( inputCallbackRef(autoFocus ? textInput.current : null); }, [autoFocus, inputCallbackRef, autoFocusInputRef]); - // useEffect(() => { - // if (!textInput.current || !selection) { - // return; - // } + useEffect(() => { + if (!textInput.current || !selection) { + return; + } - // // We are setting selection twice to trigger a scroll to the cursor on change of composer size. - // textInput.current?.setSelection((selection.start || 1) - 1, selection.start); - // textInput.current?.setSelection(selection.start, selection.start); - // // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - // }, [isComposerFullSize]); + // We are setting selection twice to trigger a scroll to the cursor on change of composer size. + textInput.current?.setSelection((selection.start || 1) - 1, selection.start); + textInput.current?.setSelection(selection.start, selection.start); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, [isComposerFullSize]); /** * Set the TextInput Ref From aa8156ddbb3b807b3226a693289a6df4ad5fa310 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 28 Oct 2024 23:36:09 +0300 Subject: [PATCH 026/128] only scroll to cursor on changing to composer to small size --- src/components/Composer/implementation/index.native.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 905fa537ff55..74c96555b644 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -60,11 +60,11 @@ function Composer( }, [autoFocus, inputCallbackRef, autoFocusInputRef]); useEffect(() => { - if (!textInput.current || !selection) { + if (!textInput.current || !selection || isComposerFullSize) { return; } - // We are setting selection twice to trigger a scroll to the cursor on change of composer size. + // We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size. textInput.current?.setSelection((selection.start || 1) - 1, selection.start); textInput.current?.setSelection(selection.start, selection.start); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps From 913563b841946fbbe5bedee861233f19ab9484dc Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 29 Oct 2024 00:30:19 +0300 Subject: [PATCH 027/128] fix tests --- src/components/Composer/implementation/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 74c96555b644..9f136effa857 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -60,7 +60,7 @@ function Composer( }, [autoFocus, inputCallbackRef, autoFocusInputRef]); useEffect(() => { - if (!textInput.current || !selection || isComposerFullSize) { + if (!textInput.current || !textInput.current.setSelection || !selection || isComposerFullSize) { return; } From d8076c19009b08cfaf29cdeae29349bd873d9a6f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 1 Nov 2024 23:00:47 +0300 Subject: [PATCH 028/128] applied small delay to setSelection --- src/components/Composer/implementation/index.native.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 9f136effa857..e5c2b309ad82 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -65,8 +65,13 @@ function Composer( } // We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size. - textInput.current?.setSelection((selection.start || 1) - 1, selection.start); - textInput.current?.setSelection(selection.start, selection.start); + const timeoutID = setTimeout(() => { + textInput.current?.setSelection((selection.start || 1) - 1, selection.start); + textInput.current?.setSelection(selection.start, selection.start); + }, 100); + + return () => clearTimeout(timeoutID); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isComposerFullSize]); From 295079ffbe9de26548ec2ed84a6e78ccf5082b82 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 1 Nov 2024 23:02:46 +0300 Subject: [PATCH 029/128] reduced the delay --- src/components/Composer/implementation/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index e5c2b309ad82..0eee0be257d4 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -68,7 +68,7 @@ function Composer( const timeoutID = setTimeout(() => { textInput.current?.setSelection((selection.start || 1) - 1, selection.start); textInput.current?.setSelection(selection.start, selection.start); - }, 100); + }, 0); return () => clearTimeout(timeoutID); From 36a58e35afcaed90cb4889cb3400fd5147e0a3bc Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:33:34 +0700 Subject: [PATCH 030/128] fix gray-out and name issue in offline category update --- src/libs/CategoryUtils.ts | 19 +++++++++++++++++-- src/libs/Middleware/SaveResponseInOnyx.ts | 1 + src/libs/actions/Policy/Category.ts | 15 ++++++++++++++- src/types/onyx/Policy.ts | 1 + 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/libs/CategoryUtils.ts b/src/libs/CategoryUtils.ts index 479ae557eab6..7723f98e7345 100644 --- a/src/libs/CategoryUtils.ts +++ b/src/libs/CategoryUtils.ts @@ -1,7 +1,7 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import type {Policy, TaxRate, TaxRatesWithDefault} from '@src/types/onyx'; -import type {ApprovalRule, ExpenseRule} from '@src/types/onyx/Policy'; +import type {ApprovalRule, ExpenseRule, MccGroup} from '@src/types/onyx/Policy'; import * as CurrencyUtils from './CurrencyUtils'; function formatDefaultTaxRateText(translate: LocaleContextProps['translate'], taxID: string, taxRate: TaxRate, policyTaxRates?: TaxRatesWithDefault) { @@ -68,4 +68,19 @@ function getCategoryDefaultTaxRate(expenseRules: ExpenseRule[], categoryName: st return categoryDefaultTaxRate; } -export {formatDefaultTaxRateText, formatRequireReceiptsOverText, getCategoryApproverRule, getCategoryExpenseRule, getCategoryDefaultTaxRate}; +function updateCategoryInMccGroup(mccGroups: Record, oldCategoryName: string, newCategoryName: string, shouldClearPendingAction?: boolean) { + if (oldCategoryName === newCategoryName) { + return mccGroups; + } + + const updatedGroups: Record = {}; + + for (const [key, group] of Object.entries(mccGroups || {})) { + updatedGroups[key] = + group.category === oldCategoryName ? {...group, category: newCategoryName, pendingAction: shouldClearPendingAction ? null : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE} : group; + } + + return updatedGroups; +} + +export {formatDefaultTaxRateText, formatRequireReceiptsOverText, getCategoryApproverRule, getCategoryExpenseRule, getCategoryDefaultTaxRate, updateCategoryInMccGroup}; diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 12c1931b0199..677939157e3b 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -10,6 +10,7 @@ const requestsToIgnoreLastUpdateID: string[] = [ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, WRITE_COMMANDS.CLOSE_ACCOUNT, WRITE_COMMANDS.DELETE_MONEY_REQUEST, + WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY, SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES, ]; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 78b0f2dec9e2..5454591b5b5f 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -34,7 +34,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyCategory, RecentlyUsedCategories, Report} from '@src/types/onyx'; -import type {ApprovalRule, CustomUnit, ExpenseRule} from '@src/types/onyx/Policy'; +import type {ApprovalRule, CustomUnit, ExpenseRule, MccGroup} from '@src/types/onyx/Policy'; import type {PolicyCategoryExpenseLimitType} from '@src/types/onyx/PolicyCategory'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -542,8 +542,12 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string const policyCategoryExpenseRule = CategoryUtils.getCategoryExpenseRule(policy?.rules?.expenseRules ?? [], policyCategory.oldName); const approvalRules = policy?.rules?.approvalRules ?? []; const expenseRules = policy?.rules?.expenseRules ?? []; + const mccGroup = policy?.mccGroup ?? {}; const updatedApprovalRules: ApprovalRule[] = lodashCloneDeep(approvalRules); const updatedExpenseRules: ExpenseRule[] = lodashCloneDeep(expenseRules); + const clonedMccGroup: Record = lodashCloneDeep(mccGroup); + const updatedMccGroup = CategoryUtils.updateCategoryInMccGroup(clonedMccGroup, policyCategory.oldName, policyCategory.newName); + const updatedMccGroupWithClearedPendingAction = CategoryUtils.updateCategoryInMccGroup(clonedMccGroup, policyCategory.oldName, policyCategory.newName, true); if (policyCategoryExpenseRule) { const ruleIndex = updatedExpenseRules.findIndex((rule) => rule.id === policyCategoryExpenseRule.id); @@ -596,10 +600,18 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string approvalRules: updatedApprovalRules, expenseRules: updatedExpenseRules, }, + mccGroup: updatedMccGroup, }, }, ], successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + mccGroup: updatedMccGroupWithClearedPendingAction, + }, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, @@ -639,6 +651,7 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string rules: { approvalRules, }, + mccGroup: updatedMccGroupWithClearedPendingAction, }, }, ], diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ecc5bd1f6606..39f36f152913 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1901,4 +1901,5 @@ export type { ApprovalRule, ExpenseRule, NetSuiteConnectionConfig, + MccGroup, }; From 58042056421a1db7097fa41c124baced6cf70546 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Mon, 4 Nov 2024 22:52:49 -0600 Subject: [PATCH 031/128] Update and rename Expense-Rules.md to Create-Expense-Rules.md Update article formatting and a few small tweaks, rename article to match standard naming convention --- .../expenses/Create-Expense-Rules.md | 61 +++++++++++++++++++ .../expenses/Expense-Rules.md | 55 ----------------- 2 files changed, 61 insertions(+), 55 deletions(-) create mode 100644 docs/articles/expensify-classic/expenses/Create-Expense-Rules.md delete mode 100644 docs/articles/expensify-classic/expenses/Expense-Rules.md diff --git a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md new file mode 100644 index 000000000000..c455be3dd721 --- /dev/null +++ b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md @@ -0,0 +1,61 @@ +--- +title: Create Expense Rules +description: Automatically categorize, tag, and report expenses based on the merchant's name + +--- +Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. + +# Create expense rules + +1. Hover over **Settings** and click **Account**. +2. Click **Expense Rules**. +2. Click **New Rule**. +3. Add what the merchant name should contain in order for the rule to be applied. *Note: If you enter just a period, the rule will apply to all expenses regardless of the merchant name. Universal Rules will always take precedence over all other expense rules.* +4. Choose from the following rules: +- **Merchant:** Updates the merchant name (e.g., “Starbucks #238” could be changed to “Starbucks”) +- **Category:** Applies a workspace category to the expense +- **Tag:** Applies a tag to the expense (e.g., a Department or Location) +- **Description:** Adds a description to the description field on the expense +- **Reimbursability:** Determines whether the expense will be marked as reimbursable or non-reimbursable +- **Billable**: Determines whether the expense is billable +- **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created if the "Create report if necessary" checkbox is selected. + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} + +{:start="7"} +7. (Optional) To apply the rule to previously entered expenses, select the **Apply to existing matching expenses** checkbox. You can also click **Preview Matching Expenses** to see if your rule matches the intended expenses. + +# How rules are applied + +In general, your expense rules will be applied in order, from **top to bottom**, (i.e., from the first rule). However, other settings can impact how expense rules are applied. Here is the hierarchy that determines how these are applied: + +1. A Universal Rule will **always** be applied over any other expense category rules. Rules that would otherwise change the expense category will **not** override the Universal Rule. +2. If Scheduled Submit and the setting “Enforce Default Report Title” are enabled on the workspace, this will take precedence over any rules trying to add the expense to a report. +3. If the expense is from a company card that is forced to a workspace with strict rule enforcement, those rules will take precedence over individual expense rules. +4. If you belong to a workspace that is tied to an accounting integration, the configuration settings for this connection may update your expense details upon export, even if the expense rules were successfully applied to the expense. + +# Create an expense rule from changes made to an expense + +If you open an expense and change it, you can then create an expense rule based on those changes by selecting the “Create a rule based on your changes" checkbox. *Note: The expense must be saved, reopened, and edited for this option to appear.* + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} + +# Delete an expense rule + +To delete an expense rule, + +1. Hover over **Settings** and click **Account**. +2. Click **Expense Rules**. +3. Scroll down to the rule you’d like to remove and click the trash can icon. + +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} + +{% include faq-begin.md %} + +## How can I use expense rules to vendor match when exporting to an accounting package? + +When exporting non-reimbursable expenses to your connected accounting package, the payee field will list "Credit Card Misc." if the merchant name on the expense in Expensify is not an exact match to a vendor in the accounting package. When an exact match is unavailable, "Credit Card Misc." prevents multiple variations of the same vendor (e.g., Starbucks and Starbucks #1234, as is often seen in credit card statements) from being created in your accounting package. + +For repeated expenses, the best practice is to use Expense Rules, which will automatically update the merchant name without having to do it manually each time. This only works for connections to QuickBooks Online, Desktop, and Xero. Vendor matching cannot be performed in this manner for NetSuite or Sage Intacct due to limitations in the API of the accounting package. + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/expenses/Expense-Rules.md b/docs/articles/expensify-classic/expenses/Expense-Rules.md deleted file mode 100644 index 295aa8d00cc9..000000000000 --- a/docs/articles/expensify-classic/expenses/Expense-Rules.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Expense Rules -description: Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant's name. - ---- -# Overview -Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. - -# How to use Expense Rules -**To create an expense rule, follow these steps:** -1. Navigate to **Settings > Account > Expense Rules** -2. Click on **New Rule** -3. Fill in the required information to set up your rule - -When creating an expense rule, you will be able to apply the following rules to expenses: - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} - -- **Merchant:** Updates the merchant name, e.g., “Starbucks #238” could be changed to “Starbucks” -- **Category:** Applies a workspace category to the expense -- **Tag:** Applies a tag to the expense, e.g., a Department or Location -- **Description:** Adds a description to the description field on the expense -- **Reimbursability:** Determines whether the expense will be marked as reimbursable or non-reimbursable -- **Billable**: Determines whether the expense is billable -- **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created - -## Tips on using Expense Rules -- If you'd like to apply a rule to all expenses (“Universal Rule”) rather than just one merchant, simply enter a period [.] and nothing else into the **“When the merchant name contains:”** field. **Note:** Universal Rules will always take precedence over all other rules for category (more on this below). -- You can apply a rule to previously entered expenses by checking the **Apply to existing matching expenses** checkbox. Click “Preview Matching Expenses” to see if your rule matches the intended expenses. -- You can create expense rules while editing an expense. To do this, simply check the box **“Create a rule based on your changes"** at the time of editing. Note that the expense must be saved, reopened, and edited for this option to appear. - - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} - - -To delete an expense rule, go to **Settings > Account > Expense Rules**, scroll down to the rule you’d like to remove, and then click the trash can icon in the upper right corner of the rule: - -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} - -# Deep Dive -In general, your expense rules will be applied in order, from **top to bottom**, i.e., from the first rule. However, other settings can impact how expense rules are applied. Here is the hierarchy that determines how these are applied: -1. A Universal Rule will **always** precede over any other expense category rules. Rules that would otherwise change the expense category will **not** override the Universal Rule. -2. If Scheduled Submit and the setting “Enforce Default Report Title” are enabled on the workspace, this will take precedence over any rules trying to add the expense to a report. -3. If the expense is from a Company Card that is forced to a workspace with strict rule enforcement, those rules will take precedence over individual expense rules. -4. If you belong to a workspace that is tied to an accounting integration, the configuration settings for this connection may update your expense details upon export, even if the expense rules were successfully applied to the expense. - - -{% include faq-begin.md %} -## How can I use Expense Rules to vendor match when exporting to an accounting package? -When exporting non-reimbursable expenses to your connected accounting package, the payee field will list "Credit Card Misc." if the merchant name on the expense in Expensify is not an exact match to a vendor in the accounting package. -When an exact match is unavailable, "Credit Card Misc." prevents multiple variations of the same vendor (e.g., Starbucks and Starbucks #1234, as is often seen in credit card statements) from being created in your accounting package. -For repeated expenses, the best practice is to use Expense Rules, which will automatically update the merchant name without having to do it manually each time. -This only works for connections to QuickBooks Online, Desktop, and Xero. Vendor matching cannot be performed in this manner for NetSuite or Sage Intacct due to limitations in the API of the accounting package. - -{% include faq-end.md %} From 1b8128a02178af9647ffe9967c2f162ba90d9d33 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Mon, 4 Nov 2024 22:59:01 -0600 Subject: [PATCH 032/128] Update redirects.csv Updating for new article name --- docs/redirects.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 06fd7c1ef502..93238b11f55b 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -591,3 +591,4 @@ https://help.expensify.com/articles/expensify-classic/articles/expensify-classic https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/add-a-payment-card-and-view-your-subscription,https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Add-a-payment-card-and-view-your-subscription https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-page-coming-soon,https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Billing-page +https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Create-Expense-Rules From d582cc3caa56c81a2889533c07b664fe574f128f Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Wed, 6 Nov 2024 05:47:41 +0700 Subject: [PATCH 033/128] remove unused code --- src/libs/Middleware/SaveResponseInOnyx.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 677939157e3b..12c1931b0199 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -10,7 +10,6 @@ const requestsToIgnoreLastUpdateID: string[] = [ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, WRITE_COMMANDS.CLOSE_ACCOUNT, WRITE_COMMANDS.DELETE_MONEY_REQUEST, - WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY, SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES, ]; From 09a7c37af25f5f79a9f0a50f02ae65ed9328c39a Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:55:43 +0700 Subject: [PATCH 034/128] Add RENAME_WORKSPACE_CATEGORY to ignore last update ID for fixing first-time category update case --- src/libs/Middleware/SaveResponseInOnyx.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 12c1931b0199..677939157e3b 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -10,6 +10,7 @@ const requestsToIgnoreLastUpdateID: string[] = [ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, WRITE_COMMANDS.CLOSE_ACCOUNT, WRITE_COMMANDS.DELETE_MONEY_REQUEST, + WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY, SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES, ]; From 0e0edac70988affb617c7e409ac8e3f08048afa9 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 11 Nov 2024 01:32:40 +0530 Subject: [PATCH 035/128] add green color to menu dot for copilot --- src/pages/settings/Security/SecuritySettingsPage.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 42ac7c52e58f..b13cf04cfb7b 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -55,6 +55,8 @@ function SecuritySettingsPage() { const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); const [selectedDelegate, setSelectedDelegate] = useState(); + const [selectedEmail, setSelectedEmail] = useState(); + const errorFields = account?.delegatedAccess?.errorFields ?? {}; const [anchorPosition, setAnchorPosition] = useState({ @@ -88,6 +90,7 @@ function SecuritySettingsPage() { setMenuPosition(); setShouldShowDelegatePopoverMenu(true); setSelectedDelegate(delegate); + setSelectedEmail(delegate.email); }; useLayoutEffect(() => { @@ -171,11 +174,12 @@ function SecuritySettingsPage() { onPendingActionDismiss: () => clearDelegateErrorsByField(email, 'addDelegate'), error, onPress, + success: selectedEmail === email, }; }), // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - [delegates, translate, styles, personalDetails, errorFields, windowWidth], + [delegates, translate, styles, personalDetails, errorFields, windowWidth, selectedEmail], ); const delegatorMenuItems: MenuItemProps[] = useMemo( @@ -209,6 +213,7 @@ function SecuritySettingsPage() { Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); setShouldShowDelegatePopoverMenu(false); setSelectedDelegate(undefined); + setSelectedEmail(undefined); }, }, { @@ -218,6 +223,7 @@ function SecuritySettingsPage() { Modal.close(() => { setShouldShowDelegatePopoverMenu(false); setShouldShowRemoveDelegateModal(true); + setSelectedEmail(undefined); }), }, ]; @@ -310,6 +316,7 @@ function SecuritySettingsPage() { menuItems={delegatePopoverMenuItems} onClose={() => { setShouldShowDelegatePopoverMenu(false); + setSelectedEmail(undefined); }} /> Date: Tue, 12 Nov 2024 18:25:39 -0600 Subject: [PATCH 036/128] Update docs/articles/expensify-classic/expenses/Create-Expense-Rules.md Co-authored-by: Daniel Gale-Rosen <5487802+dangrous@users.noreply.github.com> --- .../articles/expensify-classic/expenses/Create-Expense-Rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md index c455be3dd721..36ae8be74988 100644 --- a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md +++ b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md @@ -1,8 +1,8 @@ --- title: Create Expense Rules description: Automatically categorize, tag, and report expenses based on the merchant's name - --- + Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name. # Create expense rules From 164c125e7837fff46d18de3bd6e5de1e0d824975 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 12 Nov 2024 18:28:05 -0600 Subject: [PATCH 037/128] Update docs/articles/expensify-classic/expenses/Create-Expense-Rules.md Co-authored-by: Daniel Gale-Rosen <5487802+dangrous@users.noreply.github.com> --- .../expensify-classic/expenses/Create-Expense-Rules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md index 36ae8be74988..5cc3599f0f9b 100644 --- a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md +++ b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md @@ -22,8 +22,8 @@ Expense rules allow you to automatically categorize, tag, and report expenses ba ![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} -{:start="7"} -7. (Optional) To apply the rule to previously entered expenses, select the **Apply to existing matching expenses** checkbox. You can also click **Preview Matching Expenses** to see if your rule matches the intended expenses. +{:start="6"} +6. (Optional) To apply the rule to previously entered expenses, select the **Apply to existing matching expenses** checkbox. You can also click **Preview Matching Expenses** to see if your rule matches the intended expenses. # How rules are applied From 75ed3c0ff0c539c1d680ffbc9150d1f00360bbda Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 12 Nov 2024 18:44:42 -0600 Subject: [PATCH 038/128] Update Create-Expense-Rules.md Updating alt text --- .../expensify-classic/expenses/Create-Expense-Rules.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md index 5cc3599f0f9b..e83640403ce4 100644 --- a/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md +++ b/docs/articles/expensify-classic/expenses/Create-Expense-Rules.md @@ -20,7 +20,7 @@ Expense rules allow you to automatically categorize, tag, and report expenses ba - **Billable**: Determines whether the expense is billable - **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created if the "Create report if necessary" checkbox is selected. -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} +![Fields to create a new expense rule, including the characters a merchant's name should contain for the rule to apply, as well as what changes should be applied to the expense including the merchant name, category, tag, description, reimbursability, whether it is billable, and what report it will be added to.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"} {:start="6"} 6. (Optional) To apply the rule to previously entered expenses, select the **Apply to existing matching expenses** checkbox. You can also click **Preview Matching Expenses** to see if your rule matches the intended expenses. @@ -38,7 +38,7 @@ In general, your expense rules will be applied in order, from **top to bottom**, If you open an expense and change it, you can then create an expense rule based on those changes by selecting the “Create a rule based on your changes" checkbox. *Note: The expense must be saved, reopened, and edited for this option to appear.* -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} +![The "Create a rule based on your changes" checkbox is located in the bottom right corner of the popup window, to the left of the Save button.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"} # Delete an expense rule @@ -48,7 +48,7 @@ To delete an expense rule, 2. Click **Expense Rules**. 3. Scroll down to the rule you’d like to remove and click the trash can icon. -![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} +![The Trash icon to delete an expense rule is located at the top right of the box containing the expense rule, to the left of the Edit icon.](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"} {% include faq-begin.md %} From 390d35791eefee063aaa0a923de5ec4981034954 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 13:15:42 +0700 Subject: [PATCH 039/128] remove unnecessary code --- src/libs/actions/IOU.ts | 15 --------------- src/pages/ReportDetailsPage.tsx | 19 ------------------- 2 files changed, 34 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cf2ab1dda20f..9302ed0b0db6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5487,20 +5487,6 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT updatedReportPreviewAction.childMoneyRequestCount = reportPreviewAction.childMoneyRequestCount - 1; } - // STEP 5: Calculate the url that the user will be navigated back to - // This depends on which page they are on and which resources were deleted - // let reportIDToNavigateBack: string | undefined; - // if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { - // reportIDToNavigateBack = iouReport.reportID; - // } - - // if (iouReport?.chatReportID && shouldDeleteIOUReport) { - // reportIDToNavigateBack = iouReport.chatReportID; - // } - - // const urlToNavigateBack = reportIDToNavigateBack ? ROUTES.REPORT_WITH_ID.getRoute(reportIDToNavigateBack) : undefined; - // const urlToNavigateBack = undefined; - return { shouldDeleteTransactionThread, shouldDeleteIOUReport, @@ -5514,7 +5500,6 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT transactionViolations, reportPreviewAction, iouReport, - // urlToNavigateBack, }; } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 53815b88bdee..027fa278cca6 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -962,25 +962,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { isVisible={isDeleteModalVisible} onConfirm={navigateToTargetUrl} onCancel={() => setIsDeleteModalVisible(false)} - // onModalHide={() => { - // // We use isTransactionDeleted to know if the modal hides because the user deletes the transaction. - // if (!isTransactionDeleted.current) { - // if (caseID === CASES.DEFAULT) { - // if (navigateBackToAfterDelete.current) { - // Navigation.goBack(navigateBackToAfterDelete.current); - // } else { - // Navigation.dismissModal(); - // } - // } - // return; - // } - - // if (!navigateBackToAfterDelete.current) { - // Navigation.dismissModal(); - // } else { - // ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current, true); - // } - // }} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From 576e19456c9b3ee6172d5113197d5a66c97f5e3a Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 13:17:12 +0700 Subject: [PATCH 040/128] prettier --- src/libs/actions/IOU.ts | 36 +++++++-------------------------- src/pages/ReportDetailsPage.tsx | 14 +++++-------- 2 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9302ed0b0db6..b6d6544ba5f6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5510,17 +5510,8 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT * @param isSingleTransactionView - Whether we're in single transaction view * @returns The URL to navigate to */ -function getNavigationUrlAfterMoneyRequestDelete( - transactionID: string, - reportAction: OnyxTypes.ReportAction, - isSingleTransactionView = false -): string | undefined { - const { - shouldDeleteTransactionThread, - shouldDeleteIOUReport, - iouReport, - } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); - +function getNavigationUrlAfterMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { + const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); // Determine which report to navigate back to if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { @@ -5542,23 +5533,16 @@ function getNavigationUrlAfterMoneyRequestDelete( * @param isSingleTransactionView - Whether we're in single transaction view * @returns The URL to navigate to */ -function getNavigationUrlAfterTrackExpenseDelete( - chatReportID: string, - transactionID: string, - reportAction: OnyxTypes.ReportAction, - isSingleTransactionView = false -): string | undefined { +function getNavigationUrlAfterTrackExpenseDelete(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; - + // If not a self DM, handle it as a regular money request if (!ReportUtils.isSelfDM(chatReport)) { return getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); } const transactionThreadID = reportAction.childReportID; - const shouldDeleteTransactionThread = transactionThreadID - ? (reportAction?.childVisibleActionCount ?? 0) === 0 - : false; + const shouldDeleteTransactionThread = transactionThreadID ? (reportAction?.childVisibleActionCount ?? 0) === 0 : false; // Only navigate if in single transaction view and the thread will be deleted if (isSingleTransactionView && shouldDeleteTransactionThread && chatReport?.reportID) { @@ -5589,7 +5573,6 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo reportPreviewAction, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); - const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); // build Onyx data @@ -5965,13 +5948,8 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor } function deleteTrackExpense(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { - const urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete( - chatReportID, - transactionID, - reportAction, - isSingleTransactionView - ); - + const urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete(chatReportID, transactionID, reportAction, isSingleTransactionView); + // STEP 1: Get all collections we're updating const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; if (!ReportUtils.isSelfDM(chatReport)) { diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 027fa278cca6..4bfc053131da 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -225,7 +225,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const canUnapproveRequest = ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); - useEffect(() => { return () => { if (!isTransactionDeleted.current) { @@ -236,7 +235,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { }; }, []); - useEffect(() => { if (canDeleteRequest) { return; @@ -811,14 +809,13 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { // Where to go back after deleting the transaction and its report. It's empty if the transaction report isn't deleted. const navigateBackToAfterDelete = useRef(); - // Where to go back after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { setIsDeleteModalVisible(false); isTransactionDeleted.current = true; - + let urlToNavigateBack: string | undefined; - + if (caseID === CASES.DEFAULT) { urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); @@ -829,11 +826,11 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { } return; } - + if (!requestParentReportAction) { return; } - + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); @@ -841,13 +838,12 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); - + if (!urlToNavigateBack) { Navigation.dismissModal(); } else { ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); } - }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, setIsDeleteModalVisible, isTransactionDeleted]); const deleteTransaction = useCallback(() => { From 7145f31c185f71a15ada45acfc5733fbd1f6cc7e Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 14:00:43 +0700 Subject: [PATCH 041/128] lint & prettier --- src/libs/actions/IOU.ts | 11 +++--- src/libs/actions/Report.ts | 10 +++++ src/pages/ReportDetailsPage.tsx | 67 ++++++++++++++++----------------- src/pages/home/ReportScreen.tsx | 12 +++--- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index b6d6544ba5f6..6eaec2c751f9 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5387,10 +5387,9 @@ function updateMoneyRequestAmountAndCurrency({ * * @param transactionID - The transactionID of IOU * @param reportAction - The reportAction of the transaction in the IOU report - * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ -function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { +function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction) { // STEP 1: Get all collections we're updating const allReports = ReportConnection.getAllReports(); const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID : '-1'; @@ -5507,11 +5506,11 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT * Calculate the URL to navigate to after a money request deletion * @param transactionID - The ID of the money request being deleted * @param reportAction - The report action associated with the money request - * @param isSingleTransactionView - Whether we're in single transaction view + * @param isSingleTransactionView - whether we are in the transaction thread report * @returns The URL to navigate to */ function getNavigationUrlAfterMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { - const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction); // Determine which report to navigate back to if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { @@ -5571,7 +5570,7 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo chatReport, iouReport, reportPreviewAction, - } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + } = prepareToCleanUpMoneyRequest(transactionID, reportAction); const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); // build Onyx data @@ -5959,7 +5958,7 @@ function deleteTrackExpense(chatReportID: string, transactionID: string, reportA const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(transactionID, chatReportID); const actionableWhisperReportActionID = whisperAction?.reportActionID; - const {parameters, optimisticData, successData, failureData, shouldDeleteTransactionThread} = getDeleteTrackExpenseInformation( + const {parameters, optimisticData, successData, failureData} = getDeleteTrackExpenseInformation( chatReportID, transactionID, reportAction, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index f2b4186fa566..cc28e22b15d0 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4337,6 +4337,14 @@ function exportReportToCSV({reportID, transactionIDList}: ExportReportCSVParams, fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_REPORT_TO_CSV}), 'Expensify.csv', '', false, formData, CONST.NETWORK.METHOD.POST, onDownloadFailed); } +function setDeleteTransactionNavigateBackUrl(url: string) { + Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, url); +} + +function clearDeleteTransactionNavigateBackUrl() { + Onyx.merge(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, null); +} + export type {Video}; export { @@ -4426,4 +4434,6 @@ export { updateReportName, updateRoomVisibility, updateWriteCapability, + setDeleteTransactionNavigateBackUrl, + clearDeleteTransactionNavigateBackUrl, }; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 4bfc053131da..be4e79916c3a 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -3,7 +3,7 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx, {useOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -225,16 +225,6 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { const canUnapproveRequest = ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); - useEffect(() => { - return () => { - if (!isTransactionDeleted.current) { - return; - } - - deleteTransaction(); - }; - }, []); - useEffect(() => { if (canDeleteRequest) { return; @@ -804,10 +794,38 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { ); + const deleteTransaction = useCallback(() => { + if (caseID === CASES.DEFAULT) { + Task.deleteTask(report); + return; + } + + if (!requestParentReportAction) { + return; + } + + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + + if (isTrackExpense) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } + }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); + // A flag to indicate whether the user choose to delete the transaction or not const isTransactionDeleted = useRef(false); - // Where to go back after deleting the transaction and its report. It's empty if the transaction report isn't deleted. - const navigateBackToAfterDelete = useRef(); + + useEffect(() => { + return () => { + // Perform the actual deletion after the details page is unmounted. This prevents the [Deleted ...] text from briefly appearing when dismissing the modal. + if (!isTransactionDeleted.current) { + return; + } + + deleteTransaction(); + }; + }, [deleteTransaction]); // Where to go back after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { @@ -818,8 +836,8 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { if (caseID === CASES.DEFAULT) { urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); - Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); if (urlToNavigateBack) { + Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); Navigation.goBack(urlToNavigateBack as Route); } else { Navigation.dismissModal(); @@ -837,34 +855,15 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { } else { urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } - Onyx.set(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, urlToNavigateBack); if (!urlToNavigateBack) { Navigation.dismissModal(); } else { + Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); } }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, setIsDeleteModalVisible, isTransactionDeleted]); - const deleteTransaction = useCallback(() => { - if (caseID === CASES.DEFAULT) { - Task.deleteTask(report); - return; - } - - if (!requestParentReportAction) { - return; - } - - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); - - if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); - } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); - } - }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); - const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); return ( diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 3d1490df03d1..7157d9d5afa6 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -1,6 +1,7 @@ import {PortalHost} from '@gorhom/portal'; import {useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; +import lodashDefer from 'lodash/defer'; import lodashIsEqual from 'lodash/isEqual'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList, ViewStyle} from 'react-native'; @@ -53,9 +54,6 @@ import ReportActionsView from './report/ReportActionsView'; import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext'; import {ActionListContext, ReactionListContext} from './ReportScreenContext'; -import Onyx from 'react-native-onyx'; -import lodashDefer from 'lodash/defer'; - type ReportScreenNavigationProps = StackScreenProps; @@ -351,17 +349,19 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro // Schedule the code to run after the current call stack is cleared, // ensuring all updates are processed before hide the skeleton lodashDefer(() => { - Onyx.merge(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL, null); + Report.clearDeleteTransactionNavigateBackUrl(); }); - }, [isFocused]); + }, [isFocused, deleteTransactionNavigateBackUrl]); + + const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()); - const isLoading = isLoadingApp ?? ((!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty())); const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || (!isLinkingToMessage && !isInitialPageReady) || isEmptyObject(reportOnyx) || isLoadingReportOnyx || !isCurrentReportLoadedFromOnyx || + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || isLoading; From a343297798fa9425fc4e2767f4a3985043939422 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 09:38:11 +0700 Subject: [PATCH 042/128] Ensure report skeleton hide after interaction and update completed --- src/pages/home/ReportScreen.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7157d9d5afa6..bf2ca0488f6c 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -346,10 +346,11 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro if (!isFocused || !deleteTransactionNavigateBackUrl) { return; } - // Schedule the code to run after the current call stack is cleared, - // ensuring all updates are processed before hide the skeleton - lodashDefer(() => { - Report.clearDeleteTransactionNavigateBackUrl(); + // Clear the URL after all interactions are processed to ensure all updates are completed before hiding the skeleton + InteractionManager.runAfterInteractions(() => { + requestAnimationFrame(() => { + Report.clearDeleteTransactionNavigateBackUrl(); + }); }); }, [isFocused, deleteTransactionNavigateBackUrl]); From e41e18212dd320b54b4c9a42993fd6e505b7de72 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 09:46:00 +0700 Subject: [PATCH 043/128] Remove unused import --- src/pages/home/ReportScreen.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index bf2ca0488f6c..e0e9286b3a5d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -1,7 +1,6 @@ import {PortalHost} from '@gorhom/portal'; import {useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import lodashDefer from 'lodash/defer'; import lodashIsEqual from 'lodash/isEqual'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {FlatList, ViewStyle} from 'react-native'; From 7123b2ff987945f2abf46be661ad1b7d1cbb1397 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 11:06:37 +0700 Subject: [PATCH 044/128] Move navigateToTargetUrl to onModalHide, fix potential bug, remove unnecessary code --- src/libs/actions/IOU.ts | 2 +- src/pages/ReportDetailsPage.tsx | 42 +++++++++++++++++---------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d81b3f0b48fe..18f849b3c6ea 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5694,7 +5694,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor transactionViolations, iouReport, reportPreviewAction, - } = prepareToCleanUpMoneyRequest(transactionID, reportAction, isSingleTransactionView); + } = prepareToCleanUpMoneyRequest(transactionID, reportAction); const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index be4e79916c3a..9a09df29f78c 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -829,31 +829,29 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { // Where to go back after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { - setIsDeleteModalVisible(false); - isTransactionDeleted.current = true; - let urlToNavigateBack: string | undefined; - if (caseID === CASES.DEFAULT) { - urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); - if (urlToNavigateBack) { - Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - Navigation.goBack(urlToNavigateBack as Route); - } else { - Navigation.dismissModal(); + if (!isTransactionDeleted.current) { + if (caseID === CASES.DEFAULT) { + urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); + if (urlToNavigateBack) { + Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + Navigation.goBack(urlToNavigateBack as Route); + } else { + Navigation.dismissModal(); + } + return; } return; } - if (!requestParentReportAction) { - return; - } - - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); - if (isTrackExpense) { - urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); - } else { - urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + if (!isEmptyObject(requestParentReportAction)) { + const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + if (isTrackExpense) { + urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); + } else { + urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + } } if (!urlToNavigateBack) { @@ -955,13 +953,17 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { { + setIsDeleteModalVisible(false); + isTransactionDeleted.current = true; + }} onCancel={() => setIsDeleteModalVisible(false)} prompt={caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger shouldEnableNewFocusManagement + onModalHide={navigateToTargetUrl} /> Date: Fri, 15 Nov 2024 12:58:21 +0700 Subject: [PATCH 045/128] lint --- src/pages/ReportDetailsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 9a09df29f78c..9515ba583751 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -860,7 +860,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); } - }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, setIsDeleteModalVisible, isTransactionDeleted]); + }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, isTransactionDeleted]); const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); @@ -953,7 +953,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { { + onConfirm={() => { setIsDeleteModalVisible(false); isTransactionDeleted.current = true; }} From 6c96672536bbce89a642620b85ad1416b6ad34c8 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 13:06:07 +0700 Subject: [PATCH 046/128] Refactor for more accurate function name --- src/components/MoneyReportHeader.tsx | 2 +- src/libs/ReportUtils.ts | 4 ++-- src/pages/ReportDetailsPage.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 2dc809f9ce68..2c1b11501494 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -500,7 +500,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea isVisible={isDeleteRequestModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteRequestModalVisible(false)} - onModalHide={() => ReportUtils.navigateBackAfterDeleteTransaction(navigateBackToAfterDelete.current)} + onModalHide={() => ReportUtils.navigateBackOnDeleteTransaction(navigateBackToAfterDelete.current)} prompt={translate('iou.deleteConfirmation', {count: 1})} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ee9b303cb7d4..e993f5096c16 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4198,7 +4198,7 @@ function goBackToDetailsPage(report: OnyxEntry, backTo?: string) { Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '-1', backTo)); } -function navigateBackAfterDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) { +function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) { if (!backRoute) { return; } @@ -8675,7 +8675,7 @@ export { canWriteInReport, navigateToDetailsPage, navigateToPrivateNotes, - navigateBackAfterDeleteTransaction, + navigateBackOnDeleteTransaction as navigateBackOnDeleteTransaction, parseReportRouteParams, parseReportActionHtmlToText, requiresAttentionFromCurrentUser, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 9515ba583751..0d9b6dc626b8 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -858,7 +858,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { Navigation.dismissModal(); } else { Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackAfterDeleteTransaction(urlToNavigateBack as Route, true); + ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true); } }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, isTransactionDeleted]); From 964626bb2bd862fe415c56276fbe3e8265895fc6 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 19 Nov 2024 11:07:59 +0700 Subject: [PATCH 047/128] fix lint --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c0450dca283f..2a76e3cd1dd4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8694,7 +8694,7 @@ export { canWriteInReport, navigateToDetailsPage, navigateToPrivateNotes, - navigateBackOnDeleteTransaction as navigateBackOnDeleteTransaction, + navigateBackOnDeleteTransaction, parseReportRouteParams, parseReportActionHtmlToText, requiresAttentionFromCurrentUser, From 67eb8271724369aaa02800d51eb04e0d9d5e7558 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 19 Nov 2024 13:20:22 +0700 Subject: [PATCH 048/128] Remove unnecessary code --- src/libs/actions/Task.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 75e003655721..749918724d4e 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1138,17 +1138,6 @@ function deleteTask(report: OnyxEntry) { API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); Report.notifyNewAction(report.reportID, currentUserAccountID); - // if (shouldDeleteTaskReport) { - // Navigation.goBack(); - // if (parentReport?.reportID) { - // return ROUTES.REPORT_WITH_ID.getRoute(parentReport.reportID); - // } - // const mostRecentReportID = Report.getMostRecentReportID(report); - // if (mostRecentReportID) { - // return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); - // } - // } - const urlToNavigateBack = getNavigationUrlAfterTaskDelete(report); if (urlToNavigateBack) { Navigation.goBack(); From 62aaa285fed1116ac798ffddccdaa85f90e5d830 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 19 Nov 2024 14:07:45 +0700 Subject: [PATCH 049/128] remove unnecessary export --- src/libs/actions/Task.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 749918724d4e..5f892aeff32e 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1255,7 +1255,6 @@ export { canModifyTask, canActionTask, setNewOptimisticAssignee, - getParentReport, getNavigationUrlAfterTaskDelete, }; From 051d3cca74b8a0fbfd75dbe62811f05b7cbd4d3b Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 19 Nov 2024 14:16:16 +0700 Subject: [PATCH 050/128] Adjust tests after change deleting approach from set to merge --- tests/actions/IOUTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 1c26390f7b09..85cf2b81c01b 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2217,7 +2217,7 @@ describe('actions/IOU', () => { }); }); - expect(report).toBeFalsy(); + expect(report?.reportID).toBeFalsy(); mockFetch?.resume?.(); // Then After resuming fetch, the report for the given thread ID still does not exist @@ -2232,7 +2232,7 @@ describe('actions/IOU', () => { }); }); - expect(report).toBeFalsy(); + expect(report?.reportID).toBeFalsy(); }); it('delete the transaction thread if there are only changelogs (i.e. MODIFIED_EXPENSE actions) in the thread', async () => { From 088777930cfeb8a4cc0fba447a91e57a26f1f582 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 19 Nov 2024 14:20:38 +0700 Subject: [PATCH 051/128] Adjust more test --- tests/actions/IOUTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 85cf2b81c01b..1ac0e85beca0 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2339,7 +2339,7 @@ describe('actions/IOU', () => { }); }); - expect(report).toBeFalsy(); + expect(report?.reportID).toBeFalsy(); }); it('does not delete the transaction thread if there are visible comments in the thread', async () => { From d4ce223fd2a9438cd3bb49bb3f78e096e517fd47 Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:16:19 +0700 Subject: [PATCH 052/128] remove renaming ws category --- src/libs/Middleware/SaveResponseInOnyx.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 677939157e3b..12c1931b0199 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -10,7 +10,6 @@ const requestsToIgnoreLastUpdateID: string[] = [ SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, WRITE_COMMANDS.CLOSE_ACCOUNT, WRITE_COMMANDS.DELETE_MONEY_REQUEST, - WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY, SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES, ]; From 78f8f71a22fbb6c12d0bb7d366bacee842a3e737 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 22 Nov 2024 13:15:43 +0700 Subject: [PATCH 053/128] Update src/ONYXKEYS.ts Co-authored-by: Carlos Martins --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 9f39c7adef13..7310f4ab7340 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -219,7 +219,7 @@ const ONYXKEYS = { /** The NVP containing all information related to educational tooltip in workspace chat */ NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', - /** The NVP contain url to back after deleting transaction */ + /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', /** Whether to show save search rename tooltip */ From df0100f4a6e0cb5b1feed3d18cb88c8d150f3f7c Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 13:18:01 +0700 Subject: [PATCH 054/128] Refactor name --- src/libs/actions/IOU.ts | 10 +++++----- src/pages/ReportDetailsPage.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 210d9f544686..4706578c16e1 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5513,7 +5513,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT * @param isSingleTransactionView - whether we are in the transaction thread report * @returns The URL to navigate to */ -function getNavigationUrlAfterMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { +function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction); // Determine which report to navigate back to @@ -5541,7 +5541,7 @@ function getNavigationUrlAfterTrackExpenseDelete(chatReportID: string, transacti // If not a self DM, handle it as a regular money request if (!ReportUtils.isSelfDM(chatReport)) { - return getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + return getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); } const transactionThreadID = reportAction.childReportID; @@ -5576,7 +5576,7 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo reportPreviewAction, } = prepareToCleanUpMoneyRequest(transactionID, reportAction); - const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); // build Onyx data // Onyx operations to delete the transaction, update the IOU report action and chat report action @@ -5714,7 +5714,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor reportPreviewAction, } = prepareToCleanUpMoneyRequest(transactionID, reportAction); - const urlToNavigateBack = getNavigationUrlAfterMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); + const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, isSingleTransactionView); // STEP 2: Build Onyx data // The logic mostly resembles the cleanUpMoneyRequest function @@ -8443,7 +8443,7 @@ export { updateLastLocationPermissionPrompt, resolveDuplicates, getIOUReportActionToApproveOrPay, - getNavigationUrlAfterMoneyRequestDelete, + getNavigationUrlOnMoneyRequestDelete, getNavigationUrlAfterTrackExpenseDelete, }; export type {GPSPoint as GpsPoint, IOURequestType}; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 0d9b6dc626b8..5893ef4a76e9 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -850,7 +850,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { if (isTrackExpense) { urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - urlToNavigateBack = IOU.getNavigationUrlAfterMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = IOU.getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } } From 0ea2bbaf70cbe165a3068c928bebf40ff0e8ff3c Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 13:18:44 +0700 Subject: [PATCH 055/128] remove unnecessary code --- src/libs/actions/IOU.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4706578c16e1..d566fc2e5a5c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5978,10 +5978,6 @@ function deleteTrackExpense(chatReportID: string, transactionID: string, reportA // STEP 7: Navigate the user depending on which page they are on and which resources were deleted return urlToNavigateBack; - // if (isSingleTransactionView && shouldDeleteTransactionThread) { - // // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - // return ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '-1'); - // } } /** From 9cedf925aec661738bf33ef2affd94f9ce575422 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 22 Nov 2024 13:19:34 +0700 Subject: [PATCH 056/128] Update src/pages/ReportDetailsPage.tsx Co-authored-by: Carlos Martins --- src/pages/ReportDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 5893ef4a76e9..19362fb312e8 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -813,7 +813,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { } }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); - // A flag to indicate whether the user choose to delete the transaction or not + // A flag to indicate whether the user chose to delete the transaction or not const isTransactionDeleted = useRef(false); useEffect(() => { From 65199e2fb326c2dd920904aad0e21db4e1b08e1e Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 22 Nov 2024 13:19:42 +0700 Subject: [PATCH 057/128] Update src/pages/ReportDetailsPage.tsx Co-authored-by: Carlos Martins --- src/pages/ReportDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 19362fb312e8..71ca94709982 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -827,7 +827,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { }; }, [deleteTransaction]); - // Where to go back after deleting the transaction and its report. + // Where to navigate back to after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { let urlToNavigateBack: string | undefined; From 213fe69f82330b5db8f22bba6e785af14ffe14cf Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 13:21:15 +0700 Subject: [PATCH 058/128] Refactor --- src/libs/actions/Task.ts | 6 +++--- src/pages/ReportDetailsPage.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 5f892aeff32e..20ea7b2b1418 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -964,7 +964,7 @@ function getParentReport(report: OnyxEntry): OnyxEntry): string | undefined { +function getNavigationUrlOnTaskDelete(report: OnyxEntry): string | undefined { if (!report) { return undefined; } @@ -1138,7 +1138,7 @@ function deleteTask(report: OnyxEntry) { API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); Report.notifyNewAction(report.reportID, currentUserAccountID); - const urlToNavigateBack = getNavigationUrlAfterTaskDelete(report); + const urlToNavigateBack = getNavigationUrlOnTaskDelete(report); if (urlToNavigateBack) { Navigation.goBack(); return urlToNavigateBack; @@ -1255,7 +1255,7 @@ export { canModifyTask, canActionTask, setNewOptimisticAssignee, - getNavigationUrlAfterTaskDelete, + getNavigationUrlOnTaskDelete, }; export type {PolicyValue, Assignee, ShareDestination}; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 5893ef4a76e9..a2af7a71928b 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -833,7 +833,7 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { if (!isTransactionDeleted.current) { if (caseID === CASES.DEFAULT) { - urlToNavigateBack = Task.getNavigationUrlAfterTaskDelete(report); + urlToNavigateBack = Task.getNavigationUrlOnTaskDelete(report); if (urlToNavigateBack) { Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); Navigation.goBack(urlToNavigateBack as Route); From 51daaa0a6f7b46b26f55adda2ca1c23dd00588bc Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 13:56:16 +0700 Subject: [PATCH 059/128] resolve typecheck error --- src/libs/actions/IOU.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7d1d7623ff55..1e042cb2d371 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -60,6 +60,7 @@ import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Attendee, Participant, Split} from '@src/types/onyx/IOU'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; @@ -5503,7 +5504,7 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT * @param isSingleTransactionView - whether we are in the transaction thread report * @returns The URL to navigate to */ -function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { +function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): Route | undefined { const {shouldDeleteTransactionThread, shouldDeleteIOUReport, iouReport} = prepareToCleanUpMoneyRequest(transactionID, reportAction); // Determine which report to navigate back to @@ -5526,7 +5527,7 @@ function getNavigationUrlOnMoneyRequestDelete(transactionID: string, reportActio * @param isSingleTransactionView - Whether we're in single transaction view * @returns The URL to navigate to */ -function getNavigationUrlAfterTrackExpenseDelete(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): string | undefined { +function getNavigationUrlAfterTrackExpenseDelete(chatReportID: string, transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false): Route | undefined { const chatReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? null; // If not a self DM, handle it as a regular money request From 5e5026d0f9b6bb7ad0e4e78007278d39f2ec27b1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 23 Nov 2024 08:51:18 +0700 Subject: [PATCH 060/128] Add comment why use merge instead of set when deleting report --- src/libs/actions/IOU.ts | 8 ++++++++ src/libs/actions/Report.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 82666e12fce8..a25a5d3032b6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1784,6 +1784,8 @@ function getDeleteTrackExpenseInformation( if (shouldDeleteTransactionThread) { optimisticData.push( + // Use merge instead of set to avoid deleting the report too quickly, which could cause a brief "not found" page to appear. + // The remaining parts of the report object will be removed after the API call is successful. { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, @@ -1836,6 +1838,8 @@ function getDeleteTrackExpenseInformation( }, ]; + // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. if (shouldDeleteTransactionThread && transactionThread) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -5768,6 +5772,8 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor if (shouldDeleteTransactionThread) { optimisticData.push( + // Use merge instead of set to avoid deleting the report too quickly, which could cause a brief "not found" page to appear. + // The remaining parts of the report object will be removed after the API call is successful. { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`, @@ -5878,6 +5884,8 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }, ]; + // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. if (shouldDeleteTransactionThread && transactionThread) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index ef21096cb804..4fcfb0dde138 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2915,6 +2915,8 @@ function leaveGroupChat(reportID: string) { }); } + // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, From 5ddba66c78d1c8a39b75a2bb30b0d454fb54fad1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 23 Nov 2024 08:53:30 +0700 Subject: [PATCH 061/128] prettier --- src/libs/actions/IOU.ts | 4 ++-- src/libs/actions/Report.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index a25a5d3032b6..d4b6595600c0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1839,7 +1839,7 @@ function getDeleteTrackExpenseInformation( ]; // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. - // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. if (shouldDeleteTransactionThread && transactionThread) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -5885,7 +5885,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor ]; // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. - // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. if (shouldDeleteTransactionThread && transactionThread) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4fcfb0dde138..5ce3245aa827 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2916,7 +2916,7 @@ function leaveGroupChat(reportID: string) { } // Ensure that any remaining data is removed upon successful completion, even if the server sends a report removal response. - // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. + // This is done to prevent the removal update from lingering in the applyHTTPSOnyxUpdates function. const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, From 7a4cb93cb004aa85a69962caeaeeba9d3fa37ceb Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 25 Nov 2024 15:45:22 +0700 Subject: [PATCH 062/128] fix: Header - Approve button appears briefly when it shouldn't --- src/libs/actions/IOU.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 10eee66428e8..b2e173ed0b26 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7190,6 +7190,8 @@ function submitReport(expenseReport: OnyxTypes.Report) { const adminAccountID = policy?.role === CONST.POLICY.ROLE.ADMIN ? currentUserPersonalDetails?.accountID : undefined; const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport?.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID, adminAccountID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, isSubmitAndClosePolicy ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.SUBMITTED); + const approvalChain = ReportUtils.getApprovalChain(PolicyUtils.getPolicy(expenseReport?.policyID), expenseReport); + const managerID = approvalChain.at(0); const optimisticData: OnyxUpdate[] = !isSubmitAndClosePolicy ? [ @@ -7208,6 +7210,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, value: { ...expenseReport, + managerID, lastMessageText: ReportActionsUtils.getReportActionText(optimisticSubmittedReportAction), lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticSubmittedReportAction), stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, From 1b89a1f6f1e89dce8d7dbdc468bf89ec7d36a231 Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:53:42 +0700 Subject: [PATCH 063/128] Remove unused imports --- src/libs/actions/Policy/Category.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 07f20c834fc5..ca9d6a623569 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -33,7 +33,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyCategory, RecentlyUsedCategories, Report} from '@src/types/onyx'; -import type {ApprovalRule, CustomUnit, ExpenseRule, MccGroup} from '@src/types/onyx/Policy'; +import type {ApprovalRule, ExpenseRule, MccGroup} from '@src/types/onyx/Policy'; import type {PolicyCategoryExpenseLimitType} from '@src/types/onyx/PolicyCategory'; import type {OnyxData} from '@src/types/onyx/Request'; From feffdeedf02fc84acadd54972a0d50baf3755711 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 25 Nov 2024 18:43:16 +0700 Subject: [PATCH 064/128] fix prettier --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index b2e173ed0b26..6710823df16a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7191,7 +7191,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport?.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID, adminAccountID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, isSubmitAndClosePolicy ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.SUBMITTED); const approvalChain = ReportUtils.getApprovalChain(PolicyUtils.getPolicy(expenseReport?.policyID), expenseReport); - const managerID = approvalChain.at(0); + const managerID = approvalChain.length ? Number(approvalChain.at(0)) : undefined; const optimisticData: OnyxUpdate[] = !isSubmitAndClosePolicy ? [ From df821a91c55700aeb64a0eb1aaf93a71524788a4 Mon Sep 17 00:00:00 2001 From: Rocio Perez-Cano Date: Tue, 26 Nov 2024 15:42:03 -0500 Subject: [PATCH 065/128] Remove beta --- src/CONST.ts | 1 - src/libs/Permissions.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 81911d3f996f..c739861536bd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -668,7 +668,6 @@ const CONST = { BETAS: { ALL: 'all', DEFAULT_ROOMS: 'defaultRooms', - DUPE_DETECTION: 'dupeDetection', P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests', SPOTNANA_TRAVEL: 'spotnanaTravel', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 1d3428dfcfce..4a7bba3932a3 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -13,10 +13,6 @@ function canUseDefaultRooms(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.DEFAULT_ROOMS) || canUseAllBetas(betas); } -function canUseDupeDetection(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.DUPE_DETECTION) || canUseAllBetas(betas); -} - function canUseSpotnanaTravel(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.SPOTNANA_TRAVEL) || canUseAllBetas(betas); } @@ -49,7 +45,6 @@ function canUseLinkPreviews(): boolean { export default { canUseDefaultRooms, canUseLinkPreviews, - canUseDupeDetection, canUseSpotnanaTravel, canUseNetSuiteUSATax, canUseCombinedTrackSubmit, From e791adc0a191047801129cb919a4c85ec55f7a85 Mon Sep 17 00:00:00 2001 From: Rocio Perez-Cano Date: Tue, 26 Nov 2024 15:43:19 -0500 Subject: [PATCH 066/128] Missed a couple of spots --- src/libs/TransactionUtils/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index e7eea51c7755..672b9b739287 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -826,10 +826,6 @@ function getRecentTransactions(transactions: Record, size = 2): * @param checkDismissed - whether to check if the violation has already been dismissed as well */ function isDuplicate(transactionID: string, checkDismissed = false): boolean { - if (!Permissions.canUseDupeDetection(allBetas ?? [])) { - return false; - } - const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); @@ -892,7 +888,7 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); - if (!Permissions.canUseDupeDetection(allBetas ?? []) && hasOnlyDupeDetectionViolation) { + if (hasOnlyDupeDetectionViolation) { return false; } From e363a396c28f11180c2d6e16c27ccec6d8165f06 Mon Sep 17 00:00:00 2001 From: Rocio Perez-Cano Date: Tue, 26 Nov 2024 15:51:03 -0500 Subject: [PATCH 067/128] Remove ony key --- src/libs/TransactionUtils/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 672b9b739287..4a0c05ddec0c 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -14,7 +14,6 @@ import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import {toLocaleDigit} from '@libs/LocaleDigitUtils'; import * as Localize from '@libs/Localize'; import * as NumberUtils from '@libs/NumberUtils'; -import Permissions from '@libs/Permissions'; import {getCleanedTagName, getDistanceRateCustomUnitRate} from '@libs/PolicyUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; // eslint-disable-next-line import/no-cycle @@ -73,12 +72,6 @@ Onyx.connect({ }, }); -let allBetas: OnyxEntry; -Onyx.connect({ - key: ONYXKEYS.BETAS, - callback: (value) => (allBetas = value), -}); - function isDistanceRequest(transaction: OnyxEntry): boolean { // This is used during the expense creation flow before the transaction has been saved to the server if (lodashHas(transaction, 'iouRequestType')) { From 3f375387b1881fe524e8b80cc3d1af4a456466de Mon Sep 17 00:00:00 2001 From: Rocio Perez-Cano Date: Tue, 26 Nov 2024 15:54:09 -0500 Subject: [PATCH 068/128] Remove unused import --- 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 4a0c05ddec0c..460a329227c7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -24,7 +24,7 @@ import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; import type {IOUType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, Report, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type {OnyxInputOrEntry, Policy, RecentWaypoint, Report, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import type {SearchPolicy, SearchReport} from '@src/types/onyx/SearchResults'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; From 6990c38208bcf95be85d2006ba1d994c5f4ce655 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Tue, 26 Nov 2024 14:26:08 -0800 Subject: [PATCH 069/128] Remove payment card form Zip Code validation --- src/components/AddPaymentCard/PaymentCardForm.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 5aaa23b238f7..1fe4167b889c 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -165,10 +165,6 @@ function PaymentCardForm({ errors.addressStreet = translate(label.error.addressStreet); } - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = translate(label.error.addressZipCode); - } - if (!values.acceptTerms) { errors.acceptTerms = translate('common.error.acceptTerms'); } From 0e29ba13a5b15ca2567844e106fc6dbeba578699 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Tue, 26 Nov 2024 15:44:14 -0800 Subject: [PATCH 070/128] Removed inputMode to allow non-numeric Zip Codes (UK) --- src/components/AddPaymentCard/PaymentCardForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 1fe4167b889c..cee7edb3af61 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -282,7 +282,6 @@ function PaymentCardForm({ label={translate('common.zip')} aria-label={translate('common.zip')} role={CONST.ROLE.PRESENTATION} - inputMode={CONST.INPUT_MODE.NUMERIC} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} containerStyles={[styles.mt5]} /> From 38d8ba76cb64fa4dadaba9ffb3fe65bec39eed7f Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:02:25 +0700 Subject: [PATCH 071/128] Update MCC group for failure data --- src/libs/actions/Policy/Category.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index ca9d6a623569..873a8bfabc56 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -650,7 +650,7 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string rules: { approvalRules, }, - mccGroup: updatedMccGroupWithClearedPendingAction, + mccGroup: updatedMccGroup, }, }, ], From 173ed3cc5b3fc8c2325e8c21c77c48591018261b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 27 Nov 2024 16:30:45 -0700 Subject: [PATCH 072/128] play sound when paying on search --- src/libs/API/types.ts | 4 ++-- src/libs/actions/Search.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index d7258f1dd49e..a9e0a7646d57 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -315,7 +315,6 @@ const WRITE_COMMANDS = { DELETE_MONEY_REQUEST_ON_SEARCH: 'DeleteMoneyRequestOnSearch', HOLD_MONEY_REQUEST_ON_SEARCH: 'HoldMoneyRequestOnSearch', APPROVE_MONEY_REQUEST_ON_SEARCH: 'ApproveMoneyRequestOnSearch', - PAY_MONEY_REQUEST_ON_SEARCH: 'PayMoneyRequestOnSearch', UNHOLD_MONEY_REQUEST_ON_SEARCH: 'UnholdMoneyRequestOnSearch', REQUEST_REFUND: 'User_RefundPurchase', UPDATE_NETSUITE_SUBSIDIARY: 'UpdateNetSuiteSubsidiary', @@ -763,7 +762,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; [WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH]: Parameters.ApproveMoneyRequestOnSearchParams; - [WRITE_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH]: Parameters.PayMoneyRequestOnSearchParams; [WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.UnholdMoneyRequestOnSearchParams; [WRITE_COMMANDS.REQUEST_REFUND]: null; @@ -1025,6 +1023,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate', COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding', CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP: 'ConnectPolicyToQuickbooksDesktop', + PAY_MONEY_REQUEST_ON_SEARCH: 'PayMoneyRequestOnSearch', } as const; type SideEffectRequestCommand = ValueOf; @@ -1045,6 +1044,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.DISCONNECT_AS_DELEGATE]: EmptyObject; [SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject; [SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP]: Parameters.ConnectPolicyToQuickBooksDesktopParams; + [SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH]: Parameters.PayMoneyRequestOnSearchParams; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 44b5bb7f7ce9..0a91c1dfff81 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -6,12 +6,13 @@ import type {PaymentData, SearchQueryJSON} from '@components/Search/types'; import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import * as API from '@libs/API'; import type {ExportSearchItemsToCSVParams} from '@libs/API/parameters'; -import {WRITE_COMMANDS} from '@libs/API/types'; +import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ApiUtils from '@libs/ApiUtils'; import fileDownload from '@libs/fileDownload'; import enhanceParameters from '@libs/Network/enhanceParameters'; import * as ReportUtils from '@libs/ReportUtils'; import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; +import playSound, {SOUNDS} from '@libs/Sound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm'; @@ -277,7 +278,15 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans const optimisticData: OnyxUpdate[] = createActionLoadingData(true); const finallyData: OnyxUpdate[] = createActionLoadingData(false); - API.write(WRITE_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH, {hash, paymentData: JSON.stringify(paymentData)}, {optimisticData, finallyData}); + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH, {hash, paymentData: JSON.stringify(paymentData)}, {optimisticData, finallyData}).then( + (response) => { + if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) { + return; + } + playSound(SOUNDS.SUCCESS); + }, + ); } function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { From 1bb1e00208d76af887d82531ca4b4daf3562d37d Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 28 Nov 2024 19:58:23 +0100 Subject: [PATCH 073/128] feat: add perf markers to applying remote updates --- src/CONST.ts | 3 +++ src/libs/actions/OnyxUpdates.ts | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index ff4a9f2de91a..52fe22a661cb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1305,6 +1305,9 @@ const CONST = { SIDEBAR_LOADED: 'sidebar_loaded', LOAD_SEARCH_OPTIONS: 'load_search_options', SEND_MESSAGE: 'send_message', + APPLY_AIRSHIP_UPDATES: 'apply_airship_updates', + APPLY_PUSHER_UPDATES: 'apply_pusher_updates', + APPLY_HTTPS_UPDATES: 'apply_https_updates', COLD: 'cold', WARM: 'warm', REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500, diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 1ba50d08e449..52dfa9dfd742 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -3,6 +3,7 @@ import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import Log from '@libs/Log'; import * as SequentialQueue from '@libs/Network/SequentialQueue'; +import Performance from '@libs/Performance'; import PusherUtils from '@libs/PusherUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -26,6 +27,7 @@ let pusherEventsPromise = Promise.resolve(); let airshipEventsPromise = Promise.resolve(); function applyHTTPSOnyxUpdates(request: Request, response: Response) { + Performance.markStart(CONST.TIMING.APPLY_HTTPS_UPDATES); console.debug('[OnyxUpdateManager] Applying https update'); // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. @@ -61,12 +63,15 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { return Promise.resolve(); }) .then(() => { + Performance.markEnd(CONST.TIMING.APPLY_HTTPS_UPDATES); console.debug('[OnyxUpdateManager] Done applying HTTPS update'); return Promise.resolve(response); }); } function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) { + Performance.markStart(CONST.TIMING.APPLY_PUSHER_UPDATES); + pusherEventsPromise = pusherEventsPromise.then(() => { console.debug('[OnyxUpdateManager] Applying pusher update'); }); @@ -74,6 +79,7 @@ function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) { pusherEventsPromise = updates .reduce((promise, update) => promise.then(() => PusherUtils.triggerMultiEventHandler(update.eventType, update.data)), pusherEventsPromise) .then(() => { + Performance.markEnd(CONST.TIMING.APPLY_PUSHER_UPDATES); console.debug('[OnyxUpdateManager] Done applying Pusher update'); }); @@ -81,6 +87,8 @@ function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) { } function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[]) { + Performance.markStart(CONST.TIMING.APPLY_AIRSHIP_UPDATES); + airshipEventsPromise = airshipEventsPromise.then(() => { console.debug('[OnyxUpdateManager] Applying Airship updates'); }); @@ -88,6 +96,7 @@ function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[]) { airshipEventsPromise = updates .reduce((promise, update) => promise.then(() => Onyx.update(update.data)), airshipEventsPromise) .then(() => { + Performance.markEnd(CONST.TIMING.APPLY_AIRSHIP_UPDATES); console.debug('[OnyxUpdateManager] Done applying Airship updates'); }); From 425e743a8b6ef00587b9ec1ab48e6760e308e693 Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:05:04 +0700 Subject: [PATCH 074/128] Update MCC group for failed data --- src/libs/actions/Policy/Category.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 65d428a3a365..0115a62aa167 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -658,7 +658,7 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string rules: { approvalRules, }, - mccGroup: updatedMccGroup, + mccGroup, }, }, ], From 958981455e63f4723ba1d5bc22d855ed60ed85f5 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:00:29 +0100 Subject: [PATCH 075/128] init callbacks for useState in CalendarPicker --- src/components/DatePicker/CalendarPicker/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DatePicker/CalendarPicker/index.tsx b/src/components/DatePicker/CalendarPicker/index.tsx index 9906f9b04c3c..4e1fef2d827e 100644 --- a/src/components/DatePicker/CalendarPicker/index.tsx +++ b/src/components/DatePicker/CalendarPicker/index.tsx @@ -53,14 +53,14 @@ function CalendarPicker({ const {preferredLocale, translate} = useLocalize(); const pressableRef = useRef(null); - const [currentDateView, setCurrentDateView] = useState(getInitialCurrentDateView(value, minDate, maxDate)); + const [currentDateView, setCurrentDateView] = useState(() => getInitialCurrentDateView(value, minDate, maxDate)); const [isYearPickerVisible, setIsYearPickerVisible] = useState(false); const minYear = getYear(new Date(minDate)); const maxYear = getYear(new Date(maxDate)); - const [years, setYears] = useState( + const [years, setYears] = useState(() => Array.from({length: maxYear - minYear + 1}, (v, i) => i + minYear).map((year) => ({ text: year.toString(), value: year, From 290b2d5e2d2ac245dc318e45a8532f0884d84c9e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:00:35 +0100 Subject: [PATCH 076/128] init callbacks for useState in BaseVideoPlayer --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 140d3f5eccc4..a77993e90f92 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -73,7 +73,7 @@ function BaseVideoPlayer({ const [isEnded, setIsEnded] = useState(false); const [isBuffering, setIsBuffering] = useState(true); // we add "#t=0.001" at the end of the URL to skip first milisecond of the video and always be able to show proper video preview when video is paused at the beginning - const [sourceURL] = useState(VideoUtils.addSkipTimeTagToURL(url.includes('blob:') || url.includes('file:///') ? url : addEncryptedAuthTokenToURL(url), 0.001)); + const [sourceURL] = useState(() => VideoUtils.addSkipTimeTagToURL(url.includes('blob:') || url.includes('file:///') ? url : addEncryptedAuthTokenToURL(url), 0.001)); const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); const [controlStatusState, setControlStatusState] = useState(controlsStatus); From a748336bfb9d265bc1fce408c884243980c3b14b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:00:42 +0100 Subject: [PATCH 077/128] init callbacks for useState in DebugDetailsDateTimePickerPage --- src/pages/Debug/DebugDetailsDateTimePickerPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Debug/DebugDetailsDateTimePickerPage.tsx b/src/pages/Debug/DebugDetailsDateTimePickerPage.tsx index 920b5b52076c..c68c1a93894b 100644 --- a/src/pages/Debug/DebugDetailsDateTimePickerPage.tsx +++ b/src/pages/Debug/DebugDetailsDateTimePickerPage.tsx @@ -26,7 +26,7 @@ function DebugDetailsDateTimePickerPage({ }: DebugDetailsDateTimePickerPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [date, setDate] = useState(DateUtils.extractDate(fieldValue)); + const [date, setDate] = useState(() => DateUtils.extractDate(fieldValue)); return ( From aa453ca010fba27a24437ac5b6d66cb5dac5d1ca Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:00:50 +0100 Subject: [PATCH 078/128] init callbacks for useState in DebugReportActionCreatePage --- src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx index c1ef0e0fdfec..18adfc311f58 100644 --- a/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx +++ b/src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx @@ -48,7 +48,7 @@ function DebugReportActionCreatePage({ const styles = useThemeStyles(); const [session] = useOnyx(ONYXKEYS.SESSION); const [personalDetailsList] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [draftReportAction, setDraftReportAction] = useState(getInitialReportAction(reportID, session, personalDetailsList)); + const [draftReportAction, setDraftReportAction] = useState(() => getInitialReportAction(reportID, session, personalDetailsList)); const [error, setError] = useState(); return ( From 489d605fe08eca846a5c68f9720ee8c3c99bdef2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:00:56 +0100 Subject: [PATCH 079/128] init callbacks for useState in DebugTransactionViolationCreatePage --- .../DebugTransactionViolationCreatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 4e36ab8fcd12..be44d1983885 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -63,7 +63,7 @@ function DebugTransactionViolationCreatePage({ const {translate} = useLocalize(); const styles = useThemeStyles(); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); - const [draftTransactionViolation, setDraftTransactionViolation] = useState(getInitialTransactionViolation()); + const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); const editJSON = useCallback( From 395c4b4a43576583aca0e644c53b45517829ac9b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:04 +0100 Subject: [PATCH 080/128] init callbacks for useState in ReimbursementAccountPage --- src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx index dd8a9b767eb1..b23fef35d0f5 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx @@ -204,7 +204,7 @@ function ReimbursementAccountPage({route, policy}: ReimbursementAccountPageProps which acts similarly to `componentDidUpdate` when the `reimbursementAccount` dependency changes. */ const [hasACHDataBeenLoaded, setHasACHDataBeenLoaded] = useState(reimbursementAccount !== CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA && isPreviousPolicy); - const [shouldShowContinueSetupButton, setShouldShowContinueSetupButton] = useState(getShouldShowContinueSetupButtonInitialValue()); + const [shouldShowContinueSetupButton, setShouldShowContinueSetupButton] = useState(() => getShouldShowContinueSetupButtonInitialValue()); const handleNextNonUSDBankAccountStep = () => { switch (nonUSDBankAccountStep) { From 154f91e17853828f753fb6d0689bf0a4e72df821 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:12 +0100 Subject: [PATCH 081/128] init callbacks for useState in ReportActionsList --- src/pages/home/report/ReportActionsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d562669a6bac..169dae7bce86 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -162,7 +162,7 @@ function ReportActionsList({ const reportScrollManager = useReportScrollManager(); const userActiveSince = useRef(DateUtils.getDBTime()); const lastMessageTime = useRef(null); - const [isVisible, setIsVisible] = useState(Visibility.isVisible()); + const [isVisible, setIsVisible] = useState(() => Visibility.isVisible()); const isFocused = useIsFocused(); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); From f38efcdf0e6ff0ff596a443cc4cf5944800d2494 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:18 +0100 Subject: [PATCH 082/128] init callbacks for useState in IOURequestStepAttendees --- src/pages/iou/request/step/IOURequestStepAttendees.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index f1d253db0c18..409ef1cfe02d 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -40,7 +40,7 @@ function IOURequestStepAttendees({ }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || -1}`); - const [attendees, setAttendees] = useState(TransactionUtils.getAttendees(transaction)); + const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); From c1605b49a8c2c980867a3377298b1148d86946a1 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:24 +0100 Subject: [PATCH 083/128] init callbacks for useState in StatusClearAfterPage --- .../settings/Profile/CustomStatus/StatusClearAfterPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx index 2d3538b8fbc5..b46c2ab36162 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx @@ -87,7 +87,7 @@ function StatusClearAfterPage({customStatus}: StatusClearAfterPageProps) { const clearAfter = currentUserPersonalDetails.status?.clearAfter ?? ''; const draftClearAfter = customStatus?.clearAfter ?? ''; - const [draftPeriod, setDraftPeriod] = useState(getSelectedStatusType(draftClearAfter || clearAfter)); + const [draftPeriod, setDraftPeriod] = useState(() => getSelectedStatusType(draftClearAfter || clearAfter)); const statusType = useMemo( () => Object.entries(CONST.CUSTOM_STATUS_TYPES).map(([key, value]) => ({ From e895b54dc7e9bbe152f392336a629ce443d7afe8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:30 +0100 Subject: [PATCH 084/128] init callbacks for useState in CardSection --- src/pages/settings/Subscription/CardSection/CardSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index bf0fdcbb6da3..153c5a65b3b4 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -65,7 +65,7 @@ function CardSection() { Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); }, []); - const [billingStatus, setBillingStatus] = useState(CardSectionUtils.getBillingStatus(translate, defaultCard?.accountData ?? {})); + const [billingStatus, setBillingStatus] = useState(() => CardSectionUtils.getBillingStatus(translate, defaultCard?.accountData ?? {})); const nextPaymentDate = !isEmptyObject(privateSubscription) ? CardSectionUtils.getNextBillingDate() : undefined; From 22c995596caea92a700595e10ede4090d7db271d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 29 Nov 2024 17:01:35 +0100 Subject: [PATCH 085/128] init callbacks for useState in TransactionStartDateStep --- .../companyCards/assignCard/TransactionStartDateStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx index cd630d83d36c..737e25810346 100644 --- a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx @@ -29,7 +29,7 @@ function TransactionStartDateStep() { const [dateOptionSelected, setDateOptionSelected] = useState(data?.dateOption ?? CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING); const [isModalOpened, setIsModalOpened] = useState(false); - const [startDate, setStartDate] = useState(format(new Date(), CONST.DATE.FNS_FORMAT_STRING)); + const [startDate, setStartDate] = useState(() => format(new Date(), CONST.DATE.FNS_FORMAT_STRING)); const handleBackButtonPress = () => { if (isEditing) { From d80be2ac924a7d2d314dd0a3d5fee31ffc180839 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Sat, 30 Nov 2024 00:07:14 +0300 Subject: [PATCH 086/128] added comment --- src/components/Composer/implementation/index.native.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 3a84e01d70c5..96527b105853 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -62,8 +62,9 @@ function Composer( return; } - // We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size. + // We need the delay for setSelection to properly work for IOS. const timeoutID = setTimeout(() => { + // We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size. textInput.current?.setSelection((selection.start || 1) - 1, selection.start); textInput.current?.setSelection(selection.start, selection.start); }, 0); From dc8a2db51b679c71134de901b34e257e2cfe266f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 30 Nov 2024 15:14:55 +0800 Subject: [PATCH 087/128] update the focused index whilet the popover is hidden if the selected item is changed --- src/components/PopoverMenu.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 9b5c0b1b6f56..4c4f55e7d825 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ import lodashIsEqual from 'lodash/isEqual'; import type {ReactNode, RefObject} from 'react'; -import React, {useLayoutEffect, useState} from 'react'; +import React, {useEffect, useLayoutEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; @@ -314,6 +314,13 @@ function PopoverMenu({ setCurrentMenuItems(menuItems); }, [menuItems]); + useEffect(() => { + if (isVisible) { + return; + } + setFocusedIndex(currentMenuItemsFocusedIndex); + }, [isVisible, currentMenuItemsFocusedIndex]); + return ( Date: Sat, 30 Nov 2024 15:15:26 +0800 Subject: [PATCH 088/128] reset the focused index to the selected item if any when the popover hides --- src/components/PopoverMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 4c4f55e7d825..0876cbcf4851 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -300,7 +300,7 @@ function PopoverMenu({ ); const onModalHide = () => { - setFocusedIndex(-1); + setFocusedIndex(currentMenuItemsFocusedIndex); }; // When the menu items are changed, we want to reset the sub-menu to make sure From 5bad35f0fbed416f8d23f0cabd04a02d93fce1c5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 30 Nov 2024 15:25:17 +0800 Subject: [PATCH 089/128] lint --- src/components/PopoverMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 0876cbcf4851..f2871fb72fb8 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -319,7 +319,7 @@ function PopoverMenu({ return; } setFocusedIndex(currentMenuItemsFocusedIndex); - }, [isVisible, currentMenuItemsFocusedIndex]); + }, [isVisible, currentMenuItemsFocusedIndex, setFocusedIndex]); return ( Date: Sun, 1 Dec 2024 16:26:01 +0800 Subject: [PATCH 090/128] add comment --- src/components/PopoverMenu.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index f2871fb72fb8..24f49a5be8e9 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -318,6 +318,10 @@ function PopoverMenu({ if (isVisible) { return; } + + // Update the focused item to match the selected item, but only when the popover is not visible. + // This ensures that if the popover is visible, highlight from the keyboard navigation is not overridden + // by external updates to the selected item, preventing user experience disruption. setFocusedIndex(currentMenuItemsFocusedIndex); }, [isVisible, currentMenuItemsFocusedIndex, setFocusedIndex]); From 95803faa60a117fc0187b45fbac8138221dc9fd1 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 1 Dec 2024 22:10:38 +0800 Subject: [PATCH 091/128] update comment --- src/components/PopoverMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 24f49a5be8e9..c628a00a89a1 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -321,7 +321,7 @@ function PopoverMenu({ // Update the focused item to match the selected item, but only when the popover is not visible. // This ensures that if the popover is visible, highlight from the keyboard navigation is not overridden - // by external updates to the selected item, preventing user experience disruption. + // by external updates. setFocusedIndex(currentMenuItemsFocusedIndex); }, [isVisible, currentMenuItemsFocusedIndex, setFocusedIndex]); From 85bd2421a8934aa673159183311966ae074bf2e1 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 2 Dec 2024 11:53:05 +0700 Subject: [PATCH 092/128] fix: enable paste html in compose box --- src/components/TextInput/BaseTextInput/index.native.tsx | 2 ++ src/components/TextInput/BaseTextInput/index.tsx | 6 ++++-- src/hooks/useHtmlPaste/index.ts | 7 +++++-- src/hooks/useHtmlPaste/types.ts | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 9de6b6dd6d08..f6b0ef1e0157 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -16,6 +16,7 @@ import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; import TextInputClearButton from '@components/TextInput/TextInputClearButton'; import TextInputLabel from '@components/TextInput/TextInputLabel'; +import useHtmlPaste from '@hooks/useHtmlPaste'; import useLocalize from '@hooks/useLocalize'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -98,6 +99,7 @@ function BaseTextInput( const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); + useHtmlPaste(input, undefined, false, isMarkdownEnabled); // AutoFocus which only works on mount: useEffect(() => { diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index e36ae60255fc..b1adf21d2ca8 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -1,7 +1,7 @@ import {Str} from 'expensify-common'; -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, MutableRefObject} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import type {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TextInputFocusEventData, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TextInput, TextInputFocusEventData, ViewStyle} from 'react-native'; import {ActivityIndicator, Animated, StyleSheet, View} from 'react-native'; import Checkbox from '@components/Checkbox'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -17,6 +17,7 @@ import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; import TextInputClearButton from '@components/TextInput/TextInputClearButton'; import TextInputLabel from '@components/TextInput/TextInputLabel'; +import useHtmlPaste from '@hooks/useHtmlPaste'; import useLocalize from '@hooks/useLocalize'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -103,6 +104,7 @@ function BaseTextInput( const labelTranslateY = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_TRANSLATE_Y : styleConst.INACTIVE_LABEL_TRANSLATE_Y)).current; const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); + useHtmlPaste(input as MutableRefObject, undefined, false, isMarkdownEnabled); // AutoFocus which only works on mount: useEffect(() => { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 6199a36abdca..af30cc628158 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -27,7 +27,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { } }; -const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { +const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, isMarkdownEnabled = true) => { const navigation = useNavigation(); /** @@ -129,6 +129,9 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi ); useEffect(() => { + if (!isMarkdownEnabled) { + return; + } // we need to re-register listener on navigation focus/blur if the component (like Composer) is not unmounting // when navigating away to different screen (report) to avoid paste event on other screen being wrongly handled // by current screen paste listener @@ -149,7 +152,7 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi document.removeEventListener('paste', handlePaste, true); }; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); + }, [isMarkdownEnabled]); }; export default useHtmlPaste; diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 305ebe5fbd0f..6518ccd8ef3e 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -5,6 +5,7 @@ type UseHtmlPaste = ( textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, removeListenerOnScreenBlur?: boolean, + isMarkdownEnabled?: boolean, ) => void; export default UseHtmlPaste; From 5252494ab421a1d51367591334c2b68cb6b8047c Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 2 Dec 2024 13:00:43 +0700 Subject: [PATCH 093/128] fix: lint --- src/hooks/useHtmlPaste/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index af30cc628158..791c81c6ba98 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -4,6 +4,7 @@ import Parser from '@libs/Parser'; import type UseHtmlPaste from './types'; const insertByCommand = (text: string) => { + // eslint-disable-next-line deprecation/deprecation document.execCommand('insertText', false, text); }; From 021d78410cffe51bb367e0fa78243547e8105c2c Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 2 Dec 2024 18:48:10 +0700 Subject: [PATCH 094/128] fix: get manager ID from approval chain --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 6710823df16a..97bd96c61212 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7191,7 +7191,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport?.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID, adminAccountID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, isSubmitAndClosePolicy ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.SUBMITTED); const approvalChain = ReportUtils.getApprovalChain(PolicyUtils.getPolicy(expenseReport?.policyID), expenseReport); - const managerID = approvalChain.length ? Number(approvalChain.at(0)) : undefined; + const managerID = PersonalDetailsUtils.getAccountIDsByLogins(approvalChain).at(0); const optimisticData: OnyxUpdate[] = !isSubmitAndClosePolicy ? [ From 97f676ffd8def3585a5a48aa92dbd6e6df0db887 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 2 Dec 2024 13:19:49 +0100 Subject: [PATCH 095/128] refactor StatusClearAfterPage for useOnyx --- .../CustomStatus/StatusClearAfterPage.tsx | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx index c1cd4669e5eb..55c0100a84db 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx @@ -1,7 +1,6 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import FormProvider from '@components/Form/FormProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -19,7 +18,6 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type * as OnyxTypes from '@src/types/onyx'; type CustomStatusTypes = ValueOf; @@ -30,13 +28,6 @@ type StatusType = { isSelected: boolean; }; -type StatusClearAfterPageOnyxProps = { - /** User's custom status */ - customStatus: OnyxEntry; -}; - -type StatusClearAfterPageProps = StatusClearAfterPageOnyxProps; - /** * @param data - either a value from CONST.CUSTOM_STATUS_TYPES or a dateTime string in the format YYYY-MM-DD HH:mm */ @@ -80,11 +71,12 @@ const useValidateCustomDate = (data: string) => { return {customDateError, customTimeError, validateCustomDate}; }; -function StatusClearAfterPage({customStatus}: StatusClearAfterPageProps) { +function StatusClearAfterPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const clearAfter = currentUserPersonalDetails.status?.clearAfter ?? ''; + const [customStatus] = useOnyx(ONYXKEYS.CUSTOM_STATUS_DRAFT); const draftClearAfter = customStatus?.clearAfter ?? ''; const [draftPeriod, setDraftPeriod] = useState(() => getSelectedStatusType(draftClearAfter || clearAfter)); @@ -222,8 +214,4 @@ function StatusClearAfterPage({customStatus}: StatusClearAfterPageProps) { StatusClearAfterPage.displayName = 'StatusClearAfterPage'; -export default withOnyx({ - customStatus: { - key: ONYXKEYS.CUSTOM_STATUS_DRAFT, - }, -})(StatusClearAfterPage); +export default StatusClearAfterPage; From 7d0f8a349fc1ed61e7b2b32444503e7205d64eab Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 2 Dec 2024 16:20:18 +0100 Subject: [PATCH 096/128] simplify ReportActionsList --- src/pages/home/report/ReportActionsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 6a718b76b6bd..688d0d675cd6 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -162,7 +162,7 @@ function ReportActionsList({ const reportScrollManager = useReportScrollManager(); const userActiveSince = useRef(DateUtils.getDBTime()); const lastMessageTime = useRef(null); - const [isVisible, setIsVisible] = useState(() => Visibility.isVisible()); + const [isVisible, setIsVisible] = useState(Visibility.isVisible); const isFocused = useIsFocused(); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); From 8b4892784665e7276a6acaa5a09c33f03f7115b4 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 3 Dec 2024 02:43:06 +0530 Subject: [PATCH 097/128] fix horizontal position of popover --- src/pages/settings/Security/SecuritySettingsPage.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 2115b333d1a5..a93a9f154276 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -78,12 +78,11 @@ function SecuritySettingsPage() { } const position = getClickedTargetLocation(delegateButtonRef.current); - setAnchorPosition({ - horizontal: windowWidth - position.x, + horizontal: position.right - position.left, vertical: position.y + position.height, }); - }, [windowWidth, delegateButtonRef]); + }, [delegateButtonRef]); const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, delegate: Delegate) => { delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; From 2bdc4a3b9d87ba7dcabf323ae7371d0de6400972 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 3 Dec 2024 12:49:47 +0700 Subject: [PATCH 098/128] refactor getMoneyRequestInformation function --- src/libs/actions/IOU.ts | 133 +++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 77 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 35d72d1b005c..561d86f029a4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -166,7 +166,7 @@ type GPSPoint = { }; type RequestMoneyTransactionParams = { - attendees: Attendee[] | undefined; + attendees?: Attendee[]; amount: number; currency: string; comment?: string; @@ -205,6 +205,16 @@ type RequestMoneyInformation = { transactionParams: RequestMoneyTransactionParams; }; +type GetMoneyRequestInformation = { + parentChatReport: OnyxEntry; + transactionParams: RequestMoneyTransactionParams; + participantParams: RequestMoneyParticipantParams; + policyParams?: RequestMoneyPolicyParams; + moneyRequestReportID?: string; + existingTransactionID?: string; + existingTransaction?: OnyxEntry; +}; + let allPersonalDetails: OnyxTypes.PersonalDetailsList = {}; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, @@ -2043,31 +2053,12 @@ function getSendInvoiceInformation( * Gathers all the data needed to submit an expense. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then * it creates optimistic versions of them and uses those instead */ -function getMoneyRequestInformation( - parentChatReport: OnyxEntry, - participant: Participant, - comment: string, - amount: number, - currency: string, - created: string, - merchant: string, - receipt: Receipt | undefined, - existingTransactionID: string | undefined, - category: string | undefined, - tag: string | undefined, - taxCode: string | undefined, - taxAmount: number | undefined, - billable: boolean | undefined, - policy: OnyxEntry | undefined, - policyTagList: OnyxEntry | undefined, - policyCategories: OnyxEntry | undefined, - payeeAccountID = userAccountID, - payeeEmail = currentUserEmail, - moneyRequestReportID = '', - linkedTrackedExpenseReportAction?: OnyxTypes.ReportAction, - attendees?: Attendee[], - existingTransaction: OnyxEntry | undefined = undefined, -): MoneyRequestInformation { +function getMoneyRequestInformation(moneyRequestInformation: GetMoneyRequestInformation): MoneyRequestInformation { + const {parentChatReport, transactionParams, participantParams, policyParams = {}, existingTransaction, existingTransactionID, moneyRequestReportID = ''} = moneyRequestInformation; + const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; + const {policy, policyCategories, policyTagList} = policyParams; + const {attendees, amount, comment, currency, created, merchant, receipt, category, tag, taxCode, taxAmount, billable, linkedTrackedExpenseReportAction} = transactionParams; + const payerEmail = PhoneNumber.addSMSDomainIfPhoneNumber(participant.login ?? ''); const payerAccountID = Number(participant.accountID); const isPolicyExpenseChat = participant.isPolicyExpenseChat; @@ -3597,8 +3588,7 @@ function shareTrackedExpense( */ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { const {report, participantParams, policyParams = {}, transactionParams, gpsPoints, action, reimbursible} = requestMoneyInformation; - const {participant, payeeAccountID, payeeEmail} = participantParams; - const {policy, policyCategories, policyTagList} = policyParams; + const {payeeAccountID} = participantParams; const { amount, currency, @@ -3636,32 +3626,17 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { transactionThreadReportID, createdReportActionIDForThread, onyxData, - } = getMoneyRequestInformation( - isMovingTransactionFromTrackExpense ? undefined : currentChatReport, - participant, - comment, - amount, - currency, - created, - merchant, - receipt, - isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && ReportActionsUtils.isMoneyRequestAction(linkedTrackedExpenseReportAction) - ? ReportActionsUtils.getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID - : undefined, - category, - tag, - taxCode, - taxAmount, - billable, - policy, - policyTagList, - policyCategories, - payeeAccountID, - payeeEmail, + } = getMoneyRequestInformation({ + parentChatReport: isMovingTransactionFromTrackExpense ? undefined : currentChatReport, + participantParams, + policyParams, + transactionParams, moneyRequestReportID, - linkedTrackedExpenseReportAction, - attendees, - ); + existingTransactionID: + isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && ReportActionsUtils.isMoneyRequestAction(linkedTrackedExpenseReportAction) + ? ReportActionsUtils.getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID + : undefined, + }); const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport.reportID; switch (action) { @@ -5318,31 +5293,35 @@ function createDistanceRequest( createdReportActionIDForThread, payerEmail, onyxData: moneyRequestOnyxData, - } = getMoneyRequestInformation( - currentChatReport, - participant, - comment, - amount, - currency, - created, - merchant, - optimisticReceipt, - undefined, - category, - tag, - taxCode, - taxAmount, - billable, - policy, - policyTagList, - policyCategories, - userAccountID, - currentUserEmail, - moneyRequestReportID, - undefined, - undefined, + } = getMoneyRequestInformation({ + parentChatReport: currentChatReport, existingTransaction, - ); + moneyRequestReportID, + participantParams: { + participant, + payeeAccountID: userAccountID, + payeeEmail: currentUserEmail, + }, + policyParams: { + policy, + policyCategories, + policyTagList, + }, + transactionParams: { + amount, + currency, + comment, + created, + merchant, + receipt: optimisticReceipt, + category, + tag, + taxCode, + taxAmount, + billable, + }, + }); + onyxData = moneyRequestOnyxData; parameters = { From e2fa2debed670f3c05ba3c8dc4b80d1f46978d51 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 3 Dec 2024 13:33:13 +0700 Subject: [PATCH 099/128] fix typecheck error --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 561d86f029a4..e4d7f29e4f82 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2057,7 +2057,7 @@ function getMoneyRequestInformation(moneyRequestInformation: GetMoneyRequestInfo const {parentChatReport, transactionParams, participantParams, policyParams = {}, existingTransaction, existingTransactionID, moneyRequestReportID = ''} = moneyRequestInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; const {policy, policyCategories, policyTagList} = policyParams; - const {attendees, amount, comment, currency, created, merchant, receipt, category, tag, taxCode, taxAmount, billable, linkedTrackedExpenseReportAction} = transactionParams; + const {attendees, amount, comment = '', currency, created, merchant, receipt, category, tag, taxCode, taxAmount, billable, linkedTrackedExpenseReportAction} = transactionParams; const payerEmail = PhoneNumber.addSMSDomainIfPhoneNumber(participant.login ?? ''); const payerAccountID = Number(participant.accountID); From d81e8b67cad5c968d7bd91e6c57d68882ff09441 Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 3 Dec 2024 15:03:01 +0700 Subject: [PATCH 100/128] fix: prevent blink when opening violation debug tab --- src/libs/Navigation/OnyxTabNavigator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index 0e7dfd4a0a0b..c9b43498734a 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -124,7 +124,7 @@ function OnyxTabNavigator({ const index = state.index; const routeNames = state.routeNames; const newSelectedTab = routeNames.at(index); - if (selectedTab === newSelectedTab) { + if (selectedTab === newSelectedTab || (selectedTab && !routeNames.includes(selectedTab))) { return; } Tab.setSelectedTab(id, newSelectedTab as SelectedTabRequest); From 1f14d11ea9ce00a11c9dc6a30dcc46706a8e8bdc Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 3 Dec 2024 16:30:13 +0700 Subject: [PATCH 101/128] rename type --- src/libs/actions/IOU.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e4d7f29e4f82..19648f89222d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -205,7 +205,7 @@ type RequestMoneyInformation = { transactionParams: RequestMoneyTransactionParams; }; -type GetMoneyRequestInformation = { +type MoneyRequestInformationParams = { parentChatReport: OnyxEntry; transactionParams: RequestMoneyTransactionParams; participantParams: RequestMoneyParticipantParams; @@ -2053,7 +2053,7 @@ function getSendInvoiceInformation( * Gathers all the data needed to submit an expense. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then * it creates optimistic versions of them and uses those instead */ -function getMoneyRequestInformation(moneyRequestInformation: GetMoneyRequestInformation): MoneyRequestInformation { +function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInformationParams): MoneyRequestInformation { const {parentChatReport, transactionParams, participantParams, policyParams = {}, existingTransaction, existingTransactionID, moneyRequestReportID = ''} = moneyRequestInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; const {policy, policyCategories, policyTagList} = policyParams; From 1c3a1c053197ba9881688e68d004ca25e2737b93 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 3 Dec 2024 13:58:44 +0100 Subject: [PATCH 102/128] Make autocomplete accountIDs work when pasting user emails --- .../Search/SearchPageHeaderInput.tsx | 24 ++------ .../Search/SearchRouter/SearchRouter.tsx | 13 ++--- src/libs/SearchQueryUtils.ts | 58 ++++++++++++++----- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/components/Search/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeaderInput.tsx index 85ca37b7a79a..dfc3d586fa83 100644 --- a/src/components/Search/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeaderInput.tsx @@ -13,7 +13,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@libs/actions/Search'; -import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; @@ -112,28 +111,15 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps const submitSearch = useCallback( (queryString: SearchQueryString) => { - if (!queryString) { + const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); + const updatedQuery = SearchQueryUtils.getQueryWithUpdatedValues(queryWithSubstitutions, queryJSON.policyID); + if (!updatedQuery) { return; } - const cleanedQueryString = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); - const userQueryJSON = SearchQueryUtils.buildSearchQueryJSON(cleanedQueryString); + Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: updatedQuery})); - if (!userQueryJSON) { - Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} user query failed to parse`, {}, false); - return; - } - - if (queryJSON.policyID) { - userQueryJSON.policyID = queryJSON.policyID; - } - - const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(userQueryJSON, SearchQueryUtils.getUpdatedAmountValue); - const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); - - Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); - - if (query !== originalInputQuery) { + if (updatedQuery !== originalInputQuery) { SearchActions.clearAllFilters(); setTextInputValue(''); setAutocompleteQueryValue(''); diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 14e84aaa80e5..e26b81d0222e 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -250,17 +250,14 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) const submitSearch = useCallback( (queryString: SearchQueryString) => { - const cleanedQueryString = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); - const queryJSON = SearchQueryUtils.buildSearchQueryJSON(cleanedQueryString); - if (!queryJSON) { + const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); + const updatedQuery = SearchQueryUtils.getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID); + if (!updatedQuery) { return; } - queryJSON.policyID = activeWorkspaceID; - onRouterClose(); - const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(queryJSON, SearchQueryUtils.getUpdatedAmountValue); - const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); - Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); + onRouterClose(); + Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: updatedQuery})); setTextInputValue(''); setAutocompleteQueryValue(''); diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index f975c575400d..e8f3d29a8a05 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -11,7 +11,9 @@ import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import * as CardUtils from './CardUtils'; import * as CurrencyUtils from './CurrencyUtils'; import localeCompare from './LocaleCompare'; +import Log from './Log'; import {validateAmount} from './MoneyRequestUtils'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import {getTagNamesFromTagsLists} from './PolicyUtils'; import * as ReportUtils from './ReportUtils'; import * as searchParser from './SearchParser/searchParser'; @@ -163,21 +165,32 @@ function getFilters(queryJSON: SearchQueryJSON) { } /** - * Returns an updated amount value for query filters, correctly formatted to "backend" amount + * @private + * Returns an updated filter value for some query filters. + * - for `AMOUNT` it formats value to "backend" amount + * - for personal filters it tries to substitute any user emails with accountIDs */ -function getUpdatedAmountValue(filterName: ValueOf, filter: string | string[]) { - if (filterName !== CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { - return filter; +function getUpdatedFilterValue(filterName: ValueOf, filterValue: string | string[]) { + if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO) { + if (typeof filterValue === 'string') { + return PersonalDetailsUtils.getPersonalDetailByEmail(filterValue)?.accountID.toString() ?? filterValue; + } + + return filterValue.map((email) => PersonalDetailsUtils.getPersonalDetailByEmail(email)?.accountID.toString() ?? email); } - if (typeof filter === 'string') { - const backendAmount = CurrencyUtils.convertToBackendAmount(Number(filter)); - return Number.isNaN(backendAmount) ? filter : backendAmount.toString(); + if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { + if (typeof filterValue === 'string') { + const backendAmount = CurrencyUtils.convertToBackendAmount(Number(filterValue)); + return Number.isNaN(backendAmount) ? filterValue : backendAmount.toString(); + } + return filterValue.map((amount) => { + const backendAmount = CurrencyUtils.convertToBackendAmount(Number(amount)); + return Number.isNaN(backendAmount) ? amount : backendAmount.toString(); + }); } - return filter.map((amount) => { - const backendAmount = CurrencyUtils.convertToBackendAmount(Number(amount)); - return Number.isNaN(backendAmount) ? amount : backendAmount.toString(); - }); + + return filterValue; } /** @@ -625,6 +638,26 @@ function traverseAndUpdatedQuery(queryJSON: SearchQueryJSON, computeNodeValue: ( return standardQuery; } +/** + * Returns new string query, after parsing it and traversing to update some filter values. + * If there are any personal emails, it will try to substitute them with accountIDs + */ +function getQueryWithUpdatedValues(query: string, policyID?: string) { + const queryJSON = buildSearchQueryJSON(query); + + if (!queryJSON) { + Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} user query failed to parse`, {}, false); + return; + } + + if (policyID) { + queryJSON.policyID = policyID; + } + + const standardizedQuery = traverseAndUpdatedQuery(queryJSON, getUpdatedFilterValue); + return buildSearchQueryString(standardizedQuery); +} + export { buildSearchQueryJSON, buildSearchQueryString, @@ -635,7 +668,6 @@ export { getPolicyIDFromSearchQuery, buildCannedSearchQuery, isCannedSearchQuery, - traverseAndUpdatedQuery, - getUpdatedAmountValue, sanitizeSearchValue, + getQueryWithUpdatedValues, }; From f5cd07f64cf3b01bf7192c57fed9380d69c297ce Mon Sep 17 00:00:00 2001 From: Monil Bhavsar Date: Tue, 3 Dec 2024 18:55:49 +0530 Subject: [PATCH 103/128] Remove conditional code and move logic to server --- src/hooks/useViolations.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 5eb77f2d45e7..cc541eff1487 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -49,9 +49,6 @@ const violationFields: Record = { type ViolationsMap = Map; -// We don't want to show these violations on NewDot -const excludedViolationsName = ['taxAmountChanged', 'taxRateChanged']; - /** * @param violations – List of transaction violations * @param shouldShowOnlyViolations – Whether we should only show violations of type 'violation' @@ -59,9 +56,6 @@ const excludedViolationsName = ['taxAmountChanged', 'taxRateChanged']; function useViolations(violations: TransactionViolation[], shouldShowOnlyViolations: boolean) { const violationsByField = useMemo((): ViolationsMap => { const filteredViolations = violations.filter((violation) => { - if (excludedViolationsName.includes(violation.name)) { - return false; - } if (shouldShowOnlyViolations) { return violation.type === CONST.VIOLATION_TYPES.VIOLATION; } From cc0c6981762c21766cda8ee4ba2f10f4f1a2c206 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:52:43 -0600 Subject: [PATCH 104/128] Update docs/redirects.csv Removing duplicate Co-authored-by: Daniel Gale-Rosen <5487802+dangrous@users.noreply.github.com> --- docs/redirects.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/redirects.csv b/docs/redirects.csv index 00ffc08f2678..37cbaf0245a2 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -600,7 +600,6 @@ https://help.expensify.com/articles/expensify-classic/expenses/Track-per-diem-ex https://community.expensify.com/discussion/5116/faq-where-can-i-use-the-expensify-card,https://help.expensify.com/articles/new-expensify/expensify-card/Use-your-Expensify-Card#where-can-i-use-my-expensify-card https://help.expensify.com/articles/other/Expensify-Lounge,https://help.expensify.com/Hidden/Expensify-Lounge https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-expenses -https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Approve-expenses https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/spending-insights/Default-Export-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ From ab1d89ef9cc0cf4c4b0fc0a4d623b90ea67fa6cf Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Dec 2024 13:01:41 -0700 Subject: [PATCH 105/128] add comment --- src/libs/actions/Search.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index bb64fe10db26..39d38db637e2 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -281,6 +281,7 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans const optimisticData: OnyxUpdate[] = createActionLoadingData(true); const finallyData: OnyxUpdate[] = createActionLoadingData(false); + // PayMoneyRequestOnSearch only works online (pattern C) and we need to play the success sound only when the request is successful // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH, {hash, paymentData: JSON.stringify(paymentData)}, {optimisticData, finallyData}).then( (response) => { From 79be512e394291c1288ae64c7c60570e36e8ac5b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Dec 2024 13:02:03 -0700 Subject: [PATCH 106/128] move comment --- src/libs/API/types.ts | 2 ++ src/libs/actions/Search.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index d5d24eb205bf..892bad17928e 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1027,6 +1027,8 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate', COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding', CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP: 'ConnectPolicyToQuickbooksDesktop', + + // PayMoneyRequestOnSearch only works online (pattern C) and we need to play the success sound only when the request is successful PAY_MONEY_REQUEST_ON_SEARCH: 'PayMoneyRequestOnSearch', } as const; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 39d38db637e2..bb64fe10db26 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -281,7 +281,6 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans const optimisticData: OnyxUpdate[] = createActionLoadingData(true); const finallyData: OnyxUpdate[] = createActionLoadingData(false); - // PayMoneyRequestOnSearch only works online (pattern C) and we need to play the success sound only when the request is successful // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH, {hash, paymentData: JSON.stringify(paymentData)}, {optimisticData, finallyData}).then( (response) => { From 2ad0ddff8573a40039e6bfadeac63a88b2fe31d7 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader <56457735+ikevin127@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:30:51 -0800 Subject: [PATCH 107/128] updated postcode input label Updated postcode input label to match the label found in the form on Settings > Profile > Private > Address , as requested in https://github.com/Expensify/App/issues/52642#issuecomment-2515725750. --- src/components/AddPaymentCard/PaymentCardForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index cee7edb3af61..9843996602f1 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -279,8 +279,8 @@ function PaymentCardForm({ InputComponent={TextInput} defaultValue={data?.addressZipCode} inputID={INPUT_IDS.ADDRESS_ZIP_CODE} - label={translate('common.zip')} - aria-label={translate('common.zip')} + label={translate('common.zipPostCode')} + aria-label={translate('common.zipPostCode')} role={CONST.ROLE.PRESENTATION} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} containerStyles={[styles.mt5]} From 917676cd7f28a0bfd5e67b75b53e8b666b75ca17 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Wed, 4 Dec 2024 10:11:28 +0100 Subject: [PATCH 108/128] Correct paths for upload --- .github/workflows/testBuildHybrid.yml | 60 +++++---------------------- 1 file changed, 11 insertions(+), 49 deletions(-) diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 10131683df92..cfc33070dfa9 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -86,7 +86,7 @@ jobs: run: working-directory: Mobile-Expensify/react-native outputs: - APK_FILE_NAME: ${{ steps.build.outputs.APK_FILE_NAME }} + S3_APK_PATH: ${{ steps.exportAndroidS3Path.outputs.S3_APK_PATH }} steps: - name: Checkout uses: actions/checkout@v4 @@ -139,9 +139,6 @@ jobs: bundler-cache: true working-directory: 'Mobile-Expensify/react-native' - - name: Install New Expensify Gems - run: bundle install - - name: Install 1Password CLI uses: 1password/install-cli-action@v1 @@ -175,60 +172,25 @@ jobs: ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} ANDROID_UPLOAD_KEYSTORE_ALIAS: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} ANDROID_UPLOAD_KEY_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} - run: | - bundle exec fastlane android build_adhoc_hybrid - - # Refresh environment variables from GITHUB_ENV that are updated when running fastlane - # shellcheck disable=SC1090 - source "$GITHUB_ENV" - - # apkPath is set within the Fastfile - echo "APK_FILE_NAME=$(basename "$apkPath")" >> "$GITHUB_OUTPUT" - - - uploadAndroid: - name: Upload Android hybrid app to S3 - needs: [androidHybrid] - runs-on: ubuntu-latest - outputs: - S3_APK_PATH: ${{ steps.exportS3Path.outputs.S3_APK_PATH }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1.190.0 - with: - bundler-cache: true - - - name: Download Android build artifacts - uses: actions/download-artifact@v4 - with: - path: /tmp/artifacts - pattern: android-*-artifact - merge-multiple: true - - - name: Log downloaded artifact paths - run: ls -R /tmp/artifacts - + run: bundle exec fastlane android build_adhoc_hybrid + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - - - name: Upload AdHoc build to S3 + + - name: Upload Android AdHoc build to S3 run: bundle exec fastlane android upload_s3 env: - apkPath: /tmp/artifacts/${{ needs.androidHybrid.outputs.APK_FILE_NAME }} S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ad-hoc-expensify-cash - S3_REGION: us-east-1 + S3_REGION: us-east-1 - - name: Export S3 paths - id: exportS3Path + - name: Export S3 path + id: exportAndroidS3Path run: | # $s3APKPath is set from within the Fastfile, android upload_s3 lane echo "S3_APK_PATH=$s3APKPath" >> "$GITHUB_OUTPUT" @@ -236,7 +198,7 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, uploadAndroid] #TODO add ios job + needs: [validateActor, getBranchRef, androidHybrid] #TODO add ios job if: ${{ always() }} steps: - name: Checkout @@ -255,5 +217,5 @@ jobs: with: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} GITHUB_TOKEN: ${{ github.token }} - ANDROID: ${{ needs.uploadAndroid.result }} - ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} \ No newline at end of file + ANDROID: ${{ needs.androidHybrid.result }} + ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }} \ No newline at end of file From 0537b119b6afadaf30990582228db66cbe36688f Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 4 Dec 2024 16:27:02 +0700 Subject: [PATCH 109/128] fix: remove duplicate code --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 97bd96c61212..a627f0f53d32 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7190,7 +7190,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { const adminAccountID = policy?.role === CONST.POLICY.ROLE.ADMIN ? currentUserPersonalDetails?.accountID : undefined; const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport?.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID, adminAccountID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, isSubmitAndClosePolicy ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.SUBMITTED); - const approvalChain = ReportUtils.getApprovalChain(PolicyUtils.getPolicy(expenseReport?.policyID), expenseReport); + const approvalChain = ReportUtils.getApprovalChain(policy, expenseReport); const managerID = PersonalDetailsUtils.getAccountIDsByLogins(approvalChain).at(0); const optimisticData: OnyxUpdate[] = !isSubmitAndClosePolicy From 9f4263dc6f8016f980013fdfd12fe24259addd31 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 4 Dec 2024 10:55:07 +0100 Subject: [PATCH 110/128] Fix feed default name display in the selector --- .../companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx index 0f4968a43cfc..723242c55494 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx @@ -46,7 +46,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS const feeds: CardFeedListItem[] = (Object.keys(availableCards) as CompanyCardFeed[]).map((feed) => ({ value: feed, - text: cardFeeds?.settings?.companyCardNicknames?.[feed] ?? CardUtils.getCardFeedName(feed), + text: CardUtils.getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames), keyForList: feed, isSelected: feed === selectedFeed, brickRoadIndicator: companyFeeds[feed]?.errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, From bed227c229efeab4e435d9e1b00d802a9d29ec58 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 4 Dec 2024 12:23:21 +0100 Subject: [PATCH 111/128] Fix feed custom name display in the selector button --- .../companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index fb4067ab9c8d..0f8893c5bce2 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -42,8 +42,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; - const feedName = CardUtils.getCardFeedName(selectedFeed); - const formattedFeedName = translate('workspace.companyCards.feedName', {feedName}); + const formattedFeedName = CardUtils.getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames); const isCustomFeed = CardUtils.isCustomFeed(selectedFeed); const companyFeeds = CardUtils.getCompanyFeeds(cardFeeds); const currentFeedData = companyFeeds?.[selectedFeed]; @@ -58,7 +57,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed, shouldS Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID))} style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldChangeLayout && styles.mb3]} - accessibilityLabel={formattedFeedName} + accessibilityLabel={formattedFeedName ?? ''} > Date: Wed, 4 Dec 2024 12:46:02 +0100 Subject: [PATCH 112/128] Add tests for `getQueryWithUpdatedValues` fn --- src/libs/SearchQueryUtils.ts | 2 +- tests/unit/Search/SearchQueryUtilsTest.ts | 66 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/unit/Search/SearchQueryUtilsTest.ts diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index e8f3d29a8a05..0f1354414ee0 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -279,7 +279,7 @@ function buildSearchQueryString(queryJSON?: SearchQueryJSON) { for (const filter of filters) { const filterValueString = buildFilterValuesString(filter.key, filter.filters); - queryParts.push(filterValueString); + queryParts.push(filterValueString.trim()); } return queryParts.join(' '); diff --git a/tests/unit/Search/SearchQueryUtilsTest.ts b/tests/unit/Search/SearchQueryUtilsTest.ts new file mode 100644 index 000000000000..f847e44c3315 --- /dev/null +++ b/tests/unit/Search/SearchQueryUtilsTest.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +// we need "dirty" object key names in these tests +import {getQueryWithUpdatedValues} from '@src/libs/SearchQueryUtils'; + +const personalDetailsFakeData = { + 'johndoe@example.com': { + accountID: 12345, + }, + 'janedoe@example.com': { + accountID: 78901, + }, +} as Record; + +jest.mock('@libs/PersonalDetailsUtils', () => { + return { + getPersonalDetailByEmail(email: string) { + return personalDetailsFakeData[email]; + }, + }; +}); + +// The default query is generated by default values from parser, which are defined in grammar. +// We don't want to test or mock the grammar and the parser, so we're simply defining this string directly here. +const defaultQuery = `type:expense status:all sortBy:date sortOrder:desc`; + +describe('getQueryWithUpdatedValues', () => { + test('returns default query for empty value', () => { + const userQuery = ''; + + const result = getQueryWithUpdatedValues(userQuery); + + expect(result).toEqual(defaultQuery); + }); + + test('returns query with updated amounts', () => { + const userQuery = 'foo test amount:20000'; + + const result = getQueryWithUpdatedValues(userQuery); + + expect(result).toEqual(`${defaultQuery} amount:2000000 foo test`); + }); + + test('returns query with user emails substituted', () => { + const userQuery = 'from:johndoe@example.com hello'; + + const result = getQueryWithUpdatedValues(userQuery); + + expect(result).toEqual(`${defaultQuery} from:12345 hello`); + }); + + test('returns query with user emails substituted and preserves user ids', () => { + const userQuery = 'from:johndoe@example.com to:112233'; + + const result = getQueryWithUpdatedValues(userQuery); + + expect(result).toEqual(`${defaultQuery} from:12345 to:112233`); + }); + + test('returns query with all of the fields correctly substituted', () => { + const userQuery = 'from:9876,87654 to:janedoe@example.com hello amount:150 test'; + + const result = getQueryWithUpdatedValues(userQuery); + + expect(result).toEqual(`${defaultQuery} from:9876,87654 to:78901 amount:15000 hello test`); + }); +}); From 146aa7d92d55a0c6f24186d7d1a5f54a27633be2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 4 Dec 2024 13:11:16 +0100 Subject: [PATCH 113/128] Fix card assignment flow when members are changed --- .../workspace/companyCards/WorkspaceCompanyCardsPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index f66e448a658d..ac0c493244cf 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -39,8 +39,7 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) { const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const selectedFeed = CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds); const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`); - - const policy = PolicyUtils.getPolicy(policyID); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const filteredCardList = CardUtils.getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined); @@ -76,8 +75,9 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) { }; let currentStep: AssignCardStep = CONST.COMPANY_CARD.STEP.ASSIGNEE; + const employeeList = Object.values(policy?.employeeList ?? {}).filter((employee) => !PolicyUtils.isDeletedPolicyEmployee(employee, isOffline)); - if (Object.keys(policy?.employeeList ?? {}).length === 1) { + if (employeeList.length === 1) { const userEmail = Object.keys(policy?.employeeList ?? {}).at(0) ?? ''; data.email = userEmail; const personalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(userEmail); From c464eedfdd8b5fd64c5771fb3361ee0eaac7e080 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 4 Dec 2024 14:12:33 +0200 Subject: [PATCH 114/128] Fix bank selection error --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/workspace/companyCards/addNew/SelectBankStep.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index fe791f4e18a4..d79695ed8b48 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3311,6 +3311,7 @@ const translations = { error: { pleaseSelectProvider: 'Please select a card provider before continuing.', pleaseSelectBankAccount: 'Please select a bank account before continuing.', + pleaseSelectBank: 'Please select a bank before continuing.', pleaseSelectFeedType: 'Please select a feed type before continuing.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 42da5f745ded..5ce47db18d35 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3351,6 +3351,7 @@ const translations = { error: { pleaseSelectProvider: 'Seleccione un proveedor de tarjetas antes de continuar.', pleaseSelectBankAccount: 'Seleccione una cuenta bancaria antes de continuar.', + pleaseSelectBank: 'Seleccione una bancaria antes de continuar.', pleaseSelectFeedType: 'Seleccione un tipo de pienso antes de continuar.', }, }, diff --git a/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx index 9591f792ea59..2c6745fabe14 100644 --- a/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx @@ -99,7 +99,7 @@ function SelectBankStep() { )} From 6542b02b8b2a01c14ef892bd277df5f42a048c78 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 4 Dec 2024 16:36:28 +0300 Subject: [PATCH 115/128] updated comment --- src/components/Composer/implementation/index.native.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 96527b105853..cea339de07e2 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -62,7 +62,9 @@ function Composer( return; } - // We need the delay for setSelection to properly work for IOS. + // We need the delay for setSelection to properly work for IOS in bridgeless mode due to a react native + // internal bug of dispatching the event before the component is ready for it. + // (see https://github.com/Expensify/App/pull/50520#discussion_r1861960311 for more context) const timeoutID = setTimeout(() => { // We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size. textInput.current?.setSelection((selection.start || 1) - 1, selection.start); From 539ca23e46e658ac5667b0ab6db5814cecda970c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 4 Dec 2024 21:51:41 +0800 Subject: [PATCH 116/128] simplify code --- src/components/PopoverMenu.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index c628a00a89a1..0f32a5164b0a 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -137,6 +137,10 @@ const renderWithConditionalWrapper = (shouldUseScrollView: boolean, contentConta return <>{children}; }; +function getSelectedItemIndex(menuItems: PopoverMenuItem[]) { + return menuItems.findIndex((option) => option.isSelected); +} + function PopoverMenu({ menuItems, onItemSelected, @@ -174,7 +178,7 @@ function PopoverMenu({ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); const [currentMenuItems, setCurrentMenuItems] = useState(menuItems); - const currentMenuItemsFocusedIndex = currentMenuItems?.findIndex((option) => option.isSelected); + const currentMenuItemsFocusedIndex = getSelectedItemIndex(currentMenuItems); const [enteredSubMenuIndexes, setEnteredSubMenuIndexes] = useState(CONST.EMPTY_ARRAY); const {windowHeight} = useWindowDimensions(); @@ -312,9 +316,7 @@ function PopoverMenu({ } setEnteredSubMenuIndexes(CONST.EMPTY_ARRAY); setCurrentMenuItems(menuItems); - }, [menuItems]); - useEffect(() => { if (isVisible) { return; } @@ -322,8 +324,10 @@ function PopoverMenu({ // Update the focused item to match the selected item, but only when the popover is not visible. // This ensures that if the popover is visible, highlight from the keyboard navigation is not overridden // by external updates. - setFocusedIndex(currentMenuItemsFocusedIndex); - }, [isVisible, currentMenuItemsFocusedIndex, setFocusedIndex]); + setFocusedIndex(getSelectedItemIndex(menuItems)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [menuItems, setFocusedIndex]); return ( Date: Wed, 4 Dec 2024 14:20:53 +0000 Subject: [PATCH 117/128] Update Add-Domain-Members-and-Admins.md --- .../domains/Add-Domain-Members-and-Admins.md | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/domains/Add-Domain-Members-and-Admins.md b/docs/articles/expensify-classic/domains/Add-Domain-Members-and-Admins.md index 14b5225801d0..71993956f4f4 100644 --- a/docs/articles/expensify-classic/domains/Add-Domain-Members-and-Admins.md +++ b/docs/articles/expensify-classic/domains/Add-Domain-Members-and-Admins.md @@ -1,6 +1,6 @@ --- -title: Add Domain Members and Admins -description: Add members and admins to a domain +title: Add and remove Domain Members and Admins +description: Add and remove members and admins to a domain ---
@@ -34,7 +34,19 @@ Once the member verifies their email address, all Domain Admins will be notified 1. Hover over Settings, then click **Domains**. 2. Click the name of the domain. 3. Click the **Domain Members** tab on the left. -4. Under the Domain Members section, enter the first part of the member’s email address and click **Invite**. +4. Under the Domain Members section, enter the first part of the member’s email address and click **Invite**. + +# Close a Domain Member’s account + +1. Hover over Settings, then click **Domains**. +2. Click the name of the domain. +3. Click the **Domain Members** tab on the left. +4. Find the user account you’d like to close, and select it +5. Click **Close** to close the account + +{% include info.html %} +Any closed account can be reopened at any time, by reinviting the user via the Domain Member page +{% include end-info.html %} # Add Domain Admin @@ -47,4 +59,12 @@ Once the member verifies their email address, all Domain Admins will be notified This can be any email address—it does not have to be an email address under the domain. {% include end-info.html %} +# Remove Domain Admin + +1. Hover over Settings, then click **Domains**. +2. Click the name of the domain. +3. Click the **Domain Admins** tab on the left. +4. Under the Domain Admins section, click the red trash can button next to the Domain Admin you’d like to remove +
+ From 8a74c2901528069f307d166d856d3251181cc7bd Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Wed, 4 Dec 2024 15:53:29 +0100 Subject: [PATCH 118/128] remove comment --- .github/workflows/testBuildHybrid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index cfc33070dfa9..c84fe41fddae 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -198,7 +198,7 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, androidHybrid] #TODO add ios job + needs: [validateActor, getBranchRef, androidHybrid] if: ${{ always() }} steps: - name: Checkout From 29abfa45bebf3a25c2acc662361ebd27ddd8c358 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Wed, 4 Dec 2024 15:08:44 +0000 Subject: [PATCH 119/128] Add info blurb about using the "switch country" button --- .../bank-accounts/Connect-Personal-Bank-Account.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-Bank-Account.md b/docs/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-Bank-Account.md index a7b7ed1c4f4f..b77f4c88605e 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-Bank-Account.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-Personal-Bank-Account.md @@ -10,7 +10,7 @@ To connect a deposit-only account, 1. Hover over **Settings**, then click **Account**. 2. Click the **Payments** tab on the left. -3. Click **Add Deposit-Only Bank Account**, then click **Connect to your bank**. +3. Click **Add Deposit-Only Bank Account**, then click **Connect to your bank**. 4. Click **Continue**. 5. Search for your bank account in the list of banks and follow the prompts to sign in to your bank account. - If your bank doesn’t appear, click the X in the right corner of the Plaid pop-up window, then click **Connect Manually**. You’ll then manually enter your account information and click **Save & Continue**. @@ -19,6 +19,10 @@ To connect a deposit-only account, You’ll now receive reimbursements for your expense reports and invoices directly to this bank account. +{% include info.html %} +If your organization has global reimbursement enabled and you want to add a bank account outside of the US, you can do so by following the steps above. However, after clicking on **Add Deposit-Only Bank Account**, look for a button that says **Switch Country**. This will allow you to add a deposit account from a supported country and receive reimbursements in your local currency. +{% include end-info.html %} + {% include faq-begin.md %} **I connected my deposit-only bank account. Why haven’t I received my reimbursement?** From 30ff2caca7ef97394e292d0ef8858a2eeff42654 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 4 Dec 2024 23:28:06 +0800 Subject: [PATCH 120/128] lint --- src/components/PopoverMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 0f32a5164b0a..556f05096c3b 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ import lodashIsEqual from 'lodash/isEqual'; import type {ReactNode, RefObject} from 'react'; -import React, {useEffect, useLayoutEffect, useState} from 'react'; +import React, {useLayoutEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {ModalProps} from 'react-native-modal'; @@ -326,7 +326,7 @@ function PopoverMenu({ // by external updates. setFocusedIndex(getSelectedItemIndex(menuItems)); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler react-hooks/exhaustive-deps }, [menuItems, setFocusedIndex]); return ( From e5ba8d88755abb43674b12ebe6ecf453db4178ec Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 4 Dec 2024 23:31:25 +0800 Subject: [PATCH 121/128] lint --- src/components/PopoverMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 556f05096c3b..6025314b36b2 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -326,7 +326,7 @@ function PopoverMenu({ // by external updates. setFocusedIndex(getSelectedItemIndex(menuItems)); - // eslint-disable-next-line react-compiler/react-compiler react-hooks/exhaustive-deps + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [menuItems, setFocusedIndex]); return ( From 11ba3771273ef333e9d502193da761df8c71198f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 4 Dec 2024 23:36:48 +0800 Subject: [PATCH 122/128] move comment --- src/components/PopoverMenu.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 6025314b36b2..f5b430499300 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -317,13 +317,12 @@ function PopoverMenu({ setEnteredSubMenuIndexes(CONST.EMPTY_ARRAY); setCurrentMenuItems(menuItems); - if (isVisible) { - return; - } - // Update the focused item to match the selected item, but only when the popover is not visible. // This ensures that if the popover is visible, highlight from the keyboard navigation is not overridden // by external updates. + if (isVisible) { + return; + } setFocusedIndex(getSelectedItemIndex(menuItems)); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps From b1556469ff8efdbb4ef5c6fc18ed3e92dc169dac Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 4 Dec 2024 16:58:03 +0100 Subject: [PATCH 123/128] fix: correct bottom padding on edit approval workflow --- .../workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index d5919cadbda1..6e512160bd7f 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -121,7 +121,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true featureName={CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED} > Date: Wed, 4 Dec 2024 18:13:29 +0000 Subject: [PATCH 124/128] Update version to 9.0.71-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f34e7d57d401..bc03f3d05a9e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009007100 - versionName "9.0.71-0" + versionCode 1009007101 + versionName "9.0.71-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ba4e887b78f6..9bd5621117d4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.71.0 + 9.0.71.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 190ebad05946..83c4685df84d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.71.0 + 9.0.71.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 9f38cc295fe1..333db7764f01 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.71 CFBundleVersion - 9.0.71.0 + 9.0.71.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 924994ee49ab..32824b626d5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.71-0", + "version": "9.0.71-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.71-0", + "version": "9.0.71-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b2981df5c583..4e471034ffdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.71-0", + "version": "9.0.71-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 61f4a7babc1d340ee811078f9a292f5db89ece34 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:24:31 -0500 Subject: [PATCH 125/128] Update and rename Set-Notifications.md to Email-Notifications.md updating verbiage + title of article --- ...otifications.md => Email-Notifications.md} | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) rename docs/articles/expensify-classic/settings/{Set-Notifications.md => Email-Notifications.md} (60%) diff --git a/docs/articles/expensify-classic/settings/Set-Notifications.md b/docs/articles/expensify-classic/settings/Email-Notifications.md similarity index 60% rename from docs/articles/expensify-classic/settings/Set-Notifications.md rename to docs/articles/expensify-classic/settings/Email-Notifications.md index da55dafb833c..ff7449c5f9fd 100644 --- a/docs/articles/expensify-classic/settings/Set-Notifications.md +++ b/docs/articles/expensify-classic/settings/Email-Notifications.md @@ -1,10 +1,9 @@ --- -title: Set notifications -description: This article is about how to troubleshoot notifications from Expensify. +title: Expensify Email notifications +description: Troubleshooting steps for receiving emails and notifications from Expensify. --- -# Overview -Sometimes members may have trouble receiving important email notifications from Expensify, such as Expensify Magic Code emails, account validation emails, secondary login validations, integration emails, or report action notifications (rejections, approvals, etc.). +Occasionally, members may have trouble receiving email notifications from Expensify, such as Expensify Magic Code emails, account validation emails, secondary login validations, integration emails, or report action notifications. # Troubleshooting missing Expensify notifications @@ -12,45 +11,48 @@ Sometimes members may have trouble receiving important email notifications from Emails can sometimes be delayed and could take up to 30-60 minutes to arrive in your inbox. If you're expecting a notification that still hasn't arrived after waiting: - Check your **Email Preferences** on the web via **Settings > Account > Preferences**. In the **Contact Preferences** section, ensure that the relevant boxes are checked for the email type you're missing. - Check your email spam and trash folders, as Expensify messages might end up there inadvertently. - - Check to make sure you haven't unintentionally blocked Expensify emails. Allowlist the domain expensify.com with your email provider. + - Check to make sure you haven't unintentionally blocked Expensify emails. whitelist the domain expensify.com with your email provider. ## Issue: A banner that says “We’re having trouble emailing you” shows the top of your screen. -Confirm the email address on your Expensify account is a deliverable email address, and then click the link in the banner that says "here". If successful, you will see a confirmation that your email was unblocked. +Confirm that the email address on your Expensify account is deliverable, and then click the link in the banner that says "here." If successful, you will see a confirmation that your email was unblocked. ![ExpensifyHelp_EmailError]({{site.url}}/assets/images/ExpensifyHelp_EmailError.png){:width="100%"} **If unsuccessful, you will see another error:** - If the new error or SMTP message includes a URL, navigate to that URL for further instructions. - If the new error or SMTP message includes "mimecast.com", consult with your company's IT team. - - If the new error or SMTP message includes "blacklist", it means your company has configured their email servers to use a third-party email reputation or blocklisting service. Consult with your company's IT team. + - If the new error or SMTP message includes "blacklist," it means your company has configured its email servers to use a third-party email reputation or blocklisting service. Consult with your company's IT team. ![ExpensifyHelp_SMTPError]({{site.url}}/assets/images/ExpensifyHelp_SMTPError.png){:width="100%"} # Further troubleshooting for public domains -If you are still not receiving Expensify notifications and have an email address on a public domain such as gmail.com or yahoo.com, you may need to add Expensify's domain expensify.com to your email's allowlist by taking the following steps: +If you are still not receiving Expensify notifications and have an email address on a public domain such as gmail.com or yahoo.com, you may need to add Expensify's domain expensify.com to your email's whitelist by taking the following steps: - Search for messages from expensify.com in your spam folder, open them, and click “Not Spam” at the top of each message. - - Configure an email filter that identifies Expensify's email domain expensify.com and directs all incoming messages to your inbox, to avoid messages going to spam. - - Add specific known Expensify email addresses such as concierge@expensify.com to your email contacts list. + Configure an email filter that identifies Expensify's email domain as expensify.com and directs all incoming messages to your inbox to prevent messages from going to spam. + - Add specific known Expensify email addresses, such as concierge@expensify.com, to your email contacts list. # Further troubleshooting for private domains If your organization uses a private domain, Expensify emails may be blocked at the server level. This can sometimes happen unexpectedly due to broader changes in email provider's handling or filtering of incoming messages. Consult your internal IT team to assist with the following: - - Ensure that the domain expensify.com is allowlisted on domain email servers. This domains is the sources of various notification emails, so it's important it is allowlisted. - - Confirm there is no server-level email blocking and that spam filters are not blocking Expensify emails. Even if you have received messages from our Concierge support in the past, ensure that expensify.com is allowlisted. + - Ensure that the domain expensify.com is allowed on the domain email servers. This domain is the source of various notification emails, so it's important it is whitelisted. + - Confirm there is no server-level email blocking + - Make sure spam filters are not blocking Expensify emails. + +Even if you have received messages from our Concierge support in the past, ensure that expensify.com is whitelisted. ## Companies using Outlook - Add Expensify to your personal Safe Senders list by following these steps: [Outlook email client](https://support.microsoft.com/en-us/office/add-recipients-of-my-email-messages-to-the-safe-senders-list-be1baea0-beab-4a30-b968-9004332336ce) / [Outlook.com](https://support.microsoft.com/en-us/office/safe-senders-in-outlook-com-470d4ee6-e3b6-402b-8cd9-a6f00eda7339) - **Company IT administrators:** Add Expensify to your domain's Safe Sender list by following the steps here: [Create safe sender lists in EOP](https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365) -- **Company IT administrators:** Add expensify.com to the domain's explicit allowlist. You may need to contact Outlook support for specific instructions, as each company's setup varies. +**Company IT administrators:** Add expensify.com to the domain's explicit whitelist. As each company's setup varies, you may need to contact Outlook support for specific instructions. - **Company administrators:** Contact Outlook support to see if there are additional steps to take based on your domain's email configuration. ## Companies using Google Workspaces: -- **Company IT administrators:** Adjust your domain's email allowlist and safe senders lists to include expensify.com by following these steps: [Allowlists, denylists, and approved senders](https://support.google.com/a/answer/60752) +- **Company IT administrators:** Adjust your domain's email whitelist and safe senders lists to include expensify.com by following these steps: [Allowlists, denylists, and approved senders](https://support.google.com/a/answer/60752) {% include faq-begin.md %} @@ -60,10 +62,10 @@ Expensify's emails are SPF and DKIM-signed, meaning they are cryptographically s ## Why do legitimate emails from Expensify sometimes end up marked as spam? -The problem typically arises when our domain or one of our sending IP addresses gets erroneously flagged by a 3rd party domain or IP reputation services. Many IT departments use lists published by such services to filter email for the entire company. +The problem typically arises when a third-party domain or IP reputation service erroneously flags our domain or one of our sending IP addresses. Many IT departments use lists published by such services to filter email for the entire company. ## What is the best way to ensure emails are not accidentally marked as Spam? -For server-level spam detection, the safest approach to allowlisting email from Expensify is to verify DKIM and SPF, rather than solely relying on the third-party reputation of the sending IP address. +For server-level spam detection, the safest approach to whitelisting email from Expensify is to verify DKIM and SPF, rather than solely relying on the third-party reputation of the sending IP address. {% include faq-end.md %} From 5737700d5308e938149e7b31ee35a21d002add20 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:26:25 -0500 Subject: [PATCH 126/128] Update redirects.csv adding redirect since title changed --- docs/redirects.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/redirects.csv b/docs/redirects.csv index 37cbaf0245a2..5bf8fa223ab9 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -604,4 +604,5 @@ https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-T https://help.expensify.com/articles/expensify-classic/spending-insights/Default-Export-Templates,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options,https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports/ https://help.expensify.com/articles/expensify-classic/travel/Edit-or-cancel-travel-arrangements,https://help.expensify.com/articles/expensify-classic/travel/Book-with-Expensify-Travel -https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills \ No newline at end of file +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-and-Pay-Bills +https://help.expensify.com/articles/expensify-classic/settings/Set-Notifications,https://help.expensify.com/articles/expensify-classic/settings/Email-Notifications From f4fdd25aa96fd0472c46138343dbff58ab66b9bd Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 4 Dec 2024 12:23:54 -0800 Subject: [PATCH 127/128] Revert "Optimistic update for moving tasks for "Manage Team's Expenses" to #admins room" --- .../LHNOptionsList/OptionRowLHN.tsx | 11 +-- src/libs/actions/Report.ts | 87 ++++++------------- src/types/onyx/Policy.ts | 6 -- 3 files changed, 29 insertions(+), 75 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 8bf19a1edcbf..3e3f4d1b8e5d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -9,7 +9,6 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useSession} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; @@ -52,12 +51,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasCompletedGuidedSetupFlowSelector, }); - const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); - const session = useSession(); - - // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. - const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); - const shouldShowToooltipOnThisReport = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true}); const {translate} = useLocalize(); @@ -180,7 +173,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > personalDetail?.login === assignedGuideEmail); - let assignedGuideAccountID: number; - if (assignedGuidePersonalDetail) { - assignedGuideAccountID = assignedGuidePersonalDetail.accountID; - } else { - assignedGuideAccountID = generateAccountID(assignedGuideEmail); - Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { - [assignedGuideAccountID]: { - login: assignedGuideEmail, - displayName: assignedGuideEmail, - }, - }); - } - const actorAccountID = shouldPostTasksInAdminsRoom ? assignedGuideAccountID : CONST.ACCOUNT_ID.CONCIERGE; // Text message const textComment = ReportUtils.buildOptimisticAddCommentReportAction(data.message, undefined, actorAccountID, 1); @@ -3574,9 +3556,7 @@ function prepareOnboardingOptimisticData( targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); - const emailCreatingAction = - engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE : CONST.EMAIL.CONCIERGE; - const taskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(emailCreatingAction); + const taskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(CONST.EMAIL.CONCIERGE); const taskReportAction = ReportUtils.buildOptimisticTaskCommentReportAction( currentTask.reportID, taskTitle, @@ -3756,27 +3736,21 @@ function prepareOnboardingOptimisticData( lastMentionedTime: DateUtils.getDBTime(), hasOutstandingChildTask, lastVisibleActionCreated, - lastActorAccountID: actorAccountID, }, }, { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_INTRO_SELECTED, - value: {choice: engagementChoice}, - }, - ); - - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { - optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, value: { [textCommentAction.reportActionID]: textCommentAction as ReportAction, }, - }); - } - + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_INTRO_SELECTED, + value: {choice: engagementChoice}, + }, + ); if (!wasInvited) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -3786,17 +3760,13 @@ function prepareOnboardingOptimisticData( } const successData: OnyxUpdate[] = [...tasksForSuccessData]; - - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [textCommentAction.reportActionID]: {pendingAction: null}, - }, - }); - } + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, + value: { + [textCommentAction.reportActionID]: {pendingAction: null}, + }, + }); let failureReport: Partial = { lastMessageTranslationKey: '', @@ -3826,16 +3796,7 @@ function prepareOnboardingOptimisticData( key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`, value: failureReport, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_INTRO_SELECTED, - value: {choice: null}, - }, - ); - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { - failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, value: { @@ -3843,8 +3804,13 @@ function prepareOnboardingOptimisticData( errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), } as ReportAction, }, - }); - } + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_INTRO_SELECTED, + value: {choice: null}, + }, + ); if (!wasInvited) { failureData.push({ @@ -3886,10 +3852,9 @@ function prepareOnboardingOptimisticData( }); } - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - const guidedSetupData: GuidedSetupData = shouldPostTasksInAdminsRoom ? [] : [{type: 'message', ...textMessage}]; + const guidedSetupData: GuidedSetupData = [{type: 'message', ...textMessage}]; - if (!shouldPostTasksInAdminsRoom && 'video' in data && data.video && videoCommentAction && videoMessage) { + if ('video' in data && data.video && videoCommentAction && videoMessage) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 2628b7b44341..88e41aed5bb4 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1842,12 +1842,6 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Workspace account ID configured for Expensify Card */ workspaceAccountID?: number; - - /** Setup specialist guide assigned for the policy */ - assignedGuide?: { - /** The guide's email */ - email: string; - }; } & Partial, 'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes >; From 0e28aa4d7ab0e5ba4d2b050c47dc78b35a45992f Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 4 Dec 2024 23:25:56 +0000 Subject: [PATCH 128/128] Update version to 9.0.71-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bc03f3d05a9e..3294a7f3ed8d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009007101 - versionName "9.0.71-1" + versionCode 1009007102 + versionName "9.0.71-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 9bd5621117d4..a268b601b5db 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.71.1 + 9.0.71.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 83c4685df84d..cae0d7a07cf6 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.71.1 + 9.0.71.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 333db7764f01..4b2c45a06882 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.71 CFBundleVersion - 9.0.71.1 + 9.0.71.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 32824b626d5f..c3079b1f9a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.71-1", + "version": "9.0.71-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.71-1", + "version": "9.0.71-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4e471034ffdf..dfc64c7be0c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.71-1", + "version": "9.0.71-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",