diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1eabf5607ccde..abca1cb5b8329 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2218,6 +2218,12 @@ const ROUTES = { // eslint-disable-next-line no-restricted-syntax -- Legacy route generation getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/expensify-card/issue-new`, backTo), }, + WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE: { + route: 'workspaces/:policyID/expensify-card/issue-new/confirm-magic-code', + + // eslint-disable-next-line no-restricted-syntax -- Legacy route generation + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/expensify-card/issue-new/confirm-magic-code`, backTo), + }, WORKSPACE_EXPENSIFY_CARD_BANK_ACCOUNT: { route: 'workspaces/:policyID/expensify-card/choose-bank-account', getRoute: (policyID: string | undefined) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6a9d82763bf20..dad711c977834 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -591,6 +591,7 @@ const SCREENS = { EXPENSIFY_CARD_DETAILS: 'Workspace_ExpensifyCard_Details', EXPENSIFY_CARD_LIMIT: 'Workspace_ExpensifyCard_Limit', EXPENSIFY_CARD_ISSUE_NEW: 'Workspace_ExpensifyCard_New', + EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE: 'Workspace_ExpensifyCard_New_Confirm_Magic_Code', EXPENSIFY_CARD_NAME: 'Workspace_ExpensifyCard_Name', EXPENSIFY_CARD_SELECT_FEED: 'Workspace_ExpensifyCard_Select_Feed', EXPENSIFY_CARD_LIMIT_TYPE: 'Workspace_ExpensifyCard_LimitType', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 3375edcd96536..d21610ab0b6d9 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -762,6 +762,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage').default, [SCREENS.WORKSPACE.COMPANY_CARD_EXPORT]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: () => require('../../../../pages/workspace/expensifyCard/issueNew/IssueNewCardPage').default, + [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE]: () => + require('../../../../pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceCardSettingsPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_ACCOUNT]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceSettlementAccountPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_FREQUENCY]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceSettlementFrequencyPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 317e9f0c34f6e..1d8d9e92db9b8 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -246,6 +246,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: { path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.route, }, + [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE]: { + path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE.route, + }, [SCREENS.WORKSPACE.EXPENSIFY_CARD_NAME]: { path: ROUTES.WORKSPACE_EXPENSIFY_CARD_NAME.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f6c0b4c305f6b..755e7779cea5a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1153,6 +1153,11 @@ type SettingsNavigatorParamList = { // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md backTo?: Routes; }; + [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE]: { + policyID: string; + // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md + backTo?: Routes; + }; [SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT]: { policyID: string; }; diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index c2ed44ca96d78..5d08fbd08219d 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -1,37 +1,30 @@ -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; -import ValidateCodeActionModal from '@components/ValidateCodeActionModal'; -import useDefaultFundID from '@hooks/useDefaultFundID'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; -import {clearIssueNewCardError, clearIssueNewCardFlow, issueExpensifyCard, setIssueNewCardStepAndData} from '@libs/actions/Card'; -import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; +import {setIssueNewCardStepAndData} from '@libs/actions/Card'; +import {resetValidateActionCodeSent} from '@libs/actions/User'; import {getTranslationKeyForLimitType} from '@libs/CardUtils'; import {convertToShortDisplayString} from '@libs/CurrencyUtils'; -import {getLatestErrorMessage, getLatestErrorMessageField} from '@libs/ErrorUtils'; +import {getLatestErrorMessage} from '@libs/ErrorUtils'; import {getUserNameByEmail} from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Route} from '@src/ROUTES'; import type {IssueNewCardStep} from '@src/types/onyx/Card'; type ConfirmationStepProps = { /** ID of the policy that the card will be issued under */ policyID: string | undefined; - /** Route to navigate to */ - backTo?: Route; - /** Array of step names */ stepNames: readonly string[]; @@ -39,19 +32,13 @@ type ConfirmationStepProps = { startStepIndex: number; }; -function ConfirmationStep({policyID, backTo, stepNames, startStepIndex}: ConfirmationStepProps) { +function ConfirmationStep({policyID, stepNames, startStepIndex}: ConfirmationStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {canBeMissing: true}); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); - const validateError = getLatestErrorMessageField(issueNewCard); - const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false); const data = issueNewCard?.data; - const isSuccessful = issueNewCard?.isSuccessful; - const defaultFundID = useDefaultFundID(policyID); - const {isBetaEnabled} = usePermissions(); const hasApprovalError = !!policy?.errorFields?.approvalMode; const isAddApprovalEnabled = policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL && !hasApprovalError; const shouldDisableSubmitButton = !isAddApprovalEnabled && data?.limitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART; @@ -63,18 +50,6 @@ function ConfirmationStep({policyID, backTo, stepNames, startStepIndex}: Confirm resetValidateActionCodeSent(); }, []); - useEffect(() => { - if (!isSuccessful) { - return; - } - setIsValidateCodeActionModalVisible(false); - }, [isSuccessful]); - - const submit = (validateCode: string) => { - // NOTE: For Expensify Card UK/EU, the backend will automatically detect the correct feedCountry to use - issueExpensifyCard(defaultFundID, policyID, isBetaEnabled(CONST.BETAS.EXPENSIFY_CARD_EU_UK) ? '' : CONST.COUNTRY.US, validateCode, data); - }; - const errorMessage = getLatestErrorMessage(issueNewCard) || (shouldDisableSubmitButton ? translate('workspace.card.issueNewCard.disabledApprovalForSmartLimitError') : ''); const editStep = (step: IssueNewCardStep) => { @@ -87,19 +62,6 @@ function ConfirmationStep({policyID, backTo, stepNames, startStepIndex}: Confirm const translationForLimitType = getTranslationKeyForLimitType(data?.limitType); - const onRedirect = useCallback(() => { - if (!isSuccessful) { - return; - } - if (backTo) { - Navigation.goBack(backTo); - } else { - Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID)); - } - - clearIssueNewCardFlow(policyID); - }, [backTo, policyID, isSuccessful]); - return ( setIsValidateCodeActionModalVisible(true)} + onSubmit={() => { + if (!policyID) { + return; + } + Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE.getRoute(policyID, ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID))); + }} buttonText={translate('workspace.card.issueCard')} /> - {!!issueNewCard && ( - clearIssueNewCardError(policyID)} - onClose={() => setIsValidateCodeActionModalVisible(false)} - isVisible={isValidateCodeActionModalVisible} - title={translate('cardPage.validateCardTitle')} - descriptionPrimary={translate('cardPage.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})} - onModalHide={onRedirect} - disableAnimation - /> - )} ); } diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx new file mode 100644 index 0000000000000..9971896ff353f --- /dev/null +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx @@ -0,0 +1,78 @@ +import React, {useCallback, useEffect} from 'react'; +import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; +import useDefaultFundID from '@hooks/useDefaultFundID'; +import useInitial from '@hooks/useInitial'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; +import {clearIssueNewCardError, clearIssueNewCardFlow, issueExpensifyCard} from '@libs/actions/Card'; +import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; +import {getLatestErrorMessageField} from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type IssueNewCardConfirmMagicCodePageProps = PlatformStackScreenProps; + +function IssueNewCardConfirmMagicCodePage({route}: IssueNewCardConfirmMagicCodePageProps) { + const {translate} = useLocalize(); + const policyID = route.params.policyID; + const backTo = route.params.backTo; + const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false}); + const primaryLogin = account?.primaryLogin ?? ''; + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {canBeMissing: true}); + const validateError = getLatestErrorMessageField(issueNewCard); + const data = issueNewCard?.data; + const isSuccessful = issueNewCard?.isSuccessful; + const defaultFundID = useDefaultFundID(policyID); + const {isBetaEnabled} = usePermissions(); + const firstAssigneeEmail = useInitial(issueNewCard?.data?.assigneeEmail); + const shouldUseBackToParam = !firstAssigneeEmail || firstAssigneeEmail === issueNewCard?.data?.assigneeEmail; + + useEffect(() => { + if (!isSuccessful) { + return; + } + if (backTo && shouldUseBackToParam) { + Navigation.goBack(backTo); + } else { + Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID), {forceReplace: true}); + } + clearIssueNewCardFlow(policyID); + }, [backTo, isSuccessful, policyID, shouldUseBackToParam]); + + const handleSubmit = useCallback( + (validateCode: string) => { + // NOTE: For Expensify Card UK/EU, the backend will automatically detect the correct feedCountry to use + issueExpensifyCard(defaultFundID, policyID, isBetaEnabled(CONST.BETAS.EXPENSIFY_CARD_EU_UK) ? '' : CONST.COUNTRY.US, validateCode, data); + }, + [isBetaEnabled, data, defaultFundID, policyID], + ); + + const handleClose = useCallback(() => { + resetValidateActionCodeSent(); + Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.getRoute(policyID, backTo)); + }, [policyID, backTo]); + + return ( + requestValidateCodeAction()} + validateCodeActionErrorField={data?.cardType === CONST.EXPENSIFY_CARD.CARD_TYPE.PHYSICAL ? 'createExpensifyCard' : 'createAdminIssuedVirtualCard'} + handleSubmitForm={handleSubmit} + validateError={validateError} + clearError={() => clearIssueNewCardError(policyID)} + onClose={handleClose} + /> + ); +} + +IssueNewCardConfirmMagicCodePage.displayName = 'ExpensifyCardVerifyAccountPage'; + +export default IssueNewCardConfirmMagicCodePage; diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx index 80c9c5465e0fb..ae0c6187c51ee 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx @@ -3,7 +3,6 @@ import React, {useEffect, useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import ScreenWrapper from '@components/ScreenWrapper'; -import useInitial from '@hooks/useInitial'; import useOnyx from '@hooks/useOnyx'; import {startIssueNewCardFlow} from '@libs/actions/Card'; import Navigation from '@libs/Navigation/Navigation'; @@ -50,8 +49,6 @@ function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {canBeMissing: true}); const {currentStep} = issueNewCard ?? {}; const backTo = route?.params?.backTo; - const firstAssigneeEmail = useInitial(issueNewCard?.data?.assigneeEmail); - const shouldUseBackToParam = !firstAssigneeEmail || firstAssigneeEmail === issueNewCard?.data?.assigneeEmail; const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isActingAsDelegateSelector, canBeMissing: true}); const stepNames = issueNewCard?.isChangeAssigneeDisabled ? CONST.EXPENSIFY_CARD.ASSIGNEE_EXCLUDED_STEP_NAMES : CONST.EXPENSIFY_CARD.STEP_NAMES; const startStepIndex = useMemo(() => getStartStepIndex(issueNewCard), [issueNewCard]); @@ -107,7 +104,6 @@ function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { return (