-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix:blank page appears in validate code form page #55588
base: main
Are you sure you want to change the base?
Changes from all commits
6373c37
172c7b0
c4e944f
6232476
d216e60
217ac60
622df4f
9f1f207
5493c6b
dd436e9
a404826
6640264
6a12518
3f12bb5
433778f
c001758
72e88b7
0afd14a
9b7b20d
c19c2af
0c12b86
b6e0138
5cce05c
426d8be
00d7c51
8d37911
23498e9
0d1f9fe
a317155
5b3af3c
e37d564
589f444
0e4b20f
bc83a7b
1c0cced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import React, {forwardRef, useEffect, useRef, useState} from 'react'; | ||
import type {ForwardedRef} from 'react'; | ||
import {InteractionManager, View} from 'react-native'; | ||
import {useOnyx} from 'react-native-onyx'; | ||
import useThemeStyles from '@hooks/useThemeStyles'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
import Text from './Text'; | ||
import type {ValidateCodeActionModalProps} from './ValidateCodeActionModal/type'; | ||
import ValidateCodeForm from './ValidateCodeActionModal/ValidateCodeForm'; | ||
import type {ValidateCodeFormHandle} from './ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm'; | ||
|
||
type ValidateCodeActionFormProps = { | ||
/** Ref for validate code form */ | ||
forwardedRef: ForwardedRef<ValidateCodeFormHandle>; | ||
}; | ||
|
||
type ValidateCodeActionProps = ValidateCodeActionModalProps & ValidateCodeActionFormProps; | ||
|
||
function ValidateCodeActionForm({ | ||
isVisible, | ||
descriptionPrimary, | ||
descriptionSecondary, | ||
validatePendingAction, | ||
validateError, | ||
handleSubmitForm, | ||
clearError, | ||
sendValidateCode, | ||
hasMagicCodeBeenSent, | ||
isLoading, | ||
forwardedRef, | ||
}: ValidateCodeActionProps) { | ||
const themeStyles = useThemeStyles(); | ||
const firstRenderRef = useRef(true); | ||
const isClosedRef = useRef(false); | ||
|
||
const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); | ||
|
||
const [canSendHasMagicCodeBeenSent, setCanSendHasMagicCodeBeenSent] = useState(false); | ||
|
||
useEffect( | ||
() => () => { | ||
firstRenderRef.current = true; | ||
isClosedRef.current = true; | ||
}, | ||
[], | ||
); | ||
|
||
useEffect(() => { | ||
if (!firstRenderRef.current || hasMagicCodeBeenSent) { | ||
// eslint-disable-next-line rulesdir/prefer-early-return | ||
return () => { | ||
if (isClosedRef.current && isVisible) { | ||
clearError(); | ||
} | ||
}; | ||
} | ||
firstRenderRef.current = false; | ||
if (isVisible) { | ||
sendValidateCode(); | ||
} | ||
if (hasMagicCodeBeenSent) { | ||
InteractionManager.runAfterInteractions(() => { | ||
setCanSendHasMagicCodeBeenSent(true); | ||
}); | ||
} | ||
}, [isVisible, sendValidateCode, hasMagicCodeBeenSent, clearError]); | ||
|
||
if (isVisible) { | ||
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={canSendHasMagicCodeBeenSent} | ||
/> | ||
</View> | ||
); | ||
} | ||
} | ||
|
||
ValidateCodeActionForm.displayName = 'ValidateCodeActionForm'; | ||
|
||
export default forwardRef<ValidateCodeFormHandle, ValidateCodeActionProps>((props, ref) => ( | ||
<ValidateCodeActionForm | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
forwardedRef={ref} | ||
/> | ||
)); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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, | ||||||||||||||||||
|
@@ -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'; | ||||||||||||||||||
|
@@ -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); | ||||||||||||||||||
|
@@ -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(() => { | ||||||||||||||||||
|
@@ -172,15 +172,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 />; | ||||||||||||||||||
|
@@ -262,20 +262,50 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { | |||||||||||||||||
/> | ||||||||||||||||||
</OfflineWithFeedback> | ||||||||||||||||||
)} | ||||||||||||||||||
{getDeleteConfirmationModal()} | ||||||||||||||||||
</> | ||||||||||||||||||
); | ||||||||||||||||||
|
||||||||||||||||||
return ( | ||||||||||||||||||
<ScreenWrapper | ||||||||||||||||||
onEntryTransitionEnd={() => validateCodeFormRef.current?.focus?.()} | ||||||||||||||||||
testID={ContactMethodDetailsPage.displayName} | ||||||||||||||||||
focusTrapSettings={{ | ||||||||||||||||||
focusTrapOptions: isMobileSafari() | ||||||||||||||||||
? undefined | ||||||||||||||||||
: { | ||||||||||||||||||
// It is added because input form's focusing bothers 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 || (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.flex1} | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you elaborate why do we need this style? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This style makes put "Verify" button to bottom. W/o the style the button will be placed beneath validate code form. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping @jacobkim9881 again ^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The elements can fit the necessary space. The explain, form and button elements are placed in its position with this style. |
||||||||||||||||||
> | ||||||||||||||||||
{isFailedAddContactMethod && ( | ||||||||||||||||||
<ErrorMessageRow | ||||||||||||||||||
errors={getLatestErrorField(loginData, 'addedLogin')} | ||||||||||||||||||
|
@@ -288,33 +318,22 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { | |||||||||||||||||
canDismissError | ||||||||||||||||||
/> | ||||||||||||||||||
)} | ||||||||||||||||||
{isValidateCodeFormVisible && !loginData.validatedDate && !!loginData && ( | ||||||||||||||||||
<ValidateCodeActionForm | ||||||||||||||||||
hasMagicCodeBeenSent={hasMagicCodeBeenSent} | ||||||||||||||||||
isVisible={isValidateCodeFormVisible && !loginData.validatedDate && !!loginData} | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jacobkim9881 Do we need this prop anymore? 👀 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need App/src/components/ValidateCodeActionForm.tsx Lines 47 to 51 in 0e4b20f
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it always works without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will works without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It works without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean as you can see the value of prop
is same as condition to render App/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx Lines 321 to 322 in bc83a7b
therefore I am concerned is there any way that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was an exception. I checked clear function runs when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you going to clean up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me update without |
||||||||||||||||||
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> | ||||||||||||||||||
); | ||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jacobkim9881 will it cause any issue if we apply it for mweb Safari too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It bothers keyboard showing on mWeb Safari:
REC-20250205094156.mp4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On mWeb Safari, number pad shows at contact method page. It is because of
onActive
event fired by focus trap. Number pad gets ready for putting numbers on the validate form. On the screen, contact method page is seen still but focusing is on validate form. As a result before transition animation shows, number pad shows earlier.