about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2024-07-02 14:43:34 -0700
committerGitHub <noreply@github.com>2024-07-02 22:43:34 +0100
commit63bb8fda2d28e11d7e60808e1e86384d48ec1b47 (patch)
tree077d366a888ceb4c0c925707e2d2989ad7bf7fdb /src
parent4bb4452f0858f2f5f8f6ead3012307cdf4b6a67f (diff)
downloadvoidsky-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>
Diffstat (limited to 'src')
-rw-r--r--src/screens/Login/LoginForm.tsx60
-rw-r--r--src/screens/Signup/BackNextButtons.tsx73
-rw-r--r--src/screens/Signup/StepCaptcha/index.tsx16
-rw-r--r--src/screens/Signup/StepHandle.tsx218
-rw-r--r--src/screens/Signup/StepInfo/index.tsx87
-rw-r--r--src/screens/Signup/index.tsx146
-rw-r--r--src/screens/Signup/state.ts26
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) {