diff options
author | Paul Frazee <pfrazee@gmail.com> | 2024-07-02 14:43:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-02 22:43:34 +0100 |
commit | 63bb8fda2d28e11d7e60808e1e86384d48ec1b47 (patch) | |
tree | 077d366a888ceb4c0c925707e2d2989ad7bf7fdb | |
parent | 4bb4452f0858f2f5f8f6ead3012307cdf4b6a67f (diff) | |
download | voidsky-63bb8fda2d28e11d7e60808e1e86384d48ec1b47.tar.zst |
Improve textinput performance in login and account creation (#4673)
* Change login form to use uncontrolled inputs * Debounce state updates in account creation to reduce flicker * Refactor state-control of account creation forms to fix perf without relying on debounces * Remove canNext and enforce is13 * Re-add live validation to signup form (#4720) * Update validation in real time * Disable on invalid * Clear server error on typing * Remove unnecessary clearing of error --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
-rw-r--r-- | src/screens/Login/LoginForm.tsx | 60 | ||||
-rw-r--r-- | src/screens/Signup/BackNextButtons.tsx | 73 | ||||
-rw-r--r-- | src/screens/Signup/StepCaptcha/index.tsx | 16 | ||||
-rw-r--r-- | src/screens/Signup/StepHandle.tsx | 218 | ||||
-rw-r--r-- | src/screens/Signup/StepInfo/index.tsx | 87 | ||||
-rw-r--r-- | src/screens/Signup/index.tsx | 146 | ||||
-rw-r--r-- | src/screens/Signup/state.ts | 26 |
7 files changed, 357 insertions, 269 deletions
diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index 7cfd38e34..35b124b61 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -60,12 +60,13 @@ export const LoginForm = ({ const {track} = useAnalytics() const t = useTheme() const [isProcessing, setIsProcessing] = useState<boolean>(false) + const [isReady, setIsReady] = useState<boolean>(false) const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] = useState<boolean>(false) - const [identifier, setIdentifier] = useState<string>(initialHandle) - const [password, setPassword] = useState<string>('') - const [authFactorToken, setAuthFactorToken] = useState<string>('') - const passwordInputRef = useRef<TextInput>(null) + const identifierValueRef = useRef<string>(initialHandle || '') + const passwordValueRef = useRef<string>('') + const authFactorTokenValueRef = useRef<string>('') + const passwordRef = useRef<TextInput>(null) const {_} = useLingui() const {login} = useSessionApi() const requestNotificationsPermission = useRequestNotificationsPermission() @@ -84,6 +85,10 @@ export const LoginForm = ({ setError('') setIsProcessing(true) + const identifier = identifierValueRef.current.toLowerCase().trim() + const password = passwordValueRef.current + const authFactorToken = authFactorTokenValueRef.current + try { // try to guess the handle if the user just gave their own username let fullIdent = identifier @@ -152,7 +157,22 @@ export const LoginForm = ({ } } - const isReady = !!serviceDescription && !!identifier && !!password + const checkIsReady = () => { + if ( + !!serviceDescription && + !!identifierValueRef.current && + !!passwordValueRef.current + ) { + if (!isReady) { + setIsReady(true) + } + } else { + if (isReady) { + setIsReady(false) + } + } + } + return ( <FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}> <View> @@ -181,14 +201,15 @@ export const LoginForm = ({ autoComplete="username" returnKeyType="next" textContentType="username" + defaultValue={initialHandle || ''} + onChangeText={v => { + identifierValueRef.current = v + checkIsReady() + }} onSubmitEditing={() => { - passwordInputRef.current?.focus() + passwordRef.current?.focus() }} blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field - value={identifier} - onChangeText={str => - setIdentifier((str || '').toLowerCase().trim()) - } editable={!isProcessing} accessibilityHint={_( msg`Input the username or email address you used at signup`, @@ -200,7 +221,7 @@ export const LoginForm = ({ <TextField.Icon icon={Lock} /> <TextField.Input testID="loginPasswordInput" - inputRef={passwordInputRef} + inputRef={passwordRef} label={_(msg`Password`)} autoCapitalize="none" autoCorrect={false} @@ -210,16 +231,14 @@ export const LoginForm = ({ secureTextEntry={true} textContentType="password" clearButtonMode="while-editing" - value={password} - onChangeText={setPassword} + onChangeText={v => { + passwordValueRef.current = v + checkIsReady() + }} onSubmitEditing={onPressNext} blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing editable={!isProcessing} - accessibilityHint={ - identifier === '' - ? _(msg`Input your password`) - : _(msg`Input the password tied to ${identifier}`) - } + accessibilityHint={_(msg`Input your password`)} /> <Button testID="forgotPasswordButton" @@ -258,8 +277,9 @@ export const LoginForm = ({ returnKeyType="done" textContentType="username" blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field - value={authFactorToken} - onChangeText={setAuthFactorToken} + onChangeText={v => { + authFactorTokenValueRef.current = v + }} onSubmitEditing={onPressNext} editable={!isProcessing} accessibilityHint={_( diff --git a/src/screens/Signup/BackNextButtons.tsx b/src/screens/Signup/BackNextButtons.tsx new file mode 100644 index 000000000..73bd428c8 --- /dev/null +++ b/src/screens/Signup/BackNextButtons.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {Loader} from '#/components/Loader' + +export interface BackNextButtonsProps { + hideNext?: boolean + showRetry?: boolean + isLoading: boolean + isNextDisabled?: boolean + onBackPress: () => void + onNextPress?: () => void + onRetryPress?: () => void +} + +export function BackNextButtons({ + hideNext, + showRetry, + isLoading, + isNextDisabled, + onBackPress, + onNextPress, + onRetryPress, +}: BackNextButtonsProps) { + const {_} = useLingui() + + return ( + <View style={[a.flex_row, a.justify_between, a.pb_lg, a.pt_3xl]}> + <Button + label={_(msg`Go back to previous step`)} + variant="solid" + color="secondary" + size="medium" + onPress={onBackPress}> + <ButtonText> + <Trans>Back</Trans> + </ButtonText> + </Button> + {!hideNext && + (showRetry ? ( + <Button + label={_(msg`Press to retry`)} + variant="solid" + color="primary" + size="medium" + onPress={onRetryPress}> + <ButtonText> + <Trans>Retry</Trans> + </ButtonText> + {isLoading && <ButtonIcon icon={Loader} />} + </Button> + ) : ( + <Button + testID="nextBtn" + label={_(msg`Continue to next step`)} + variant="solid" + color="primary" + size="medium" + disabled={isLoading || isNextDisabled} + onPress={onNextPress}> + <ButtonText> + <Trans>Next</Trans> + </ButtonText> + {isLoading && <ButtonIcon icon={Loader} />} + </Button> + ))} + </View> + ) +} diff --git a/src/screens/Signup/StepCaptcha/index.tsx b/src/screens/Signup/StepCaptcha/index.tsx index b2a91a641..bf3576490 100644 --- a/src/screens/Signup/StepCaptcha/index.tsx +++ b/src/screens/Signup/StepCaptcha/index.tsx @@ -12,6 +12,7 @@ import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' import {atoms as a, useTheme} from '#/alf' import {FormError} from '#/components/forms/FormError' +import {BackNextButtons} from '../BackNextButtons' const CAPTCHA_PATH = '/gate/signup' @@ -61,6 +62,16 @@ export function StepCaptcha() { [_, dispatch, state.handle], ) + const onBackPress = React.useCallback(() => { + logger.error('Signup Flow Error', { + errorMessage: + 'User went back from captcha step. Possibly encountered an error.', + registrationHandle: state.handle, + }) + + dispatch({type: 'prev'}) + }, [dispatch, state.handle]) + return ( <ScreenTransition> <View style={[a.gap_lg]}> @@ -86,6 +97,11 @@ export function StepCaptcha() { </View> <FormError error={state.error} /> </View> + <BackNextButtons + hideNext + isLoading={state.isLoading} + onBackPress={onBackPress} + /> </ScreenTransition> ) } diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx index 2266f4387..b443e822a 100644 --- a/src/screens/Signup/StepHandle.tsx +++ b/src/screens/Signup/StepHandle.tsx @@ -1,56 +1,96 @@ -import React from 'react' +import React, {useRef} 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 {logEvent} from '#/lib/statsig/statsig' +import {createFullHandle, validateHandle} from '#/lib/strings/handles' +import {useAgent} from '#/state/session' import {ScreenTransition} from '#/screens/Login/ScreenTransition' -import {useSignupContext} from '#/screens/Signup/state' +import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {atoms as a, useTheme} from '#/alf' 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' +import {BackNextButtons} from './BackNextButtons' export function StepHandle() { const {_} = useLingui() const t = useTheme() const {state, dispatch} = useSignupContext() + const submit = useSubmitSignup({state, dispatch}) + const agent = useAgent() + const handleValueRef = useRef<string>(state.handle) + const [draftValue, setDraftValue] = React.useState(state.handle) - const [validCheck, setValidCheck] = React.useState<IsValidHandle>({ - handleChars: false, - hyphenStartOrEnd: false, - frontLength: false, - totalLength: true, - overall: false, - }) + const onNextPress = React.useCallback(async () => { + const handle = handleValueRef.current.trim() + dispatch({ + type: 'setHandle', + value: handle, + }) - useFocusEffect( - React.useCallback(() => { - setValidCheck(validateHandle(state.handle, state.userDomain)) - }, [state.handle, state.userDomain]), - ) + const newValidCheck = validateHandle(handle, state.userDomain) + if (!newValidCheck.overall) { + return + } - const onHandleChange = React.useCallback( - (value: string) => { - if (state.error) { - dispatch({type: 'setError', value: ''}) - } + try { + dispatch({type: 'setIsLoading', value: true}) - dispatch({ - type: 'setHandle', - value, + const res = await agent.resolveHandle({ + handle: createFullHandle(handle, state.userDomain), }) - }, - [dispatch, state.error], - ) + if (res.data.did) { + dispatch({ + type: 'setError', + value: _(msg`That handle is already taken.`), + }) + return + } + } catch (e) { + // Don't have to handle + } finally { + dispatch({type: 'setIsLoading', value: false}) + } + + logEvent('signup:nextPressed', { + activeStep: state.activeStep, + phoneVerificationRequired: + state.serviceDescription?.phoneVerificationRequired, + }) + // phoneVerificationRequired is actually whether a captcha is required + if (!state.serviceDescription?.phoneVerificationRequired) { + submit() + return + } + dispatch({type: 'next'}) + }, [ + _, + dispatch, + state.activeStep, + state.serviceDescription?.phoneVerificationRequired, + state.userDomain, + submit, + agent, + ]) + + const onBackPress = React.useCallback(() => { + const handle = handleValueRef.current.trim() + dispatch({ + type: 'setHandle', + value: handle, + }) + dispatch({type: 'prev'}) + logEvent('signup:backPressed', { + activeStep: state.activeStep, + }) + }, [dispatch, state.activeStep]) + + const validCheck = validateHandle(draftValue, state.userDomain) return ( <ScreenTransition> <View style={[a.gap_lg]}> @@ -59,9 +99,17 @@ export function StepHandle() { <TextField.Icon icon={At} /> <TextField.Input testID="handleInput" - onChangeText={onHandleChange} + onChangeText={val => { + if (state.error) { + dispatch({type: 'setError', value: ''}) + } + + // These need to always be in sync. + handleValueRef.current = val + setDraftValue(val) + }} label={_(msg`Input your user handle`)} - defaultValue={state.handle} + defaultValue={draftValue} autoCapitalize="none" autoCorrect={false} autoFocus @@ -69,59 +117,69 @@ export function StepHandle() { /> </TextField.Root> </View> - <Text style={[a.text_md]}> - <Trans>Your full handle will be</Trans>{' '} - <Text style={[a.text_md, a.font_bold]}> - @{createFullHandle(state.handle, state.userDomain)} + {draftValue !== '' && ( + <Text style={[a.text_md]}> + <Trans>Your full handle will be</Trans>{' '} + <Text style={[a.text_md, a.font_bold]}> + @{createFullHandle(draftValue, state.userDomain)} + </Text> </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> + {draftValue !== '' && ( + <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> ) : ( - <Text style={[a.text_md, a.flex_1]}> - <Trans>At least 3 characters</Trans> - </Text> + <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> - </View> + )} </View> + <BackNextButtons + isLoading={state.isLoading} + isNextDisabled={!validCheck.overall} + onBackPress={onBackPress} + onNextPress={onNextPress} + /> </ScreenTransition> ) } diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index 691e23a53..47fb4c70b 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -1,8 +1,10 @@ -import React from 'react' +import React, {useRef} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import * as EmailValidator from 'email-validator' +import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {is13, is18, useSignupContext} from '#/screens/Signup/state' @@ -16,6 +18,7 @@ import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/E import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' import {Loader} from '#/components/Loader' +import {BackNextButtons} from '../BackNextButtons' function sanitizeDate(date: Date): Date { if (!date || date.toString() === 'Invalid Date') { @@ -28,13 +31,72 @@ function sanitizeDate(date: Date): Date { } export function StepInfo({ + onPressBack, + isServerError, + refetchServer, isLoadingStarterPack, }: { + onPressBack: () => void + isServerError: boolean + refetchServer: () => void isLoadingStarterPack: boolean }) { const {_} = useLingui() const {state, dispatch} = useSignupContext() + const inviteCodeValueRef = useRef<string>(state.inviteCode) + const emailValueRef = useRef<string>(state.email) + const passwordValueRef = useRef<string>(state.password) + + const onNextPress = React.useCallback(async () => { + const inviteCode = inviteCodeValueRef.current + const email = emailValueRef.current + const password = passwordValueRef.current + + if (!is13(state.dateOfBirth)) { + return + } + + if (state.serviceDescription?.inviteCodeRequired && !inviteCode) { + return dispatch({ + type: 'setError', + value: _(msg`Please enter your invite code.`), + }) + } + if (!email) { + return dispatch({ + type: 'setError', + value: _(msg`Please enter your email.`), + }) + } + if (!EmailValidator.validate(email)) { + return dispatch({ + type: 'setError', + value: _(msg`Your email appears to be invalid.`), + }) + } + if (!password) { + return dispatch({ + type: 'setError', + value: _(msg`Please choose your password.`), + }) + } + + dispatch({type: 'setInviteCode', value: inviteCode}) + dispatch({type: 'setEmail', value: email}) + dispatch({type: 'setPassword', value: password}) + dispatch({type: 'next'}) + logEvent('signup:nextPressed', { + activeStep: state.activeStep, + }) + }, [ + _, + dispatch, + state.activeStep, + state.dateOfBirth, + state.serviceDescription?.inviteCodeRequired, + ]) + return ( <ScreenTransition> <View style={[a.gap_md]}> @@ -65,10 +127,7 @@ export function StepInfo({ <TextField.Icon icon={Ticket} /> <TextField.Input onChangeText={value => { - dispatch({ - type: 'setInviteCode', - value: value.trim(), - }) + inviteCodeValueRef.current = value.trim() }} label={_(msg`Required for this provider`)} defaultValue={state.inviteCode} @@ -88,10 +147,7 @@ export function StepInfo({ <TextField.Input testID="emailInput" onChangeText={value => { - dispatch({ - type: 'setEmail', - value: value.trim(), - }) + emailValueRef.current = value.trim() }} label={_(msg`Enter your email address`)} defaultValue={state.email} @@ -110,10 +166,7 @@ export function StepInfo({ <TextField.Input testID="passwordInput" onChangeText={value => { - dispatch({ - type: 'setPassword', - value, - }) + passwordValueRef.current = value }} label={_(msg`Choose your password`)} defaultValue={state.password} @@ -147,6 +200,14 @@ export function StepInfo({ </> ) : undefined} </View> + <BackNextButtons + hideNext={!is13(state.dateOfBirth)} + showRetry={isServerError} + isLoading={state.isLoading} + onBackPress={onPressBack} + onNextPress={onNextPress} + onRetryPress={refetchServer} + /> </ScreenTransition> ) } diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index f7ca180bf..da0383884 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -7,11 +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 {logger} from '#/logger' import {useServiceQuery} from '#/state/queries/service' -import {useAgent} from '#/state/session' import {useStarterPackQuery} from 'state/queries/starter-packs' import {useActiveStarterPack} from 'state/shell/starter-pack' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' @@ -20,14 +16,12 @@ import { reducer, SignupContext, SignupStep, - useSubmitSignup, } from '#/screens/Signup/state' import {StepCaptcha} from '#/screens/Signup/StepCaptcha' import {StepHandle} from '#/screens/Signup/StepHandle' import {StepInfo} from '#/screens/Signup/StepInfo' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' -import {Button, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {InlineLinkText} from '#/components/Link' @@ -38,9 +32,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { const t = useTheme() const {screen} = useAnalytics() const [state, dispatch] = React.useReducer(reducer, initialState) - const submit = useSubmitSignup({state, dispatch}) const {gtMobile} = useBreakpoints() - const agent = useAgent() const activeStarterPack = useActiveStarterPack() const { @@ -89,72 +81,6 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { } }, [_, serviceInfo, isError]) - const onNextPress = React.useCallback(async () => { - if (state.activeStep === SignupStep.HANDLE) { - try { - dispatch({type: 'setIsLoading', value: true}) - - const res = await agent.resolveHandle({ - handle: createFullHandle(state.handle, state.userDomain), - }) - - if (res.data.did) { - dispatch({ - type: 'setError', - value: _(msg`That handle is already taken.`), - }) - return - } - } catch (e) { - // Don't have to handle - } finally { - dispatch({type: 'setIsLoading', value: false}) - } - } - - logEvent('signup:nextPressed', { - activeStep: state.activeStep, - phoneVerificationRequired: - state.serviceDescription?.phoneVerificationRequired, - }) - - // phoneVerificationRequired is actually whether a captcha is required - if ( - state.activeStep === SignupStep.HANDLE && - !state.serviceDescription?.phoneVerificationRequired - ) { - submit() - return - } - dispatch({type: 'next'}) - }, [ - _, - state.activeStep, - state.handle, - state.serviceDescription?.phoneVerificationRequired, - state.userDomain, - submit, - agent, - ]) - - const onBackPress = React.useCallback(() => { - if (state.activeStep !== SignupStep.INFO) { - if (state.activeStep === SignupStep.CAPTCHA) { - logger.error('Signup Flow Error', { - errorMessage: - 'User went back from captcha step. Possibly encountered an error.', - registrationHandle: state.handle, - }) - } - dispatch({type: 'prev'}) - } else { - onPressBack() - } - logEvent('signup:backPressed', { - activeStep: state.activeStep, - }) - }, [onPressBack, state.activeStep, state.handle]) - return ( <SignupContext.Provider value={{state, dispatch}}> <LoggedOutLayout @@ -215,64 +141,22 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { </Text> </View> - <View style={[a.pb_3xl]}> - <LayoutAnimationConfig skipEntering skipExiting> - {state.activeStep === SignupStep.INFO ? ( - <StepInfo - isLoadingStarterPack={ - isFetchingStarterPack && !isErrorStarterPack - } - /> - ) : state.activeStep === SignupStep.HANDLE ? ( - <StepHandle /> - ) : ( - <StepCaptcha /> - )} - </LayoutAnimationConfig> - </View> - - <View style={[a.flex_row, a.justify_between, a.pb_lg]}> - <Button - label={_(msg`Go back to previous step`)} - variant="solid" - color="secondary" - size="medium" - onPress={onBackPress}> - <ButtonText> - <Trans>Back</Trans> - </ButtonText> - </Button> - {state.activeStep !== SignupStep.CAPTCHA && ( - <> - {isError ? ( - <Button - label={_(msg`Press to retry`)} - variant="solid" - color="primary" - size="medium" - disabled={state.isLoading} - onPress={() => refetch()}> - <ButtonText> - <Trans>Retry</Trans> - </ButtonText> - </Button> - ) : ( - <Button - testID="nextBtn" - label={_(msg`Continue to next step`)} - variant="solid" - color="primary" - size="medium" - disabled={!state.canNext || state.isLoading} - onPress={onNextPress}> - <ButtonText> - <Trans>Next</Trans> - </ButtonText> - </Button> - )} - </> + <LayoutAnimationConfig skipEntering skipExiting> + {state.activeStep === SignupStep.INFO ? ( + <StepInfo + onPressBack={onPressBack} + isLoadingStarterPack={ + isFetchingStarterPack && !isErrorStarterPack + } + isServerError={isError} + refetchServer={refetch} + /> + ) : state.activeStep === SignupStep.HANDLE ? ( + <StepHandle /> + ) : ( + <StepCaptcha /> )} - </View> + </LayoutAnimationConfig> <Divider /> diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts index 87700cb88..826cbf1d3 100644 --- a/src/screens/Signup/state.ts +++ b/src/screens/Signup/state.ts @@ -10,7 +10,7 @@ import * as EmailValidator from 'email-validator' import {DEFAULT_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 {useSessionApi} from '#/state/session' @@ -28,7 +28,6 @@ export enum SignupStep { export type SignupState = { hasPrev: boolean - canNext: boolean activeStep: SignupStep serviceUrl: string @@ -58,12 +57,10 @@ export type SignupAction = | {type: 'setHandle'; value: string} | {type: 'setVerificationCode'; value: string} | {type: 'setError'; value: string} - | {type: 'setCanNext'; value: boolean} | {type: 'setIsLoading'; value: boolean} export const initialState: SignupState = { hasPrev: false, - canNext: false, activeStep: SignupStep.INFO, serviceUrl: DEFAULT_SERVICE, @@ -144,10 +141,6 @@ export function reducer(s: SignupState, a: SignupAction): SignupState { next.handle = a.value break } - case 'setCanNext': { - next.canNext = a.value - break - } case 'setIsLoading': { next.isLoading = a.value break @@ -160,23 +153,6 @@ export function reducer(s: SignupState, a: SignupAction): SignupState { next.hasPrev = next.activeStep !== SignupStep.INFO - switch (next.activeStep) { - case SignupStep.INFO: { - const isValidEmail = EmailValidator.validate(next.email) - next.canNext = - !!(next.email && next.password && next.dateOfBirth) && - (!next.serviceDescription?.inviteCodeRequired || !!next.inviteCode) && - is13(next.dateOfBirth) && - isValidEmail - break - } - case SignupStep.HANDLE: { - next.canNext = - !!next.handle && validateHandle(next.handle, next.userDomain).overall - break - } - } - logger.debug('signup', next) if (s.activeStep !== next.activeStep) { |