Skip to content

Commit

Permalink
Merge pull request #55588 from jacobkim9881/m53884
Browse files Browse the repository at this point in the history
fix:blank page appears in validate code form page
  • Loading branch information
dangrous authored Mar 3, 2025
2 parents 6b809c9 + 5a33b5a commit 67f43dd
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 38 deletions.
75 changes: 75 additions & 0 deletions src/components/ValidateCodeActionForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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({
descriptionPrimary,
descriptionSecondary,
validatePendingAction,
validateError,
handleSubmitForm,
clearError,
sendValidateCode,
hasMagicCodeBeenSent,
isLoading,
forwardedRef,
}: ValidateCodeActionFormProps) {
const themeStyles = useThemeStyles();

const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);

const isUnmounted = useRef(false);

useEffect(() => {
sendValidateCode();

return () => {
isUnmounted.current = true;
};
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);

useEffect(() => {
return () => {
if (!isUnmounted.current) {
return;
}
clearError();
};
}, [clearError]);

return (
<View style={[themeStyles.ph5, themeStyles.mt3, themeStyles.mb5, themeStyles.flex1]}>
<Text style={[themeStyles.mb3]}>{descriptionPrimary}</Text>
{!!descriptionSecondary && <Text style={[themeStyles.mb3]}>{descriptionSecondary}</Text>}
<ValidateCodeForm
isLoading={isLoading}
validateCodeAction={validateCodeAction}
validatePendingAction={validatePendingAction}
validateError={validateError}
handleSubmitForm={handleSubmitForm}
sendValidateCode={sendValidateCode}
clearError={clearError}
buttonStyles={[themeStyles.justifyContentEnd, themeStyles.flex1]}
ref={forwardedRef}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
/>
</View>
);
}

ValidateCodeActionForm.displayName = 'ValidateCodeActionForm';

export default forwardRef<ValidateCodeFormHandle, ValidateCodeActionFormProps>((props, ref) => (
<ValidateCodeActionForm
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
forwardedRef={ref}
/>
));
38 changes: 38 additions & 0 deletions src/components/ValidateCodeActionForm/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type {ForwardedRef} from 'react';
import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';

type ValidateCodeActionFormProps = {
/** Primary description of the modal */
descriptionPrimary: string;

/** Secondary description of the modal */
descriptionSecondary?: string | null;

/** The pending action for submitting form */
validatePendingAction?: PendingAction | null;

/** The error of submitting */
validateError?: Errors;

/** Function is called when submitting form */
handleSubmitForm: (validateCode: string) => void;

/** Function to clear error of the form */
clearError: () => void;

/** 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;

/** Ref for validate code form */
forwardedRef: ForwardedRef<ValidateCodeFormHandle>;
};

// eslint-disable-next-line import/prefer-default-export
export type {ValidateCodeActionFormProps};
100 changes: 62 additions & 38 deletions src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ 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 useBeforeRemove from '@hooks/useBeforeRemove';
import ValidateCodeActionForm from '@components/ValidateCodeActionForm';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import blurActiveElement from '@libs/Accessibility/blurActiveElement';
import {
clearContactMethod,
Expand All @@ -30,6 +30,7 @@ import {
setContactMethodAsDefault,
validateSecondaryLogin,
} from '@libs/actions/User';
import {isMobileSafari} from '@libs/Browser';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {getEarliestErrorField, getLatestErrorField} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
Expand All @@ -54,13 +55,14 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
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 [isValidateCodeFormVisible, setIsValidateCodeFormVisible] = useState(true);

const isLoadingOnyxValues = isLoadingOnyxValue(loginListResult, sessionResult, myDomainSecurityGroupsResult, securityGroupsResult, isLoadingReportDataResult);

const {formatPhoneNumber, translate} = useLocalize();
const theme = useTheme();
const themeStyles = useThemeStyles();
const {windowWidth} = useWindowDimensions();

const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const validateCodeFormRef = useRef<ValidateCodeFormHandle>(null);
Expand Down Expand Up @@ -160,10 +162,8 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo));
}, [prevValidatedDate, loginData?.validatedDate, isDefaultContactMethod, backTo, loginData]);

useBeforeRemove(() => setIsValidateCodeActionModalVisible(false));

useEffect(() => {
setIsValidateCodeActionModalVisible(!loginData?.validatedDate);
setIsValidateCodeFormVisible(!loginData?.validatedDate);
}, [loginData?.validatedDate, loginData?.errorFields?.addedLogin]);

