From b3b8bec3cbaf3f0b35d90d8b47506f7d6193ea4d Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Fri, 5 Dec 2025 14:29:38 +0500 Subject: [PATCH 01/13] 76695: Refactor ConfirmModal usage to useConfirmModal in Report and Home pages --- src/pages/EditReportFieldPage.tsx | 40 ++++---- src/pages/ReportDetailsPage.tsx | 67 ++++++------- src/pages/ReportParticipantDetailsPage.tsx | 31 +++--- src/pages/ReportParticipantsPage.tsx | 56 ++++++----- src/pages/RoomMemberDetailsPage.tsx | 31 +++--- src/pages/RoomMembersPage.tsx | 39 ++++---- src/pages/Travel/TravelTerms.tsx | 41 ++++---- src/pages/home/HeaderView.tsx | 18 +--- .../PopoverReportActionContextMenu.tsx | 48 ++++----- .../home/report/PureReportActionItem.tsx | 34 +++---- .../home/report/ReportDetailsExportPage.tsx | 29 +++--- .../FloatingActionButtonAndPopover.tsx | 98 ++++++++++++++----- 12 files changed, 280 insertions(+), 252 deletions(-) diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 3f674634c1502..64ee57197983c 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -1,16 +1,17 @@ import {Str} from 'expensify-common'; import React, {useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import ConfirmModal from '@components/ConfirmModal'; import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import {useSession} from '@components/OnyxListItemProvider'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; +import useConfirmModal from '@hooks/useConfirmModal'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import {deleteReportField, updateReportField, updateReportName} from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -50,7 +51,7 @@ function EditReportFieldPage({route}: EditReportFieldPageProps) { const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const hasViolations = hasViolationsReportUtils(report?.reportID, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const {showConfirmModal} = useConfirmModal(); const {translate} = useLocalize(); const isReportFieldTitle = isReportFieldOfTypeTitle(reportField); const reportFieldsEnabled = ((isPaidGroupPolicyExpenseReport(report) || isInvoiceReport(report)) && !!policy?.areReportFieldsEnabled) || isReportFieldTitle; @@ -78,7 +79,6 @@ function EditReportFieldPage({route}: EditReportFieldPageProps) { }; const handleReportFieldDelete = () => { - setIsDeleteModalVisible(false); goBack(); setTimeout(() => { deleteReportField(report.reportID, reportField); @@ -121,7 +121,25 @@ function EditReportFieldPage({route}: EditReportFieldPageProps) { const isReportFieldDeletable = reportField.deletable && reportField?.fieldID !== CONST.REPORT_FIELD_TITLE_FIELD_ID; if (isReportFieldDeletable) { - menuItems.push({icon: Expensicons.Trashcan, text: translate('common.delete'), onSelected: () => setIsDeleteModalVisible(true), shouldCallAfterModalHide: true}); + menuItems.push({ + icon: Expensicons.Trashcan, + text: translate('common.delete'), + onSelected: () => { + showConfirmModal({ + title: translate('workspace.reportFields.delete'), + prompt: translate('workspace.reportFields.deleteConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + shouldEnableNewFocusManagement: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + handleReportFieldDelete(); + } + }); + }, + shouldCallAfterModalHide: true, + }); } const fieldName = Str.UCFirst(reportField.name); @@ -136,19 +154,7 @@ function EditReportFieldPage({route}: EditReportFieldPageProps) { title={fieldName} threeDotsMenuItems={menuItems} shouldShowThreeDotsButton={!!menuItems?.length} - onBackButtonPress={goBack} - /> - - setIsDeleteModalVisible(false)} - prompt={translate('workspace.reportFields.deleteConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - shouldEnableNewFocusManagement + onBackButtonPress={goBack} /> {(reportField.type === CONST.REPORT_FIELD_TYPES.TEXT || isReportFieldTitle) && ( diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index db1702d97059c..1945736638502 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -1,12 +1,12 @@ import reportsSelector from '@selectors/Attributes'; import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import ConfirmModal from '@components/ConfirmModal'; import DisplayNames from '@components/DisplayNames'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; @@ -24,6 +24,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import {useSearchContext} from '@components/Search/SearchContext'; import useAncestors from '@hooks/useAncestors'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; @@ -153,6 +154,7 @@ type CaseID = ValueOf; function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetailsPageProps) { const {translate, localeCompare} = useLocalize(); const {isOffline} = useNetwork(); + const {showConfirmModal} = useConfirmModal(); const {isRestrictedToPreferredPolicy, preferredPolicyID} = usePreferredPolicy(); const styles = useThemeStyles(); const backTo = route.params.backTo; @@ -180,8 +182,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const isPolicyAdmin = useMemo(() => isPolicyAdminUtil(policy), [policy]); const isPolicyEmployee = useMemo(() => isPolicyEmployeeUtil(report?.policyID, policy), [report?.policyID, policy]); const isPolicyExpenseChat = useMemo(() => isPolicyExpenseChatUtil(report), [report]); @@ -305,13 +305,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const isWorkspaceChat = useMemo(() => isWorkspaceChatUtil(report?.chatType ?? ''), [report?.chatType]); - useEffect(() => { - if (canDeleteRequest) { - return; - } - - setIsDeleteModalVisible(false); - }, [canDeleteRequest]); useEffect(() => { // Do not fetch private notes if isLoadingPrivateNotes is already defined, or if the network is offline, or if the report is a self DM. @@ -524,7 +517,17 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail isAnonymousAction: true, action: () => { if (getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { - setIsLastMemberLeavingGroupModalVisible(true); + showConfirmModal({ + danger: true, + title: translate('groupChat.lastMemberTitle'), + prompt: translate('groupChat.lastMemberWarning'), + confirmText: translate('common.leave'), + cancelText: translate('common.cancel'), + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + leaveChat(); + } + }); return; } @@ -1004,37 +1007,23 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail key={CONST.REPORT_DETAILS_MENU_ITEM.DELETE} icon={Expensicons.Trashcan} title={caseID === CASES.DEFAULT ? translate('common.delete') : translate('reportActionContextMenu.deleteAction', {action: requestParentReportAction})} - onPress={() => setIsDeleteModalVisible(true)} + onPress={() => { + showConfirmModal({ + title: caseID === CASES.DEFAULT ? translate('task.deleteTask') : translate('iou.deleteExpense', {count: 1}), + prompt: caseID === CASES.DEFAULT ? translate('task.deleteConfirmation') : translate('iou.deleteConfirmation', {count: 1}), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + shouldEnableNewFocusManagement: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + isTransactionDeleted.current = true; + } + }); + }} /> )} - { - setIsLastMemberLeavingGroupModalVisible(false); - leaveChat(); - }} - onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)} - prompt={translate('groupChat.lastMemberWarning')} - confirmText={translate('common.leave')} - cancelText={translate('common.cancel')} - /> - { - 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 - /> ); diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index 86ddc916f94c8..943d2c4814afb 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -2,7 +2,6 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import Avatar from '@components/Avatar'; import Button from '@components/Button'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; // eslint-disable-next-line no-restricted-imports import * as Expensicons from '@components/Icon/Expensicons'; @@ -11,9 +10,11 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -41,8 +42,7 @@ function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageP const StyleUtils = useStyleUtils(); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - - const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false); + const {showConfirmModal} = useConfirmModal(); const accountID = Number(route.params.accountID); const backTo = ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID, route.params.backTo); @@ -54,7 +54,6 @@ function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageP const isCurrentUserAdmin = isGroupChatAdmin(report, currentUserPersonalDetails?.accountID); const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; const removeUser = useCallback(() => { - setIsRemoveMemberConfirmModalVisible(false); removeFromGroupChat(report?.reportID, [accountID]); Navigation.goBack(backTo); }, [backTo, report?.reportID, accountID]); @@ -100,22 +99,24 @@ function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageP <>