From 0a78fbedcb50a4966fc81925c02ff3e54bf5ff29 Mon Sep 17 00:00:00 2001 From: shahid Date: Sat, 4 Jan 2025 01:37:37 +0530 Subject: [PATCH 1/7] Add feature to delete unverified contact methods --- .../ValidateCodeActionModal/index.tsx | 9 +++ .../ValidateCodeActionModal/type.ts | 7 +++ .../Contacts/ContactMethodDetailsPage.tsx | 59 +++++++++++++------ 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx index c25401d33494..ff7bf75aaec2 100644 --- a/src/components/ValidateCodeActionModal/index.tsx +++ b/src/components/ValidateCodeActionModal/index.tsx @@ -6,6 +6,7 @@ import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -29,10 +30,14 @@ function ValidateCodeActionModal({ hasMagicCodeBeenSent, isLoading, shouldHandleNavigationBack, + shouldShowThreeDotsButton = false, + threeDotsMenuItems = [], }: ValidateCodeActionModalProps) { const themeStyles = useThemeStyles(); const firstRenderRef = useRef(true); const validateCodeFormRef = useRef(null); + const styles = useThemeStyles(); + const {windowWidth} = useWindowDimensions(); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); @@ -76,6 +81,10 @@ function ValidateCodeActionModal({ diff --git a/src/components/ValidateCodeActionModal/type.ts b/src/components/ValidateCodeActionModal/type.ts index aed82f0fa525..32a2a9b60752 100644 --- a/src/components/ValidateCodeActionModal/type.ts +++ b/src/components/ValidateCodeActionModal/type.ts @@ -1,4 +1,5 @@ import type React from 'react'; +import type {PopoverMenuItem} from '@components/PopoverMenu'; import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; type ValidateCodeActionModalProps = { @@ -46,6 +47,12 @@ type ValidateCodeActionModalProps = { /** Whether handle navigation back when modal show. */ shouldHandleNavigationBack?: boolean; + + /** List of menu items for more(three dots) menu */ + threeDotsMenuItems?: PopoverMenuItem[]; + + /** Whether we should show a more options (threedots) button */ + shouldShowThreeDotsButton?: boolean; }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index e94042b25e68..09e4859825d9 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard} from 'react-native'; +import {InteractionManager, Keyboard, Platform} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -216,22 +216,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { /> )} - - toggleDeleteModal(false)} - onModalHide={() => { - InteractionManager.runAfterInteractions(() => { - validateCodeFormRef.current?.focusLastSelected?.(); - }); - }} - prompt={translate('contacts.removeAreYouSure')} - confirmText={translate('common.yesContinue')} - cancelText={translate('common.cancel')} - isVisible={isDeleteModalOpen && !isDefaultContactMethod} - danger - /> ); @@ -273,9 +257,48 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { }} sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} + threeDotsMenuItems={[ + { + icon: Expensicons.Trashcan, + text: translate('common.remove'), + onSelected: () => { + if (Platform.OS === 'ios') { + setIsValidateCodeActionModalVisible(false); + } + toggleDeleteModal(true); + }, + }, + ]} + shouldShowThreeDotsButton={isValidateCodeActionModalVisible} /> - {!isValidateCodeActionModalVisible && getMenuItems()} + {!isValidateCodeActionModalVisible && !!loginData && !!loginData.validatedDate && getMenuItems()} + { + toggleDeleteModal(false); + // On iOS, if the secondary login is not validated, reopen the ValidateCodeActionModal + if (Platform.OS === 'ios' && !!loginData && !loginData.validatedDate) { + // Ensure the ConfirmModal is fully closed before showing the ValidateCodeActionModal + InteractionManager.runAfterInteractions(() => { + setTimeout(() => { + setIsValidateCodeActionModalVisible(true); + }, 0); + }); + } + }} + onModalHide={() => { + InteractionManager.runAfterInteractions(() => { + validateCodeFormRef.current?.focusLastSelected?.(); + }); + }} + prompt={translate('contacts.removeAreYouSure')} + confirmText={translate('common.yesContinue')} + cancelText={translate('common.cancel')} + isVisible={isDeleteModalOpen && !isDefaultContactMethod} + danger + /> ); From a88ac9656d903176488adb75776a582ad3781466 Mon Sep 17 00:00:00 2001 From: shahid Date: Wed, 8 Jan 2025 04:03:31 +0530 Subject: [PATCH 2/7] Fix iOS modal issue by nesting ConfirmModal inside ValidateCodeActionModal --- .../Contacts/ContactMethodDetailsPage.tsx | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 09e4859825d9..6a367fe61440 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, Platform} from 'react-native'; +import {InteractionManager, Keyboard} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -177,6 +177,24 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const isFailedAddContactMethod = !!loginData.errorFields?.addedLogin; const isFailedRemovedContactMethod = !!loginData.errorFields?.deletedLogin; + const getDeleteConfirmationModal = () => ( + toggleDeleteModal(false)} + onModalHide={() => { + InteractionManager.runAfterInteractions(() => { + validateCodeFormRef.current?.focusLastSelected?.(); + }); + }} + prompt={translate('contacts.removeAreYouSure')} + confirmText={translate('common.yesContinue')} + cancelText={translate('common.cancel')} + isVisible={isDeleteModalOpen && !isDefaultContactMethod} + danger + /> + ); + const getMenuItems = () => ( <> {canChangeDefaultContactMethod ? ( @@ -216,6 +234,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { /> )} + {getDeleteConfirmationModal()} ); @@ -257,48 +276,18 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { }} sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} + shouldShowThreeDotsButton={isValidateCodeActionModalVisible} threeDotsMenuItems={[ { icon: Expensicons.Trashcan, text: translate('common.remove'), - onSelected: () => { - if (Platform.OS === 'ios') { - setIsValidateCodeActionModalVisible(false); - } - toggleDeleteModal(true); - }, + onSelected: () => toggleDeleteModal(true), }, ]} - shouldShowThreeDotsButton={isValidateCodeActionModalVisible} + footer={getDeleteConfirmationModal} /> - {!isValidateCodeActionModalVisible && !!loginData && !!loginData.validatedDate && getMenuItems()} - { - toggleDeleteModal(false); - // On iOS, if the secondary login is not validated, reopen the ValidateCodeActionModal - if (Platform.OS === 'ios' && !!loginData && !loginData.validatedDate) { - // Ensure the ConfirmModal is fully closed before showing the ValidateCodeActionModal - InteractionManager.runAfterInteractions(() => { - setTimeout(() => { - setIsValidateCodeActionModalVisible(true); - }, 0); - }); - } - }} - onModalHide={() => { - InteractionManager.runAfterInteractions(() => { - validateCodeFormRef.current?.focusLastSelected?.(); - }); - }} - prompt={translate('contacts.removeAreYouSure')} - confirmText={translate('common.yesContinue')} - cancelText={translate('common.cancel')} - isVisible={isDeleteModalOpen && !isDefaultContactMethod} - danger - /> + {!isValidateCodeActionModalVisible && getMenuItems()} ); From 715d0479f9b395907283e2638767c44596115e4f Mon Sep 17 00:00:00 2001 From: shahid Date: Wed, 8 Jan 2025 21:06:44 +0530 Subject: [PATCH 3/7] Dismiss keyboard when user clicks three-dot menu --- src/components/ValidateCodeActionModal/index.tsx | 2 ++ src/components/ValidateCodeActionModal/type.ts | 3 +++ .../settings/Profile/Contacts/ContactMethodDetailsPage.tsx | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx index ff7bf75aaec2..994c7e21e3d3 100644 --- a/src/components/ValidateCodeActionModal/index.tsx +++ b/src/components/ValidateCodeActionModal/index.tsx @@ -32,6 +32,7 @@ function ValidateCodeActionModal({ shouldHandleNavigationBack, shouldShowThreeDotsButton = false, threeDotsMenuItems = [], + onThreeDotsButtonPress = () => {}, }: ValidateCodeActionModalProps) { const themeStyles = useThemeStyles(); const firstRenderRef = useRef(true); @@ -85,6 +86,7 @@ function ValidateCodeActionModal({ shouldShowThreeDotsButton={shouldShowThreeDotsButton} shouldOverlayDots threeDotsAnchorPosition={styles.threeDotsPopoverOffset(windowWidth)} + onThreeDotsButtonPress={onThreeDotsButtonPress} /> diff --git a/src/components/ValidateCodeActionModal/type.ts b/src/components/ValidateCodeActionModal/type.ts index 32a2a9b60752..39d0070b2761 100644 --- a/src/components/ValidateCodeActionModal/type.ts +++ b/src/components/ValidateCodeActionModal/type.ts @@ -53,6 +53,9 @@ type ValidateCodeActionModalProps = { /** Whether we should show a more options (threedots) button */ shouldShowThreeDotsButton?: boolean; + + /** Method to trigger when pressing more options button of the header */ + onThreeDotsButtonPress?: () => void; }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 6a367fe61440..63f9f0d467be 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -19,6 +19,7 @@ import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import blurActiveElement from '@libs/Accessibility/blurActiveElement'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -277,6 +278,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} shouldShowThreeDotsButton={isValidateCodeActionModalVisible} + onThreeDotsButtonPress={() => blurActiveElement()} threeDotsMenuItems={[ { icon: Expensicons.Trashcan, From 874605e266cbb5d1eaaccd6a952cbd6cf50d7be7 Mon Sep 17 00:00:00 2001 From: shahid Date: Fri, 10 Jan 2025 20:18:41 +0530 Subject: [PATCH 4/7] apply blurActiveElement() for web, Keyboard.dismiss() for native apps --- .../Profile/Contacts/ContactMethodDetailsPage.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 63f9f0d467be..69d0bbaa3ad2 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard} from 'react-native'; +import {InteractionManager, Keyboard, Platform} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -278,7 +278,13 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} shouldShowThreeDotsButton={isValidateCodeActionModalVisible} - onThreeDotsButtonPress={() => blurActiveElement()} + onThreeDotsButtonPress={() => { + if (Platform.OS === 'web') { + blurActiveElement(); + } else { + Keyboard.dismiss(); + } + }} threeDotsMenuItems={[ { icon: Expensicons.Trashcan, From 9ccb8bfd60d50f77c676bc9f79c3fe5f485dfad8 Mon Sep 17 00:00:00 2001 From: shahid Date: Sat, 11 Jan 2025 06:44:27 +0530 Subject: [PATCH 5/7] Hide keyboard when user clicks on three-dot menue, use blurActiveElement for web and KeyboardUtils.dismiss for native --- .../Profile/Contacts/ContactMethodDetailsPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 69d0bbaa3ad2..db2ba9c01ec7 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, Platform} from 'react-native'; +import {InteractionManager, Keyboard} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -33,6 +33,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import KeyboardUtils from '@src/utils/keyboard'; import type {ValidateCodeFormHandle} from './ValidateCodeForm/BaseValidateCodeForm'; type ContactMethodDetailsPageProps = PlatformStackScreenProps; @@ -279,11 +280,10 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} shouldShowThreeDotsButton={isValidateCodeActionModalVisible} onThreeDotsButtonPress={() => { - if (Platform.OS === 'web') { - blurActiveElement(); - } else { - Keyboard.dismiss(); - } + // Hide the keyboard when the user clicks the three-dot menu. + // Use blurActiveElement() for mWeb and KeyboardUtils.dismiss() for native apps. + blurActiveElement(); + KeyboardUtils.dismiss(); }} threeDotsMenuItems={[ { From 32da5c6689e22bbe2a79d3af30347aaa4c1b0b6e Mon Sep 17 00:00:00 2001 From: shahid Date: Sun, 12 Jan 2025 23:26:12 +0530 Subject: [PATCH 6/7] Fix modal stacking issue by using Modal.close before opening delete confirmation modal --- .../settings/Profile/Contacts/ContactMethodDetailsPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index db2ba9c01ec7..0baa9e8aacf9 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -26,6 +26,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {addSMSDomainIfPhoneNumber} from '@libs/PhoneNumber'; +import * as Modal from '@userActions/Modal'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -289,7 +290,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { { icon: Expensicons.Trashcan, text: translate('common.remove'), - onSelected: () => toggleDeleteModal(true), + onSelected: () => Modal.close(() => toggleDeleteModal(true)), }, ]} footer={getDeleteConfirmationModal} From 5413f30e8c74ca568d49f484dd520124cc35a9e3 Mon Sep 17 00:00:00 2001 From: shahid Date: Mon, 13 Jan 2025 22:31:16 +0530 Subject: [PATCH 7/7] use threeDotsMenuItems.length for showing the threeDotMenu --- .../ValidateCodeActionModal/index.tsx | 3 +-- .../ValidateCodeActionModal/type.ts | 3 --- .../Contacts/ContactMethodDetailsPage.tsx | 21 ++++++++++++------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx index 994c7e21e3d3..53d781997d44 100644 --- a/src/components/ValidateCodeActionModal/index.tsx +++ b/src/components/ValidateCodeActionModal/index.tsx @@ -30,7 +30,6 @@ function ValidateCodeActionModal({ hasMagicCodeBeenSent, isLoading, shouldHandleNavigationBack, - shouldShowThreeDotsButton = false, threeDotsMenuItems = [], onThreeDotsButtonPress = () => {}, }: ValidateCodeActionModalProps) { @@ -83,7 +82,7 @@ function ValidateCodeActionModal({ title={title} onBackButtonPress={hide} threeDotsMenuItems={threeDotsMenuItems} - shouldShowThreeDotsButton={shouldShowThreeDotsButton} + shouldShowThreeDotsButton={threeDotsMenuItems.length > 0} shouldOverlayDots threeDotsAnchorPosition={styles.threeDotsPopoverOffset(windowWidth)} onThreeDotsButtonPress={onThreeDotsButtonPress} diff --git a/src/components/ValidateCodeActionModal/type.ts b/src/components/ValidateCodeActionModal/type.ts index 39d0070b2761..4f70870ead3c 100644 --- a/src/components/ValidateCodeActionModal/type.ts +++ b/src/components/ValidateCodeActionModal/type.ts @@ -51,9 +51,6 @@ type ValidateCodeActionModalProps = { /** List of menu items for more(three dots) menu */ threeDotsMenuItems?: PopoverMenuItem[]; - /** Whether we should show a more options (threedots) button */ - shouldShowThreeDotsButton?: boolean; - /** Method to trigger when pressing more options button of the header */ onThreeDotsButtonPress?: () => void; }; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 0baa9e8aacf9..37fc4d26c7f4 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -157,6 +157,18 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { setIsValidateCodeActionModalVisible(!loginData?.validatedDate); }, [loginData?.validatedDate, loginData?.errorFields?.addedLogin]); + const getThreeDotsMenuItems = useCallback(() => { + const menuItems = []; + if (isValidateCodeActionModalVisible && !isDefaultContactMethod) { + menuItems.push({ + icon: Expensicons.Trashcan, + text: translate('common.remove'), + onSelected: () => Modal.close(() => toggleDeleteModal(true)), + }); + } + return menuItems; + }, [isValidateCodeActionModalVisible, translate, toggleDeleteModal, isDefaultContactMethod]); + if (isLoadingOnyxValues || (isLoadingReportData && isEmptyObject(loginList))) { return ; } @@ -279,20 +291,13 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { }} sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})} - shouldShowThreeDotsButton={isValidateCodeActionModalVisible} onThreeDotsButtonPress={() => { // Hide the keyboard when the user clicks the three-dot menu. // Use blurActiveElement() for mWeb and KeyboardUtils.dismiss() for native apps. blurActiveElement(); KeyboardUtils.dismiss(); }} - threeDotsMenuItems={[ - { - icon: Expensicons.Trashcan, - text: translate('common.remove'), - onSelected: () => Modal.close(() => toggleDeleteModal(true)), - }, - ]} + threeDotsMenuItems={getThreeDotsMenuItems()} footer={getDeleteConfirmationModal} />