about summary refs log tree commit diff
path: root/src/view/com/auth/create/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/auth/create/state.ts')
-rw-r--r--src/view/com/auth/create/state.ts301
1 files changed, 124 insertions, 177 deletions
diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts
index e8a7cd4ed..276eaf924 100644
--- a/src/view/com/auth/create/state.ts
+++ b/src/view/com/auth/create/state.ts
@@ -1,8 +1,7 @@
-import {useReducer} from 'react'
+import {useCallback, useReducer} from 'react'
 import {
   ComAtprotoServerDescribeServer,
   ComAtprotoServerCreateAccount,
-  BskyAgent,
 } from '@atproto/api'
 import {I18nContext, useLingui} from '@lingui/react'
 import {msg} from '@lingui/macro'
@@ -11,10 +10,14 @@ import {getAge} from 'lib/strings/time'
 import {logger} from '#/logger'
 import {createFullHandle} from '#/lib/strings/handles'
 import {cleanError} from '#/lib/strings/errors'
-import {DispatchContext as OnboardingDispatchContext} from '#/state/shell/onboarding'
-import {ApiContext as SessionApiContext} from '#/state/session'
-import {DEFAULT_SERVICE} from '#/lib/constants'
-import parsePhoneNumber, {CountryCode} from 'libphonenumber-js'
+import {useOnboardingDispatch} from '#/state/shell/onboarding'
+import {useSessionApi} from '#/state/session'
+import {DEFAULT_SERVICE, IS_PROD} from '#/lib/constants'
+import {
+  DEFAULT_PROD_FEEDS,
+  usePreferencesSetBirthDateMutation,
+  useSetSaveFeedsMutation,
+} from 'state/queries/preferences'
 
 export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
 const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
@@ -29,10 +32,6 @@ export type CreateAccountAction =
   | {type: 'set-invite-code'; value: string}
   | {type: 'set-email'; value: string}
   | {type: 'set-password'; value: string}
-  | {type: 'set-phone-country'; value: CountryCode}
-  | {type: 'set-verification-phone'; value: string}
-  | {type: 'set-verification-code'; value: string}
-  | {type: 'set-has-requested-verification-code'; value: boolean}
   | {type: 'set-handle'; value: string}
   | {type: 'set-birth-date'; value: Date}
   | {type: 'next'}
