From 9fe53fad87269990640451925137c7fdb15d4711 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:03:22 -0700 Subject: [PATCH] Use TokenField for password change and reset forms --- oxlint-suppressions.json | 5 - .../EmailDialog/components/TokenField.tsx | 19 +++- src/screens/Login/SetNewPasswordForm.tsx | 38 ++------ .../components/ChangePasswordDialog.tsx | 94 +++++++------------ 4 files changed, 59 insertions(+), 97 deletions(-) diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 1ef59ea20c..3b26cbe82b 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -965,11 +965,6 @@ "count": 1 } }, - "src/screens/Settings/components/ChangePasswordDialog.tsx": { - "typescript/no-explicit-any": { - "count": 2 - } - }, "src/screens/Settings/components/CopyButton.tsx": { "typescript/no-floating-promises": { "count": 1 diff --git a/src/components/dialogs/EmailDialog/components/TokenField.tsx b/src/components/dialogs/EmailDialog/components/TokenField.tsx index 1e37278bdf..1087fb8bbb 100644 --- a/src/components/dialogs/EmailDialog/components/TokenField.tsx +++ b/src/components/dialogs/EmailDialog/components/TokenField.tsx @@ -15,10 +15,23 @@ export function isValidCode(value?: string) { } export function TokenField({ + testID, + autoFocus, + editable, value, onChangeText, + onFocus, onSubmitEditing, -}: Pick) { +}: Pick< + TextInputProps, + | 'testID' + | 'autoFocus' + | 'editable' + | 'value' + | 'onChangeText' + | 'onFocus' + | 'onSubmitEditing' +>) { const {t: l} = useLingui() const isInvalid = Boolean(value && value.length > 10 && !isValidCode(value)) @@ -31,14 +44,18 @@ export function TokenField({ diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx index f870171be8..da51896602 100644 --- a/src/screens/Login/SetNewPasswordForm.tsx +++ b/src/screens/Login/SetNewPasswordForm.tsx @@ -9,9 +9,9 @@ import {logger} from '#/logger' import {atoms as a, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {TokenField} from '#/components/dialogs/EmailDialog/components/TokenField' import * as TextField from '#/components/forms/TextField' import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' -import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' @@ -87,17 +87,6 @@ export const SetNewPasswordForm = ({ } } - const onBlur = () => { - const formattedCode = checkAndFormatResetCode(resetCode) - if (!formattedCode) { - setError( - l`You have entered an invalid code. It should look like XXXXX-XXXXX.`, - ) - return - } - setResetCode(formattedCode) - } - return ( Reset code - - - setError('')} - onBlur={onBlur} - editable={!isProcessing} - accessibilityHint={l`Input code sent to your email for password reset`} - /> - + setError('')} + /> diff --git a/src/screens/Settings/components/ChangePasswordDialog.tsx b/src/screens/Settings/components/ChangePasswordDialog.tsx index 3015b402b5..a242560c19 100644 --- a/src/screens/Settings/components/ChangePasswordDialog.tsx +++ b/src/screens/Settings/components/ChangePasswordDialog.tsx @@ -1,8 +1,6 @@ import {useState} from 'react' import {useWindowDimensions, View} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import * as EmailValidator from 'email-validator' import {cleanError, isNetworkError} from '#/lib/strings/errors' @@ -14,6 +12,7 @@ import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {android, atoms as a, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' +import {TokenField} from '#/components/dialogs/EmailDialog/components/TokenField' import * as TextField from '#/components/forms/TextField' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' @@ -44,7 +43,7 @@ export function ChangePasswordDialog({ } function Inner() { - const {_} = useLingui() + const {t: l} = useLingui() const {currentAccount} = useSession() const client = usePdsClient() const control = Dialog.useDialogContext() @@ -57,22 +56,16 @@ function Inner() { const uiStrings = { RequestCode: { - title: _(msg`Change your password`), - message: _( - msg`If you want to change your password, we will send you a code to verify that this is your account.`, - ), + title: l`Change your password`, + message: l`If you want to change your password, we will send you a code to verify that this is your account.`, }, ChangePassword: { - title: _(msg`Enter code`), - message: _( - msg`Please enter the code you received and the new password you would like to use.`, - ), + title: l`Enter code`, + message: l`Please enter the code you received and the new password you would like to use.`, }, Done: { - title: _(msg`Password changed`), - message: _( - msg`Your password has been changed successfully! Please use your new password when you sign in to Bluesky from now on.`, - ), + title: l`Password changed`, + message: l`Your password has been changed successfully! Please use your new password when you sign in to Bluesky from now on.`, }, } @@ -81,7 +74,7 @@ function Inner() { !currentAccount?.email || !EmailValidator.validate(currentAccount.email) ) { - return setError(_(msg`Your email appears to be invalid.`)) + return setError(l`Your email appears to be invalid.`) } setError('') @@ -91,12 +84,11 @@ function Inner() { email: currentAccount.email, }) setStage(Stages.ChangePassword) - } catch (e: any) { + } catch (error) { + const e = error as Error if (isNetworkError(e)) { setError( - _( - msg`Unable to contact your service. Please check your internet connection and try again.`, - ), + l`Unable to contact your service. Please check your internet connection and try again.`, ) } else { logger.error('Failed to request password reset', {safeMessage: e}) @@ -111,20 +103,18 @@ function Inner() { const formattedCode = checkAndFormatResetCode(resetCode) if (!formattedCode) { setError( - _( - msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`, - ), + l`You have entered an invalid code. It should look like XXXXX-XXXXX.`, ) return } if (!newPassword) { setError( - _(msg`Please enter a password. It must be at least 8 characters long.`), + l`Please enter a password. It must be at least 8 characters long.`, ) return } if (newPassword.length < 8) { - setError(_(msg`Password must be at least 8 characters long.`)) + setError(l`Password must be at least 8 characters long.`) return } @@ -136,17 +126,16 @@ function Inner() { password: newPassword, }) setStage(Stages.Done) - } catch (e: any) { + } catch (error) { + const e = error as Error if (isNetworkError(e)) { setError( - _( - msg`Unable to contact your service. Please check your internet connection and try again.`, - ), + l`Unable to contact your service. Please check your internet connection and try again.`, ) } else if ( matchXrpcError(e, com.atproto.server.resetPassword) === 'InvalidToken' ) { - setError(_(msg`This confirmation code is not valid. Please try again.`)) + setError(l`This confirmation code is not valid. Please try again.`) } else { logger.error('Failed to set new password', {safeMessage: e}) setError(cleanError(e)) @@ -156,17 +145,9 @@ function Inner() { } } - const onBlur = () => { - const formattedCode = checkAndFormatResetCode(resetCode) - if (!formattedCode) { - return - } - setResetCode(formattedCode) - } - return ( @@ -190,18 +171,7 @@ function Inner() { Confirmation code - - - + @@ -209,8 +179,8 @@ function Inner() { {IS_NATIVE && (