diff options
Diffstat (limited to 'src/view/com/auth/create')
-rw-r--r-- | src/view/com/auth/create/CreateAccount.tsx | 6 | ||||
-rw-r--r-- | src/view/com/auth/create/Policies.tsx | 19 | ||||
-rw-r--r-- | src/view/com/auth/create/Step1.tsx | 27 | ||||
-rw-r--r-- | src/view/com/auth/create/Step2.tsx | 139 | ||||
-rw-r--r-- | src/view/com/auth/create/state.ts | 9 |
5 files changed, 133 insertions, 67 deletions
diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx index 8aefffa6d..d193802fe 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/Policies.tsx b/src/view/com/auth/create/Policies.tsx index 2c7d60818..803e2ad32 100644 --- a/src/view/com/auth/create/Policies.tsx +++ b/src/view/com/auth/create/Policies.tsx @@ -9,6 +9,8 @@ import {TextLink} from '../../util/Link' import {Text} from '../../util/text/Text' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema @@ -20,6 +22,7 @@ export const Policies = ({ needsGuardian: boolean }) => { const pal = usePalette('default') + const {_} = useLingui() if (!serviceDescription) { return <View /> } @@ -40,7 +43,9 @@ export const Policies = ({ /> </View> <Text style={[pal.textLight, s.pl5, s.flex1]}> - This service has not provided terms of service or a privacy policy. + <Trans> + This service has not provided terms of service or a privacy policy. + </Trans> </Text> </View> ) @@ -51,7 +56,7 @@ export const Policies = ({ <TextLink key="tos" href={tos} - text="Terms of Service" + text={_(msg`Terms of Service`)} style={[pal.link, s.underline]} />, ) @@ -61,7 +66,7 @@ export const Policies = ({ <TextLink key="pp" href={pp} - text="Privacy Policy" + text={_(msg`Privacy Policy`)} style={[pal.link, s.underline]} />, ) @@ -79,12 +84,14 @@ export const Policies = ({ return ( <View style={styles.policies}> <Text style={pal.textLight}> - By creating an account you agree to the {els}. + <Trans>By creating an account you agree to the {els}.</Trans> </Text> {needsGuardian && ( <Text style={[pal.textLight, s.bold]}> - If you are not yet an adult according to the laws of your country, - your parent or legal guardian must read these Terms on your behalf. + <Trans> + If you are not yet an adult according to the laws of your country, + your parent or legal guardian must read these Terms on your behalf. + </Trans> </Text> )} </View> diff --git a/src/view/com/auth/create/Step1.tsx b/src/view/com/auth/create/Step1.tsx index 4c7018485..1f6852f8c 100644 --- a/src/view/com/auth/create/Step1.tsx +++ b/src/view/com/auth/create/Step1.tsx @@ -4,7 +4,6 @@ import { Keyboard, StyleSheet, TouchableOpacity, - TouchableWithoutFeedback, View, } from 'react-native' import {CreateAccountState, CreateAccountDispatch, is18} from './state' @@ -19,7 +18,6 @@ import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {isWeb} from 'platform/detection' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' import {logger} from '#/logger' import { FontAwesomeIcon, @@ -49,7 +47,6 @@ export function Step1({ }) { const pal = usePalette('default') const {_} = useLingui() - const {openModal} = useModalControls() const serverInputControl = useDialogControl() const onPressSelectService = React.useCallback(() => { @@ -57,10 +54,6 @@ export function Step1({ Keyboard.dismiss() }, [serverInputControl]) - const onPressWaitlist = React.useCallback(() => { - openModal({name: 'waitlist'}) - }, [openModal]) - const birthDate = React.useMemo(() => { return sanitizeDate(uiState.birthDate) }, [uiState.birthDate]) @@ -164,23 +157,7 @@ export function Step1({ </View> )} - {!uiState.inviteCode && uiState.isInviteCodeRequired ? ( - <View style={[s.flexRow, s.alignCenter]}> - <Text style={pal.text}> - <Trans>Don't have an invite code?</Trans>{' '} - </Text> - <TouchableWithoutFeedback - onPress={onPressWaitlist} - accessibilityLabel={_(msg`Join the waitlist.`)} - accessibilityHint=""> - <View style={styles.touchable}> - <Text style={pal.link}> - <Trans>Join the waitlist.</Trans> - </Text> - </View> - </TouchableWithoutFeedback> - </View> - ) : ( + {!uiState.isInviteCodeRequired || uiState.inviteCode ? ( <> <View style={s.pb20}> <Text @@ -260,7 +237,7 @@ export function Step1({ /> )} </> - )} + ) : undefined} </> )} </View> diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 87d414bb9..5c262977f 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 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 * @field User handle @@ -23,41 +30,111 @@ export function Step2({ }) { const pal = usePalette('default') const {_} = useLingui() + const t = useTheme() + + const [validCheck, setValidCheck] = React.useState<IsValidHandle>({ + 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 ( <View> <StepHeader uiState={uiState} title={_(msg`Your user handle`)} /> - {uiState.error ? ( - <ErrorMessage message={uiState.error} style={styles.error} /> - ) : undefined} <View style={s.pb10}> - <TextInput - testID="handleInput" - icon="at" - placeholder="e.g. alice" - value={uiState.handle} - editable - autoFocus - autoComplete="off" - autoCorrect={false} - onChange={value => uiDispatch({type: 'set-handle', value})} - // TODO: Add explicit text label - accessibilityLabel={_(msg`User handle`)} - accessibilityHint={_(msg`Input your user handle`)} - /> - <Text type="lg" style={[pal.text, s.pl5, s.pt10]}> - <Trans>Your full handle will be</Trans>{' '} - <Text type="lg-bold" style={pal.text}> - @{createFullHandle(uiState.handle, uiState.userDomain)} + <View style={s.mb20}> + <TextInput + testID="handleInput" + icon="at" + placeholder="e.g. alice" + value={uiState.handle} + editable + autoFocus + autoComplete="off" + autoCorrect={false} + onChange={onHandleChange} + // TODO: Add explicit text label + accessibilityLabel={_(msg`User handle`)} + accessibilityHint={_(msg`Input your user handle`)} + /> + <Text type="lg" style={[pal.text, s.pl5, s.pt10]}> + <Trans>Your full handle will be</Trans>{' '} + <Text type="lg-bold" style={pal.text}> + @{createFullHandle(uiState.handle, uiState.userDomain)} + </Text> </Text> - </Text> + </View> + <View + style={[ + a.w_full, + a.rounded_sm, + a.border, + a.p_md, + a.gap_sm, + t.atoms.border_contrast_low, + ]}> + {uiState.error ? ( + <View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}> + <IsValidIcon valid={false} /> + <Text style={[t.atoms.text, a.text_md, a.flex]}> + {uiState.error} + </Text> + </View> + ) : undefined} + <View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}> + <IsValidIcon valid={validCheck.handleChars} /> + <Text style={[t.atoms.text, a.text_md, a.flex]}> + <Trans>May only contain letters and numbers</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={[t.atoms.text]}> + <Trans>May not be longer than 253 characters</Trans> + </Text> + ) : ( + <Text style={[t.atoms.text, a.text_md]}> + <Trans>Must be at least 3 characters</Trans> + </Text> + )} + </View> + </View> </View> </View> ) } -const styles = StyleSheet.create({ - error: { - borderRadius: 6, - marginBottom: 10, - }, -}) +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}} /> +} diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts index 276eaf924..840084dcb 100644 --- a/src/view/com/auth/create/state.ts +++ b/src/view/com/auth/create/state.ts @@ -8,11 +8,11 @@ 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' -import {DEFAULT_SERVICE, IS_PROD} from '#/lib/constants' +import {DEFAULT_SERVICE, IS_TEST_USER} from '#/lib/constants' import { DEFAULT_PROD_FEEDS, usePreferencesSetBirthDateMutation, @@ -147,7 +147,7 @@ export function useSubmitCreateAccount( : undefined, }) setBirthDate({birthDate: uiState.birthDate}) - if (IS_PROD(uiState.serviceUrl)) { + if (!IS_TEST_USER(uiState.handle)) { setSavedFeeds(DEFAULT_PROD_FEEDS) } } catch (e: any) { @@ -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 |