From 817966a2711745d9281068c690b815c62f1cc172 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 21 Feb 2024 16:48:12 -0800 Subject: [PATCH 1/3] show uiState errors in the box as well simplify copy update ui for only letters and numbers add ui validation to handle selection --- src/lib/strings/handles.ts | 29 ++++ src/view/com/auth/create/CreateAccount.tsx | 6 +- src/view/com/auth/create/Step2.tsx | 149 ++++++++++++++++----- src/view/com/auth/create/state.ts | 5 +- 4 files changed, 155 insertions(+), 34 deletions(-) diff --git a/src/lib/strings/handles.ts b/src/lib/strings/handles.ts index 6ce4624357..a18fef453e 100644 --- a/src/lib/strings/handles.ts +++ b/src/lib/strings/handles.ts @@ -1,3 +1,8 @@ +// Regex from the go implementation +// https://github.com/bluesky-social/indigo/blob/main/atproto/syntax/handle.go#L10 +const VALIDATE_REGEX = + /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/ + export function makeValidHandle(str: string): string { if (str.length > 20) { str = str.slice(0, 20) @@ -19,3 +24,27 @@ export function isInvalidHandle(handle: string): boolean { export function sanitizeHandle(handle: string, prefix = ''): string { return isInvalidHandle(handle) ? 'āš Invalid Handle' : `${prefix}${handle}` } + +export interface IsValidHandle { + handleChars: boolean + frontLength: boolean + totalLength: boolean + overall: boolean +} + +// 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 { + const fullHandle = createFullHandle(str, userDomain) + + const results = { + handleChars: + !str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')), + frontLength: str.length >= 3, + totalLength: fullHandle.length <= 253, + } + + return { + ...results, + overall: !Object.values(results).includes(false), + } +} diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx index 8aefffa6d7..d193802fe0 100644 --- a/src/view/com/auth/create/CreateAccount.tsx +++ b/src/view/com/auth/create/CreateAccount.tsx @@ -23,7 +23,7 @@ import {Step3} from './Step3' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {TextLink} from '../../util/Link' import {getAgent} from 'state/session' -import {createFullHandle} from 'lib/strings/handles' +import {createFullHandle, validateHandle} from 'lib/strings/handles' export function CreateAccount({onPressBack}: {onPressBack: () => void}) { const {screen} = useAnalytics() @@ -78,6 +78,10 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) { } if (uiState.step === 2) { + if (!validateHandle(uiState.handle, uiState.userDomain).overall) { + return + } + uiDispatch({type: 'set-processing', value: true}) try { const res = await getAgent().resolveHandle({ diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 87d414bb97..e1d8d2db0e 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -1,15 +1,22 @@ import React from 'react' -import {StyleSheet, View} from 'react-native' +import {View} from 'react-native' import {CreateAccountState, CreateAccountDispatch} from './state' import {Text} from 'view/com/util/text/Text' import {StepHeader} from './StepHeader' import {s} from 'lib/styles' import {TextInput} from '../util/TextInput' -import {createFullHandle} from 'lib/strings/handles' +import { + createFullHandle, + IsValidHandle, + validateHandle, +} from 'lib/strings/handles' import {usePalette} from 'lib/hooks/usePalette' -import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {atoms as a, useTheme} from '#/alf' +import {Check_Stroke2_Corner0_Rounded} from '#/components/icons/Check' +import {TimesLarge_Stroke2_Corner0_Rounded} from '#/components/icons/Times' +import {useFocusEffect} from '@react-navigation/native' /** STEP 3: Your user handle * @field User handle @@ -23,41 +30,121 @@ export function Step2({ }) { const pal = usePalette('default') const {_} = useLingui() + const t = useTheme() + + const [validCheck, setValidCheck] = React.useState({ + handleChars: false, + frontLength: false, + totalLength: true, + overall: false, + }) + + useFocusEffect( + React.useCallback(() => { + setValidCheck(validateHandle(uiState.handle, uiState.userDomain)) + + // Disabling this, because we only want to run this when we focus the screen + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), + ) + + const onHandleChange = React.useCallback( + (value: string) => { + if (uiState.error) { + uiDispatch({type: 'set-error', value: ''}) + } + + setValidCheck(validateHandle(value, uiState.userDomain)) + uiDispatch({type: 'set-handle', value}) + }, + [uiDispatch, uiState.error, uiState.userDomain], + ) + return ( - {uiState.error ? ( - - ) : undefined} - uiDispatch({type: 'set-handle', value})} - // TODO: Add explicit text label - accessibilityLabel={_(msg`User handle`)} - accessibilityHint={_(msg`Input your user handle`)} - /> - - Your full handle will be{' '} - - @{createFullHandle(uiState.handle, uiState.userDomain)} + + + + Your full handle will be{' '} + + @{createFullHandle(uiState.handle, uiState.userDomain)} + - + + + {uiState.error && ( + + + + {uiState.error} + + + )} + + + + May only contain letters and numbers + + + + + {!validCheck.totalLength ? ( + + May not be longer than 253 characters + + ) : ( + + Must be at least 3 characters + + )} + + ) } -const styles = StyleSheet.create({ - error: { - borderRadius: 6, - marginBottom: 10, - }, -}) +function IsValidCheck({valid}: {valid: boolean}) { + const t = useTheme() + + if (!valid) { + return ( + + ) + } + + return ( + + ) +} diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts index 68cfaceec5..7a727ec0b2 100644 --- a/src/view/com/auth/create/state.ts +++ b/src/view/com/auth/create/state.ts @@ -8,7 +8,7 @@ import {msg} from '@lingui/macro' import * as EmailValidator from 'email-validator' import {getAge} from 'lib/strings/time' import {logger} from '#/logger' -import {createFullHandle} from '#/lib/strings/handles' +import {createFullHandle, validateHandle} from '#/lib/strings/handles' import {cleanError} from '#/lib/strings/errors' import {useOnboardingDispatch} from '#/state/shell/onboarding' import {useSessionApi} from '#/state/session' @@ -282,7 +282,8 @@ function compute(state: CreateAccountState): CreateAccountState { !!state.email && !!state.password } else if (state.step === 2) { - canNext = !!state.handle + canNext = + !!state.handle && validateHandle(state.handle, state.userDomain).overall } else if (state.step === 3) { // Step 3 will automatically redirect as soon as the captcha completes canNext = false From fe364eb490e996bf49631c3ad4d6c9b8c090bc98 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 21 Feb 2024 17:03:07 -0800 Subject: [PATCH 2/3] simplify names --- src/view/com/auth/create/Step2.tsx | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index e1d8d2db0e..63ef9f4c26 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -14,8 +14,8 @@ import {usePalette} from 'lib/hooks/usePalette' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {atoms as a, useTheme} from '#/alf' -import {Check_Stroke2_Corner0_Rounded} from '#/components/icons/Check' -import {TimesLarge_Stroke2_Corner0_Rounded} from '#/components/icons/Times' +import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' +import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {useFocusEffect} from '@react-navigation/native' /** STEP 3: Your user handle @@ -97,20 +97,20 @@ export function Step2({ ]}> {uiState.error && ( - + {uiState.error} )} - + May only contain letters and numbers - {!validCheck.totalLength ? ( @@ -129,22 +129,12 @@ export function Step2({ ) } -function IsValidCheck({valid}: {valid: boolean}) { +function IsValidIcon({valid}: {valid: boolean}) { const t = useTheme() if (!valid) { - return ( - - ) + return } - return ( - - ) + return } From 090bbc094c1bb028bfa55258c1b79433c2f83629 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Fri, 23 Feb 2024 13:32:20 -0800 Subject: [PATCH 3/3] Fix accidental text-node render --- src/view/com/auth/create/Step2.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 63ef9f4c26..a389203098 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -95,14 +95,14 @@ export function Step2({ a.gap_sm, t.atoms.border_contrast_low, ]}> - {uiState.error && ( + {uiState.error ? ( {uiState.error} - )} + ) : undefined}