useEffect(() => {
Expand All @@ -175,15 +175,15 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {

const getThreeDotsMenuItems = useCallback(() => {
const menuItems = [];
if (isValidateCodeActionModalVisible && !isDefaultContactMethod) {
if (isValidateCodeFormVisible && !isDefaultContactMethod) {
menuItems.push({
icon: Trashcan,
text: translate('common.remove'),
onSelected: () => close(() => toggleDeleteModal(true)),
});
}
return menuItems;
}, [isValidateCodeActionModalVisible, translate, toggleDeleteModal, isDefaultContactMethod]);
}, [isValidateCodeFormVisible, translate, toggleDeleteModal, isDefaultContactMethod]);

if (isLoadingOnyxValues || (isLoadingReportData && isEmptyObject(loginList))) {
return <FullscreenLoadingIndicator />;
Expand Down Expand Up @@ -265,20 +265,56 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
/>
</OfflineWithFeedback>
)}
{getDeleteConfirmationModal()}
</>
);

return (
<ScreenWrapper
onEntryTransitionEnd={() => validateCodeFormRef.current?.focus?.()}
shouldEnableMaxHeight
onEntryTransitionEnd={() => {
InteractionManager.runAfterInteractions(() => {
validateCodeFormRef.current?.focus?.();
});
}}
testID={ContactMethodDetailsPage.displayName}
focusTrapSettings={{
focusTrapOptions: isMobileSafari()
? undefined
: {
// We need to check this because focusing the input form interferes with the transition animation:
// https://github.com/Expensify/App/issues/53884#issuecomment-2594568960
checkCanFocusTrap: (trapContainers: Array<HTMLElement | SVGElement>) => {
return new Promise((resolve) => {
const interval = setInterval(() => {
const trapContainer = trapContainers.at(0);
if (!trapContainer || getComputedStyle(trapContainer).visibility !== 'hidden') {
resolve();
clearInterval(interval);
}
}, 5);
});
},
},
}}
>
<HeaderWithBackButton
title={formattedContactMethod}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo))}
threeDotsMenuItems={getThreeDotsMenuItems()}
shouldShowThreeDotsButton={getThreeDotsMenuItems().length > 0}
shouldOverlayDots
threeDotsAnchorPosition={themeStyles.threeDotsPopoverOffset(windowWidth)}
onThreeDotsButtonPress={() => {
// Hide the keyboard when the user clicks the three-dot menu.
// Use blurActiveElement() for mWeb and KeyboardUtils.dismiss() for native apps.
blurActiveElement();
KeyboardUtils.dismiss();
}}
/>
<ScrollView keyboardShouldPersistTaps="handled">
<ScrollView
keyboardShouldPersistTaps="handled"
contentContainerStyle={themeStyles.flexGrow1}
style={[themeStyles.w100, themeStyles.h100, themeStyles.flex1]}
>
{isFailedAddContactMethod && (
<ErrorMessageRow
errors={getLatestErrorField(loginData, 'addedLogin')}
Expand All @@ -291,33 +327,21 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
canDismissError
/>
)}
{isValidateCodeFormVisible && !!loginData && !loginData.validatedDate && (
<ValidateCodeActionForm
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
validatePendingAction={loginData.pendingFields?.validateCodeSent}
handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode)}
validateError={!isEmptyObject(validateLoginError) ? validateLoginError : getLatestErrorField(loginData, 'validateCodeSent')}
clearError={() => clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')}
sendValidateCode={() => requestContactMethodValidateCode(contactMethod)}
descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})}
forwardedRef={validateCodeFormRef}
/>
)}

<ValidateCodeActionModal
title={formattedContactMethod}
onModalHide={() => {}}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
isVisible={isValidateCodeActionModalVisible && !loginData.validatedDate && !!loginData}
validatePendingAction={loginData.pendingFields?.validateCodeSent}
handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode)}
validateError={!isEmptyObject(validateLoginError) ? validateLoginError : getLatestErrorField(loginData, 'validateCodeSent')}
clearError={() => clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')}
onClose={() => {
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo));
setIsValidateCodeActionModalVisible(false);
}}
sendValidateCode={() => requestContactMethodValidateCode(contactMethod)}
descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod: formattedContactMethod})}
onThreeDotsButtonPress={() => {
// Hide the keyboard when the user clicks the three-dot menu.
// Use blurActiveElement() for mWeb and KeyboardUtils.dismiss() for native apps.
blurActiveElement();
KeyboardUtils.dismiss();
}}
threeDotsMenuItems={getThreeDotsMenuItems()}
footer={getDeleteConfirmationModal}
/>

{!isValidateCodeActionModalVisible && getMenuItems()}
{!isValidateCodeFormVisible && !!loginData.validatedDate && getMenuItems()}
{getDeleteConfirmationModal()}
</ScrollView>
</ScreenWrapper>
);
Expand Down

0 comments on commit 67f43dd

Please sign in to comment.