Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7d5e470
Use VALIDATE_ACTION_CODE for storing/displaying magic code errors
youssef-lr Mar 22, 2025
f50c655
Remove unused function
youssef-lr Mar 22, 2025
4ee6648
Clearr both magic code and flow specific error onClose
youssef-lr Mar 22, 2025
90117e7
Use default for validateCodeActionErrorField
youssef-lr Mar 22, 2025
13e6928
Clear validateCode errors properly
youssef-lr Mar 22, 2025
c62a055
Pass validateCodeAction to ValidateCodeForm
youssef-lr Mar 22, 2025
a973516
Use magic code errors coming from PendingContactAction instead of log…
youssef-lr Mar 22, 2025
e321cc0
Lint
youssef-lr Mar 22, 2025
0487f20
Bring back prop validatePendingAction
youssef-lr Mar 22, 2025
3a51c84
Update comment
youssef-lr Mar 22, 2025
2b1b74a
Use validateCodeAction for handling magic code errors when reporting …
youssef-lr Mar 22, 2025
6e20b2f
Update comment
youssef-lr Mar 22, 2025
480874a
Don't remove virtual cards optimistically
youssef-lr Mar 22, 2025
0c6009c
Fix virtual card modal closing when entering bad magic code
youssef-lr Mar 22, 2025
cb32f5a
Add validatePendingAction to props of ValidateCodeActionModal
youssef-lr Mar 23, 2025
947c170
Use validateCodeAction errors in copy codes page
youssef-lr Mar 23, 2025
4727877
Don't make some props required
youssef-lr Mar 23, 2025
e7a471e
Pass validateCodeAction so we can use the pending action
youssef-lr Mar 23, 2025
421e613
Revert unneeded change
youssef-lr Mar 23, 2025
ec9aed9
Improve magic code error handling when reveal virtual card
youssef-lr Mar 23, 2025
02889a7
Use validateCodeAction errors when adding delegate
youssef-lr Mar 23, 2025
648f6f5
Use validateCodeAction errors when updating delegate role
youssef-lr Mar 23, 2025
2355609
Use validateCodeAction in remaining magic code modals
youssef-lr Mar 23, 2025
f5f2752
Rename Onyx key
youssef-lr Mar 23, 2025
2cad204
Rename variable
youssef-lr Mar 23, 2025
86a25ed
Fix lint
youssef-lr Mar 23, 2025
14e6e01
Clear magic code errors in BaseValidateCodeForm instead of parent com…
youssef-lr Mar 24, 2025
a776794
Lint
youssef-lr Mar 24, 2025
c588345
Remove offline data from Onyx data when adding contact method as it's…
youssef-lr Mar 25, 2025
46416a2
Remove dead code
youssef-lr Mar 25, 2025
a4ee23f
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Mar 25, 2025
4a22e09
Revert unneeded change
youssef-lr Mar 25, 2025
2b4900b
Use validateCodeAction in base componenet and don't require it for pa…
youssef-lr Mar 25, 2025
fb58d6a
Add missing props to validate modal of contact method details
youssef-lr Mar 25, 2025
3709509
Clean up no longer needed params/variables
youssef-lr Mar 25, 2025
dc0e52c
Lint
youssef-lr Mar 25, 2025
21a9529
Allow pasing hasMagicCodeBeenSent when we're not getting it from vali…
youssef-lr Mar 25, 2025
3fb94ba
Fix missing errorField prop
youssef-lr Mar 25, 2025
cc8475a
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Mar 27, 2025
2af25c1
Address comments
youssef-lr Mar 27, 2025
7693118
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Apr 4, 2025
acb5032
Fix no error shown in validate code modal when revealing card details
youssef-lr Apr 4, 2025
a5aa550
Cleanup
youssef-lr Apr 4, 2025
fd8581f
Update comment
youssef-lr Apr 5, 2025
a3bc4cd
Allow clearig validate error in reveal details page
youssef-lr Apr 6, 2025
2ce1dbc
Also clear backend errors when submitting
youssef-lr Apr 7, 2025
aeebe79
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Apr 7, 2025
6c29811
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Apr 16, 2025
2f64f56
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Apr 22, 2025
9631c45
Update src/components/ValidateCodeActionModal/ValidateCodeForm/BaseVa…
youssef-lr Apr 22, 2025
a398641
Apply suggestions from code review
youssef-lr Apr 22, 2025
b067ea3
Lint
youssef-lr Apr 22, 2025
07af406
Merge branch 'main' into youssef_better_validation_error_handling
youssef-lr Apr 23, 2025
4ce7a5b
Use new method in BaseOnboardingWorkEmailValidation
youssef-lr Apr 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
11 changes: 3 additions & 8 deletions src/components/ValidateCodeActionForm/index.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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(() => {
Expand Down Expand Up @@ -56,15 +51,15 @@ function ValidateCodeActionForm({
{!!descriptionSecondary && <Text style={[themeStyles.mb6, descriptionSecondaryStyles]}>{descriptionSecondary}</Text>}
<ValidateCodeForm
isLoading={isLoading}
validateCodeAction={validateCodeAction}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
validatePendingAction={validatePendingAction}
validateCodeActionErrorField="validateLogin"
validateError={validateError}
handleSubmitForm={handleSubmitForm}
sendValidateCode={sendValidateCode}
clearError={clearError}
buttonStyles={[themeStyles.justifyContentEnd, themeStyles.flex1]}
ref={forwardedRef}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
submitButtonText={submitButtonText}
/>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<ValidateCodeFormHandle>;

/** 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": {<timestamp>}}}
* 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;
Expand Down Expand Up @@ -87,10 +91,10 @@ type ValidateCodeFormProps = {
};

function BaseValidateCodeForm({
hasMagicCodeBeenSent,
autoComplete = 'one-time-code',
innerRef = () => {},
validateCodeAction,
hasMagicCodeBeenSent,
validateCodeActionErrorField,
validatePendingAction,
validateError,
handleSubmitForm,
Expand All @@ -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<NodeJS.Timeout | null>(null);
const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number);
const [canShowError, setCanShowError] = useState<boolean>(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<NodeJS.Timeout>();

useImperativeHandle(innerRef, () => ({
Expand Down Expand Up @@ -169,11 +175,11 @@ function BaseValidateCodeForm({
);

useEffect(() => {
if (!hasMagicCodeBeenSent) {
if (!validateCodeSent) {
return;
}
inputValidateCodeRef.current?.clear();
}, [hasMagicCodeBeenSent]);
}, [validateCodeSent]);

useEffect(() => {
if (timeRemaining > 0) {
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #72754, we also need to clearDefaultValidationCodeError

}
},
[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);
Comment thread
amyevans marked this conversation as resolved.
setCanShowError(true);
if (!validateCode.trim()) {
setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'});
Expand All @@ -228,7 +242,7 @@ function BaseValidateCodeForm({

setFormError({});
handleSubmitForm(validateCode);
}, [validateCode, handleSubmitForm]);
}, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]);

const errorText = useMemo(() => {
if (!canShowError) {
Expand All @@ -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 (
<>
<MagicCodeInput
Expand All @@ -250,7 +268,7 @@ function BaseValidateCodeForm({
value={validateCode}
onChangeText={onTextInput}
errorText={errorText}
hasError={canShowError ? !isEmptyObject(validateError) : false}
hasError={canShowError && !isEmptyObject(finalValidateError)}
onFulfill={validateAndSubmitForm}
autoFocus={false}
allowResubmit={allowResubmit}
Expand All @@ -263,9 +281,8 @@ function BaseValidateCodeForm({
)}
<OfflineWithFeedback
pendingAction={validateCodeAction?.pendingFields?.validateCodeSent}
errors={latestActionVerifiedError}
errorRowStyles={[styles.mt2]}
onClose={() => clearValidateCodeActionError('actionVerified')}
onClose={() => clearValidateCodeActionError(validateCodeActionErrorField)}
>
{!shouldShowTimer && (
<View style={[styles.mt5, styles.dFlex, styles.flexColumn, styles.alignItemsStart]}>
Expand All @@ -284,7 +301,7 @@ function BaseValidateCodeForm({
</View>
)}
</OfflineWithFeedback>
{!!hasMagicCodeBeenSent && (
{!!validateCodeSent && (
<DotIndicatorMessage
type="success"
style={[styles.mt6, styles.flex0]}
Expand All @@ -296,9 +313,14 @@ function BaseValidateCodeForm({
<OfflineWithFeedback
shouldDisplayErrorAbove
pendingAction={validatePendingAction}
errors={canShowError ? validateError : undefined}
errors={canShowError ? finalValidateError : undefined}
errorRowStyles={[styles.mt2, styles.textWrap]}
onClose={() => clearError()}
onClose={() => {
clearError();
if (!isEmptyObject(validateCodeAction?.errorFields) && validateCodeActionErrorField) {
clearValidateCodeActionError(validateCodeActionErrorField);
}
}}
style={buttonStyles}
>
{shouldShowSkipButton && (
Expand Down
14 changes: 6 additions & 8 deletions src/components/ValidateCodeActionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ function ValidateCodeActionModal({
descriptionSecondary,
onClose,
onModalHide,
validatePendingAction,
validateError,
validatePendingAction,
validateCodeActionErrorField,
handleSubmitForm,
clearError,
footer,
sendValidateCode,
hasMagicCodeBeenSent,
isLoading,
shouldHandleNavigationBack,
disableAnimation,
Expand All @@ -40,8 +40,7 @@ function ValidateCodeActionModal({
const validateCodeFormRef = useRef<ValidateCodeFormHandle>(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();
Expand All @@ -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]);
Expand Down Expand Up @@ -102,15 +101,14 @@ function ValidateCodeActionModal({
{!!descriptionSecondary && <Text style={[themeStyles.mb3]}>{descriptionSecondary}</Text>}
<ValidateCodeForm
isLoading={isLoading}
validateCodeAction={validateCodeAction}
validatePendingAction={validatePendingAction}
validateCodeActionErrorField={validateCodeActionErrorField}
validateError={validateError}
handleSubmitForm={handleSubmitForm}
sendValidateCode={sendValidateCode}
clearError={clearError}
buttonStyles={[themeStyles.justifyContentEnd, themeStyles.flex1]}
ref={validateCodeFormRef}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
/>
</View>
</ScrollView>
Expand Down
12 changes: 6 additions & 6 deletions src/components/ValidateCodeActionModal/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
Expand Down
6 changes: 3 additions & 3 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
34 changes: 0 additions & 34 deletions src/libs/actions/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand All @@ -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 = {
Expand Down
Loading