diff --git a/src/pages/workspace/tags/ImportTagsOptionsPage.tsx b/src/pages/workspace/tags/ImportTagsOptionsPage.tsx index 5b5c80f754722..4f8cc743bbaa0 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,52 @@ 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, + translate, + ); + } else { + downloadTagsCSV( + policyID, + () => { + close(() => { + setIsDownloadFailureModalVisible(true); + }); + }, + translate, + ); + } + }} + > + {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) { @@ -131,51 +142,79 @@ 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, policyID, hasDependentTags, isMultiLevelTags], ); - 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, + const startMultiLevelTagImportFlow = useCallback(async () => { + setImportedSpreadsheetIsImportingMultiLevelTags(true); + if (hasVisibleTags) { + if (isMultiLevelTags) { + const {action} = await showConfirmModal({ + title: translate('workspace.tags.overrideMultiTagWarning.title'), + prompt: overrideMultiTagPrompt, + confirmText: translate('workspace.tags.overrideMultiTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }); + 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 { + const {action} = await showConfirmModal({ + title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + prompt: switchSingleToMultiLevelTagPrompt, + confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }); + if (action === ModalActions.CONFIRM) { + cleanPolicyTags(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), ); - } - }} - > - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt3')} - - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt4')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt5')} - {translate('workspace.tags.switchSingleToMultiLevelTagWarning.prompt6')} - + }); + } 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, isMultiLevelTags, showConfirmModal, translate, overrideMultiTagPrompt, isQuickSettingsFlow, policyID, backTo, switchSingleToMultiLevelTagPrompt]); + + useFocusEffect( + useCallback(() => { + if (!shouldRunPostUpgradeFlow || !isControlPolicy(policy)) { + return; + } + + startMultiLevelTagImportFlow(); + setShouldRunPostUpgradeFlow(false); + }, [shouldRunPostUpgradeFlow, policy, startMultiLevelTagImportFlow]), ); + if (hasAccountingConnections) { + return ; + } + return ( { + onPress={async () => { setImportedSpreadsheetIsImportingMultiLevelTags(false); if (hasVisibleTags && isMultiLevelTags) { - setIsSwitchSingleToMultipleLevelTagWarningModalVisible(true); + const {action} = await showConfirmModal({ + title: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + prompt: switchSingleToMultiLevelTagPrompt, + confirmText: translate('workspace.tags.switchSingleToMultiLevelTagWarning.title'), + cancelText: translate('common.cancel'), + danger: true, + }); + if (action === ModalActions.CONFIRM) { + cleanPolicyTags(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); + } } else { Navigation.navigate( isQuickSettingsFlow @@ -238,47 +295,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 693bea4618dfa..9ca9c0f9a9229 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -1,14 +1,14 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect} 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 = getTagListByOrderWeight(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 tagApprover = getTagApproverRule(policy, route.params?.tagName)?.approver ?? ''; const approver = getPersonalDetailByEmail(tagApprover); @@ -75,15 +74,14 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { 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); + 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); @@ -148,26 +146,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 && ( @@ -237,14 +215,32 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { {shouldShowDeleteMenuItem && ( { + onPress={async () => { if (shouldPreventDisableOrDelete) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } - setIsDeleteTagModalOpen(true); + const {action} = await showConfirmModal({ + title: translate('workspace.tags.deleteTag'), + prompt: translate('workspace.tags.deleteTagConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }); + 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 957340630af4c..a7dff2b64057c 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -4,13 +4,13 @@ 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 EmployeesSeeTagsAsText from '@components/EmployeesSeeTagsAsText'; 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'; @@ -23,6 +23,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'; @@ -87,11 +88,8 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); 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; @@ -251,7 +249,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.requiresTag')} onToggle={(newValue: boolean) => { if (isMakingLastRequiredTagListOptional(policy, policyTags, [policyTagList])) { - setIsCannotMakeLastTagOptionalModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } @@ -290,7 +293,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), [tag])) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -306,7 +314,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), [tag])) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -328,6 +341,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { glCodeContainerStyle, glCodeTextStyle, switchContainerStyle, + showConfirmModal, ]); const filterTag = useCallback((tag: TagListItem, searchInput: string) => { @@ -433,7 +447,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const deleteTags = () => { deletePolicyTags(policyData, selectedTags); - setIsDeleteTagsConfirmModalVisible(false); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { @@ -449,7 +462,13 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const navigateToImportSpreadsheet = useCallback(() => { if (isOffline) { - close(() => setIsOfflineModalVisible(true)); + showConfirmModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.thisFeatureRequiresInternet'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + shouldHandleNavigationBack: true, + }); return; } Navigation.navigate( @@ -457,7 +476,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(() => { @@ -484,7 +503,13 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { text: translate('spreadsheet.downloadCSV'), onSelected: () => { if (isOffline) { - close(() => setIsOfflineModalVisible(true)); + showConfirmModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.thisFeatureRequiresInternet'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + shouldHandleNavigationBack: true, + }); return; } close(() => { @@ -513,7 +538,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]); @@ -552,13 +589,27 @@ 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)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } - setIsDeleteTagsConfirmModalVisible(true); + 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, + }); + if (action === ModalActions.CONFIRM) { + deleteTags(); + } }, }); } @@ -590,7 +641,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(policyTagLists.at(0), selectedTagsObject)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); @@ -636,7 +692,12 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { value: CONST.POLICY.BULK_ACTION_TYPES.REQUIRE, onSelected: () => { if (isMakingLastRequiredTagListOptional(policy, policyTags, selectedTagLists)) { - setIsCannotMakeLastTagOptionalModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); @@ -833,16 +894,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 6b7aa4e1d0726..8b646df0143da 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -1,12 +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 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'; @@ -15,6 +15,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import ListItemRightCaretWithLabel from '@components/SelectionListWithModal/ListItemRightCaretWithLabel'; 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'; @@ -65,21 +66,17 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { const styles = useThemeStyles(); const icons = useMemoizedLazyExpensifyIcons(['Close', 'Checkmark', 'Trashcan']); 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 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]); @@ -141,7 +138,12 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { accessibilityLabel={translate('workspace.tags.enableTag')} onToggle={(newValue: boolean) => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, [tag])) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } updateWorkspaceTagEnabled(newValue, tag.name); @@ -150,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) => { @@ -213,16 +215,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 = @@ -248,7 +240,22 @@ 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: () => setIsDeleteTagsConfirmModalVisible(true), + 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, + }); + if (action === ModalActions.CONFIRM) { + deletePolicyTags(policyData, selectedTags); + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + setSelectedTags([]); + }); + } + }, }); } @@ -280,7 +287,12 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { value: CONST.POLICY.BULK_ACTION_TYPES.DISABLE, onSelected: () => { if (isDisablingOrDeletingLastEnabledTag(currentPolicyTag, selectedTagsObject)) { - setIsCannotDeleteOrDisableLastTagModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotDeleteOrDisableAllTags.title'), + prompt: translate('workspace.tags.cannotDeleteOrDisableAllTags.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setSelectedTags([]); @@ -356,16 +368,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); + 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])) { - setIsCannotMakeAllTagsOptionalModalVisible(true); + showConfirmModal({ + title: translate('workspace.tags.cannotMakeAllTagsOptional.title'), + prompt: translate('workspace.tags.cannotMakeAllTagsOptional.description'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } setPolicyTagsRequired(policyData, on, orderWeight); @@ -433,33 +445,6 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { showScrollIndicator /> )} - 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} - /> ); 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( - - - - - + + + + + + + , );