Skip to content
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

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6373c37
fix:blank page appears in validate code form page
jacobkim9881 Jan 22, 2025
172c7b0
lint: fixed ! assertion error
jacobkim9881 Jan 22, 2025
c4e944f
pulled original/main
jacobkim9881 Jan 22, 2025
6232476
lint assertion err fixed
jacobkim9881 Jan 23, 2025
d216e60
lint disabled prop spreading
jacobkim9881 Jan 23, 2025
217ac60
Revert "pulled original/main"
jacobkim9881 Jan 23, 2025
622df4f
comment type for ref
jacobkim9881 Jan 23, 2025
9f1f207
should resolve() if container is undefined
jacobkim9881 Jan 23, 2025
5493c6b
comment about focus trap for animation
jacobkim9881 Jan 23, 2025
dd436e9
fix: clicking header back err occurs
jacobkim9881 Jan 23, 2025
a404826
clear error only after closing the modal
jacobkim9881 Jan 24, 2025
6640264
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Jan 24, 2025
6a12518
lint fixed
jacobkim9881 Jan 24, 2025
3f12bb5
Revert "lint fixed"
jacobkim9881 Jan 24, 2025
433778f
lint fixed
jacobkim9881 Jan 24, 2025
c001758
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Jan 28, 2025
72e88b7
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Jan 29, 2025
0afd14a
moved isVisible in the validate code form
jacobkim9881 Jan 30, 2025
9b7b20d
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Jan 30, 2025
c19c2af
hide menu items while sliding out of validate page
jacobkim9881 Jan 31, 2025
0c12b86
Revert "hide menu items while sliding out of validate page"
jacobkim9881 Jan 31, 2025
b6e0138
fix: trashcan shows as navigating out on mWeb
jacobkim9881 Feb 2, 2025
5cce05c
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Feb 2, 2025
426d8be
delete this becuase transition anim works w/o it
jacobkim9881 Feb 3, 2025
00d7c51
renamed and deleted ValidateCodeActionWithoutModal
jacobkim9881 Feb 3, 2025
8d37911
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Feb 3, 2025
23498e9
lint fixed
jacobkim9881 Feb 3, 2025
0d1f9fe
focusTrap not needed in mSafari
jacobkim9881 Feb 3, 2025
a317155
fix: keyboard not show on Android app, iOS app
jacobkim9881 Feb 4, 2025
5b3af3c
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Feb 4, 2025
e37d564
fix: show remove contact method modal
jacobkim9881 Feb 4, 2025
589f444
focusTrap not needed in mSafari
jacobkim9881 Feb 4, 2025
0e4b20f
show/hide code form
jacobkim9881 Feb 5, 2025
bc83a7b
fix: clear error runs on default contact method too
jacobkim9881 Feb 6, 2025
1c0cced
Merge branch 'main' of https://github.com/Expensify/App into m53884
jacobkim9881 Feb 7, 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
91 changes: 91 additions & 0 deletions src/components/ValidateCodeActionForm.tsx
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}
/>
));
2 changes: 1 addition & 1 deletion src/components/ValidateCodeActionModal/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type ValidateCodeActionModalProps = {
isVisible: boolean;

/** Title of the modal */
title: string;
title?: string;

/** Primary description of the modal */
descriptionPrimary: string;
Expand Down
74 changes: 46 additions & 28 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 @@ -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 />;
Expand Down Expand Up @@ -262,20 +262,50 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
/>
</OfflineWithFeedback>
)}
{getDeleteConfirmationModal()}
</>
);

return (
<ScreenWrapper
onEntryTransitionEnd={() => validateCodeFormRef.current?.focus?.()}
testID={ContactMethodDetailsPage.displayName}
focusTrapSettings={{
focusTrapOptions: isMobileSafari()
? undefined
Comment on lines +273 to +274
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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.

: {
// 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}
Copy link
Contributor

Choose a reason for hiding this comment

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

can you elaborate why do we need this style?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

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

ping @jacobkim9881 again ^

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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')}
Expand All @@ -289,32 +319,20 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
/>
)}

<ValidateCodeActionModal
title={formattedContactMethod}
onModalHide={() => {}}
<ValidateCodeActionForm
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<ValidateCodeActionForm
{isValidateCodeFormVisible && !loginData.validatedDate && !!loginData && <ValidateCodeActionForm

Why don't we display/hide ValidateCodeActionForm here? But we prefer to pass isVisible prop into ValidateCodeActionForm?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have used isVisible for the modal though.

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>
);
Expand Down
Loading