diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 38e1b25c6a9f..ae424230a191 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -105,7 +105,7 @@ const ONYXKEYS = {
PENDING_CONTACT_ACTION: 'pendingContactAction',
/** Store the information of magic code */
- VALIDATE_ACTION_CODE: 'validate_action_code',
+ VALIDATE_ACTION_CODE: 'validateActionCode',
/** A list of policies that a user can join */
JOINABLE_POLICIES: 'joinablePolicies',
diff --git a/src/components/ValidateCodeActionForm/index.tsx b/src/components/ValidateCodeActionForm/index.tsx
index 75aa127f26dd..88a5ba5d614d 100644
--- a/src/components/ValidateCodeActionForm/index.tsx
+++ b/src/components/ValidateCodeActionForm/index.tsx
@@ -1,11 +1,9 @@
import React, {forwardRef, useEffect, useRef} from 'react';
import {View} from 'react-native';
-import {useOnyx} from 'react-native-onyx';
import Text from '@components/Text';
import ValidateCodeForm from '@components/ValidateCodeActionModal/ValidateCodeForm';
import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm';
import useThemeStyles from '@hooks/useThemeStyles';
-import ONYXKEYS from '@src/ONYXKEYS';
import type {ValidateCodeActionFormProps} from './type';
function ValidateCodeActionForm({
@@ -15,19 +13,16 @@ function ValidateCodeActionForm({
descriptionSecondaryStyles,
validatePendingAction,
validateError,
+ hasMagicCodeBeenSent,
handleSubmitForm,
clearError,
sendValidateCode,
- hasMagicCodeBeenSent,
isLoading,
submitButtonText,
forwardedRef,
shouldSkipInitialValidation,
}: ValidateCodeActionFormProps) {
const themeStyles = useThemeStyles();
-
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
-
const isUnmounted = useRef(false);
useEffect(() => {
@@ -56,15 +51,15 @@ function ValidateCodeActionForm({
{!!descriptionSecondary && {descriptionSecondary}}
diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
index cc75dd9e6440..f5046898d9a8 100644
--- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -23,7 +23,6 @@ import {clearValidateCodeActionError} from '@userActions/User';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {ValidateMagicCodeAction} from '@src/types/onyx';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -37,20 +36,25 @@ type ValidateCodeFormError = {
};
type ValidateCodeFormProps = {
- /** If the magic code has been resent previously */
- hasMagicCodeBeenSent?: boolean;
-
/** Specifies autocomplete hints for the system, so it can provide autofill */
autoComplete?: AutoCompleteVariant;
/** Forwarded inner ref */
innerRef?: ForwardedRef;
- /** The state of magic code that being sent */
- validateCodeAction?: ValidateMagicCodeAction;
+ hasMagicCodeBeenSent?: boolean;
- /** The pending action for submitting form */
- validatePendingAction?: PendingAction | null;
+ /** The pending action of magic code being sent
+ * if not supplied, we will retrieve it from the validateCodeAction above: `validateCodeAction.pendingFields.validateCodeSent`
+ */
+ validatePendingAction?: PendingAction;
+
+ /** The field where any magic code error will be stored. e.g. if replacing a card and magic code fails, it'll be stored in:
+ * {"errorFields": {"replaceLostCard": {}}}
+ * If replacing a virtual card, the errorField wil be 'reportVirtualCard', etc.
+ * These values are set in the backend, please reach out to an internal engineer if you're adding a validate code modal to a flow.
+ */
+ validateCodeActionErrorField: string;
/** The error of submitting */
validateError?: Errors;
@@ -87,10 +91,10 @@ type ValidateCodeFormProps = {
};
function BaseValidateCodeForm({
- hasMagicCodeBeenSent,
autoComplete = 'one-time-code',
innerRef = () => {},
- validateCodeAction,
+ hasMagicCodeBeenSent,
+ validateCodeActionErrorField,
validatePendingAction,
validateError,
handleSubmitForm,
@@ -115,13 +119,15 @@ function BaseValidateCodeForm({
const [account = {}] = useOnyx(ONYXKEYS.ACCOUNT, {
canBeMissing: true,
});
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
const shouldDisableResendValidateCode = !!isOffline || account?.isLoading;
const focusTimeoutRef = useRef(null);
const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number);
const [canShowError, setCanShowError] = useState(false);
- const latestActionVerifiedError = getLatestErrorField(validateCodeAction, 'actionVerified');
-
+ const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
+ const validateCodeSent = useMemo(() => hasMagicCodeBeenSent ?? validateCodeAction?.validateCodeSent, [hasMagicCodeBeenSent, validateCodeAction?.validateCodeSent]);
+ const latestValidateCodeError = getLatestErrorField(validateCodeAction, validateCodeActionErrorField);
const timerRef = useRef();
useImperativeHandle(innerRef, () => ({
@@ -169,11 +175,11 @@ function BaseValidateCodeForm({
);
useEffect(() => {
- if (!hasMagicCodeBeenSent) {
+ if (!validateCodeSent) {
return;
}
inputValidateCodeRef.current?.clear();
- }, [hasMagicCodeBeenSent]);
+ }, [validateCodeSent]);
useEffect(() => {
if (timeRemaining > 0) {
@@ -203,18 +209,26 @@ function BaseValidateCodeForm({
setValidateCode(text);
setFormError({});
- if (!isEmptyObject(validateError) || !isEmptyObject(latestActionVerifiedError)) {
+ if (!isEmptyObject(validateError) || !isEmptyObject(latestValidateCodeError)) {
+ // Clear flow specific error
clearError();
- clearValidateCodeActionError('actionVerified');
+
+ // Clear "incorrect magic code" error
+ clearValidateCodeActionError(validateCodeActionErrorField);
}
},
- [validateError, clearError, latestActionVerifiedError],
+ [validateError, clearError, latestValidateCodeError, validateCodeActionErrorField],
);
/**
* Check that all the form fields are valid, then trigger the submit callback
*/
const validateAndSubmitForm = useCallback(() => {
+ // Clear flow specific error
+ clearError();
+
+ // Clear "incorrect magic" code error
+ clearValidateCodeActionError(validateCodeActionErrorField);
setCanShowError(true);
if (!validateCode.trim()) {
setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'});
@@ -228,7 +242,7 @@ function BaseValidateCodeForm({
setFormError({});
handleSubmitForm(validateCode);
- }, [validateCode, handleSubmitForm]);
+ }, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]);
const errorText = useMemo(() => {
if (!canShowError) {
@@ -241,6 +255,10 @@ function BaseValidateCodeForm({
}, [canShowError, formError, account, translate]);
const shouldShowTimer = timeRemaining > 0 && !isOffline;
+
+ // latestValidateCodeError only holds an error related to bad magic code
+ // while validateError holds flow-specific errors
+ const finalValidateError = !isEmptyObject(latestValidateCodeError) ? latestValidateCodeError : validateError;
return (
<>
clearValidateCodeActionError('actionVerified')}
+ onClose={() => clearValidateCodeActionError(validateCodeActionErrorField)}
>
{!shouldShowTimer && (
@@ -284,7 +301,7 @@ function BaseValidateCodeForm({
)}
- {!!hasMagicCodeBeenSent && (
+ {!!validateCodeSent && (
clearError()}
+ onClose={() => {
+ clearError();
+ if (!isEmptyObject(validateCodeAction?.errorFields) && validateCodeActionErrorField) {
+ clearValidateCodeActionError(validateCodeActionErrorField);
+ }
+ }}
style={buttonStyles}
>
{shouldShowSkipButton && (
diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx
index 4dfa2099ebbc..13c8418ecb35 100644
--- a/src/components/ValidateCodeActionModal/index.tsx
+++ b/src/components/ValidateCodeActionModal/index.tsx
@@ -22,13 +22,13 @@ function ValidateCodeActionModal({
descriptionSecondary,
onClose,
onModalHide,
- validatePendingAction,
validateError,
+ validatePendingAction,
+ validateCodeActionErrorField,
handleSubmitForm,
clearError,
footer,
sendValidateCode,
- hasMagicCodeBeenSent,
isLoading,
shouldHandleNavigationBack,
disableAnimation,
@@ -40,8 +40,7 @@ function ValidateCodeActionModal({
const validateCodeFormRef = useRef(null);
const styles = useThemeStyles();
const threeDotsAnchorPosition = useThreeDotsAnchorPosition(styles.threeDotsPopoverOffset);
-
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
+ const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const hide = useCallback(() => {
clearError();
@@ -50,13 +49,13 @@ function ValidateCodeActionModal({
}, [onClose, clearError]);
useEffect(() => {
- if (!firstRenderRef.current || !isVisible || hasMagicCodeBeenSent) {
+ if (!firstRenderRef.current || !isVisible || validateCodeAction?.validateCodeSent) {
return;
}
firstRenderRef.current = false;
sendValidateCode();
- // We only want to send validate code on first render not on change of hasMagicCodeBeenSent, so we don't add it as a dependency.
+ // We only want to send validate code on first render not on change of validateCodeSent, so we don't add it as a dependency.
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isVisible, sendValidateCode]);
@@ -102,15 +101,14 @@ function ValidateCodeActionModal({
{!!descriptionSecondary && {descriptionSecondary}}
diff --git a/src/components/ValidateCodeActionModal/type.ts b/src/components/ValidateCodeActionModal/type.ts
index b0ff887130cd..9aa4aa0c0427 100644
--- a/src/components/ValidateCodeActionModal/type.ts
+++ b/src/components/ValidateCodeActionModal/type.ts
@@ -21,12 +21,15 @@ type ValidateCodeActionModalProps = {
/** Function to be called when the modal is closed */
onModalHide?: () => void;
- /** The pending action for submitting form */
- validatePendingAction?: PendingAction | null;
+ /** The pending action we're trying to validate */
+ validatePendingAction?: PendingAction;
- /** The error of submitting */
+ /** The error of submitting, this holds any error specific to the flow (e.g invalid reason when replacing a card) but NOT an incorrect magic code */
validateError?: Errors;
+ /** The errorField name of validateCodeAction.errorFields, e.g. "addLogin" to store the magic code error when adding a new contact method */
+ validateCodeActionErrorField: string;
+
/** Function is called when submitting form */
handleSubmitForm: (validateCode: string) => void;
@@ -39,9 +42,6 @@ type ValidateCodeActionModalProps = {
/** Function is called when validate code modal is mounted and on magic code resend */
sendValidateCode: () => void;
- /** If the magic code has been resent previously */
- hasMagicCodeBeenSent?: boolean;
-
/** Whether the form is loading or not */
isLoading?: boolean;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index f64a3b67fa26..c9075b647114 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1950,7 +1950,7 @@ const translations = {
requestNewCodeAfterErrorOccurred: 'Request a new code',
error: {
pleaseFillMagicCode: 'Please enter your magic code',
- incorrectMagicCode: 'Incorrect magic code',
+ incorrectMagicCode: 'Incorrect or invalid magic code. Please try again or request a new code.',
pleaseFillTwoFactorAuth: 'Please enter your two-factor authentication code',
},
},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index b40eafc5281c..ad25134186fd 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1952,9 +1952,9 @@ const translations = {
requestNewCode: 'Pedir un código nuevo en ',
requestNewCodeAfterErrorOccurred: 'Solicitar un nuevo código',
error: {
- pleaseFillMagicCode: 'Por favor, introduce el código mágico',
- incorrectMagicCode: 'Código mágico incorrecto',
- pleaseFillTwoFactorAuth: 'Por favor, introduce tu código de autenticación de dos factores',
+ pleaseFillMagicCode: 'Por favor, introduce el código mágico.',
+ incorrectMagicCode: 'Código mágico incorrecto o no válido. Inténtalo de nuevo o solicita otro código.',
+ pleaseFillTwoFactorAuth: 'Por favor, introduce tu código de autenticación de dos factores.',
},
},
passwordForm: {
diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts
index 8d6d327f6d28..7c340a042e2f 100644
--- a/src/libs/actions/Card.ts
+++ b/src/libs/actions/Card.ts
@@ -52,20 +52,6 @@ function reportVirtualExpensifyCardFraud(card: Card, validateCode: string) {
errors: null,
},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.CARD_LIST,
- value: {
- [cardID]: null,
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${card?.fundID}_${CONST.EXPENSIFY_CARD.BANK}`,
- value: {
- [cardID]: null,
- },
- },
];
const successData: OnyxUpdate[] = [
@@ -86,26 +72,6 @@ function reportVirtualExpensifyCardFraud(card: Card, validateCode: string) {
isLoading: false,
},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.CARD_LIST,
- value: {
- [cardID]: {
- ...card,
- errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
- },
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${card?.fundID}_${CONST.EXPENSIFY_CARD.BANK}`,
- value: {
- [cardID]: {
- ...card,
- errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
- },
- },
- },
];
const parameters: ReportVirtualExpensifyCardFraudParams = {
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index a4ac11e727bb..bf924a03b707 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -1350,7 +1350,7 @@ function MergeIntoAccountAndLogin(workEmail: string | undefined, validateCode: s
).then((response) => {
if (response?.jsonCode === CONST.JSON_CODE.EXP_ERROR) {
// If the error other than invalid code, we show a blocking screen
- if (response?.message === CONST.MERGE_ACCOUNT_INVALID_CODE_ERROR) {
+ if (response?.message === CONST.MERGE_ACCOUNT_INVALID_CODE_ERROR || response?.title === CONST.MERGE_ACCOUNT_INVALID_CODE_ERROR) {
Onyx.merge(ONYXKEYS.ONBOARDING_ERROR_MESSAGE, translateLocal('contacts.genericFailureMessages.validateSecondaryLogin'));
} else {
Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {isMergingAccountBlocked: true});
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 59ab12b44d6f..8ea01da4572f 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -335,6 +335,12 @@ function clearUnvalidatedNewContactMethodAction() {
Onyx.merge(ONYXKEYS.PENDING_CONTACT_ACTION, null);
}
+function clearPendingContactActionErrors() {
+ Onyx.merge(ONYXKEYS.PENDING_CONTACT_ACTION, {
+ errorFields: null,
+ });
+}
+
/**
* When user adds a new contact method, they need to verify the magic code first
* So we add the temporary contact method to Onyx to use it later, after user verified magic code.
@@ -345,76 +351,6 @@ function addPendingContactMethod(contactMethod: string) {
});
}
-/**
- * Validates the action to add secondary contact method
- */
-function saveNewContactMethodAndRequestValidationCode(contactMethod: string) {
- const optimisticData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.PENDING_CONTACT_ACTION,
- value: {
- contactMethod,
- errorFields: {
- actionVerified: null,
- },
- pendingFields: {
- actionVerified: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- },
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM,
- value: {isLoading: true},
- },
- ];
-
- const successData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.PENDING_CONTACT_ACTION,
- value: {
- validateCodeSent: true,
- errorFields: {
- actionVerified: null,
- },
- pendingFields: {
- actionVerified: null,
- },
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM,
- value: {isLoading: false},
- },
- ];
-
- const failureData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.PENDING_CONTACT_ACTION,
- value: {
- validateCodeSent: null,
- errorFields: {
- actionVerified: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.requestContactMethodValidateCode'),
- },
- pendingFields: {
- actionVerified: null,
- },
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM,
- value: {isLoading: false},
- },
- ];
-
- API.write(WRITE_COMMANDS.RESEND_VALIDATE_CODE, null, {optimisticData, successData, failureData});
-}
-
/**
* Adds a secondary login to a user's account
*/
@@ -430,9 +366,6 @@ function addNewContactMethod(contactMethod: string, validateCode = '') {
errorFields: {
addedLogin: null,
},
- pendingFields: {
- addedLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- },
},
},
},
@@ -443,17 +376,6 @@ function addNewContactMethod(contactMethod: string, validateCode = '') {
},
];
const successData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.LOGIN_LIST,
- value: {
- [contactMethod]: {
- pendingFields: {
- addedLogin: null,
- },
- },
- },
- },
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PENDING_CONTACT_ACTION,
@@ -464,9 +386,6 @@ function addNewContactMethod(contactMethod: string, validateCode = '') {
errorFields: {
actionVerified: null,
},
- pendingFields: {
- actionVerified: null,
- },
},
},
{
@@ -476,20 +395,6 @@ function addNewContactMethod(contactMethod: string, validateCode = '') {
},
];
const failureData: OnyxUpdate[] = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.LOGIN_LIST,
- value: {
- [contactMethod]: {
- errorFields: {
- addedLogin: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.addContactMethod'),
- },
- pendingFields: {
- addedLogin: null,
- },
- },
- },
- },
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
@@ -497,7 +402,7 @@ function addNewContactMethod(contactMethod: string, validateCode = '') {
},
{
onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.PENDING_CONTACT_ACTION,
+ key: ONYXKEYS.VALIDATE_ACTION_CODE,
value: {validateCodeSent: null},
},
];
@@ -1512,8 +1417,8 @@ export {
updateDraftCustomStatus,
clearDraftCustomStatus,
requestRefund,
- saveNewContactMethodAndRequestValidationCode,
clearUnvalidatedNewContactMethodAction,
+ clearPendingContactActionErrors,
requestValidateCodeAction,
addPendingContactMethod,
clearValidateCodeActionError,
diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx
index 057f2ff3cba9..ba5c727c4701 100644
--- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx
+++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx
@@ -19,10 +19,10 @@ type MissingPersonalDetailsMagicCodeModalProps = {
function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModalVisible, handleSubmitForm}: MissingPersonalDetailsMagicCodeModalProps) {
const {translate} = useLocalize();
- const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
- const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
+ const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true});
+ const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined;
const validateLoginError = getLatestError(privateDetailsErrors);
const primaryLogin = account?.primaryLogin ?? '';
@@ -50,7 +50,7 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa
};
const clearError = () => {
- if (!validateLoginError) {
+ if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) {
return;
}
clearPersonalDetailsErrors();
@@ -60,12 +60,12 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa
requestValidateCodeAction()}
- hasMagicCodeBeenSent={validateCodeAction?.validateCodeSent}
handleSubmitForm={handleSubmitForm}
isLoading={privatePersonalDetails?.isLoading}
/>
diff --git a/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx b/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx
index f0e0828999dd..8fc5b5f2db05 100644
--- a/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx
+++ b/src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx
@@ -27,7 +27,6 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS, {canBeMissing: true});
const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const [getAccessiblePoliciesAction] = useOnyx(ONYXKEYS.VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES, {canBeMissing: true});
const [joinablePolicies] = useOnyx(ONYXKEYS.JOINABLE_POLICIES, {canBeMissing: true});
const joinablePoliciesLength = Object.keys(joinablePolicies ?? {}).length;
@@ -79,7 +78,7 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
{translate('onboarding.peopleYouMayKnow')}
{translate('onboarding.workspaceYouMayJoin', {domain, email})}
{
getAccessiblePolicies(code);
setHasMagicCodeBeenSent(false);
diff --git a/src/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsx b/src/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsx
index dbed150a3ae7..dbaed73ee5d5 100644
--- a/src/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsx
+++ b/src/pages/OnboardingWorkEmailValidation/BaseOnboardingWorkEmailValidation.tsx
@@ -33,7 +33,6 @@ function BaseOnboardingWorkEmailValidation({shouldUseNativeStyles}: BaseOnboardi
const [onboardingEmail] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_WORK_EMAIL_FORM, {canBeMissing: true});
const workEmail = onboardingEmail?.onboardingWorkEmail;
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {canBeMissing: true});
const isVsb = onboardingValues && 'signupQualifier' in onboardingValues && onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB;
@@ -118,9 +117,9 @@ function BaseOnboardingWorkEmailValidation({shouldUseNativeStyles}: BaseOnboardi
{translate('onboarding.workEmailValidation.title')}
{translate('onboarding.workEmailValidation.magicCodeSent', {workEmail})}
setOnboardingErrorMessage('')}
buttonStyles={[styles.flex2, styles.justifyContentEnd, styles.mb5]}
shouldShowSkipButton
diff --git a/src/pages/ReimbursementAccount/VerifiedBankAccountFlowEntryPoint.tsx b/src/pages/ReimbursementAccount/VerifiedBankAccountFlowEntryPoint.tsx
index ff1b6ea6bd23..1c4adf11ab5e 100644
--- a/src/pages/ReimbursementAccount/VerifiedBankAccountFlowEntryPoint.tsx
+++ b/src/pages/ReimbursementAccount/VerifiedBankAccountFlowEntryPoint.tsx
@@ -91,19 +91,18 @@ function VerifiedBankAccountFlowEntryPoint({
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED);
- const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED, {canBeMissing: true});
+ const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
const errors = reimbursementAccount?.errors ?? {};
const pendingAction = reimbursementAccount?.pendingAction ?? null;
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
+ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true});
const optionPressed = useRef('');
const isAccountValidated = useAccountValidation();
const contactMethod = account?.primaryLogin ?? '';
const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]);
const validateLoginError = getEarliestErrorField(loginData, 'validateLogin');
- const hasMagicCodeBeenSent = !!loginData?.validateCodeSent;
const plaidDesktopMessage = getPlaidDesktopMessage();
const bankAccountRoute = `${ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES.NEW, ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}`;
const personalBankAccounts = bankAccountList ? Object.keys(bankAccountList).filter((key) => bankAccountList[key].accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) : [];
@@ -304,7 +303,7 @@ function VerifiedBankAccountFlowEntryPoint({
descriptionPrimary={translate('contacts.featureRequiresValidate')}
descriptionSecondary={translate('contacts.enterMagicCode', {contactMethod})}
isVisible={!!isValidateCodeActionModalVisible}
- hasMagicCodeBeenSent={hasMagicCodeBeenSent}
+ validateCodeActionErrorField="validateLogin"
validatePendingAction={loginData?.pendingFields?.validateCodeSent}
sendValidateCode={() => requestValidateCodeAction()}
handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode)}
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx
index 37e23eb81f3e..341fc9ece40d 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx
@@ -15,6 +15,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import ValidateCodeActionForm from '@components/ValidateCodeActionForm';
+import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useTheme from '@hooks/useTheme';
@@ -46,18 +47,17 @@ 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;
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 [loginList, loginListResult] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true});
+ const [session, sessionResult] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
+ const [myDomainSecurityGroups, myDomainSecurityGroupsResult] = useOnyx(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, {canBeMissing: true});
+ const [securityGroups, securityGroupsResult] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP, {canBeMissing: true});
+ const [isLoadingReportData, isLoadingReportDataResult] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true, canBeMissing: true});
const [isValidateCodeFormVisible, setIsValidateCodeFormVisible] = useState(true);
- const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: true});
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
const isLoadingOnyxValues = isLoadingOnyxValue(loginListResult, sessionResult, myDomainSecurityGroupsResult, securityGroupsResult, isLoadingReportDataResult);
@@ -341,7 +341,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
{isValidateCodeFormVisible && !!loginData && !loginData.validatedDate && (
validateSecondaryLogin(loginList, contactMethod, validateCode)}
validateError={!isEmptyObject(validateLoginError) ? validateLoginError : getLatestErrorField(loginData, 'validateCodeSent')}
clearError={() => clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')}
diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
index 2cf44004238a..7a916666b46e 100644
--- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
+++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
@@ -30,6 +30,7 @@ import {
addPendingContactMethod,
clearContactMethod,
clearContactMethodErrors,
+ clearPendingContactActionErrors,
clearUnvalidatedNewContactMethodAction,
requestValidateCodeAction,
} from '@userActions/User';
@@ -48,8 +49,8 @@ function NewContactMethodPage({route, navigation}: NewContactMethodPageProps) {
const {translate} = useLocalize();
const loginInputRef = useRef(null);
const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false);
- const [pendingContactAction] = useOnyx(ONYXKEYS.PENDING_CONTACT_ACTION);
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
+ const [pendingContactAction] = useOnyx(ONYXKEYS.PENDING_CONTACT_ACTION, {canBeMissing: true});
+ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true});
const loginData = loginList?.[pendingContactAction?.contactMethod ?? contactMethod];
const validateLoginError = getLatestErrorField(loginData, 'addedLogin');
const isUserValidated = useAccountValidation();
@@ -180,7 +181,7 @@ function NewContactMethodPage({route, navigation}: NewContactMethodPageProps) {
)}
{
@@ -188,6 +189,7 @@ function NewContactMethodPage({route, navigation}: NewContactMethodPageProps) {
return;
}
clearContactMethodErrors(addSMSDomainIfPhoneNumber(pendingContactAction?.contactMethod ?? contactMethod), 'addedLogin');
+ clearPendingContactActionErrors();
}}
onClose={() => {
if (pendingContactAction?.contactMethod) {
@@ -197,7 +199,6 @@ function NewContactMethodPage({route, navigation}: NewContactMethodPageProps) {
setIsValidateCodeActionModalVisible(false);
}}
isVisible={isValidateCodeActionModalVisible}
- hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={() => requestValidateCodeAction()}
descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod})}
diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx
deleted file mode 100644
index 6ec1dbc5d67e..000000000000
--- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ /dev/null
@@ -1,281 +0,0 @@
-import {useFocusEffect} from '@react-navigation/native';
-import type {ForwardedRef} from 'react';
-import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
-import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
-import Button from '@components/Button';
-import DotIndicatorMessage from '@components/DotIndicatorMessage';
-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 Session from '@userActions/Session';
-import * as User from '@userActions/User';
-import CONST from '@src/CONST';
-import type {TranslationPaths} from '@src/languages/types';
-import ONYXKEYS from '@src/ONYXKEYS';
-import type {Account, LoginList, PendingContactAction} from '@src/types/onyx';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
-
-type ValidateCodeFormHandle = {
- focus: () => void;
- focusLastSelected: () => void;
-};
-
-type ValidateCodeFormError = {
- validateCode?: TranslationPaths;
-};
-
-type BaseValidateCodeFormOnyxProps = {
- /** The details about the account that the user is signing in with */
- account: OnyxEntry;
-};
-
-type ValidateCodeFormProps = {
- /** The contact method being valdiated */
- contactMethod: string;
-
- /** If the magic code has been resent previously */
- hasMagicCodeBeenSent?: boolean;
-
- /** Login list for the user that is signed in */
- loginList?: LoginList;
-
- /** Specifies autocomplete hints for the system, so it can provide autofill */
- autoComplete?: AutoCompleteVariant;
-
- /** Forwarded inner ref */
- innerRef?: ForwardedRef;
-
- /** Whether we are validating the action taken to add the magic code */
- isValidatingAction?: boolean;
-
- /** The contact that's going to be added after successful validation */
- pendingContact?: PendingContactAction;
-};
-
-type BaseValidateCodeFormProps = BaseValidateCodeFormOnyxProps & ValidateCodeFormProps;
-
-function BaseValidateCodeForm({
- account = {},
- contactMethod,
- hasMagicCodeBeenSent,
- loginList,
- autoComplete = 'one-time-code',
- innerRef = () => {},
- isValidatingAction = false,
- pendingContact,
-}: 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 loginData = loginList?.[pendingContact?.contactMethod ?? contactMethod];
- const inputValidateCodeRef = useRef(null);
- const validateLoginError = ErrorUtils.getEarliestErrorField(loginData, 'validateLogin');
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
- const shouldDisableResendValidateCode = !!isOffline || account?.isLoading;
- const focusTimeoutRef = useRef(null);
- const validateCodeSentIsPressedRef = useRef(false);
- const [showDotIndicator, setShowDotIndicator] = useState(false);
-
- 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);
- };
- }, []),
- );
-
- useEffect(() => {
- Session.clearAccountMessages();
- if (!validateLoginError) {
- return;
- }
- User.clearContactMethodErrors(contactMethod, 'validateLogin');
- // contactMethod is not added as a dependency since it does not change between renders
- // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- if (!hasMagicCodeBeenSent) {
- return;
- }
- inputValidateCodeRef.current?.clear();
- }, [hasMagicCodeBeenSent]);
-
- useEffect(() => {
- // `validateCodeSent` is updated asynchronously,
- // and `validateCodeSentIsPressedRef.current` is updated faster than `hasMagicCodeBeenSent`.
- // This can cause the component to hide and show the `DotIndicatorMessage` multiple times
- // in quick succession, leading to a flickering effect.
- if ((hasMagicCodeBeenSent ?? !!pendingContact?.validateCodeSent) && validateCodeSentIsPressedRef.current) {
- setShowDotIndicator(true);
- } else {
- setShowDotIndicator(false);
- }
- }, [hasMagicCodeBeenSent, pendingContact, validateCodeSentIsPressedRef]);
-
- /**
- * Request a validate code / magic code be sent to verify this contact method
- */
- const resendValidateCode = () => {
- if (!!pendingContact?.contactMethod && isValidatingAction) {
- User.saveNewContactMethodAndRequestValidationCode(pendingContact?.contactMethod);
- } else {
- User.requestContactMethodValidateCode(contactMethod);
- }
-
- inputValidateCodeRef.current?.clear();
- validateCodeSentIsPressedRef.current = true;
- };
- /**
- * Handle text input and clear formError upon text change
- */
- const onTextInput = useCallback(
- (text: string) => {
- setValidateCode(text);
- setFormError({});
-
- if (validateLoginError) {
- User.clearContactMethodErrors(contactMethod, 'validateLogin');
- }
- },
- [validateLoginError, contactMethod],
- );
-
- /**
- * 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({});
-
- if (!!pendingContact?.contactMethod && isValidatingAction) {
- User.addNewContactMethod(pendingContact?.contactMethod, validateCode);
- return;
- }
-
- User.validateSecondaryLogin(loginList, contactMethod, validateCode);
- }, [loginList, validateCode, contactMethod, isValidatingAction, pendingContact?.contactMethod]);
-
- return (
- <>
-
- User.clearContactMethodErrors(contactMethod, 'validateCodeSent')}
- >
-
-
- {translate('validateCodeForm.magicCodeNotReceived')}
-
- {showDotIndicator && (
-
- )}
-
-
- User.clearContactMethodErrors(contactMethod, 'validateLogin')}
- >
-
-
- >
- );
-}
-
-BaseValidateCodeForm.displayName = 'BaseValidateCodeForm';
-
-export type {ValidateCodeFormProps, ValidateCodeFormHandle};
-
-export default withOnyx({
- account: {key: ONYXKEYS.ACCOUNT},
-})(BaseValidateCodeForm);
diff --git a/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx b/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx
index 3b352e4bb60f..f00de86b28b7 100644
--- a/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx
+++ b/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx
@@ -10,6 +10,7 @@ import {addDelegate, clearDelegateErrorsByField} from '@userActions/Delegate';
import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
type DelegateMagicCodeModalProps = {
login: string;
@@ -22,8 +23,8 @@ type DelegateMagicCodeModalProps = {
function DelegateMagicCodeModal({login, role, onClose, isValidateCodeActionModalVisible, shouldHandleNavigationBack, disableAnimation}: DelegateMagicCodeModalProps) {
const {translate} = useLocalize();
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const currentDelegate = account?.delegatedAccess?.delegates?.find((d) => d.email === login);
const addDelegateErrors = account?.delegatedAccess?.errorFields?.addDelegate?.[login];
const validateLoginError = getLatestError(addDelegateErrors);
@@ -42,7 +43,7 @@ function DelegateMagicCodeModal({login, role, onClose, isValidateCodeActionModal
};
const clearError = () => {
- if (!validateLoginError) {
+ if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) {
return;
}
clearDelegateErrorsByField(currentDelegate?.email ?? '', 'addDelegate');
@@ -54,11 +55,11 @@ function DelegateMagicCodeModal({login, role, onClose, isValidateCodeActionModal
shouldHandleNavigationBack={shouldHandleNavigationBack}
clearError={clearError}
onClose={onBackButtonPress}
+ validateCodeActionErrorField="addDelegate"
validateError={validateLoginError}
isVisible={isValidateCodeActionModalVisible}
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={() => requestValidateCodeAction()}
- hasMagicCodeBeenSent={validateCodeAction?.validateCodeSent}
handleSubmitForm={(validateCode) => addDelegate(login, role, validateCode)}
descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
/>
diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodeModal.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodeModal.tsx
index 8ddbe1a350e6..0585cd4814e0 100644
--- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodeModal.tsx
+++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodeModal.tsx
@@ -9,6 +9,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
type UpdateDelegateMagicCodeModalProps = {
login: string;
@@ -18,8 +19,8 @@ type UpdateDelegateMagicCodeModalProps = {
};
function UpdateDelegateMagicCodeModal({login, role, isValidateCodeActionModalVisible, onClose}: UpdateDelegateMagicCodeModalProps) {
const {translate} = useLocalize();
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
const currentDelegate = account?.delegatedAccess?.delegates?.find((d) => d.email === login);
const updateDelegateErrors = account?.delegatedAccess?.errorFields?.updateDelegateRole?.[login];
@@ -37,7 +38,7 @@ function UpdateDelegateMagicCodeModal({login, role, isValidateCodeActionModalVis
};
const clearError = () => {
- if (!updateDelegateErrors) {
+ if (isEmptyObject(updateDelegateErrors) && isEmptyObject(validateCodeAction?.errorFields)) {
return;
}
clearDelegateErrorsByField(currentDelegate?.email ?? '', 'updateDelegateRole');
@@ -47,12 +48,12 @@ function UpdateDelegateMagicCodeModal({login, role, isValidateCodeActionModalVis
requestValidateCodeAction()}
- hasMagicCodeBeenSent={validateCodeAction?.validateCodeSent}
handleSubmitForm={(validateCode) => updateDelegateRole(login, role, validateCode)}
descriptionPrimary={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
/>
diff --git a/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx b/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx
index ded5d16dabbc..b2b89fd912c5 100644
--- a/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx
+++ b/src/pages/settings/Security/TwoFactorAuth/CopyCodesPage.tsx
@@ -42,16 +42,14 @@ function CopyCodesPage({route}: TwoFactorAuthPageProps) {
const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
const [error, setError] = useState('');
- const [account, accountMetadata] = useOnyx(ONYXKEYS.ACCOUNT);
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
- const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
+ const [account, accountMetadata] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true});
const isUserValidated = useAccountValidation();
const contactMethod = account?.primaryLogin ?? '';
const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]);
const validateLoginError = getEarliestErrorField(loginData, 'validateLogin');
- const hasMagicCodeBeenSent = !!validateCodeAction?.validateCodeSent;
const [isValidateModalVisible, setIsValidateModalVisible] = useState(!isUserValidated);
@@ -175,7 +173,7 @@ function CopyCodesPage({route}: TwoFactorAuthPageProps) {
descriptionPrimary={translate('contacts.featureRequiresValidate')}
descriptionSecondary={translate('contacts.enterMagicCode', {contactMethod})}
isVisible={isValidateModalVisible}
- hasMagicCodeBeenSent={hasMagicCodeBeenSent}
+ validateCodeActionErrorField="validateLogin"
validatePendingAction={loginData?.pendingFields?.validateCodeSent}
sendValidateCode={() => requestValidateCodeAction()}
handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode, true)}
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx
index a785fd5db163..255354338f5e 100644
--- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx
+++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx
@@ -22,6 +22,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {requestValidateCodeAction} from '@libs/actions/User';
import {formatCardExpiration, getDomainCards, maskCard} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
+import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
@@ -35,6 +36,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {ExpensifyCardDetails} from '@src/types/onyx/Card';
+import type {Errors} from '@src/types/onyx/OnyxCommon';
import RedDotCardSection from './RedDotCardSection';
import CardDetails from './WalletPage/CardDetails';
@@ -66,7 +68,6 @@ function ExpensifyCardPage({
},
}: ExpensifyCardPageProps) {
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: false});
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: false});
const styles = useThemeStyles();
@@ -105,6 +106,7 @@ function ExpensifyCardPage({
const [cardsDetails, setCardsDetails] = useState>({});
const [isCardDetailsLoading, setIsCardDetailsLoading] = useState>({});
const [cardsDetailsErrors, setCardsDetailsErrors] = useState>({});
+ const [validateError, setValidateError] = useState({});
const openValidateCodeModal = (revealedCardID: number) => {
setCurrentCardID(revealedCardID);
@@ -127,16 +129,23 @@ function ExpensifyCardPage({
...prevState,
[currentCardID]: '',
}));
+ setIsValidateCodeActionModalVisible(false);
})
.catch((error: string) => {
+ // Displaying magic code errors is handled in the modal, no need to set it on the card
+ // TODO: remove setValidateError once backend deploys https://github.com/Expensify/Web-Expensify/pull/46007
+ if (error === 'validateCodeForm.error.incorrectMagicCode') {
+ setValidateError(() => getMicroSecondOnyxErrorWithTranslationKey('validateCodeForm.error.incorrectMagicCode'));
+ return;
+ }
setCardsDetailsErrors((prevState) => ({
...prevState,
[currentCardID]: error,
}));
+ setIsValidateCodeActionModalVisible(false);
})
.finally(() => {
setIsCardDetailsLoading((prevState: Record) => ({...prevState, [currentCardID]: false}));
- setIsValidateCodeActionModalVisible(false);
});
};
@@ -147,7 +156,6 @@ function ExpensifyCardPage({
const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType);
const primaryLogin = account?.primaryLogin ?? '';
- const loginData = loginList?.[primaryLogin];
const isSignedInAsdelegate = !!account?.delegatedAccess?.delegate || false;
if (isNotFound) {
@@ -366,11 +374,12 @@ function ExpensifyCardPage({
)}
{}}
+ clearError={() => setValidateError({})}
+ validateError={validateError}
+ validateCodeActionErrorField="revealExpensifyCardDetails"
sendValidateCode={() => requestValidateCodeAction()}
onClose={() => setIsValidateCodeActionModalVisible(false)}
isVisible={isValidateCodeActionModalVisible}
- hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
title={translate('cardPage.validateCardTitle')}
descriptionPrimary={translate('cardPage.enterMagicCode', {contactMethod: primaryLogin})}
/>
diff --git a/src/pages/settings/Wallet/ReportCardLostPage.tsx b/src/pages/settings/Wallet/ReportCardLostPage.tsx
index 64cb1c0422a8..3722343e1f5d 100644
--- a/src/pages/settings/Wallet/ReportCardLostPage.tsx
+++ b/src/pages/settings/Wallet/ReportCardLostPage.tsx
@@ -62,11 +62,11 @@ function ReportCardLostPage({
const {translate} = useLocalize();
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const [formData] = useOnyx(ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM);
- const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
- const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
+ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true});
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [formData] = useOnyx(ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM, {canBeMissing: true});
+ const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
+ const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true});
const [reason, setReason] = useState