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

feat: add timer on validate code action modal #51663

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type ValidateCodeFormProps = {
/** 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;
};

Expand Down Expand Up @@ -90,6 +91,10 @@ function BaseValidateCodeForm({
// 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 [isResent, setIsResent] = useState(false);

const timerRef = useRef<NodeJS.Timeout>();

useImperativeHandle(innerRef, () => ({
focus() {
Expand Down Expand Up @@ -135,12 +140,28 @@ function BaseValidateCodeForm({
inputValidateCodeRef.current?.clear();
}, [hasMagicCodeBeenSent]);

useEffect(() => {
if (timeRemaining > 0) {
timerRef.current = setTimeout(() => {
setTimeRemaining(timeRemaining - 1);
}, 1000);
}
return () => {
clearTimeout(timerRef.current);
};
}, [timeRemaining]);

/**
* Request a validate code / magic code be sent to verify this contact method
*/
const resendValidateCode = () => {
if (hasMagicCodeBeenSent && !isResent) {
return;
}

sendValidateCode();
inputValidateCodeRef.current?.clear();
setTimeRemaining(CONST.REQUEST_CODE_DELAY);
};

/**
Expand Down Expand Up @@ -177,6 +198,7 @@ function BaseValidateCodeForm({
handleSubmitForm(validateCode);
}, [validateCode, handleSubmitForm]);

const shouldShowTimer = timeRemaining > 0 && !isOffline;
return (
<>
<MagicCodeInput
Expand All @@ -190,35 +212,46 @@ function BaseValidateCodeForm({
onFulfill={validateAndSubmitForm}
autoFocus
/>
{shouldShowTimer && (
<Text style={[styles.mt5]}>
{translate('validateCodeForm.requestNewCode')}
<Text style={[styles.textBlue]}>00:{String(timeRemaining).padStart(2, '0')}</Text>
</Text>
)}
<OfflineWithFeedback
pendingAction={validateCodeAction?.pendingFields?.validateCodeSent}
errors={ErrorUtils.getLatestErrorField(validateCodeAction, 'actionVerified')}
errorRowStyles={[styles.mt2]}
onClose={() => User.clearValidateCodeActionError('actionVerified')}
>
<View style={[styles.mt5, styles.dFlex, styles.flexColumn, styles.alignItemsStart]}>
<PressableWithFeedback
disabled={shouldDisableResendValidateCode}
style={[styles.mr1]}
onPress={resendValidateCode}
underlayColor={theme.componentBG}
hoverDimmingValue={1}
pressDimmingValue={0.2}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('validateCodeForm.magicCodeNotReceived')}
>
<Text style={[StyleUtils.getDisabledLinkStyles(shouldDisableResendValidateCode)]}>{translate('validateCodeForm.magicCodeNotReceived')}</Text>
</PressableWithFeedback>
{hasMagicCodeBeenSent && (
<DotIndicatorMessage
type="success"
style={[styles.mt6, styles.flex0]}
// eslint-disable-next-line @typescript-eslint/naming-convention
messages={{0: translate('validateCodeModal.successfulNewCodeRequest')}}
/>
)}
</View>
{!shouldShowTimer && (
<View style={[styles.mt5, styles.dFlex, styles.flexColumn, styles.alignItemsStart]}>
<PressableWithFeedback
disabled={shouldDisableResendValidateCode}
style={[styles.mr1]}
onPress={() => {
resendValidateCode();
setIsResent(true);
}}
underlayColor={theme.componentBG}
hoverDimmingValue={1}
pressDimmingValue={0.2}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('validateCodeForm.magicCodeNotReceived')}
>
<Text style={[StyleUtils.getDisabledLinkStyles(shouldDisableResendValidateCode)]}>{translate('validateCodeForm.magicCodeNotReceived')}</Text>
</PressableWithFeedback>
</View>
)}
</OfflineWithFeedback>
{hasMagicCodeBeenSent && (
<DotIndicatorMessage
type="success"
style={[styles.mt6, styles.flex0]}
// eslint-disable-next-line @typescript-eslint/naming-convention
messages={{0: translate('validateCodeModal.successfulNewCodeRequest')}}
/>
)}
<OfflineWithFeedback
pendingAction={validatePendingAction}
errors={validateError}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ValidateCodeActionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ function ValidateCodeActionModal({
}, [onClose, clearError]);

useEffect(() => {
if (!firstRenderRef.current || !isVisible) {
if (!firstRenderRef.current || !isVisible || hasMagicCodeBeenSent) {
return;
}
firstRenderRef.current = false;

sendValidateCode();
}, [isVisible, sendValidateCode]);
}, [isVisible, sendValidateCode, hasMagicCodeBeenSent]);

return (
<Modal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
User.deleteContactMethod(contactMethod, loginList ?? {}, backTo);
}, [contactMethod, loginList, toggleDeleteModal, backTo]);

const sendValidateCode = () => {
if (loginData?.validateCodeSent) {
return;
}

User.requestContactMethodValidateCode(contactMethod);
};

const prevValidatedDate = usePrevious(loginData?.validatedDate);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
Expand Down Expand Up @@ -276,7 +268,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo));
setIsValidateCodeActionModalVisible(false);
}}
sendValidateCode={sendValidateCode}
sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)}
description={translate('contacts.enterMagicCode', {contactMethod})}
footer={() => getMenuItems()}
/>
Expand Down
13 changes: 3 additions & 10 deletions src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,6 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) {
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(navigateBackTo));
}, [navigateBackTo]);

const sendValidateCode = () => {
if (loginData?.validateCodeSent) {
return;
}

User.requestValidateCodeAction();
};

return (
<AccessOrNotFoundWrapper shouldBeBlocked={isActingAsDelegate}>
<ScreenWrapper
Expand Down Expand Up @@ -176,8 +168,9 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) {
setIsValidateCodeActionModalVisible(false);
}}
isVisible={isValidateCodeActionModalVisible}
title={contactMethod}
sendValidateCode={sendValidateCode}
hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={() => User.requestValidateCodeAction()}
description={translate('contacts.enterMagicCode', {contactMethod})}
/>
</ScreenWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,15 @@ function DelegateMagicCodeModal({login, role, onClose}: DelegateMagicCodeModalPr
Delegate.clearAddDelegateErrors(currentDelegate?.email ?? '', 'addDelegate');
};

const sendValidateCode = () => {
if (currentDelegate?.validateCodeSent) {
return;
}

User.requestValidateCodeAction();
};

return (
<ValidateCodeActionModal
clearError={clearError}
onClose={onBackButtonPress}
validateError={validateLoginError}
isVisible={isValidateCodeActionModalVisible}
title={translate('delegate.makeSureItIsYou')}
sendValidateCode={sendValidateCode}
sendValidateCode={() => User.requestValidateCodeAction()}
hasMagicCodeBeenSent={!!currentDelegate?.validateCodeSent}
handleSubmitForm={(validateCode) => Delegate.addDelegate(login, role, validateCode)}
description={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
/>
Expand Down
17 changes: 5 additions & 12 deletions src/pages/settings/Wallet/ExpensifyCardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ function ExpensifyCardPage({
const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(cardsToShow?.at(0)?.availableSpend);
const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType);

const primaryLogin = account?.primaryLogin ?? '';
const loginData = loginList?.[primaryLogin];

const goToGetPhysicalCardFlow = () => {
let updatedDraftValues = draftValues;
if (!draftValues) {
Expand All @@ -146,17 +149,6 @@ function ExpensifyCardPage({
GetPhysicalCardUtils.goToNextPhysicalCardRoute(domain, GetPhysicalCardUtils.getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails));
};

const sendValidateCode = () => {
const primaryLogin = account?.primaryLogin ?? '';
const loginData = loginList?.[primaryLogin];

if (loginData?.validateCodeSent) {
return;
}

requestValidateCodeAction();
};

if (isNotFound) {
return <NotFoundPage onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WALLET)} />;
}
Expand Down Expand Up @@ -310,9 +302,10 @@ function ExpensifyCardPage({
<ValidateCodeActionModal
handleSubmitForm={handleRevealDetails}
clearError={() => {}}
sendValidateCode={sendValidateCode}
sendValidateCode={() => requestValidateCodeAction()}
onClose={() => setIsValidateCodeActionModalVisible(false)}
isVisible={isValidateCodeActionModalVisible}
hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
title={translate('cardPage.validateCardTitle')}
description={translate('cardPage.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})}
/>
Expand Down
1 change: 1 addition & 0 deletions src/pages/settings/Wallet/VerifyAccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) {
sendValidateCode={() => User.requestValidateCodeAction()}
handleSubmitForm={handleSubmitForm}
validateError={validateLoginError}
hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
isVisible={isValidateCodeActionModalVisible}
title={translate('contacts.validateAccount')}
description={translate('contacts.featureRequiresValidate')}
Expand Down
Loading