Basic minimum password requirements, plus field-specific errors (#7811)

* add min password requirement

* add field specific errors

* move email tld check to after other email checks

* add password length check to change password dialog

* Update src/view/com/modals/ChangePassword.tsx

Co-authored-by: Hailey <me@haileyok.com>

* Update src/screens/Signup/StepInfo/index.tsx

Co-authored-by: Hailey <me@haileyok.com>

* fix lint

---------

Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
Samuel Newman
2025-02-24 12:44:21 -08:00
committed by GitHub
parent 3d954a00e0
commit ae9176c9c2
3 changed files with 79 additions and 23 deletions
+46 -19
View File
@@ -75,22 +75,6 @@ export function StepInfo({
const emailChanged = prevEmailValueRef.current !== email
const password = passwordValueRef.current
if (emailChanged && tldtsRef.current) {
if (isEmailMaybeInvalid(email, tldtsRef.current)) {
prevEmailValueRef.current = email
setHasWarnedEmail(true)
return dispatch({
type: 'setError',
value: _(
msg`It looks like you may have entered your email address incorrectly. Are you sure it's right?`,
),
})
}
} else if (hasWarnedEmail) {
setHasWarnedEmail(false)
}
prevEmailValueRef.current = email
if (!is13(state.dateOfBirth)) {
return
}
@@ -99,24 +83,50 @@ export function StepInfo({
return dispatch({
type: 'setError',
value: _(msg`Please enter your invite code.`),
field: 'invite-code',
})
}
if (!email) {
return dispatch({
type: 'setError',
value: _(msg`Please enter your email.`),
field: 'email',
})
}
if (!EmailValidator.validate(email)) {
return dispatch({
type: 'setError',
value: _(msg`Your email appears to be invalid.`),
field: 'email',
})
}
if (emailChanged && tldtsRef.current) {
if (isEmailMaybeInvalid(email, tldtsRef.current)) {
prevEmailValueRef.current = email
setHasWarnedEmail(true)
return dispatch({
type: 'setError',
value: _(
msg`Please double-check that you have entered your email address correctly.`,
),
})
}
} else if (hasWarnedEmail) {
setHasWarnedEmail(false)
}
prevEmailValueRef.current = email
if (!password) {
return dispatch({
type: 'setError',
value: _(msg`Please choose your password.`),
field: 'password',
})
}
if (password.length < 8) {
return dispatch({
type: 'setError',
value: _(msg`Your password must be at least 8 characters long.`),
field: 'password',
})
}
@@ -149,11 +159,17 @@ export function StepInfo({
<TextField.LabelText>
<Trans>Invite code</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Root isInvalid={state.errorField === 'invite-code'}>
<TextField.Icon icon={Ticket} />
<TextField.Input
onChangeText={value => {
inviteCodeValueRef.current = value.trim()
if (
state.errorField === 'invite-code' &&
value.trim().length > 0
) {
dispatch({type: 'clearError'})
}
}}
label={_(msg`Required for this provider`)}
defaultValue={state.inviteCode}
@@ -173,7 +189,7 @@ export function StepInfo({
<TextField.LabelText>
<Trans>Email</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Root isInvalid={state.errorField === 'email'}>
<TextField.Icon icon={Envelope} />
<TextField.Input
testID="emailInput"
@@ -183,6 +199,13 @@ export function StepInfo({
if (hasWarnedEmail) {
setHasWarnedEmail(false)
}
if (
state.errorField === 'email' &&
value.trim().length > 0 &&
EmailValidator.validate(value.trim())
) {
dispatch({type: 'clearError'})
}
}}
label={_(msg`Enter your email address`)}
defaultValue={state.email}
@@ -201,13 +224,16 @@ export function StepInfo({
<TextField.LabelText>
<Trans>Password</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Root isInvalid={state.errorField === 'password'}>
<TextField.Icon icon={Lock} />
<TextField.Input
testID="passwordInput"
inputRef={passwordInputRef}
onChangeText={value => {
passwordValueRef.current = value
if (state.errorField === 'password' && value.length >= 8) {
dispatch({type: 'clearError'})
}
}}
label={_(msg`Choose your password`)}
defaultValue={state.password}
@@ -219,6 +245,7 @@ export function StepInfo({
onSubmitEditing={native(() =>
birthdateInputRef.current?.focus(),
)}
passwordRules="minlength: 8;"
/>
</TextField.Root>
</View>
+19 -1
View File
@@ -31,6 +31,13 @@ type SubmitTask = {
mutableProcessed: boolean // OK to mutate assuming it's never read in render.
}
type ErrorField =
| 'invite-code'
| 'email'
| 'handle'
| 'password'
| 'date-of-birth'
export type SignupState = {
hasPrev: boolean
activeStep: SignupStep
@@ -45,6 +52,7 @@ export type SignupState = {
handle: string
error: string
errorField?: ErrorField
isLoading: boolean
pendingSubmit: null | SubmitTask
@@ -62,7 +70,8 @@ export type SignupAction =
| {type: 'setDateOfBirth'; value: Date}
| {type: 'setInviteCode'; value: string}
| {type: 'setHandle'; value: string}
| {type: 'setError'; value: string}
| {type: 'setError'; value: string; field?: ErrorField}
| {type: 'clearError'}
| {type: 'setIsLoading'; value: boolean}
| {type: 'submit'; task: SubmitTask}
@@ -80,6 +89,7 @@ export const initialState: SignupState = {
inviteCode: '',
error: '',
errorField: undefined,
isLoading: false,
pendingSubmit: null,
@@ -102,6 +112,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
next.activeStep--
next.error = ''
next.errorField = undefined
}
break
}
@@ -110,6 +121,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
next.activeStep++
next.error = ''
next.errorField = undefined
}
break
}
@@ -156,6 +168,12 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
}
case 'setError': {
next.error = a.value
next.errorField = a.field
break
}
case 'clearError': {
next.error = ''
next.errorField = undefined
break
}
case 'submit': {
+14 -3
View File
@@ -81,8 +81,7 @@ export function Component() {
const onChangePassword = async () => {
const formattedCode = checkAndFormatResetCode(resetCode)
// TODO Better password strength check
if (!formattedCode || !newPassword) {
if (!formattedCode) {
setError(
_(
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
@@ -90,6 +89,16 @@ export function Component() {
)
return
}
if (!newPassword) {
setError(
_(msg`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.`))
return
}
setError('')
setIsProcessing(true)
@@ -104,7 +113,9 @@ export function Component() {
logger.warn('Failed to set new password', {error: e})
if (isNetworkError(e)) {
setError(
'Unable to contact your service. Please check your Internet connection.',
_(
msg`Unable to contact your service. Please check your Internet connection.`,
),
)
} else {
setError(cleanError(errMsg))