@@ -49,10 +48,6 @@ export interface CreateAccountState {
   inviteCode: string
   email: string
   password: string
-  phoneCountry: CountryCode
-  verificationPhone: string
-  verificationCode: string
-  hasRequestedVerificationCode: boolean
   handle: string
   birthDate: Date
 
@@ -60,13 +55,14 @@ export interface CreateAccountState {
   canBack: boolean
   canNext: boolean
   isInviteCodeRequired: boolean
-  isPhoneVerificationRequired: boolean
+  isCaptchaRequired: boolean
 }
 
 export type CreateAccountDispatch = (action: CreateAccountAction) => void
 
 export function useCreateAccount() {
   const {_} = useLingui()
+
   return useReducer(createReducer({_}), {
     step: 1,
     error: undefined,
@@ -77,144 +73,126 @@ export function useCreateAccount() {
     inviteCode: '',
     email: '',
     password: '',
-    phoneCountry: 'US',
-    verificationPhone: '',
-    verificationCode: '',
-    hasRequestedVerificationCode: false,
     handle: '',
     birthDate: DEFAULT_DATE,
 
     canBack: false,
     canNext: false,
     isInviteCodeRequired: false,
-    isPhoneVerificationRequired: false,
+    isCaptchaRequired: false,
   })
 }
 
-export async function requestVerificationCode({
-  uiState,
-  uiDispatch,
-  _,
-}: {
-  uiState: CreateAccountState
-  uiDispatch: CreateAccountDispatch
-  _: I18nContext['_']
-}) {
-  const phoneNumber = parsePhoneNumber(
-    uiState.verificationPhone,
-    uiState.phoneCountry,
-  )?.number
-  if (!phoneNumber) {
-    return
-  }
-  uiDispatch({type: 'set-error', value: ''})
-  uiDispatch({type: 'set-processing', value: true})
-  uiDispatch({type: 'set-verification-phone', value: phoneNumber})
-  try {
-    const agent = new BskyAgent({service: uiState.serviceUrl})
-    await agent.com.atproto.temp.requestPhoneVerification({
-      phoneNumber,
-    })
-    uiDispatch({type: 'set-has-requested-verification-code', value: true})
-  } catch (e: any) {
-    logger.error(
-      `Failed to request sms verification code (${e.status} status)`,
-      {message: e},
-    )
-    uiDispatch({type: 'set-error', value: cleanError(e.toString())})
-  }
-  uiDispatch({type: 'set-processing', value: false})
-}
+export function useSubmitCreateAccount(
+  uiState: CreateAccountState,
+  uiDispatch: CreateAccountDispatch,
+) {
+  const {_} = useLingui()
+  const {createAccount} = useSessionApi()
+  const {mutate: setBirthDate} = usePreferencesSetBirthDateMutation()
+  const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
+  const onboardingDispatch = useOnboardingDispatch()
 
-export async function submit({
-  createAccount,
-  onboardingDispatch,
-  uiState,
-  uiDispatch,
-  _,
-}: {
-  createAccount: SessionApiContext['createAccount']
-  onboardingDispatch: OnboardingDispatchContext
-  uiState: CreateAccountState
-  uiDispatch: CreateAccountDispatch
-  _: I18nContext['_']
-}) {
-  if (!uiState.email) {
-    uiDispatch({type: 'set-step', value: 1})
-    return uiDispatch({
-      type: 'set-error',
-      value: _(msg`Please enter your email.`),
-    })
-  }
-  if (!EmailValidator.validate(uiState.email)) {
-    uiDispatch({type: 'set-step', value: 1})
-    return uiDispatch({
-      type: 'set-error',
-      value: _(msg`Your email appears to be invalid.`),
-    })
-  }
-  if (!uiState.password) {
-    uiDispatch({type: 'set-step', value: 1})
-    return uiDispatch({
-      type: 'set-error',
-      value: _(msg`Please choose your password.`),
-    })
-  }
-  if (
-    uiState.isPhoneVerificationRequired &&
-    (!uiState.verificationPhone || !uiState.verificationCode)
-  ) {
-    uiDispatch({type: 'set-step', value: 2})
-    return uiDispatch({
-      type: 'set-error',
-      value: _(msg`Please enter the code you received by SMS.`),
-    })
-  }
-  if (!uiState.handle) {
-    uiDispatch({type: 'set-step', value: 3})
-    return uiDispatch({
-      type: 'set-error',
-      value: _(msg`Please choose your handle.`),
-    })
-  }
-  uiDispatch({type: 'set-error', value: ''})
-  uiDispatch({type: 'set-processing', value: true})
+  return useCallback(
+    async (verificationCode?: string) => {
+      if (!uiState.email) {
+        uiDispatch({type: 'set-step', value: 1})
+        console.log('no email?')
+        return uiDispatch({
+          type: 'set-error',
+          value: _(msg`Please enter your email.`),
+        })
+      }
+      if (!EmailValidator.validate(uiState.email)) {
+        uiDispatch({type: 'set-step', value: 1})
+        return uiDispatch({
+          type: 'set-error',
+          value: _(msg`Your email appears to be invalid.`),
+        })
+      }
+      if (!uiState.password) {
+        uiDispatch({type: 'set-step', value: 1})
+        return uiDispatch({
+          type: 'set-error',
+          value: _(msg`Please choose your password.`),
+        })
+      }
+      if (!uiState.handle) {
+        uiDispatch({type: 'set-step', value: 2})
+        return uiDispatch({
+          type: 'set-error',
+          value: _(msg`Please choose your handle.`),
+        })
+      }
+      if (uiState.isCaptchaRequired && !verificationCode) {
+        uiDispatch({type: 'set-step', value: 3})
+        return uiDispatch({
+          type: 'set-error',
+          value: _(msg`Please complete the verification captcha.`),
+        })
+      }
+      uiDispatch({type: 'set-error', value: ''})
+      uiDispatch({type: 'set-processing', value: true})
 
-  try {
-    onboardingDispatch({type: 'start'}) // start now to avoid flashing the wrong view
-    await createAccount({
-      service: uiState.serviceUrl,
-      email: uiState.email,
-      handle: createFullHandle(uiState.handle, uiState.userDomain),
-      password: uiState.password,
-      inviteCode: uiState.inviteCode.trim(),
-      verificationPhone: uiState.verificationPhone.trim(),
-      verificationCode: uiState.verificationCode.trim(),
-    })
-  } catch (e: any) {
-    onboardingDispatch({type: 'skip'}) // undo starting the onboard
-    let errMsg = e.toString()
-    if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
-      errMsg = _(
-        msg`Invite code not accepted. Check that you input it correctly and try again.`,
-      )
-      uiDispatch({type: 'set-step', value: 1})
-    } else if (e.error === 'InvalidPhoneVerification') {
-      uiDispatch({type: 'set-step', value: 2})
-    }
+      try {
+        onboardingDispatch({type: 'start'}) // start now to avoid flashing the wrong view
+        await createAccount({
+          service: uiState.serviceUrl,
+          email: uiState.email,
+          handle: createFullHandle(uiState.handle, uiState.userDomain),
+          password: uiState.password,
+          inviteCode: uiState.inviteCode.trim(),
+          verificationCode: uiState.isCaptchaRequired
+            ? verificationCode
+            : undefined,
+        })
+        setBirthDate({birthDate: uiState.birthDate})
+        if (IS_PROD(uiState.serviceUrl)) {
+          setSavedFeeds(DEFAULT_PROD_FEEDS)
+        }
+      } catch (e: any) {
+        onboardingDispatch({type: 'skip'}) // undo starting the onboard
+        let errMsg = e.toString()
+        if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
+          errMsg = _(
+            msg`Invite code not accepted. Check that you input it correctly and try again.`,
+          )
+          uiDispatch({type: 'set-step', value: 1})
+        }
 
-    if ([400, 429].includes(e.status)) {
-      logger.warn('Failed to create account', {message: e})
-    } else {
-      logger.error(`Failed to create account (${e.status} status)`, {
-        message: e,
-      })
-    }
+        if ([400, 429].includes(e.status)) {
+          logger.warn('Failed to create account', {message: e})
+        } else {
+          logger.error(`Failed to create account (${e.status} status)`, {
+            message: e,
+          })
+        }
 
-    uiDispatch({type: 'set-processing', value: false})
-    uiDispatch({type: 'set-error', value: cleanError(errMsg)})
-    throw e
-  }
+        const error = cleanError(errMsg)
+        const isHandleError = error.toLowerCase().includes('handle')
+
+        uiDispatch({type: 'set-processing', value: false})
+        uiDispatch({type: 'set-error', value: cleanError(errMsg)})
+        uiDispatch({type: 'set-step', value: isHandleError ? 2 : 1})
+      }
+    },
+    [
+      uiState.email,
+      uiState.password,
+      uiState.handle,
+      uiState.isCaptchaRequired,
+      uiState.serviceUrl,
+      uiState.userDomain,
+      uiState.inviteCode,
+      uiState.birthDate,
+      uiDispatch,
+      _,
+      onboardingDispatch,
+      createAccount,
+      setBirthDate,
+      setSavedFeeds,
+    ],
+  )
 }
 
 export function is13(state: CreateAccountState) {
@@ -269,22 +247,6 @@ function createReducer({_}: {_: I18nContext['_']}) {
       case 'set-password': {
         return compute({...state, password: action.value})
       }
-      case 'set-phone-country': {
-        return compute({...state, phoneCountry: action.value})
-      }
-      case 'set-verification-phone': {
-        return compute({
-          ...state,
-          verificationPhone: action.value,
-          hasRequestedVerificationCode: false,
-        })
-      }
-      case 'set-verification-code': {
-        return compute({...state, verificationCode: action.value.trim()})
-      }
-      case 'set-has-requested-verification-code': {
-        return compute({...state, hasRequestedVerificationCode: action.value})
-      }
       case 'set-handle': {
         return compute({...state, handle: action.value})
       }
@@ -302,18 +264,10 @@ function createReducer({_}: {_: I18nContext['_']}) {
             })
           }
         }
-        let increment = 1
-        if (state.step === 1 && !state.isPhoneVerificationRequired) {
-          increment = 2
-        }
-        return compute({...state, error: '', step: state.step + increment})
+        return compute({...state, error: '', step: state.step + 1})
       }
       case 'back': {
-        let decrement = 1
-        if (state.step === 3 && !state.isPhoneVerificationRequired) {
-          decrement = 2
-        }
-        return compute({...state, error: '', step: state.step - decrement})
+        return compute({...state, error: '', step: state.step - 1})
       }
     }
   }
@@ -328,23 +282,16 @@ function compute(state: CreateAccountState): CreateAccountState {
       !!state.email &&
       !!state.password
   } else if (state.step === 2) {
-    canNext =
-      !state.isPhoneVerificationRequired ||
-      (!!state.verificationPhone &&
-        isValidVerificationCode(state.verificationCode))
-  } else if (state.step === 3) {
     canNext = !!state.handle
+  } else if (state.step === 3) {
+    // Step 3 will automatically redirect as soon as the captcha completes
+    canNext = false
   }
   return {
     ...state,
     canBack: state.step > 1,
     canNext,
     isInviteCodeRequired: !!state.serviceDescription?.inviteCodeRequired,
-    isPhoneVerificationRequired:
-      !!state.serviceDescription?.phoneVerificationRequired,
+    isCaptchaRequired: !!state.serviceDescription?.phoneVerificationRequired,
   }
 }
-
-function isValidVerificationCode(str: string): boolean {
-  return /[0-9]{6}/.test(str)
-}