From 6c56fffa7ea0cdd718c44b213c91f39c7bfe8f87 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 28 Nov 2025 16:22:46 +0200 Subject: [PATCH] resend code logic --- .../contacts/screens/VerifyNumber.tsx | 62 ++++++++++++++++--- src/components/contacts/state.ts | 10 +++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/components/contacts/screens/VerifyNumber.tsx b/src/components/contacts/screens/VerifyNumber.tsx index 634d187229..80a4b7a872 100644 --- a/src/components/contacts/screens/VerifyNumber.tsx +++ b/src/components/contacts/screens/VerifyNumber.tsx @@ -1,11 +1,12 @@ -import {useMemo, useState} from 'react' -import {View} from 'react-native' +import {useEffect, useMemo, useState} from 'react' +import {Text as NestedText, View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useMutation} from '@tanstack/react-query' import {wait} from '#/lib/async/wait' import {getPhoneCodeFromCountryCode} from '#/lib/international-telephone-codes' +import {clamp} from '#/lib/numbers' import {cleanError, isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {atoms as a, useGutters, useTheme} from '#/alf' @@ -15,6 +16,7 @@ import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/componen import {type Props as SVGIconProps} from '#/components/icons/common' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import * as Layout from '#/components/Layout' +import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {OTPInput} from '../components/OTPInput' @@ -88,11 +90,14 @@ export function VerifyNumber({ }, }) - const {mutate: resendCode} = useMutation({ + const {mutate: resendCode, isPending: isResendingCode} = useMutation({ mutationFn: async () => { await new Promise(resolve => setTimeout(resolve, 2000)) }, - onSuccess: () => Toast.show(_(msg`Code resent`)), + onSuccess: () => { + dispatch({type: 'RESEND_VERIFICATION_CODE'}) + Toast.show(_(msg`Code resent`)) + }, onMutate: () => { setError(null) }, @@ -164,9 +169,11 @@ export function VerifyNumber({ resendCode()} onRetry={() => verifyNumber(otpCode)} + lastCodeSentAt={state.lastSentAt} /> @@ -181,9 +188,11 @@ export function VerifyNumber({ function OTPStatus({ error, isPending, + isResendingCode, isSuccess, onResend, onRetry, + lastCodeSentAt, }: { error: { retryable: boolean @@ -191,13 +200,29 @@ function OTPStatus({ message: string } | null isPending: boolean + isResendingCode: boolean isSuccess: boolean onResend: () => void onRetry: () => void + lastCodeSentAt: Date | null }) { const {_} = useLingui() const t = useTheme() + const [time, setTime] = useState(Date.now()) + useEffect(() => { + const interval = setInterval(() => { + setTime(Date.now()) + }, 1000) + return () => clearInterval(interval) + }, []) + + const timeUntilCanResend = Math.max( + 0, + 30000 - (time - (lastCodeSentAt?.getTime() ?? 0)), + ) + const isWaiting = timeUntilCanResend > 0 + let Icon: React.ComponentType | null = null let text = '' let textColor = t.atoms.text_contrast_medium.color @@ -209,7 +234,8 @@ function OTPStatus({ text = _(msg`Phone verified`) textColor = t.palette.positive_500 } else if (isPending) { - text = _(msg`Wait a moment...`) + Icon = Loader + text = _(msg`Verifying...`) } else if (error) { Icon = WarningIcon text = error.message @@ -226,7 +252,7 @@ function OTPStatus({ } return ( - + {text && ( {Icon && } @@ -247,7 +273,8 @@ function OTPStatus({ size="small" color="secondary_inverted" label={_(msg`Retry`)} - onPress={onRetry}> + onPress={onRetry} + style={[a.mt_2xl]}> Retry @@ -257,13 +284,28 @@ function OTPStatus({ {showResendButton && ( )} diff --git a/src/components/contacts/state.ts b/src/components/contacts/state.ts index 2ff1f6fb07..c3b8d26d89 100644 --- a/src/components/contacts/state.ts +++ b/src/components/contacts/state.ts @@ -48,6 +48,9 @@ export type Action = phoneNumber: string } } + | { + type: 'RESEND_VERIFICATION_CODE' + } | { type: 'VERIFY_PHONE_NUMBER_SUCCESS' } @@ -77,6 +80,13 @@ function reducer(state: State, action: Action): State { lastSentAt: new Date(), } } + case 'RESEND_VERIFICATION_CODE': { + assertCurrentStep(state, '2: verify number') + return { + ...state, + lastSentAt: new Date(), + } + } case 'VERIFY_PHONE_NUMBER_SUCCESS': { assertCurrentStep(state, '2: verify number') return {