From 14ffa198bab8d0d095a53eb04faab5ac0e3e0309 Mon Sep 17 00:00:00 2001 From: Getabalew Date: Thu, 19 Sep 2024 11:37:23 +0300 Subject: [PATCH 01/25] refactor: reuse ValidateCodeActionModal --- src/ROUTES.ts | 1 - src/SCREENS.ts | 1 - .../ValidateCodeForm/BaseValidateCodeForm.tsx | 15 +- .../ValidateCodeActionModal/index.tsx | 20 +- .../ValidateCodeActionModal/type.ts | 8 + .../ModalStackNavigators/index.tsx | 1 - .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 - src/libs/Navigation/linkingConfig/config.ts | 3 - src/libs/actions/Delegate.ts | 9 +- .../Contacts/ContactMethodDetailsPage.tsx | 185 ++++++++-------- .../Profile/Contacts/ContactMethodsPage.tsx | 7 +- .../Profile/Contacts/NewContactMethodPage.tsx | 1 + .../Contacts/ValidateContactActionPage.tsx | 72 ------ .../AddDelegate/ConfirmDelegatePage.tsx | 6 +- .../AddDelegate/DelegateMagicCodePage.tsx | 58 +++-- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 208 ------------------ .../ValidateCodeForm/index.android.tsx | 14 -- .../AddDelegate/ValidateCodeForm/index.tsx | 14 -- .../settings/Wallet/ExpensifyCardPage.tsx | 2 + 19 files changed, 163 insertions(+), 463 deletions(-) delete mode 100644 src/pages/settings/Profile/Contacts/ValidateContactActionPage.tsx delete mode 100644 src/pages/settings/Security/AddDelegate/ValidateCodeForm/BaseValidateCodeForm.tsx delete mode 100644 src/pages/settings/Security/AddDelegate/ValidateCodeForm/index.android.tsx delete mode 100644 src/pages/settings/Security/AddDelegate/ValidateCodeForm/index.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 27504998c49c..c9116f337f9e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -217,7 +217,6 @@ const ROUTES = { route: 'settings/profile/contact-methods/:contactMethod/details', getRoute: (contactMethod: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details`, backTo), }, - SETINGS_CONTACT_METHOD_VALIDATE_ACTION: 'settings/profile/contact-methods/validate-action', SETTINGS_NEW_CONTACT_METHOD: { route: 'settings/profile/contact-methods/new', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/profile/contact-methods/new', backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 8168afba89ab..66cc2b420f44 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -74,7 +74,6 @@ const SCREENS = { DISPLAY_NAME: 'Settings_Display_Name', CONTACT_METHODS: 'Settings_ContactMethods', CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails', - CONTACT_METHOD_VALIDATE_ACTION: 'Settings_ValidateContactMethodAction', NEW_CONTACT_METHOD: 'Settings_NewContactMethod', STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After', STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date', diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index 247c0c606901..f6df07278ad8 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -64,6 +64,8 @@ type ValidateCodeFormProps = { /** Function to clear error of the form */ clearError: () => void; + + sendValidateCode: () => void; }; type BaseValidateCodeFormProps = BaseValidateCodeFormOnyxProps & ValidateCodeFormProps; @@ -78,6 +80,7 @@ function BaseValidateCodeForm({ validateError, handleSubmitForm, clearError, + sendValidateCode, }: BaseValidateCodeFormProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -128,14 +131,6 @@ function BaseValidateCodeForm({ }, []), ); - useEffect(() => { - if (!validateError) { - return; - } - clearError(); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [clearError, validateError]); - useEffect(() => { if (!hasMagicCodeBeenSent) { return; @@ -147,7 +142,7 @@ function BaseValidateCodeForm({ * Request a validate code / magic code be sent to verify this contact method */ const resendValidateCode = () => { - User.requestValidateCodeAction(); + sendValidateCode(); inputValidateCodeRef.current?.clear(); }; @@ -196,7 +191,7 @@ function BaseValidateCodeForm({ errorText={formError?.validateCode ? translate(formError?.validateCode) : ErrorUtils.getLatestErrorMessage(account ?? {})} hasError={!isEmptyObject(validateError)} onFulfill={validateAndSubmitForm} - autoFocus={false} + autoFocus /> (null); @@ -30,7 +42,8 @@ function ValidateCodeActionModal({isVisible, title, description, onClose, valida return; } firstRenderRef.current = false; - User.requestValidateCodeAction(); + + sendValidateCode(); }, [isVisible]); return ( @@ -61,10 +74,13 @@ function ValidateCodeActionModal({isVisible, title, description, onClose, valida validatePendingAction={validatePendingAction} validateError={validateError} handleSubmitForm={handleSubmitForm} + sendValidateCode={sendValidateCode} clearError={clearError} ref={validateCodeFormRef} + hasMagicCodeBeenSent={hasMagicCodeBeenSent} /> + {footer} ); diff --git a/src/components/ValidateCodeActionModal/type.ts b/src/components/ValidateCodeActionModal/type.ts index 3cbfe62513d1..821f54ff0302 100644 --- a/src/components/ValidateCodeActionModal/type.ts +++ b/src/components/ValidateCodeActionModal/type.ts @@ -1,3 +1,4 @@ +import React from 'react'; import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; type ValidateCodeActionModalProps = { @@ -24,6 +25,13 @@ type ValidateCodeActionModalProps = { /** Function to clear error of the form */ clearError: () => void; + + footer?: React.JSX.Element; + + sendValidateCode: () => void; + + /** If the magic code has been resent previously */ + hasMagicCodeBeenSent?: boolean; }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b41b58530a6b..b24c6b3ea4f6 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -190,7 +190,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default, [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default, [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: () => require('../../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default, - [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VALIDATE_ACTION]: () => require('../../../../pages/settings/Profile/Contacts/ValidateContactActionPage').default, [SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: () => require('../../../../pages/settings/Profile/Contacts/NewContactMethodPage').default, [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: () => require('../../../../pages/settings/Preferences/PriorityModePage').default, [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: () => require('../../../../pages/workspace/accounting/PolicyAccountingPage').default, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 609162bedd13..3dc91a1bb530 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -6,7 +6,6 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = SCREENS.SETTINGS.PROFILE.DISPLAY_NAME, SCREENS.SETTINGS.PROFILE.CONTACT_METHODS, SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS, - SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VALIDATE_ACTION, SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD, SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER, SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 2ca2db10a1a7..abfa9625926f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -249,9 +249,6 @@ const config: LinkingOptions['config'] = { [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: { path: ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.route, }, - [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VALIDATE_ACTION]: { - path: ROUTES.SETINGS_CONTACT_METHOD_VALIDATE_ACTION, - }, [SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: { path: ROUTES.SETTINGS_NEW_CONTACT_METHOD.route, exact: true, diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 50d2ee7fc194..54165c4afa62 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -161,10 +161,6 @@ function clearDelegatorErrors() { Onyx.merge(ONYXKEYS.ACCOUNT, {delegatedAccess: {delegators: delegatedAccess.delegators.map((delegator) => ({...delegator, errorFields: undefined}))}}); } -function requestValidationCode() { - API.write(WRITE_COMMANDS.RESEND_VALIDATE_CODE, null); -} - function addDelegate(email: string, role: DelegateRole, validateCode: string) { const existingDelegate = delegatedAccess?.delegates?.find((delegate) => delegate.email === email); @@ -206,6 +202,7 @@ function addDelegate(email: string, role: DelegateRole, validateCode: string) { delegatedAccess: { delegates: optimisticDelegateData(), }, + isLoading: true, }, }, ]; @@ -250,6 +247,7 @@ function addDelegate(email: string, role: DelegateRole, validateCode: string) { delegatedAccess: { delegates: successDelegateData(), }, + isLoading: false, }, }, ]; @@ -292,6 +290,7 @@ function addDelegate(email: string, role: DelegateRole, validateCode: string) { delegatedAccess: { delegates: failureDelegateData(), }, + isLoading: false, }, }, ]; @@ -325,4 +324,4 @@ function removePendingDelegate(email: string) { }); } -export {connect, disconnect, clearDelegatorErrors, addDelegate, requestValidationCode, clearAddDelegateErrors, removePendingDelegate}; +export {connect, disconnect, clearDelegatorErrors, addDelegate, clearAddDelegateErrors, removePendingDelegate}; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 9fcc28f51912..e4751ebb0293 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -15,6 +15,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; +import ValidateCodeActionModal from '@components/ValidateCodeActionModal'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; @@ -25,6 +26,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; +import {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -35,12 +37,17 @@ import type {ValidateCodeFormHandle} from './ValidateCodeForm/BaseValidateCodeFo type ContactMethodDetailsPageProps = StackScreenProps; +type ValidateCodeFormError = { + validateCode?: TranslationPaths; +}; + function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const [loginList, loginListResult] = useOnyx(ONYXKEYS.LOGIN_LIST); const [session, sessionResult] = useOnyx(ONYXKEYS.SESSION); const [myDomainSecurityGroups, myDomainSecurityGroupsResult] = useOnyx(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS); const [securityGroups, securityGroupsResult] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP); const [isLoadingReportData, isLoadingReportDataResult] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); + const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(true); const isLoadingOnyxValues = isLoadingOnyxValue(loginListResult, sessionResult, myDomainSecurityGroupsResult, securityGroupsResult, isLoadingReportDataResult); @@ -75,6 +82,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { }, [route.params.contactMethod]); const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]); const isDefaultContactMethod = useMemo(() => session?.email === loginData?.partnerUserID, [session?.email, loginData?.partnerUserID]); + const validateLoginError = ErrorUtils.getEarliestErrorField(loginData, 'validateLogin'); /** * Attempt to set this contact method as user's "Default contact method" @@ -145,6 +153,10 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo)); }, [prevValidatedDate, loginData?.validatedDate, isDefaultContactMethod, backTo]); + useEffect(() => { + setIsValidateCodeActionModalVisible(!loginData?.validatedDate && !loginData?.errorFields?.addedLogin); + }, [loginData?.validatedDate, loginData?.errorFields?.addedLogin]); + if (isLoadingOnyxValues || (isLoadingReportData && isEmptyObject(loginList))) { return ; } @@ -168,100 +180,97 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const isFailedAddContactMethod = !!loginData.errorFields?.addedLogin; const isFailedRemovedContactMethod = !!loginData.errorFields?.deletedLogin; + const MenuItems = () => ( + <> + {canChangeDefaultContactMethod ? ( + User.clearContactMethodErrors(contactMethod, 'defaultLogin')} + > + + + ) : null} + {isDefaultContactMethod ? ( + User.clearContactMethodErrors(contactMethod, isFailedRemovedContactMethod ? 'deletedLogin' : 'defaultLogin')} + > + {translate('contacts.yourDefaultContactMethod')} + + ) : ( + User.clearContactMethodErrors(contactMethod, 'deletedLogin')} + > + toggleDeleteModal(true)} + /> + + )} + + ); + return ( - validateCodeFormRef.current?.focus?.()} - testID={ContactMethodDetailsPage.displayName} - > - Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo))} + + toggleDeleteModal(false)} + onModalHide={() => { + InteractionManager.runAfterInteractions(() => { + validateCodeFormRef.current?.focusLastSelected?.(); + }); + }} + prompt={translate('contacts.removeAreYouSure')} + confirmText={translate('common.yesContinue')} + cancelText={translate('common.cancel')} + isVisible={isDeleteModalOpen && !isDefaultContactMethod} + danger /> - - toggleDeleteModal(false)} - onModalHide={() => { - InteractionManager.runAfterInteractions(() => { - validateCodeFormRef.current?.focusLastSelected?.(); - }); + + {isFailedAddContactMethod && ( + { + User.clearContactMethod(contactMethod); + Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo)); }} - prompt={translate('contacts.removeAreYouSure')} - confirmText={translate('common.yesContinue')} - cancelText={translate('common.cancel')} - isVisible={isDeleteModalOpen && !isDefaultContactMethod} - danger + canDismissError /> + )} - {isFailedAddContactMethod && ( - { - User.clearContactMethod(contactMethod); - Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo)); - }} - canDismissError - /> - )} - - {!loginData.validatedDate && !isFailedAddContactMethod && ( - - + User.validateSecondaryLogin(loginList, contactMethod, validateCode)} + validateError={!isEmptyObject(validateLoginError) ? validateLoginError : ErrorUtils.getLatestErrorField(loginData, 'validateCodeSent')} + clearError={() => User.clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')} + onClose={() => { + Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo)); + setIsValidateCodeActionModalVisible(false); + }} + sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} + description={translate('contacts.enterMagicCode', {contactMethod})} + footer={} + /> - - - )} - {canChangeDefaultContactMethod ? ( - User.clearContactMethodErrors(contactMethod, 'defaultLogin')} - > - - - ) : null} - {isDefaultContactMethod ? ( - User.clearContactMethodErrors(contactMethod, isFailedRemovedContactMethod ? 'deletedLogin' : 'defaultLogin')} - > - {translate('contacts.yourDefaultContactMethod')} - - ) : ( - User.clearContactMethodErrors(contactMethod, 'deletedLogin')} - > - toggleDeleteModal(true)} - /> - - )} - - + {!isValidateCodeActionModalVisible && } + ); } diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx index 3f23b3a802be..cbe44ea648ca 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx @@ -81,12 +81,7 @@ function ContactMethodsPage({loginList, session, route}: ContactMethodsPageProps { - if (!login?.validatedDate && !login?.validateCodeSent) { - User.requestContactMethodValidateCode(loginName); - } - Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(partnerUserID, navigateBackTo)); - }} + onPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(partnerUserID, navigateBackTo))} brickRoadIndicator={indicator} shouldShowBasicTitle shouldShowRightIcon diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx index 4ea878e82987..6824b5988a62 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx @@ -153,6 +153,7 @@ function NewContactMethodPage({loginList, route}: NewContactMethodPageProps) { onClose={() => setIsValidateCodeActionModalVisible(false)} isVisible={isValidateCodeActionModalVisible} title={contactMethod} + sendValidateCode={() => User.requestValidateCodeAction()} description={translate('contacts.enterMagicCode', {contactMethod})} /> diff --git a/src/pages/settings/Profile/Contacts/ValidateContactActionPage.tsx b/src/pages/settings/Profile/Contacts/ValidateContactActionPage.tsx deleted file mode 100644 index 157588a67397..000000000000 --- a/src/pages/settings/Profile/Contacts/ValidateContactActionPage.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, {useEffect, useRef} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import DotIndicatorMessage from '@components/DotIndicatorMessage'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as User from '@libs/actions/User'; -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import ValidateCodeForm from './ValidateCodeForm'; -import type {ValidateCodeFormHandle} from './ValidateCodeForm/BaseValidateCodeForm'; - -function ValidateContactActionPage() { - const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const themeStyles = useThemeStyles(); - const {translate} = useLocalize(); - const validateCodeFormRef = useRef(null); - const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); - - const [pendingContactAction] = useOnyx(ONYXKEYS.PENDING_CONTACT_ACTION); - const loginData = loginList?.[pendingContactAction?.contactMethod ?? '']; - - useEffect(() => { - if (!loginData || !!loginData.pendingFields?.addedLogin) { - return; - } - - // Navigate to methods page on successful magic code verification - Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS.route); - }, [loginData, loginData?.pendingFields, loginList]); - - const onBackButtonPress = () => { - User.clearUnvalidatedNewContactMethodAction(); - Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.route); - }; - - return ( - - - - - - - - ); -} - -ValidateContactActionPage.displayName = 'ValidateContactActionPage'; - -export default ValidateContactActionPage; diff --git a/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx index 8c8292b1f320..f54c43b73726 100644 --- a/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx @@ -10,7 +10,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import {requestValidationCode} from '@libs/actions/Delegate'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -43,10 +42,7 @@ function ConfirmDelegatePage({route}: ConfirmDelegatePageProps) { text={translate('delegate.addCopilot')} style={styles.mt6} pressOnEnter - onPress={() => { - requestValidationCode(); - Navigation.navigate(ROUTES.SETTINGS_DELEGATE_MAGIC_CODE.getRoute(login, role)); - }} + onPress={() => Navigation.navigate(ROUTES.SETTINGS_DELEGATE_MAGIC_CODE.getRoute(login, role))} /> ); diff --git a/src/pages/settings/Security/AddDelegate/DelegateMagicCodePage.tsx b/src/pages/settings/Security/AddDelegate/DelegateMagicCodePage.tsx index 9497507f041a..603fa1e5aa02 100644 --- a/src/pages/settings/Security/AddDelegate/DelegateMagicCodePage.tsx +++ b/src/pages/settings/Security/AddDelegate/DelegateMagicCodePage.tsx @@ -1,33 +1,31 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useRef} from 'react'; +import React, {useEffect, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/Text'; +import ValidateCodeActionModal from '@components/ValidateCodeActionModal'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; +import * as User from '@libs/actions/User'; +import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as Delegate from '@userActions/Delegate'; import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import ValidateCodeForm from './ValidateCodeForm'; -import type {ValidateCodeFormHandle} from './ValidateCodeForm/BaseValidateCodeForm'; type DelegateMagicCodePageProps = StackScreenProps; function DelegateMagicCodePage({route}: DelegateMagicCodePageProps) { const {translate} = useLocalize(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(true); + const login = route.params.login; const role = route.params.role as ValueOf; - const styles = useThemeStyles(); - const validateCodeFormRef = useRef(null); - const currentDelegate = account?.delegatedAccess?.delegates?.find((d) => d.email === login); + const validateLoginError = ErrorUtils.getLatestErrorField(currentDelegate, 'addDelegate'); useEffect(() => { if (!currentDelegate || !!currentDelegate.pendingFields?.email || !!currentDelegate.errorFields?.addDelegate) { @@ -39,32 +37,28 @@ function DelegateMagicCodePage({route}: DelegateMagicCodePageProps) { }, [login, currentDelegate, role]); const onBackButtonPress = () => { + setIsValidateCodeActionModalVisible(false); Navigation.goBack(ROUTES.SETTINGS_DELEGATE_CONFIRM.getRoute(login, role)); }; + const clearError = () => { + if (!validateLoginError) { + return; + } + Delegate.clearAddDelegateErrors(currentDelegate?.email ?? '', 'addDelegate'); + }; + return ( - - {({safeAreaPaddingBottomStyle}) => ( - <> - - {translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})} - - - )} - + User.requestValidateCodeAction()} + handleSubmitForm={(validateCode) => Delegate.addDelegate(login, role, validateCode)} + description={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})} + /> ); } diff --git a/src/pages/settings/Security/AddDelegate/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/settings/Security/AddDelegate/ValidateCodeForm/BaseValidateCodeForm.tsx deleted file mode 100644 index c9816862ad35..000000000000 --- a/src/pages/settings/Security/AddDelegate/ValidateCodeForm/BaseValidateCodeForm.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import {useFocusEffect} from '@react-navigation/native'; -import type {ForwardedRef} from 'react'; -import React, {useCallback, useImperativeHandle, useRef, useState} from 'react'; -import {View} from 'react-native'; -import type {StyleProp, ViewStyle} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import Button from '@components/Button'; -import FixedFooter from '@components/FixedFooter'; -import MagicCodeInput from '@components/MagicCodeInput'; -import type {AutoCompleteVariant, MagicCodeInputHandle} from '@components/MagicCodeInput'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import * as Delegate from '@userActions/Delegate'; -import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {DelegateRole} from '@src/types/onyx/Account'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; - -type ValidateCodeFormHandle = { - focus: () => void; - focusLastSelected: () => void; -}; - -type ValidateCodeFormError = { - validateCode?: TranslationPaths; -}; - -type BaseValidateCodeFormProps = { - /** Specifies autocomplete hints for the system, so it can provide autofill */ - autoComplete?: AutoCompleteVariant; - - /** Forwarded inner ref */ - innerRef?: ForwardedRef; - - /** The email of the delegate */ - delegate: string; - - /** The role of the delegate */ - role: DelegateRole; - - /** Any additional styles to apply */ - wrapperStyle?: StyleProp; -}; - -function BaseValidateCodeForm({autoComplete = 'one-time-code', innerRef = () => {}, delegate, role, wrapperStyle}: BaseValidateCodeFormProps) { - const {translate} = useLocalize(); - const {isOffline} = useNetwork(); - const theme = useTheme(); - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const [formError, setFormError] = useState({}); - const [validateCode, setValidateCode] = useState(''); - const inputValidateCodeRef = useRef(null); - const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const login = account?.primaryLogin; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case - const focusTimeoutRef = useRef(null); - - const currentDelegate = account?.delegatedAccess?.delegates?.find((d) => d.email === delegate); - const validateLoginError = ErrorUtils.getLatestErrorField(currentDelegate, 'addDelegate'); - - const shouldDisableResendValidateCode = !!isOffline || currentDelegate?.isLoading; - - useImperativeHandle(innerRef, () => ({ - focus() { - inputValidateCodeRef.current?.focus(); - }, - focusLastSelected() { - if (!inputValidateCodeRef.current) { - return; - } - if (focusTimeoutRef.current) { - clearTimeout(focusTimeoutRef.current); - } - focusTimeoutRef.current = setTimeout(() => { - inputValidateCodeRef.current?.focusLastSelected(); - }, CONST.ANIMATED_TRANSITION); - }, - })); - - useFocusEffect( - useCallback(() => { - if (!inputValidateCodeRef.current) { - return; - } - if (focusTimeoutRef.current) { - clearTimeout(focusTimeoutRef.current); - } - focusTimeoutRef.current = setTimeout(() => { - inputValidateCodeRef.current?.focusLastSelected(); - }, CONST.ANIMATED_TRANSITION); - return () => { - if (!focusTimeoutRef.current) { - return; - } - clearTimeout(focusTimeoutRef.current); - }; - }, []), - ); - - /** - * Request a validate code / magic code be sent to verify this contact method - */ - const resendValidateCode = () => { - if (!login) { - return; - } - Delegate.requestValidationCode(); - - inputValidateCodeRef.current?.clear(); - }; - - /** - * Handle text input and clear formError upon text change - */ - const onTextInput = useCallback( - (text: string) => { - setValidateCode(text); - setFormError({}); - if (validateLoginError) { - Delegate.clearAddDelegateErrors(currentDelegate?.email ?? '', 'addDelegate'); - } - }, - [currentDelegate?.email, validateLoginError], - ); - - /** - * Check that all the form fields are valid, then trigger the submit callback - */ - const validateAndSubmitForm = useCallback(() => { - if (!validateCode.trim()) { - setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); - return; - } - - if (!ValidationUtils.isValidValidateCode(validateCode)) { - setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); - return; - } - - setFormError({}); - - Delegate.addDelegate(delegate, role, validateCode); - }, [delegate, role, validateCode]); - - return ( - - - - - - - {translate('validateCodeForm.magicCodeNotReceived')} - - - - - - -