Compare commits

...

2 Commits

Author SHA1 Message Date
Hailey 23a113e817 better name 2024-04-29 22:45:58 -07:00
Hailey 9cfbbe9512 simplify handle validation during signup 2024-04-29 21:48:25 -07:00
4 changed files with 63 additions and 103 deletions
+22 -18
View File
@@ -25,28 +25,32 @@ export function sanitizeHandle(handle: string, prefix = ''): string {
return isInvalidHandle(handle) ? '⚠Invalid Handle' : `${prefix}${handle}`
}
export interface IsValidHandle {
handleChars: boolean
hyphenStartOrEnd: boolean
frontLength: boolean
totalLength: boolean
overall: boolean
}
type ValidateHandleResult =
| 'charsError'
| 'hyphenError'
| 'frontLengthError'
| 'totalLengthError'
| 'valid'
// More checks from https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/index.ts#L72
export function validateHandle(str: string, userDomain: string): IsValidHandle {
export function validateHandle(
str: string,
userDomain: string,
): ValidateHandleResult {
const fullHandle = createFullHandle(str, userDomain)
const results = {
handleChars:
!str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')),
hyphenStartOrEnd: !str.startsWith('-') && !str.endsWith('-'),
frontLength: str.length >= 3,
totalLength: fullHandle.length <= 253,
if (str.length < 3) {
return 'frontLengthError'
}
if (fullHandle.length > 253) {
return 'totalLengthError'
}
if (str.startsWith('-') || str.endsWith('-')) {
return 'hyphenError'
}
if (!str || !VALIDATE_REGEX.test(fullHandle) || str.includes('.')) {
return 'charsError'
}
return {
...results,
overall: !Object.values(results).includes(false),
}
return 'valid'
}
+4 -78
View File
@@ -2,41 +2,20 @@ import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {
createFullHandle,
IsValidHandle,
validateHandle,
} from '#/lib/strings/handles'
import {createFullHandle} from '#/lib/strings/handles'
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext} from '#/screens/Signup/state'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {FormError} from '#/components/forms/FormError'
import * as TextField from '#/components/forms/TextField'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
import {Text} from '#/components/Typography'
export function StepHandle() {
const {_} = useLingui()
const t = useTheme()
const {state, dispatch} = useSignupContext()
const [validCheck, setValidCheck] = React.useState<IsValidHandle>({
handleChars: false,
hyphenStartOrEnd: false,
frontLength: false,
totalLength: true,
overall: false,
})
useFocusEffect(
React.useCallback(() => {
setValidCheck(validateHandle(state.handle, state.userDomain))
}, [state.handle, state.userDomain]),
)
const onHandleChange = React.useCallback(
(value: string) => {
if (state.error) {
@@ -75,61 +54,8 @@ export function StepHandle() {
@{createFullHandle(state.handle, state.userDomain)}
</Text>
</Text>
<View
style={[
a.w_full,
a.rounded_sm,
a.border,
a.p_md,
a.gap_sm,
t.atoms.border_contrast_low,
]}>
{state.error ? (
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon valid={false} />
<Text style={[a.text_md, a.flex_1]}>{state.error}</Text>
</View>
) : undefined}
{validCheck.hyphenStartOrEnd ? (
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon valid={validCheck.handleChars} />
<Text style={[a.text_md, a.flex_1]}>
<Trans>Only contains letters, numbers, and hyphens</Trans>
</Text>
</View>
) : (
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon valid={validCheck.hyphenStartOrEnd} />
<Text style={[a.text_md, a.flex_1]}>
<Trans>Doesn't begin or end with a hyphen</Trans>
</Text>
</View>
)}
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon
valid={validCheck.frontLength && validCheck.totalLength}
/>
{!validCheck.totalLength ? (
<Text style={[a.text_md, a.flex_1]}>
<Trans>No longer than 253 characters</Trans>
</Text>
) : (
<Text style={[a.text_md, a.flex_1]}>
<Trans>At least 3 characters</Trans>
</Text>
)}
</View>
</View>
<FormError error={state.error} />
</View>
</ScreenTransition>
)
}
function IsValidIcon({valid}: {valid: boolean}) {
const t = useTheme()
if (!valid) {
return <Times size="md" style={{color: t.palette.negative_500}} />
}
return <Check size="md" style={{color: t.palette.positive_700}} />
}
+34 -1
View File
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
import {useAnalytics} from '#/lib/analytics/analytics'
import {FEEDBACK_FORM_URL} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig'
import {createFullHandle} from '#/lib/strings/handles'
import {createFullHandle, validateHandle} from '#/lib/strings/handles'
import {useServiceQuery} from '#/state/queries/service'
import {useAgent} from '#/state/session'
import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
@@ -74,6 +74,39 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
const onNextPress = React.useCallback(async () => {
if (state.activeStep === SignupStep.HANDLE) {
try {
const isValidHandle = validateHandle(state.handle, state.userDomain)
switch (isValidHandle) {
case 'charsError':
dispatch({
type: 'setError',
value: _(
msg`Handle may only contain letters, numbers, and hyphens`,
),
})
return
case 'totalLengthError':
dispatch({
type: 'setError',
value: _(msg`Handle may not be longer than 253 characters`),
})
return
case 'frontLengthError':
dispatch({
type: 'setError',
value: _(msg`Handle should be at least 3 characters`),
})
return
case 'hyphenError':
dispatch({
type: 'setError',
value: _(msg`Handle may not begin or end with a hyphen`),
})
return
case 'valid':
break
}
dispatch({type: 'setIsLoading', value: true})
const res = await getAgent().resolveHandle({
+3 -6
View File
@@ -10,7 +10,7 @@ import * as EmailValidator from 'email-validator'
import {DEFAULT_SERVICE, IS_PROD_SERVICE} from '#/lib/constants'
import {cleanError} from '#/lib/strings/errors'
import {createFullHandle, validateHandle} from '#/lib/strings/handles'
import {createFullHandle} from '#/lib/strings/handles'
import {getAge} from '#/lib/strings/time'
import {logger} from '#/logger'
import {
@@ -175,11 +175,8 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
isValidEmail
break
}
case SignupStep.HANDLE: {
next.canNext =
!!next.handle && validateHandle(next.handle, next.userDomain).overall
break
}
default:
next.canNext = true
}
logger.debug('signup', next)