diff --git a/native/account/registration/avatar-selection.react.js b/native/account/registration/avatar-selection.react.js --- a/native/account/registration/avatar-selection.react.js +++ b/native/account/registration/avatar-selection.react.js @@ -8,6 +8,7 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import type { CoolOrNerdMode, @@ -88,7 +89,13 @@ [], ); + const [registrationInProgress, setRegistrationInProgress] = + React.useState(false); + React.useEffect(() => { + if (registrationInProgress) { + return undefined; + } setRegistrationMode({ registrationMode: 'on', successCallback: setClientAvatarFromSelection, @@ -96,9 +103,27 @@ return () => { setRegistrationMode({ registrationMode: 'off' }); }; - }, [setRegistrationMode, setClientAvatarFromSelection]); + }, [ + registrationInProgress, + setRegistrationMode, + setClientAvatarFromSelection, + ]); + + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { register } = registrationContext; - const onProceed = React.useCallback(() => {}, []); + const onProceed = React.useCallback(async () => { + setRegistrationInProgress(true); + try { + await register({ + ...userSelections, + avatarData, + }); + } finally { + setRegistrationInProgress(false); + } + }, [register, userSelections, avatarData]); const clientAvatar = avatarData?.clientAvatar; const userInfoOverride = React.useMemo( @@ -118,7 +143,7 @@ @@ -128,7 +153,7 @@ diff --git a/native/account/registration/registration-context-provider.react.js b/native/account/registration/registration-context-provider.react.js --- a/native/account/registration/registration-context-provider.react.js +++ b/native/account/registration/registration-context-provider.react.js @@ -3,12 +3,20 @@ import * as React from 'react'; import { RegistrationContext } from './registration-context.js'; +import { useRegistrationServerCall } from './registration-server-call.js'; type Props = { +children: React.Node, }; function RegistrationContextProvider(props: Props): React.Node { - const contextValue = React.useMemo(() => ({}), []); + const registrationServerCall = useRegistrationServerCall(); + const contextValue = React.useMemo( + () => ({ + register: registrationServerCall, + }), + [registrationServerCall], + ); + return ( {props.children} diff --git a/native/account/registration/registration-context.js b/native/account/registration/registration-context.js --- a/native/account/registration/registration-context.js +++ b/native/account/registration/registration-context.js @@ -2,7 +2,11 @@ import * as React from 'react'; -export type RegistrationContextType = {}; +import type { RegistrationServerCallInput } from './registration-types.js'; + +export type RegistrationContextType = { + +register: RegistrationServerCallInput => Promise, +}; const RegistrationContext: React.Context = React.createContext(); diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js new file mode 100644 --- /dev/null +++ b/native/account/registration/registration-server-call.js @@ -0,0 +1,126 @@ +// @flow + +import * as React from 'react'; +import { Alert, Platform } from 'react-native'; +import { useDispatch } from 'react-redux'; + +import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; +import { registerActionTypes, register } from 'lib/actions/user-actions.js'; +import type { LogInStartingPayload } from 'lib/types/account-types.js'; +import { + useServerCall, + useDispatchActionPromise, +} from 'lib/utils/action-utils.js'; + +import type { + RegistrationServerCallInput, + UsernameAccountSelection, + EthereumAccountSelection, +} from './registration-types.js'; +import { NavContext } from '../../navigation/navigation-context.js'; +import { useSelector } from '../../redux/redux-utils.js'; +import { nativeLogInExtraInfoSelector } from '../../selectors/account-selectors.js'; +import { setNativeCredentials } from '../native-credentials.js'; +import { useSIWEServerCall } from '../siwe-hooks.js'; + +function useRegistrationServerCall(): RegistrationServerCallInput => Promise { + const navContext = React.useContext(NavContext); + const logInExtraInfo = useSelector(state => + nativeLogInExtraInfoSelector({ + redux: state, + navContext, + }), + ); + + const dispatchActionPromise = useDispatchActionPromise(); + const callRegister = useServerCall(register); + + const registerUsernameAccount = React.useCallback( + async (accountSelection: UsernameAccountSelection) => { + const extraInfo = await logInExtraInfo(); + const registerPromise = (async () => { + try { + const result = await callRegister({ + ...extraInfo, + username: accountSelection.username, + password: accountSelection.password, + }); + await setNativeCredentials({ + username: result.currentUserInfo.username, + password: accountSelection.password, + }); + return result; + } catch (e) { + if (e.message === 'username_reserved') { + Alert.alert( + 'Username reserved', + 'This username is currently reserved. Please contact support@' + + 'comm.app if you would like to claim this account.', + ); + } else if (e.message === 'username_taken') { + Alert.alert( + 'Username taken', + 'An account with that username already exists', + ); + } else if (e.message === 'client_version_unsupported') { + const app = Platform.select({ + ios: 'App Store', + android: 'Play Store', + }); + Alert.alert( + 'App out of date', + 'Your app version is pretty old, and the server doesn’t know how ' + + `to speak to it anymore. Please use the ${app} app to update!`, + ); + } else { + Alert.alert('Unknown error', 'Uhh... try again?'); + } + throw e; + } + })(); + dispatchActionPromise( + registerActionTypes, + registerPromise, + undefined, + ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload), + ); + await registerPromise; + }, + [logInExtraInfo, callRegister, dispatchActionPromise], + ); + + const siweServerCallParams = React.useMemo(() => { + const onServerCallFailure = () => { + Alert.alert('Unknown error', 'Uhh... try again?'); + }; + return { onFailure: onServerCallFailure }; + }, []); + const siweServerCall = useSIWEServerCall(siweServerCallParams); + + const registerEthereumAccount = React.useCallback( + (accountSelection: EthereumAccountSelection) => { + return siweServerCall(accountSelection); + }, + [siweServerCall], + ); + + const dispatch = useDispatch(); + return React.useCallback( + async (input: RegistrationServerCallInput) => { + if (input.accountSelection.accountType === 'username') { + await registerUsernameAccount(input.accountSelection); + } else { + await registerEthereumAccount(input.accountSelection); + } + dispatch({ + type: setDataLoadedActionType, + payload: { + dataLoaded: true, + }, + }); + }, + [registerUsernameAccount, registerEthereumAccount, dispatch], + ); +} + +export { useRegistrationServerCall }; diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js --- a/native/account/registration/registration-types.js +++ b/native/account/registration/registration-types.js @@ -36,3 +36,10 @@ +updateUserAvatarRequest: UpdateUserAvatarRequest, +clientAvatar: ClientAvatar, }; + +export type RegistrationServerCallInput = { + +coolOrNerdMode: CoolOrNerdMode, + +keyserverUsername: string, + +accountSelection: AccountSelection, + +avatarData: ?AvatarData, +};