diff --git a/native/account/registration/avatar-selection.react.js b/native/account/registration/avatar-selection.react.js
index 2e1666245..f1eba5c06 100644
--- a/native/account/registration/avatar-selection.react.js
+++ b/native/account/registration/avatar-selection.react.js
@@ -1,160 +1,185 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { Text, View } from 'react-native';
import RegistrationButtonContainer from './registration-button-container.react.js';
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,
AccountSelection,
AvatarData,
} from './registration-types.js';
import {
EditUserAvatarContext,
type UserAvatarSelection,
} from '../../avatars/edit-user-avatar-provider.react.js';
import EditUserAvatar from '../../avatars/edit-user-avatar.react.js';
import type { NavigationRoute } from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
export type AvatarSelectionParams = {
+userSelections: {
+coolOrNerdMode: CoolOrNerdMode,
+keyserverUsername: string,
+accountSelection: AccountSelection,
},
};
const ensDefaultSelection = {
needsUpload: false,
updateUserAvatarRequest: { type: 'ens' },
clientAvatar: { type: 'ens' },
};
type Props = {
+navigation: RegistrationNavigationProp<'AvatarSelection'>,
+route: NavigationRoute<'AvatarSelection'>,
};
function AvatarSelection(props: Props): React.Node {
const { userSelections } = props.route.params;
const { accountSelection } = userSelections;
const username =
accountSelection.accountType === 'username'
? accountSelection.username
: accountSelection.address;
const editUserAvatarContext = React.useContext(EditUserAvatarContext);
invariant(editUserAvatarContext, 'editUserAvatarContext should be set');
const { setRegistrationMode } = editUserAvatarContext;
const prefetchedAvatarURI =
accountSelection.accountType === 'ethereum'
? accountSelection.avatarURI
: undefined;
const [avatarData, setAvatarData] = React.useState(
prefetchedAvatarURI ? ensDefaultSelection : undefined,
);
const setClientAvatarFromSelection = React.useCallback(
(selection: UserAvatarSelection) => {
if (selection.needsUpload) {
setAvatarData({
...selection,
clientAvatar: {
type: 'image',
uri: selection.mediaSelection.uri,
},
});
} else if (selection.updateUserAvatarRequest.type !== 'remove') {
const clientRequest = selection.updateUserAvatarRequest;
invariant(
clientRequest.type !== 'image',
'image avatars need to be uploaded',
);
setAvatarData({
...selection,
clientAvatar: clientRequest,
});
} else {
setAvatarData(undefined);
}
},
[],
);
+ const [registrationInProgress, setRegistrationInProgress] =
+ React.useState(false);
+
React.useEffect(() => {
+ if (registrationInProgress) {
+ return undefined;
+ }
setRegistrationMode({
registrationMode: 'on',
successCallback: setClientAvatarFromSelection,
});
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(
() => ({
username,
avatar: clientAvatar,
}),
[username, clientAvatar],
);
const styles = useStyles(unboundStyles);
return (
Pick an avatar
);
}
const unboundStyles = {
scrollViewContentContainer: {
paddingHorizontal: 0,
},
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
paddingHorizontal: 16,
},
stagedAvatarSection: {
marginTop: 16,
backgroundColor: 'panelForeground',
paddingVertical: 24,
alignItems: 'center',
},
editUserAvatar: {
alignItems: 'center',
justifyContent: 'center',
},
};
export default AvatarSelection;
diff --git a/native/account/registration/registration-context-provider.react.js b/native/account/registration/registration-context-provider.react.js
index 33dc9ac81..9cd2d0b6e 100644
--- a/native/account/registration/registration-context-provider.react.js
+++ b/native/account/registration/registration-context-provider.react.js
@@ -1,19 +1,27 @@
// @flow
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}
);
}
export { RegistrationContextProvider };
diff --git a/native/account/registration/registration-context.js b/native/account/registration/registration-context.js
index c5b2e6fd7..92ac217fa 100644
--- a/native/account/registration/registration-context.js
+++ b/native/account/registration/registration-context.js
@@ -1,10 +1,14 @@
// @flow
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();
export { RegistrationContext };
diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js
new file mode 100644
index 000000000..b75828676
--- /dev/null
+++ b/native/account/registration/registration-server-call.js
@@ -0,0 +1,118 @@
+// @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,
+} 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 dispatch = useDispatch();
+ return React.useCallback(
+ async (input: RegistrationServerCallInput) => {
+ if (input.accountSelection.accountType === 'username') {
+ await registerUsernameAccount(input.accountSelection);
+ } else {
+ await siweServerCall(input.accountSelection);
+ }
+ dispatch({
+ type: setDataLoadedActionType,
+ payload: {
+ dataLoaded: true,
+ },
+ });
+ },
+ [registerUsernameAccount, siweServerCall, dispatch],
+ );
+}
+
+export { useRegistrationServerCall };
diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js
index d573bc3d0..71c9189f3 100644
--- a/native/account/registration/registration-types.js
+++ b/native/account/registration/registration-types.js
@@ -1,38 +1,45 @@
// @flow
import type {
UpdateUserAvatarRequest,
ClientAvatar,
} from 'lib/types/avatar-types.js';
import type { NativeMediaSelection } from 'lib/types/media-types.js';
import type { SIWEResult } from 'lib/types/siwe-types.js';
export type CoolOrNerdMode = 'cool' | 'nerd';
export type EthereumAccountSelection = {
+accountType: 'ethereum',
...SIWEResult,
+avatarURI: ?string,
};
export type UsernameAccountSelection = {
+accountType: 'username',
+username: string,
+password: string,
};
export type AccountSelection =
| EthereumAccountSelection
| UsernameAccountSelection;
export type AvatarData =
| {
+needsUpload: true,
+mediaSelection: NativeMediaSelection,
+clientAvatar: ClientAvatar,
}
| {
+needsUpload: false,
+updateUserAvatarRequest: UpdateUserAvatarRequest,
+clientAvatar: ClientAvatar,
};
+
+export type RegistrationServerCallInput = {
+ +coolOrNerdMode: CoolOrNerdMode,
+ +keyserverUsername: string,
+ +accountSelection: AccountSelection,
+ +avatarData: ?AvatarData,
+};