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:
@@ -75,22 +75,6 @@ export function StepInfo({
|
|||||||
const emailChanged = prevEmailValueRef.current !== email
|
const emailChanged = prevEmailValueRef.current !== email
|
||||||
const password = passwordValueRef.current
|
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)) {
|
if (!is13(state.dateOfBirth)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,24 +83,50 @@ export function StepInfo({
|
|||||||
return dispatch({
|
return dispatch({
|
||||||
type: 'setError',
|
type: 'setError',
|
||||||
value: _(msg`Please enter your invite code.`),
|
value: _(msg`Please enter your invite code.`),
|
||||||
|
field: 'invite-code',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!email) {
|
if (!email) {
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: 'setError',
|
type: 'setError',
|
||||||
value: _(msg`Please enter your email.`),
|
value: _(msg`Please enter your email.`),
|
||||||
|
field: 'email',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!EmailValidator.validate(email)) {
|
if (!EmailValidator.validate(email)) {
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: 'setError',
|
type: 'setError',
|
||||||
value: _(msg`Your email appears to be invalid.`),
|
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) {
|
if (!password) {
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: 'setError',
|
type: 'setError',
|
||||||
value: _(msg`Please choose your password.`),
|
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>
|
<TextField.LabelText>
|
||||||
<Trans>Invite code</Trans>
|
<Trans>Invite code</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={state.errorField === 'invite-code'}>
|
||||||
<TextField.Icon icon={Ticket} />
|
<TextField.Icon icon={Ticket} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
onChangeText={value => {
|
onChangeText={value => {
|
||||||
inviteCodeValueRef.current = value.trim()
|
inviteCodeValueRef.current = value.trim()
|
||||||
|
if (
|
||||||
|
state.errorField === 'invite-code' &&
|
||||||
|
value.trim().length > 0
|
||||||
|
) {
|
||||||
|
dispatch({type: 'clearError'})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
label={_(msg`Required for this provider`)}
|
label={_(msg`Required for this provider`)}
|
||||||
defaultValue={state.inviteCode}
|
defaultValue={state.inviteCode}
|
||||||
@@ -173,7 +189,7 @@ export function StepInfo({
|
|||||||
<TextField.LabelText>
|
<TextField.LabelText>
|
||||||
<Trans>Email</Trans>
|
<Trans>Email</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={state.errorField === 'email'}>
|
||||||
<TextField.Icon icon={Envelope} />
|
<TextField.Icon icon={Envelope} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
testID="emailInput"
|
testID="emailInput"
|
||||||
@@ -183,6 +199,13 @@ export function StepInfo({
|
|||||||
if (hasWarnedEmail) {
|
if (hasWarnedEmail) {
|
||||||
setHasWarnedEmail(false)
|
setHasWarnedEmail(false)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
state.errorField === 'email' &&
|
||||||
|
value.trim().length > 0 &&
|
||||||
|
EmailValidator.validate(value.trim())
|
||||||
|
) {
|
||||||
|
dispatch({type: 'clearError'})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
label={_(msg`Enter your email address`)}
|
label={_(msg`Enter your email address`)}
|
||||||
defaultValue={state.email}
|
defaultValue={state.email}
|
||||||
@@ -201,13 +224,16 @@ export function StepInfo({
|
|||||||
<TextField.LabelText>
|
<TextField.LabelText>
|
||||||
<Trans>Password</Trans>
|
<Trans>Password</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={state.errorField === 'password'}>
|
||||||
<TextField.Icon icon={Lock} />
|
<TextField.Icon icon={Lock} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
testID="passwordInput"
|
testID="passwordInput"
|
||||||
inputRef={passwordInputRef}
|
inputRef={passwordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={value => {
|
||||||
passwordValueRef.current = value
|
passwordValueRef.current = value
|
||||||
|
if (state.errorField === 'password' && value.length >= 8) {
|
||||||
|
dispatch({type: 'clearError'})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
label={_(msg`Choose your password`)}
|
label={_(msg`Choose your password`)}
|
||||||
defaultValue={state.password}
|
defaultValue={state.password}
|
||||||
@@ -219,6 +245,7 @@ export function StepInfo({
|
|||||||
onSubmitEditing={native(() =>
|
onSubmitEditing={native(() =>
|
||||||
birthdateInputRef.current?.focus(),
|
birthdateInputRef.current?.focus(),
|
||||||
)}
|
)}
|
||||||
|
passwordRules="minlength: 8;"
|
||||||
/>
|
/>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ type SubmitTask = {
|
|||||||
mutableProcessed: boolean // OK to mutate assuming it's never read in render.
|
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 = {
|
export type SignupState = {
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
activeStep: SignupStep
|
activeStep: SignupStep
|
||||||
@@ -45,6 +52,7 @@ export type SignupState = {
|
|||||||
handle: string
|
handle: string
|
||||||
|
|
||||||
error: string
|
error: string
|
||||||
|
errorField?: ErrorField
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
|
|
||||||
pendingSubmit: null | SubmitTask
|
pendingSubmit: null | SubmitTask
|
||||||
@@ -62,7 +70,8 @@ export type SignupAction =
|
|||||||
| {type: 'setDateOfBirth'; value: Date}
|
| {type: 'setDateOfBirth'; value: Date}
|
||||||
| {type: 'setInviteCode'; value: string}
|
| {type: 'setInviteCode'; value: string}
|
||||||
| {type: 'setHandle'; value: string}
|
| {type: 'setHandle'; value: string}
|
||||||
| {type: 'setError'; value: string}
|
| {type: 'setError'; value: string; field?: ErrorField}
|
||||||
|
| {type: 'clearError'}
|
||||||
| {type: 'setIsLoading'; value: boolean}
|
| {type: 'setIsLoading'; value: boolean}
|
||||||
| {type: 'submit'; task: SubmitTask}
|
| {type: 'submit'; task: SubmitTask}
|
||||||
|
|
||||||
@@ -80,6 +89,7 @@ export const initialState: SignupState = {
|
|||||||
inviteCode: '',
|
inviteCode: '',
|
||||||
|
|
||||||
error: '',
|
error: '',
|
||||||
|
errorField: undefined,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
||||||
pendingSubmit: null,
|
pendingSubmit: null,
|
||||||
@@ -102,6 +112,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
|||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
next.activeStep--
|
next.activeStep--
|
||||||
next.error = ''
|
next.error = ''
|
||||||
|
next.errorField = undefined
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -110,6 +121,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
|||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
next.activeStep++
|
next.activeStep++
|
||||||
next.error = ''
|
next.error = ''
|
||||||
|
next.errorField = undefined
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -156,6 +168,12 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
|
|||||||
}
|
}
|
||||||
case 'setError': {
|
case 'setError': {
|
||||||
next.error = a.value
|
next.error = a.value
|
||||||
|
next.errorField = a.field
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'clearError': {
|
||||||
|
next.error = ''
|
||||||
|
next.errorField = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'submit': {
|
case 'submit': {
|
||||||
|
|||||||
@@ -81,8 +81,7 @@ export function Component() {
|
|||||||
|
|
||||||
const onChangePassword = async () => {
|
const onChangePassword = async () => {
|
||||||
const formattedCode = checkAndFormatResetCode(resetCode)
|
const formattedCode = checkAndFormatResetCode(resetCode)
|
||||||
// TODO Better password strength check
|
if (!formattedCode) {
|
||||||
if (!formattedCode || !newPassword) {
|
|
||||||
setError(
|
setError(
|
||||||
_(
|
_(
|
||||||
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
|
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
|
||||||
@@ -90,6 +89,16 @@ export function Component() {
|
|||||||
)
|
)
|
||||||
return
|
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('')
|
setError('')
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
@@ -104,7 +113,9 @@ export function Component() {
|
|||||||
logger.warn('Failed to set new password', {error: e})
|
logger.warn('Failed to set new password', {error: e})
|
||||||
if (isNetworkError(e)) {
|
if (isNetworkError(e)) {
|
||||||
setError(
|
setError(
|
||||||
'Unable to contact your service. Please check your Internet connection.',
|
_(
|
||||||
|
msg`Unable to contact your service. Please check your Internet connection.`,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
setError(cleanError(errMsg))
|
setError(cleanError(errMsg))
|
||||||
|
|||||||
Reference in New Issue
Block a user