-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Changes from 32 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
91e940d
943ce63
a2c3878
600d0d5
3fc7ee4
c715494
ef17df1
9b73217
08b8d76
aac99d3
bc398e4
3a7ebdf
35ab2c8
f415985
09e4bc1
acdfb6e
46fc5ae
c563704
44074dd
56778e3
d0f1782
043e4df
0eb5dd7
caef7cd
7bfcb5d
962def0
521653b
5dec1b7
c9e11ad
04a1970
6833658
e9d0ca3
c9f9ef1
5a33b5a
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,91 @@ | ||
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 [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); | ||
|
||
const [canSendHasMagicCodeBeenSent, setCanSendHasMagicCodeBeenSent] = useState(false); | ||
|
||
useEffect( | ||
() => () => { | ||
firstRenderRef.current = true; | ||
}, | ||
[], | ||
); | ||
|
||
useEffect(() => { | ||
if (!firstRenderRef.current || !isVisible || hasMagicCodeBeenSent) { | ||
return () => { | ||
clearError(); | ||
}; | ||
} | ||
firstRenderRef.current = false; | ||
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: | ||||||
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.
Suggested change
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! |
||||||
// 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')) { | ||||||
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.
Suggested change
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! |
||||||
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')} | ||||||
|
@@ -289,32 +319,20 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { | |||||
/> | ||||||
)} | ||||||
|
||||||
<ValidateCodeActionModal | ||||||
title={formattedContactMethod} | ||||||
onModalHide={() => {}} | ||||||
<ValidateCodeActionForm | ||||||
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.
Suggested change
Why don't we display/hide 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. Done! 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 have used |
||||||
hasMagicCodeBeenSent={hasMagicCodeBeenSent} | ||||||
isVisible={isValidateCodeActionModalVisible && !loginData.validatedDate && !!loginData} | ||||||
isVisible={isValidateCodeFormVisible && !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} | ||||||
forwardedRef={validateCodeFormRef} | ||||||
/> | ||||||
|
||||||
{!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.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.
Yeah it looks like there are a number of issues with focus traps and mWeb Safari across the app (just search for
isMobileSafari
haha. I think this should be okay for now.