From 98867e66dc162d73c154f0878a1d99eba438276f Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 17 Dec 2025 23:41:26 +0700 Subject: [PATCH 1/8] fix: refactor ConfirmModal usage to useConfirmModal in workspace tags pages --- .../workspace/tags/ImportTagsOptionsPage.tsx | 232 +++++++++--------- src/pages/workspace/tags/TagSettingsPage.tsx | 74 +++--- .../workspace/tags/WorkspaceTagsPage.tsx | 139 ++++++----- .../workspace/tags/WorkspaceViewTagsPage.tsx | 127 +++++----- 4 files changed, 303 insertions(+), 269 deletions(-) diff --git a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx index 6a63a35e61ace..0cedbaab0691c 100644 --- a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx +++ b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx @@ -1,13 +1,14 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useMemo, useState} from 'react'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; -import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useConfirmModal from '@hooks/useConfirmModal'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -49,10 +50,9 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { const isQuickSettingsFlow = !!backTo; const {translate} = useLocalize(); const styles = useThemeStyles(); - const [isSwitchSingleToMultipleLevelTagWarningModalVisible, setIsSwitchSingleToMultipleLevelTagWarningModalVisible] = useState(false); + const {showConfirmModal} = useConfirmModal(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MultiTag', 'Tag']); - const [isOverridingMultiTag, setIsOverridingMultiTag] = useState(false); const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); const [shouldRunPostUpgradeFlow, setShouldRunPostUpgradeFlow] = useState(false); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); @@ -70,41 +70,47 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { return Object.values(singleLevelTags).some((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); }, [isMultiLevelTags, policyTagLists]); - const startMultiLevelTagImportFlow = useCallback(() => { - setImportedSpreadsheetIsImportingMultiLevelTags(true); - if (hasVisibleTags) { - if (isMultiLevelTags) { - setIsOverridingMultiTag(true); - } else { - setIsSwitchSingleToMultipleLevelTagWarningModalVisible(true); - } - } else { - Navigation.navigate( - isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } - }, [hasVisibleTags, policyID, isQuickSettingsFlow, backTo, isMultiLevelTags]); - - useFocusEffect( - useCallback(() => { - if (!shouldRunPostUpgradeFlow || !isControlPolicy(policy)) { - return; - } - - startMultiLevelTagImportFlow(); - setShouldRunPostUpgradeFlow(false); - }, [shouldRunPostUpgradeFlow, policy, startMultiLevelTagImportFlow]), + const overrideMultiTagPrompt = useMemo( + () => ( + + {translate('workspace.tags.overrideMultiTagWarning.prompt1')} + <> + {translate('workspace.tags.overrideMultiTagWarning.prompt2')} + { + if (isMultiLevelTags) { + downloadMultiLevelTagsCSV( + policyID, + () => { + close(() => { + setIsDownloadFailureModalVisible(true); + }); + }, + hasDependentTags, + ); + } else { + downloadTagsCSV(policyID, () => { + close(() => { + setIsDownloadFailureModalVisible(true); + }); + }); + } + }} + > + {translate('workspace.tags.overrideMultiTagWarning.prompt3')} + + {translate('workspace.tags.overrideMultiTagWarning.prompt4')} + + + ), + [translate, isMultiLevelTags, policyID, hasDependentTags], ); - if (hasAccountingConnections) { - return ; - } - - const overrideMultiTagPrompt = ( - - {translate('workspace.tags.overrideMultiTagWarning.prompt1')} - <> - {translate('workspace.tags.overrideMultiTagWarning.prompt2')} + const switchSingleToMultiLevelTagPrompt = useMemo( + () => ( + + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt1')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt2')} { if (isMultiLevelTags) { @@ -126,46 +132,87 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { } }} > - {translate('workspace.tags.overrideMultiTagWarning.prompt3')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt3')} - {translate('workspace.tags.overrideMultiTagWarning.prompt4')} - - + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt4')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt5')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt6')} + + ), + [translate, isMultiLevelTags, policyID, hasDependentTags], ); - const switchSingleToMultiLevelTagPrompt = ( - - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt1')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt2')} - { - if (isMultiLevelTags) { - downloadMultiLevelTagsCSV( - policyID, - () => { - close(() => { - setIsDownloadFailureModalVisible(true); - }); - }, - hasDependentTags, - ); - } else { - downloadTagsCSV(policyID, () => { - close(() => { - setIsDownloadFailureModalVisible(true); - }); - }); - } - }} - > - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt3')} - - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt4')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt5')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt6')} - + const showSwitchSingleToMultiLevelTagWarningModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + prompt: switchSingleToMultiLevelTagPrompt, + confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + cleanPolicyTags(policyID); + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } + }); + }, [showConfirmModal, translate, switchSingleToMultiLevelTagPrompt, policyID, isQuickSettingsFlow, backTo]); + + const showOverrideMultiTagModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.overrideMultiTagWarning.title'), + prompt: overrideMultiTagPrompt, + confirmText: translate('workspace.tags.overrideMultiTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } + }); + }, [showConfirmModal, translate, overrideMultiTagPrompt, policyID, isQuickSettingsFlow, backTo]); + + const startMultiLevelTagImportFlow = useCallback(() => { + setImportedSpreadsheetIsImportingMultiLevelTags(true); + if (hasVisibleTags) { + if (isMultiLevelTags) { + showOverrideMultiTagModal(); + } else { + showSwitchSingleToMultiLevelTagWarningModal(); + } + } else { + Navigation.navigate( + isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } + }, [hasVisibleTags, policyID, isQuickSettingsFlow, backTo, isMultiLevelTags, showOverrideMultiTagModal, showSwitchSingleToMultiLevelTagWarningModal]); + + useFocusEffect( + useCallback(() => { + if (!shouldRunPostUpgradeFlow || !isControlPolicy(policy)) { + return; + } + + startMultiLevelTagImportFlow(); + setShouldRunPostUpgradeFlow(false); + }, [shouldRunPostUpgradeFlow, policy, startMultiLevelTagImportFlow]), ); + if (hasAccountingConnections) { + return ; + } + return ( { setImportedSpreadsheetIsImportingMultiLevelTags(false); if (hasVisibleTags) { - setIsSwitchSingleToMultipleLevelTagWarningModalVisible(true); + showSwitchSingleToMultiLevelTagWarningModal(); } else { Navigation.navigate( isQuickSettingsFlow @@ -227,47 +274,6 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { isVisible={isDownloadFailureModalVisible} onClose={() => setIsDownloadFailureModalVisible(false)} /> - { - cleanPolicyTags(policyID); - setIsSwitchSingleToMultipleLevelTagWarningModalVisible(false); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - }} - title={translate('workspace.tags.switchSingleToMultiLevelTagWarning.title')} - prompt={switchSingleToMultiLevelTagPrompt} - confirmText={translate('workspace.tags.switchSingleToMultiLevelTagWarning.title')} - danger - cancelText={translate('common.cancel')} - onCancel={() => { - setIsSwitchSingleToMultipleLevelTagWarningModalVisible(false); - setImportedSpreadsheetIsImportingMultiLevelTags(false); - }} - /> - { - setIsOverridingMultiTag(false); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - }} - title={translate('workspace.tags.overrideMultiTagWarning.title')} - prompt={overrideMultiTagPrompt} - confirmText={translate('workspace.tags.overrideMultiTagWarning.title')} - danger - cancelText={translate('common.cancel')} - onCancel={() => { - setIsOverridingMultiTag(false); - setImportedSpreadsheetIsImportingMultiLevelTags(false); - }} - /> ); } diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index dda57b6a02531..5faec45e6848e 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -1,14 +1,14 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import Switch from '@components/Switch'; import Text from '@components/Text'; +import useConfirmModal from '@hooks/useConfirmModal'; import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -44,15 +44,14 @@ type TagSettingsPageProps = function TagSettingsPage({route, navigation}: TagSettingsPageProps) { const {orderWeight, policyID, tagName, backTo, parentTagsFilter} = route.params; const styles = useThemeStyles(); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['Lock']); const {translate} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); const policyData = usePolicyData(policyID); const {policy, tags: policyTags} = policyData; const policyTag = useMemo(() => getTagListByOrderWeight(policyTags, orderWeight), [policyTags, orderWeight]); const {environmentURL} = useEnvironment(); const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); - const [isDeleteTagModalOpen, setIsDeleteTagModalOpen] = React.useState(false); - const [isCannotDeleteOrDisableLastTagModalVisible, setIsCannotDeleteOrDisableLastTagModalVisible] = useState(false); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Lock', 'Trashcan'] as const); const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_TAGS.SETTINGS_TAG_SETTINGS; const approverText = useMemo(() => { const tagApprover = getTagApproverRule(policy, route.params?.tagName)?.approver ?? ''; @@ -76,19 +75,42 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { navigation.setParams({tagName: currentPolicyTag?.name}); }, [tagName, currentPolicyTag, navigation]); + // eslint-disable-next-line rulesdir/no-negated-variables + const showCannotDeleteOrDisableLastTagModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + + const showDeleteTagModal = () => { + showConfirmModal({ + title: translate('workspace.tags.deleteTag'), + prompt: translate('workspace.tags.deleteTagConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + if (!currentPolicyTag?.name) { + return; + } + deletePolicyTags(policyData, [currentPolicyTag.name]); + Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); + }); + }; + if (!currentPolicyTag) { return ; } - const deleteTagAndHideModal = () => { - deletePolicyTags(policyData, [currentPolicyTag.name]); - setIsDeleteTagModalOpen(false); - Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); - }; - const updateWorkspaceTagEnabled = (value: boolean) => { if (shouldPreventDisableOrDelete) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } setWorkspaceTagEnabled(policyData, {[currentPolicyTag.name]: {name: currentPolicyTag.name, enabled: value}}, policyTag.orderWeight); @@ -153,26 +175,6 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { shouldSetModalVisibility={false} onBackButtonPress={() => Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined)} /> - setIsDeleteTagModalOpen(false)} - shouldSetModalVisibility={false} - prompt={translate('workspace.tags.deleteTagConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - /> - setIsCannotDeleteOrDisableLastTagModalVisible(false)} - onCancel={() => setIsCannotDeleteOrDisableLastTagModalVisible(false)} - title={translate('workspace.tags.cannotDeleteOrDisableAllTags.title')} - prompt={translate('workspace.tags.cannotDeleteOrDisableAllTags.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> {!hasDependentTags && ( @@ -242,14 +244,14 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { {shouldShowDeleteMenuItem && ( { if (shouldPreventDisableOrDelete) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } - setIsDeleteTagModalOpen(true); + showDeleteTagModal(); }} /> )} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index d2068e4413b40..ccf957717fe86 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -4,12 +4,12 @@ import ActivityIndicator from '@components/ActivityIndicator'; import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; -import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import EmptyStateComponent from '@components/EmptyStateComponent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ImportedFromAccountingSoftware from '@components/ImportedFromAccountingSoftware'; import LottieAnimations from '@components/LottieAnimations'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; @@ -22,6 +22,7 @@ import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Switch from '@components/Switch'; import Text from '@components/Text'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; +import useConfirmModal from '@hooks/useConfirmModal'; import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -81,11 +82,8 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const {translate, localeCompare} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); - const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false); - const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); - const [isCannotDeleteOrDisableLastTagModalVisible, setIsCannotDeleteOrDisableLastTagModalVisible] = useState(false); - const [isCannotMakeLastTagOptionalModalVisible, setIsCannotMakeLastTagOptionalModalVisible] = useState(false); const {backTo, policyID} = route.params; const policyData = usePolicyData(policyID); const {policy, tags: policyTags} = policyData; @@ -214,6 +212,26 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { [policyData], ); + // eslint-disable-next-line rulesdir/no-negated-variables + const showCannotDeleteOrDisableLastTagModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + + // eslint-disable-next-line rulesdir/no-negated-variables + const showCannotMakeLastTagOptionalModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + const tagList = useMemo(() => { if (isMultiLevelTags) { return policyTagLists.map((policyTagList) => { @@ -242,7 +260,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.requiresTag')} onToggle={(newValue: boolean) => { if (isMakingLastRequiredTagListOptional(policy, policyTags, [policyTagList])) { - setIsCannotMakeLastTagOptionalModalVisible(true); + showCannotMakeLastTagOptionalModal(); return; } @@ -271,7 +289,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), [tag])) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -280,7 +298,18 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { /> ), })); - }, [isMultiLevelTags, policyTagLists, hasDependentTags, translate, policy, policyTags, updateWorkspaceRequiresTag, updateWorkspaceTagEnabled]); + }, [ + isMultiLevelTags, + policyTagLists, + hasDependentTags, + translate, + policy, + policyTags, + updateWorkspaceRequiresTag, + updateWorkspaceTagEnabled, + showCannotDeleteOrDisableLastTagModal, + showCannotMakeLastTagOptionalModal, + ]); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { const results = tokenizedSearch([tag], searchInput, (option) => [option.text ?? '', option.value ?? '']); @@ -366,9 +395,8 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } }; - const deleteTags = () => { + const deleteTags = useCallback(() => { deletePolicyTags(policyData, selectedTags); - setIsDeleteTagsConfirmModalVisible(false); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { @@ -377,14 +405,39 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { turnOffMobileSelectionMode(); } }); + }, [policyData, selectedTags, isMobileSelectionModeEnabled, policyTagLists, setSelectedTags]); + + const showDeleteTagsModal = () => { + showConfirmModal({ + title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), + prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + deleteTags(); + }); }; const isLoading = !isOffline && policyTags === undefined; const hasVisibleTags = tagList.some((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); + const showOfflineModal = useCallback(() => { + showConfirmModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.thisFeatureRequiresInternet'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + shouldHandleNavigationBack: true, + }); + }, [showConfirmModal, translate]); + const navigateToImportSpreadsheet = useCallback(() => { if (isOffline) { - close(() => setIsOfflineModalVisible(true)); + showOfflineModal(); return; } Navigation.navigate( @@ -392,7 +445,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT_OPTIONS.getRoute(policyID), ); - }, [backTo, isOffline, isQuickSettingsFlow, policyID]); + }, [backTo, isOffline, isQuickSettingsFlow, policyID, showOfflineModal]); const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); const secondaryActions = useMemo(() => { @@ -419,7 +472,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { text: translate('spreadsheet.downloadCSV'), onSelected: () => { if (isOffline) { - close(() => setIsOfflineModalVisible(true)); + showOfflineModal(); return; } close(() => { @@ -443,7 +496,19 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } return menuItems; - }, [translate, navigateToTagsSettings, hasAccountingConnections, hasVisibleTags, navigateToImportSpreadsheet, isOffline, isMultiLevelTags, policyID, hasDependentTags, expensifyIcons]); + }, [ + translate, + navigateToTagsSettings, + hasAccountingConnections, + hasVisibleTags, + navigateToImportSpreadsheet, + isOffline, + isMultiLevelTags, + policyID, + hasDependentTags, + expensifyIcons, + showOfflineModal, + ]); const getHeaderButtons = () => { const selectedTagsObject = selectedTags.map((key) => policyTagLists.at(0)?.tags?.[key]); @@ -484,11 +549,11 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } - setIsDeleteTagsConfirmModalVisible(true); + showDeleteTagsModal(); }, }); } @@ -520,7 +585,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } setSelectedTags([]); @@ -566,7 +631,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.REQUIRE, onSelected: () => { if (isMakingLastRequiredTagListOptional(policy, policyTags, selectedTagLists)) { - setIsCannotMakeLastTagOptionalModalVisible(true); + showCannotMakeLastTagOptionalModal(); return; } setSelectedTags([]); @@ -758,16 +823,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { )} - setIsOfflineModalVisible(false)} - title={translate('common.youAppearToBeOffline')} - prompt={translate('common.thisFeatureRequiresInternet')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - onCancel={() => setIsOfflineModalVisible(false)} - shouldHandleNavigationBack - /> setIsDownloadFailureModalVisible(false)} /> - setIsDeleteTagsConfirmModalVisible(false)} - title={translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags')} - prompt={translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - /> - setIsCannotDeleteOrDisableLastTagModalVisible(false)} - onCancel={() => setIsCannotDeleteOrDisableLastTagModalVisible(false)} - title={translate('workspace.tags.cannotDeleteOrDisableAllTags.title')} - prompt={translate('workspace.tags.cannotDeleteOrDisableAllTags.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> - setIsCannotMakeLastTagOptionalModalVisible(false)} - onCancel={() => setIsCannotMakeLastTagOptionalModalVisible(false)} - title={translate('workspace.tags.cannotMakeAllTagsOptional.title')} - prompt={translate('workspace.tags.cannotMakeAllTagsOptional.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> ); } diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index ec1abffc952c1..980d525a2e0e5 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -1,13 +1,12 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, View} from 'react-native'; import ActivityIndicator from '@components/ActivityIndicator'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import SearchBar from '@components/SearchBar'; @@ -16,7 +15,9 @@ import CustomListHeader from '@components/SelectionListWithModal/CustomListHeade import ListItemRightCaretWithLabel from '@components/SelectionListWithSections/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionListWithSections/TableListItem'; import Switch from '@components/Switch'; +import useConfirmModal from '@hooks/useConfirmModal'; import useFilteredSelection from '@hooks/useFilteredSelection'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -64,21 +65,18 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const {translate, localeCompare} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); const dropdownButtonRef = useRef(null); - const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false); const isFocused = useIsFocused(); const policyData = usePolicyData(policyID); const {policy, tags: policyTags} = policyData; - + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Trashcan', 'Close', 'Checkmark'] as const); const isMobileSelectionModeEnabled = useMobileSelectionMode(); const currentTagListName = useMemo(() => getTagListName(policyTags, orderWeight), [policyTags, orderWeight]); const hasDependentTags = useMemo(() => hasDependentTagsPolicyUtils(policy, policyTags), [policy, policyTags]); const isMultiLevelTags = isMultiLevelTagsPolicyUtils(policyTags); const currentPolicyTag = policyTags?.[currentTagListName]; const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_TAGS.SETTINGS_TAG_LIST_VIEW; - const [isCannotMakeAllTagsOptionalModalVisible, setIsCannotMakeAllTagsOptionalModalVisible] = useState(false); - const [isCannotDeleteOrDisableLastTagModalVisible, setIsCannotDeleteOrDisableLastTagModalVisible] = useState(false); - const [isRequiresMultiLevelTagsModalVisible, setIsRequiresMultiLevelTagsModalVisible] = useState(false); const fetchTags = useCallback(() => { openPolicyTagsPage(policyID); }, [policyID]); @@ -119,6 +117,54 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { [policyData, orderWeight], ); + // eslint-disable-next-line rulesdir/no-negated-variables + const showCannotDeleteOrDisableLastTagModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + + // eslint-disable-next-line rulesdir/no-negated-variables + const showCannotMakeAllTagsOptionalModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + + const showRequiresMultiLevelTagsModal = useCallback(() => { + showConfirmModal({ + title: translate('workspace.tags.cannotMakeTagListRequired.title'), + prompt: translate('workspace.tags.cannotMakeTagListRequired.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + + const showDeleteTagsModal = () => { + showConfirmModal({ + title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), + prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + deletePolicyTags(policyData, selectedTags); + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + setSelectedTags([]); + }); + }); + }; + const tagList = useMemo( () => Object.values(currentPolicyTag?.tags ?? {}).map((tag) => ({ @@ -140,7 +186,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, [tag])) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -149,7 +195,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { /> ), })), - [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled], + [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled, showCannotDeleteOrDisableLastTagModal], ); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { @@ -212,16 +258,6 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { ); }; - const deleteTags = () => { - deletePolicyTags(policyData, selectedTags); - setIsDeleteTagsConfirmModalVisible(false); - - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - setSelectedTags([]); - }); - }; - const isLoading = !isOffline && policyTags === undefined; const listHeaderContent = @@ -244,10 +280,10 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { if (!isThereAnyAccountingConnection && !isMultiLevelTags && selectedTags.length > 0) { options.push({ - icon: Expensicons.Trashcan, + icon: expensifyIcons.Trashcan, text: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, - onSelected: () => setIsDeleteTagsConfirmModalVisible(true), + onSelected: () => showDeleteTagsModal(), }); } @@ -274,12 +310,12 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { if (enabledTagCount > 0) { const selectedTagsObject = selectedTags.map((key) => currentPolicyTag?.tags[key]); options.push({ - icon: Expensicons.Close, + icon: expensifyIcons.Close, text: translate(enabledTagCount === 1 ? 'workspace.tags.disableTag' : 'workspace.tags.disableTags'), value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, selectedTagsObject)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showCannotDeleteOrDisableLastTagModal(); return; } setSelectedTags([]); @@ -291,7 +327,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { if (disabledTagCount > 0) { options.push({ - icon: Expensicons.Checkmark, + icon: expensifyIcons.Checkmark, text: translate(disabledTagCount === 1 ? 'workspace.tags.enableTag' : 'workspace.tags.enableTags'), value: CONST.POLICY.BULK_ACTION_TYPES.ENABLE, onSelected: () => { @@ -355,16 +391,6 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { {!shouldUseNarrowLayout && getHeaderButtons()} {shouldUseNarrowLayout && {getHeaderButtons()}} - setIsDeleteTagsConfirmModalVisible(false)} - title={translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags')} - prompt={translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - /> {!hasDependentTags && ( { if (!isMultiLevelTags) { - setIsRequiresMultiLevelTagsModalVisible(true); + showRequiresMultiLevelTagsModal(); return; } if (isMakingLastRequiredTagListOptional(policy, policyTags, [currentPolicyTag])) { - setIsCannotMakeAllTagsOptionalModalVisible(true); + showCannotMakeAllTagsOptionalModal(); return; } setPolicyTagsRequired(policyData, on, orderWeight); @@ -434,33 +460,6 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { shouldShowRightCaret /> )} - setIsCannotDeleteOrDisableLastTagModalVisible(false)} - onCancel={() => setIsCannotDeleteOrDisableLastTagModalVisible(false)} - title={translate('workspace.tags.cannotDeleteOrDisableAllTags.title')} - prompt={translate('workspace.tags.cannotDeleteOrDisableAllTags.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> - setIsCannotMakeAllTagsOptionalModalVisible(false)} - onCancel={() => setIsCannotMakeAllTagsOptionalModalVisible(false)} - title={translate('workspace.tags.cannotMakeAllTagsOptional.title')} - prompt={translate('workspace.tags.cannotMakeAllTagsOptional.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> - setIsRequiresMultiLevelTagsModalVisible(false)} - onCancel={() => setIsRequiresMultiLevelTagsModalVisible(false)} - title={translate('workspace.tags.cannotMakeTagListRequired.title')} - prompt={translate('workspace.tags.cannotMakeTagListRequired.description')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> ); From 12c8346f9998512e518d83379e518e658b968280 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 18 Dec 2025 10:52:36 +0700 Subject: [PATCH 2/8] resolve comments --- src/pages/workspace/tags/TagSettingsPage.tsx | 4 ++-- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 4 ++-- src/pages/workspace/tags/WorkspaceViewTagsPage.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index 5faec45e6848e..1ff91ee66011b 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -85,7 +85,7 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { }); }, [showConfirmModal, translate]); - const showDeleteTagModal = () => { + const showDeleteTagModal = useCallback(() => { showConfirmModal({ title: translate('workspace.tags.deleteTag'), prompt: translate('workspace.tags.deleteTagConfirmation'), @@ -102,7 +102,7 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { deletePolicyTags(policyData, [currentPolicyTag.name]); Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); }); - }; + }, [showConfirmModal, translate, currentPolicyTag?.name, policyData, isQuickSettingsFlow, policyID, backTo]); if (!currentPolicyTag) { return ; diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index ccf957717fe86..df7f0fa72ec99 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -407,7 +407,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { }); }, [policyData, selectedTags, isMobileSelectionModeEnabled, policyTagLists, setSelectedTags]); - const showDeleteTagsModal = () => { + const showDeleteTagsModal = useCallback(() => { showConfirmModal({ title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), @@ -420,7 +420,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } deleteTags(); }); - }; + }, [showConfirmModal, translate, selectedTags.length, deleteTags]); const isLoading = !isOffline && policyTags === undefined; const hasVisibleTags = tagList.some((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index 980d525a2e0e5..c7137ca8c10a8 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -146,7 +146,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { }); }, [showConfirmModal, translate]); - const showDeleteTagsModal = () => { + const showDeleteTagsModal = useCallback(() => { showConfirmModal({ title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), @@ -163,7 +163,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { setSelectedTags([]); }); }); - }; + }, [showConfirmModal, translate, selectedTags, policyData]); const tagList = useMemo( () => From 2ebf8a72b34c3b720abc35a0d86aa2c2a7218e93 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 18 Dec 2025 11:18:26 +0700 Subject: [PATCH 3/8] fix test --- tests/ui/WorkspaceTagsTest.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/ui/WorkspaceTagsTest.tsx b/tests/ui/WorkspaceTagsTest.tsx index 6da15aae64acb..25a32cef40798 100644 --- a/tests/ui/WorkspaceTagsTest.tsx +++ b/tests/ui/WorkspaceTagsTest.tsx @@ -5,6 +5,7 @@ import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; +import {ModalProvider} from '@components/Modal/Global/ModalContext'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID'; import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout'; @@ -30,15 +31,17 @@ const renderPage = (initialRouteName: typeof SCREENS.WORKSPACE.TAGS, initialPara return render( - - - - - + + + + + + + , ); From f87288f5c4f488953dab097f51fecd15665cffde Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 19 Dec 2025 12:22:09 +0700 Subject: [PATCH 4/8] remove useCallback and useMemo function --- .../workspace/tags/ImportTagsOptionsPage.tsx | 99 +++++++------ src/pages/workspace/tags/TagSettingsPage.tsx | 60 ++++---- .../workspace/tags/WorkspaceTagsPage.tsx | 138 ++++++++---------- .../workspace/tags/WorkspaceViewTagsPage.tsx | 97 ++++++------ 4 files changed, 184 insertions(+), 210 deletions(-) diff --git a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx index d7fddc663cadc..fbb2a40b59175 100644 --- a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx +++ b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx @@ -149,61 +149,53 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { ); - const showSwitchSingleToMultiLevelTagWarningModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), - prompt: switchSingleToMultiLevelTagPrompt, - confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), - cancelText: translate('common.cancel'), - danger: true, - }).then((result) => { - if (result.action === ModalActions.CONFIRM) { - cleanPolicyTags(policyID); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } else { - setImportedSpreadsheetIsImportingMultiLevelTags(false); - } - }); - }, [showConfirmModal, translate, switchSingleToMultiLevelTagPrompt, policyID, isQuickSettingsFlow, backTo]); - - const showOverrideMultiTagModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.overrideMultiTagWarning.title'), - prompt: overrideMultiTagPrompt, - confirmText: translate('workspace.tags.overrideMultiTagWarning.title'), - cancelText: translate('common.cancel'), - danger: true, - }).then((result) => { - if (result.action === ModalActions.CONFIRM) { - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } else { - setImportedSpreadsheetIsImportingMultiLevelTags(false); - } - }); - }, [showConfirmModal, translate, overrideMultiTagPrompt, policyID, isQuickSettingsFlow, backTo]); - const startMultiLevelTagImportFlow = useCallback(() => { setImportedSpreadsheetIsImportingMultiLevelTags(true); if (hasVisibleTags) { if (isMultiLevelTags) { - showOverrideMultiTagModal(); + showConfirmModal({ + title: translate('workspace.tags.overrideMultiTagWarning.title'), + prompt: overrideMultiTagPrompt, + confirmText: translate('workspace.tags.overrideMultiTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } + }); } else { - showSwitchSingleToMultiLevelTagWarningModal(); + showConfirmModal({ + title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + prompt: switchSingleToMultiLevelTagPrompt, + confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + cleanPolicyTags(policyID); + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } + }); } } else { Navigation.navigate( isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), ); } - }, [hasVisibleTags, policyID, isQuickSettingsFlow, backTo, isMultiLevelTags, showOverrideMultiTagModal, showSwitchSingleToMultiLevelTagWarningModal]); + }, [hasVisibleTags, policyID, isQuickSettingsFlow, backTo, isMultiLevelTags]); useFocusEffect( useCallback(() => { @@ -246,7 +238,24 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { onPress={() => { setImportedSpreadsheetIsImportingMultiLevelTags(false); if (hasVisibleTags) { - showSwitchSingleToMultiLevelTagWarningModal(); + showConfirmModal({ + title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + prompt: switchSingleToMultiLevelTagPrompt, + confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + cleanPolicyTags(policyID); + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } + }); } else { Navigation.navigate( isQuickSettingsFlow diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index 1ff91ee66011b..00b61380cdf26 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -75,42 +75,18 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { navigation.setParams({tagName: currentPolicyTag?.name}); }, [tagName, currentPolicyTag, navigation]); - // eslint-disable-next-line rulesdir/no-negated-variables - const showCannotDeleteOrDisableLastTagModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), - prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - const showDeleteTagModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.deleteTag'), - prompt: translate('workspace.tags.deleteTagConfirmation'), - confirmText: translate('common.delete'), - cancelText: translate('common.cancel'), - danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } - if (!currentPolicyTag?.name) { - return; - } - deletePolicyTags(policyData, [currentPolicyTag.name]); - Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); - }); - }, [showConfirmModal, translate, currentPolicyTag?.name, policyData, isQuickSettingsFlow, policyID, backTo]); - if (!currentPolicyTag) { return ; } const updateWorkspaceTagEnabled = (value: boolean) => { if (shouldPreventDisableOrDelete) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setWorkspaceTagEnabled(policyData, {[currentPolicyTag.name]: {name: currentPolicyTag.name, enabled: value}}, policyTag.orderWeight); @@ -248,10 +224,30 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { title={translate('common.delete')} onPress={() => { if (shouldPreventDisableOrDelete) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } - showDeleteTagModal(); + showConfirmModal({ + title: translate('workspace.tags.deleteTag'), + prompt: translate('workspace.tags.deleteTagConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + if (!currentPolicyTag?.name) { + return; + } + deletePolicyTags(policyData, [currentPolicyTag.name]); + Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); + }); }} /> )} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index c941878dcf032..3ae8aa2a94018 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -212,26 +212,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { [policyData], ); - // eslint-disable-next-line rulesdir/no-negated-variables - const showCannotDeleteOrDisableLastTagModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), - prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - // eslint-disable-next-line rulesdir/no-negated-variables - const showCannotMakeLastTagOptionalModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), - prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - const tagList = useMemo(() => { if (isMultiLevelTags) { return policyTagLists.map((policyTagList) => { @@ -260,7 +240,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.requiresTag')} onToggle={(newValue: boolean) => { if (isMakingLastRequiredTagListOptional(policy, policyTags, [policyTagList])) { - showCannotMakeLastTagOptionalModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } @@ -289,7 +274,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), [tag])) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -298,18 +288,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { /> ), })); - }, [ - isMultiLevelTags, - policyTagLists, - hasDependentTags, - translate, - policy, - policyTags, - updateWorkspaceRequiresTag, - updateWorkspaceTagEnabled, - showCannotDeleteOrDisableLastTagModal, - showCannotMakeLastTagOptionalModal, - ]); + }, [isMultiLevelTags, policyTagLists, hasDependentTags, translate, policy, policyTags, updateWorkspaceRequiresTag, updateWorkspaceTagEnabled]); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { const results = tokenizedSearch([tag], searchInput, (option) => [option.text ?? '', option.value ?? '']); @@ -407,37 +386,18 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { }); }, [policyData, selectedTags, isMobileSelectionModeEnabled, policyTagLists, setSelectedTags]); - const showDeleteTagsModal = useCallback(() => { - showConfirmModal({ - title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), - prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), - confirmText: translate('common.delete'), - cancelText: translate('common.cancel'), - danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } - deleteTags(); - }); - }, [showConfirmModal, translate, selectedTags.length, deleteTags]); - const isLoading = !isOffline && policyTags === undefined; const hasVisibleTags = tagList.some((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline); - const showOfflineModal = useCallback(() => { - showConfirmModal({ - title: translate('common.youAppearToBeOffline'), - prompt: translate('common.thisFeatureRequiresInternet'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - shouldHandleNavigationBack: true, - }); - }, [showConfirmModal, translate]); - const navigateToImportSpreadsheet = useCallback(() => { if (isOffline) { - showOfflineModal(); + showConfirmModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.thisFeatureRequiresInternet'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + shouldHandleNavigationBack: true, + }); return; } Navigation.navigate( @@ -445,7 +405,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT_OPTIONS.getRoute(policyID), ); - }, [backTo, isOffline, isQuickSettingsFlow, policyID, showOfflineModal]); + }, [backTo, isOffline, isQuickSettingsFlow, policyID]); const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); const secondaryActions = useMemo(() => { @@ -472,7 +432,13 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { text: translate('spreadsheet.downloadCSV'), onSelected: () => { if (isOffline) { - showOfflineModal(); + showConfirmModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.thisFeatureRequiresInternet'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + shouldHandleNavigationBack: true, + }); return; } close(() => { @@ -501,19 +467,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } return menuItems; - }, [ - translate, - navigateToTagsSettings, - hasAccountingConnections, - hasVisibleTags, - navigateToImportSpreadsheet, - isOffline, - isMultiLevelTags, - policyID, - hasDependentTags, - expensifyIcons, - showOfflineModal, - ]); + }, [translate, navigateToTagsSettings, hasAccountingConnections, hasVisibleTags, navigateToImportSpreadsheet, isOffline, isMultiLevelTags, policyID, hasDependentTags, expensifyIcons]); const getHeaderButtons = () => { const selectedTagsObject = selectedTags.map((key) => policyTagLists.at(0)?.tags?.[key]); @@ -554,11 +508,27 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } - showDeleteTagsModal(); + showConfirmModal({ + title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), + prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + deleteTags(); + }); }, }); } @@ -590,7 +560,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); @@ -636,7 +611,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.REQUIRE, onSelected: () => { if (isMakingLastRequiredTagListOptional(policy, policyTags, selectedTagLists)) { - showCannotMakeLastTagOptionalModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index c7137ca8c10a8..7f3cad4ef7e76 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -117,54 +117,6 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { [policyData, orderWeight], ); - // eslint-disable-next-line rulesdir/no-negated-variables - const showCannotDeleteOrDisableLastTagModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), - prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - // eslint-disable-next-line rulesdir/no-negated-variables - const showCannotMakeAllTagsOptionalModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), - prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - const showRequiresMultiLevelTagsModal = useCallback(() => { - showConfirmModal({ - title: translate('workspace.tags.cannotMakeTagListRequired.title'), - prompt: translate('workspace.tags.cannotMakeTagListRequired.description'), - confirmText: translate('common.buttonConfirm'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - const showDeleteTagsModal = useCallback(() => { - showConfirmModal({ - title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), - prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), - confirmText: translate('common.delete'), - cancelText: translate('common.cancel'), - danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } - deletePolicyTags(policyData, selectedTags); - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - setSelectedTags([]); - }); - }); - }, [showConfirmModal, translate, selectedTags, policyData]); - const tagList = useMemo( () => Object.values(currentPolicyTag?.tags ?? {}).map((tag) => ({ @@ -186,7 +138,12 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, [tag])) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -195,7 +152,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { /> ), })), - [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled, showCannotDeleteOrDisableLastTagModal], + [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled], ); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { @@ -283,7 +240,24 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { icon: expensifyIcons.Trashcan, text: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, - onSelected: () => showDeleteTagsModal(), + onSelected: () => { + showConfirmModal({ + title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), + prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + deletePolicyTags(policyData, selectedTags); + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + setSelectedTags([]); + }); + }); + }, }); } @@ -315,7 +289,12 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, selectedTagsObject)) { - showCannotDeleteOrDisableLastTagModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); @@ -399,11 +378,21 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { isActive={!!currentPolicyTag?.required} onToggle={(on) => { if (!isMultiLevelTags) { - showRequiresMultiLevelTagsModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeTagListRequired.title'), + prompt: translate('workspace.tags.cannotMakeTagListRequired.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } if (isMakingLastRequiredTagListOptional(policy, policyTags, [currentPolicyTag])) { - showCannotMakeAllTagsOptionalModal(); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setPolicyTagsRequired(policyData, on, orderWeight); From 1b41ef27b6952f2d30dcf510a9bf555eba26d650 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 19 Dec 2025 12:24:30 +0700 Subject: [PATCH 5/8] fix lint --- src/pages/workspace/tags/TagSettingsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index 00b61380cdf26..de0894bc60c2c 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; From f5732d5fdb93258e206ecfd34da670996a5f699f Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 26 Jan 2026 23:21:14 +0700 Subject: [PATCH 6/8] resolve commets --- .../workspace/tags/ImportTagsOptionsPage.tsx | 148 +++++++++--------- src/pages/workspace/tags/TagSettingsPage.tsx | 12 +- .../workspace/tags/WorkspaceTagsPage.tsx | 29 ++-- .../workspace/tags/WorkspaceViewTagsPage.tsx | 14 +- 4 files changed, 105 insertions(+), 98 deletions(-) diff --git a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx index b4b6e7a57a3df..2e3bf9b680308 100644 --- a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx +++ b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx @@ -111,91 +111,92 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { [translate, isMultiLevelTags, policyID, hasDependentTags], ); - const switchSingleToMultiLevelTagPrompt = ( - - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt1')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt2')} - { - if (isMultiLevelTags) { - downloadMultiLevelTagsCSV( - policyID, - () => { - close(() => { - setIsDownloadFailureModalVisible(true); - }); - }, - hasDependentTags, - translate, - ); - } else { - downloadTagsCSV( - policyID, - () => { - close(() => { - setIsDownloadFailureModalVisible(true); - }); - }, - translate, - ); - } - }} - > - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt3')} - - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt4')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt5')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt6')} - + const switchSingleToMultiLevelTagPrompt = useMemo( + () => ( + + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt1')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt2')} + { + if (isMultiLevelTags) { + downloadMultiLevelTagsCSV( + policyID, + () => { + close(() => { + setIsDownloadFailureModalVisible(true); + }); + }, + hasDependentTags, + translate, + ); + } else { + downloadTagsCSV( + policyID, + () => { + close(() => { + setIsDownloadFailureModalVisible(true); + }); + }, + translate, + ); + } + }} + > + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt3')} + + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt4')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt5')} + {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt6')} + + ), + [translate, policyID, hasDependentTags, isMultiLevelTags], ); - const startMultiLevelTagImportFlow = useCallback(() => { + const startMultiLevelTagImportFlow = useCallback(async () => { setImportedSpreadsheetIsImportingMultiLevelTags(true); if (hasVisibleTags) { if (isMultiLevelTags) { - showConfirmModal({ + const {action} = await showConfirmModal({ title: translate('workspace.tags.overrideMultiTagWarning.title'), prompt: overrideMultiTagPrompt, confirmText: translate('workspace.tags.overrideMultiTagWarning.title'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action === ModalActions.CONFIRM) { - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } else { - setImportedSpreadsheetIsImportingMultiLevelTags(false); - } }); + if (action === ModalActions.CONFIRM) { + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } } else { - showConfirmModal({ + const {action} = await showConfirmModal({ title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), prompt: switchSingleToMultiLevelTagPrompt, confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action === ModalActions.CONFIRM) { - cleanPolicyTags(policyID); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } else { - setImportedSpreadsheetIsImportingMultiLevelTags(false); - } }); + if (action === ModalActions.CONFIRM) { + cleanPolicyTags(policyID); + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } } } else { Navigation.navigate( isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), ); } - }, [hasVisibleTags, policyID, isQuickSettingsFlow, backTo, isMultiLevelTags]); + }, [hasVisibleTags, isMultiLevelTags, showConfirmModal, translate, overrideMultiTagPrompt, isQuickSettingsFlow, policyID, backTo, switchSingleToMultiLevelTagPrompt]); useFocusEffect( useCallback(() => { @@ -236,27 +237,26 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { title={translate('workspace.tags.tagLevel.singleLevel')} icon={expensifyIcons.Tag} shouldShowRightIcon - onPress={() => { + onPress={async () => { setImportedSpreadsheetIsImportingMultiLevelTags(false); if (hasVisibleTags && isMultiLevelTags) { - showConfirmModal({ + const {action} = await showConfirmModal({ title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), prompt: switchSingleToMultiLevelTagPrompt, confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action === ModalActions.CONFIRM) { - cleanPolicyTags(policyID); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); - } else { - setImportedSpreadsheetIsImportingMultiLevelTags(false); - } }); + if (action === ModalActions.CONFIRM) { + cleanPolicyTags(policyID); + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + } else { + setImportedSpreadsheetIsImportingMultiLevelTags(false); + } } else { Navigation.navigate( isQuickSettingsFlow diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index 0dbda8f3023fc..9ca9c0f9a9229 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -217,7 +217,7 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { { + onPress={async () => { if (shouldPreventDisableOrDelete) { showConfirmModal({ title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), @@ -227,22 +227,20 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { }); return; } - showConfirmModal({ + const {action} = await showConfirmModal({ title: translate('workspace.tags.deleteTag'), prompt: translate('workspace.tags.deleteTagConfirmation'), confirmText: translate('common.delete'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } + }); + if (action === ModalActions.CONFIRM) { if (!currentPolicyTag?.name) { return; } deletePolicyTags(policyData, [currentPolicyTag.name]); Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); - }); + } }} /> )} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 224e29731967e..595f64c34f878 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -339,6 +339,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { glCodeContainerStyle, glCodeTextStyle, switchContainerStyle, + showConfirmModal, ]); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { @@ -473,7 +474,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) : ROUTES.WORKSPACE_TAGS_IMPORT_OPTIONS.getRoute(policyID), ); - }, [backTo, isOffline, isQuickSettingsFlow, policyID]); + }, [backTo, isOffline, isQuickSettingsFlow, policyID, showConfirmModal, translate]); const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); const secondaryActions = useMemo(() => { @@ -535,7 +536,19 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } return menuItems; - }, [translate, navigateToTagsSettings, hasAccountingConnections, hasVisibleTags, navigateToImportSpreadsheet, isOffline, isMultiLevelTags, policyID, hasDependentTags, expensifyIcons]); + }, [ + translate, + navigateToTagsSettings, + hasAccountingConnections, + hasVisibleTags, + navigateToImportSpreadsheet, + isOffline, + isMultiLevelTags, + policyID, + hasDependentTags, + expensifyIcons, + showConfirmModal, + ]); const getHeaderButtons = () => { const selectedTagsObject = selectedTags.map((key) => policyTagLists.at(0)?.tags?.[key]); @@ -574,7 +587,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { icon: expensifyIcons.Trashcan, text: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, - onSelected: () => { + onSelected: async () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { showConfirmModal({ title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), @@ -585,18 +598,16 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { return; } - showConfirmModal({ + const {action} = await showConfirmModal({ title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), confirmText: translate('common.delete'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } - deleteTags(); }); + if (action === ModalActions.CONFIRM) { + deleteTags(); + } }, }); } diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index ec3833a4745ad..8b646df0143da 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -152,7 +152,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { /> ), })), - [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled], + [currentPolicyTag, hasDependentTags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled, showConfirmModal], ); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { @@ -240,23 +240,21 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { icon: icons.Trashcan, text: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, - onSelected: () => { - showConfirmModal({ + onSelected: async () => { + const {action} = await showConfirmModal({ title: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags'), prompt: translate(selectedTags.length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation'), confirmText: translate('common.delete'), cancelText: translate('common.cancel'), danger: true, - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } + }); + if (action === ModalActions.CONFIRM) { deletePolicyTags(policyData, selectedTags); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { setSelectedTags([]); }); - }); + } }, }); } From d632377124fd23995fc64976efbf4d3f7cbb9827 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 28 Jan 2026 15:45:52 +0700 Subject: [PATCH 7/8] fix navigate to import page --- .../workspace/tags/ImportTagsOptionsPage.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx index 2e3bf9b680308..4f8cc743bbaa0 100644 --- a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx +++ b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx @@ -182,11 +182,13 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { }); if (action === ModalActions.CONFIRM) { cleanPolicyTags(policyID); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); + Navigation.setNavigationActionToMicrotaskQueue(() => { + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + }); } else { setImportedSpreadsheetIsImportingMultiLevelTags(false); } @@ -249,11 +251,13 @@ function ImportTagsOptionsPage({route}: ImportTagsOptionsPageProps) { }); if (action === ModalActions.CONFIRM) { cleanPolicyTags(policyID); - Navigation.navigate( - isQuickSettingsFlow - ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) - : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), - ); + Navigation.setNavigationActionToMicrotaskQueue(() => { + Navigation.navigate( + isQuickSettingsFlow + ? ROUTES.SETTINGS_TAGS_IMPORT.getRoute(policyID, ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo)) + : ROUTES.WORKSPACE_TAGS_IMPORT.getRoute(policyID), + ); + }); } else { setImportedSpreadsheetIsImportingMultiLevelTags(false); } From 095fe2970c5d535c963b3baf1f7e5277bbd51a8a Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 4 Feb 2026 02:42:46 +0700 Subject: [PATCH 8/8] remove useless useCallback --- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index e8314165d3340..a7dff2b64057c 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -445,7 +445,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { } }; - const deleteTags = useCallback(() => { + const deleteTags = () => { deletePolicyTags(policyData, selectedTags); // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -455,7 +455,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { turnOffMobileSelectionMode(); } }); - }, [policyData, selectedTags, isMobileSelectionModeEnabled, policyTagLists, setSelectedTags]); + }; const isLoading = !isOffline && policyTags === undefined; const hasVisibleTags = tagList.some((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline);