diff options
Diffstat (limited to 'src/view/com/auth/create/state.ts')
-rw-r--r-- | src/view/com/auth/create/state.ts | 301 |
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) -} |