mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add possibility to change one's email with confirmation
This commit is contained in:
@@ -18,6 +18,7 @@ export type AuthenticatedUser = {|
|
||||
onLogout: () => void,
|
||||
onLogin: () => void,
|
||||
onEdit: () => void,
|
||||
onChangeEmail: () => void,
|
||||
onCreateAccount: () => void,
|
||||
onRefreshUserProfile: () => void,
|
||||
onRefreshFirebaseProfile: () => void,
|
||||
@@ -35,6 +36,7 @@ export const initialAuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {},
|
||||
onRefreshFirebaseProfile: () => {},
|
||||
|
@@ -10,7 +10,7 @@ import Authentication, {
|
||||
type LoginForm,
|
||||
type RegisterForm,
|
||||
type EditForm,
|
||||
type ForgotPasswordForm,
|
||||
type ChangeEmailForm,
|
||||
type AuthError,
|
||||
} from '../Utils/GDevelopServices/Authentication';
|
||||
import { User as FirebaseUser } from 'firebase/auth';
|
||||
@@ -24,6 +24,7 @@ import AuthenticatedUserContext, {
|
||||
} from './AuthenticatedUserContext';
|
||||
import CreateAccountDialog from './CreateAccountDialog';
|
||||
import EditProfileDialog from './EditProfileDialog';
|
||||
import ChangeEmailDialog from './ChangeEmailDialog';
|
||||
import EmailVerificationPendingDialog from './EmailVerificationPendingDialog';
|
||||
|
||||
type Props = {|
|
||||
@@ -43,6 +44,8 @@ type State = {|
|
||||
resetPasswordDialogOpen: boolean,
|
||||
emailVerificationPendingDialogOpen: boolean,
|
||||
forgotPasswordInProgress: boolean,
|
||||
changeEmailDialogOpen: boolean,
|
||||
changeEmailInProgress: boolean,
|
||||
|};
|
||||
|
||||
export default class AuthenticatedUserProvider extends React.Component<
|
||||
@@ -61,6 +64,8 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
resetPasswordDialogOpen: false,
|
||||
emailVerificationPendingDialogOpen: false,
|
||||
forgotPasswordInProgress: false,
|
||||
changeEmailDialogOpen: false,
|
||||
changeEmailInProgress: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -76,6 +81,7 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
onLogout: this._doLogout,
|
||||
onLogin: () => this.openLoginDialog(true),
|
||||
onEdit: () => this.openEditProfileDialog(true),
|
||||
onChangeEmail: () => this.openChangeEmailDialog(true),
|
||||
onCreateAccount: () => this.openCreateAccountDialog(true),
|
||||
onRefreshUserProfile: this._fetchUserProfile,
|
||||
onRefreshFirebaseProfile: this._reloadFirebaseProfile,
|
||||
@@ -241,7 +247,7 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_doForgotPassword = (form: ForgotPasswordForm) => {
|
||||
_doForgotPassword = (form: LoginForm) => {
|
||||
const { authentication } = this.props;
|
||||
if (!authentication) return;
|
||||
|
||||
@@ -271,6 +277,27 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
});
|
||||
};
|
||||
|
||||
_doChangeEmail = (form: ChangeEmailForm) => {
|
||||
const { authentication } = this.props;
|
||||
if (!authentication) return;
|
||||
|
||||
watchPromiseInState(this, 'changeEmailInProgress', () =>
|
||||
authentication
|
||||
.changeEmail(authentication.getAuthorizationHeader, form)
|
||||
.then(
|
||||
() => {
|
||||
this._fetchUserProfile();
|
||||
this.openChangeEmailDialog(false);
|
||||
},
|
||||
(authError: AuthError) => {
|
||||
this.setState({
|
||||
authError,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
openEmailVerificationPendingDialog = (open: boolean = true) => {
|
||||
this.setState({
|
||||
emailVerificationPendingDialogOpen: open,
|
||||
@@ -307,6 +334,13 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
});
|
||||
};
|
||||
|
||||
openChangeEmailDialog = (open: boolean = true) => {
|
||||
this.setState({
|
||||
changeEmailDialogOpen: open,
|
||||
authError: null,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -336,6 +370,16 @@ export default class AuthenticatedUserProvider extends React.Component<
|
||||
error={this.state.authError}
|
||||
/>
|
||||
)}
|
||||
{this.state.authenticatedUser.firebaseUser &&
|
||||
this.state.changeEmailDialogOpen && (
|
||||
<ChangeEmailDialog
|
||||
firebaseUser={this.state.authenticatedUser.firebaseUser}
|
||||
onClose={() => this.openChangeEmailDialog(false)}
|
||||
onChangeEmail={this._doChangeEmail}
|
||||
changeEmailInProgress={this.state.changeEmailInProgress}
|
||||
error={this.state.authError}
|
||||
/>
|
||||
)}
|
||||
{this.state.createAccountDialogOpen && (
|
||||
<CreateAccountDialog
|
||||
onClose={() => this.openCreateAccountDialog(false)}
|
||||
|
93
newIDE/app/src/Profile/ChangeEmailDialog.js
Normal file
93
newIDE/app/src/Profile/ChangeEmailDialog.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import { User as FirebaseUser } from 'firebase/auth';
|
||||
import {
|
||||
type ChangeEmailForm,
|
||||
type AuthError,
|
||||
} from '../Utils/GDevelopServices/Authentication';
|
||||
import LeftLoader from '../UI/LeftLoader';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
import TextField from '../UI/TextField';
|
||||
import { getEmailErrorText } from './CreateAccountDialog';
|
||||
|
||||
type Props = {|
|
||||
firebaseUser: FirebaseUser,
|
||||
onClose: () => void,
|
||||
onChangeEmail: (form: ChangeEmailForm) => void,
|
||||
changeEmailInProgress: boolean,
|
||||
error: ?AuthError,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
form: ChangeEmailForm,
|
||||
|};
|
||||
|
||||
export default class ChangeEmailDialog extends Component<Props, State> {
|
||||
state = {
|
||||
form: {
|
||||
email: this.props.firebaseUser.email,
|
||||
},
|
||||
};
|
||||
|
||||
_onChangeEmail = () => {
|
||||
const { form } = this.state;
|
||||
this.props.onChangeEmail(form);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onClose, changeEmailInProgress, error } = this.props;
|
||||
const actions = [
|
||||
<FlatButton
|
||||
label={<Trans>Back</Trans>}
|
||||
disabled={changeEmailInProgress}
|
||||
key="back"
|
||||
primary={false}
|
||||
onClick={onClose}
|
||||
/>,
|
||||
<LeftLoader isLoading={changeEmailInProgress} key="change-email">
|
||||
<RaisedButton
|
||||
label={<Trans>Save</Trans>}
|
||||
primary
|
||||
onClick={this._onChangeEmail}
|
||||
disabled={changeEmailInProgress}
|
||||
/>
|
||||
</LeftLoader>,
|
||||
];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={<Trans>Change your email</Trans>}
|
||||
actions={actions}
|
||||
onRequestClose={() => {
|
||||
if (!changeEmailInProgress) onClose();
|
||||
}}
|
||||
maxWidth="sm"
|
||||
cannotBeDismissed={true}
|
||||
open
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
<TextField
|
||||
value={this.state.form.email}
|
||||
floatingLabelText={<Trans>Email</Trans>}
|
||||
errorText={getEmailErrorText(error)}
|
||||
fullWidth
|
||||
required
|
||||
onChange={(e, value) => {
|
||||
this.setState({
|
||||
form: {
|
||||
...this.state.form,
|
||||
email: value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ColumnStackLayout>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
@@ -40,6 +40,8 @@ export const getEmailErrorText = (error: ?AuthError) => {
|
||||
return 'This email was already used for another account';
|
||||
if (error.code === 'auth/operation-not-allowed')
|
||||
return 'Service seems to be unavailable, please try again later';
|
||||
if (error.code === 'auth/requires-recent-login')
|
||||
return 'Please log out and log in again to verify your identify, then change your email';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
@@ -11,17 +11,18 @@ import RaisedButton from '../UI/RaisedButton';
|
||||
import TextField from '../UI/TextField';
|
||||
import { I18n } from '@lingui/react';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
import { ColumnStackLayout, ResponsiveLineStackLayout } from '../UI/Layout';
|
||||
import AlertMessage from '../UI/AlertMessage';
|
||||
import { type AuthenticatedUser } from './AuthenticatedUserContext';
|
||||
import { useIsMounted } from '../Utils/UseIsMounted';
|
||||
|
||||
type Props = {|
|
||||
onEditProfile: Function,
|
||||
onEditProfile: () => void,
|
||||
onChangeEmail: () => void,
|
||||
authenticatedUser: AuthenticatedUser,
|
||||
|};
|
||||
|
||||
export default ({ onEditProfile, authenticatedUser }: Props) => {
|
||||
export default ({ onEditProfile, onChangeEmail, authenticatedUser }: Props) => {
|
||||
const profile = authenticatedUser.profile;
|
||||
const firebaseUser = authenticatedUser.firebaseUser;
|
||||
const isMounted = useIsMounted();
|
||||
@@ -97,7 +98,8 @@ export default ({ onEditProfile, authenticatedUser }: Props) => {
|
||||
</Line>
|
||||
<Line>
|
||||
<TextField
|
||||
value={profile.email}
|
||||
// The firebase user is the source of truth for the emails.
|
||||
value={firebaseUser.email}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Email</Trans>}
|
||||
@@ -117,13 +119,17 @@ export default ({ onEditProfile, authenticatedUser }: Props) => {
|
||||
rowsMax={5}
|
||||
/>
|
||||
</Line>
|
||||
<Line justifyContent="flex-end">
|
||||
<ResponsiveLineStackLayout justifyContent="flex-end">
|
||||
<RaisedButton
|
||||
label={<Trans>Change my email</Trans>}
|
||||
onClick={onChangeEmail}
|
||||
/>
|
||||
<RaisedButton
|
||||
label={<Trans>Edit my profile</Trans>}
|
||||
primary
|
||||
onClick={onEditProfile}
|
||||
/>
|
||||
</Line>
|
||||
</ResponsiveLineStackLayout>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
|
@@ -93,6 +93,7 @@ export default class ProfileDialog extends Component<Props, State> {
|
||||
<ProfileDetails
|
||||
authenticatedUser={authenticatedUser}
|
||||
onEditProfile={authenticatedUser.onEdit}
|
||||
onChangeEmail={authenticatedUser.onChangeEmail}
|
||||
/>
|
||||
<SubscriptionDetails
|
||||
subscription={authenticatedUser.subscription}
|
||||
|
@@ -10,37 +10,38 @@ import {
|
||||
sendPasswordResetEmail,
|
||||
signOut,
|
||||
sendEmailVerification,
|
||||
updateEmail,
|
||||
} from 'firebase/auth';
|
||||
import { GDevelopFirebaseConfig, GDevelopUserApi } from './ApiConfigs';
|
||||
import axios from 'axios';
|
||||
import { showErrorBox } from '../../UI/Messages/MessageBox';
|
||||
|
||||
export type Profile = {
|
||||
export type Profile = {|
|
||||
id: string,
|
||||
email: string,
|
||||
username: ?string,
|
||||
description: ?string,
|
||||
};
|
||||
|};
|
||||
|
||||
export type LoginForm = {
|
||||
export type LoginForm = {|
|
||||
email: string,
|
||||
password: string,
|
||||
};
|
||||
|};
|
||||
|
||||
export type RegisterForm = {
|
||||
export type RegisterForm = {|
|
||||
email: string,
|
||||
password: string,
|
||||
username: string,
|
||||
};
|
||||
|};
|
||||
|
||||
export type EditForm = {
|
||||
export type EditForm = {|
|
||||
username: string,
|
||||
description: string,
|
||||
};
|
||||
|};
|
||||
|
||||
export type ForgotPasswordForm = {
|
||||
export type ChangeEmailForm = {|
|
||||
email: string,
|
||||
};
|
||||
|};
|
||||
|
||||
export type AuthError = {
|
||||
code:
|
||||
@@ -52,7 +53,8 @@ export type AuthError = {
|
||||
| 'auth/operation-not-allowed'
|
||||
| 'auth/weak-password'
|
||||
| 'auth/username-used'
|
||||
| 'auth/malformed-username',
|
||||
| 'auth/malformed-username'
|
||||
| 'auth/requires-recent-login',
|
||||
};
|
||||
|
||||
export default class Authentication {
|
||||
@@ -100,8 +102,12 @@ export default class Authentication {
|
||||
return getAuthorizationHeader()
|
||||
.then(authorizationHeader => {
|
||||
if (!this.firebaseUser) {
|
||||
console.error('Cannot get user if not logged in');
|
||||
throw new Error('Cannot get user if not logged in');
|
||||
console.error(
|
||||
'Cannot create the user as it is not logged in any more.'
|
||||
);
|
||||
throw new Error(
|
||||
'Cannot create the user as it is not logged in any more.'
|
||||
);
|
||||
}
|
||||
return axios.post(
|
||||
`${GDevelopUserApi.baseUrl}/user`,
|
||||
@@ -140,7 +146,7 @@ export default class Authentication {
|
||||
});
|
||||
};
|
||||
|
||||
forgotPassword = (form: ForgotPasswordForm): Promise<void> => {
|
||||
forgotPassword = (form: LoginForm): Promise<void> => {
|
||||
return sendPasswordResetEmail(this.auth, form.email);
|
||||
};
|
||||
|
||||
@@ -169,12 +175,58 @@ export default class Authentication {
|
||||
});
|
||||
};
|
||||
|
||||
changeEmail = (
|
||||
getAuthorizationHeader: () => Promise<string>,
|
||||
form: ChangeEmailForm
|
||||
) => {
|
||||
return updateEmail(this.firebaseUser, form.email)
|
||||
.then(() => {
|
||||
console.log('Email successfully changed in Firebase.');
|
||||
return getAuthorizationHeader();
|
||||
})
|
||||
.then(authorizationHeader => {
|
||||
if (!this.firebaseUser) {
|
||||
console.error(
|
||||
'Cannot finish editing the user email as it is not logged in any more.'
|
||||
);
|
||||
throw new Error(
|
||||
'Cannot finish editing the user email as it is not logged in any more.'
|
||||
);
|
||||
}
|
||||
return axios.patch(
|
||||
`${GDevelopUserApi.baseUrl}/user/${this.firebaseUser.uid}`,
|
||||
{
|
||||
email: form.email,
|
||||
},
|
||||
{
|
||||
params: {
|
||||
userId: this.firebaseUser.uid,
|
||||
},
|
||||
headers: {
|
||||
Authorization: authorizationHeader,
|
||||
},
|
||||
}
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Email successfully changed in the GDevelop services.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('An error happened during email change.', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
getUserProfile = (getAuthorizationHeader: () => Promise<string>) => {
|
||||
return getAuthorizationHeader()
|
||||
.then(authorizationHeader => {
|
||||
if (!this.firebaseUser) {
|
||||
console.error('Cannot get user if not logged in');
|
||||
throw new Error('Cannot get user if not logged in');
|
||||
console.error(
|
||||
'Cannot get the user profile as it is not logged in any more.'
|
||||
);
|
||||
throw new Error(
|
||||
'Cannot get the user profile as it is not logged in any more.'
|
||||
);
|
||||
}
|
||||
return axios.get(
|
||||
`${GDevelopUserApi.baseUrl}/user/${this.firebaseUser.uid}`,
|
||||
@@ -202,8 +254,12 @@ export default class Authentication {
|
||||
return getAuthorizationHeader()
|
||||
.then(authorizationHeader => {
|
||||
if (!this.firebaseUser) {
|
||||
console.error('Cannot get user if not logged in');
|
||||
throw new Error('Cannot get user if not logged in');
|
||||
console.error(
|
||||
'Cannot finish editing the user as it is not logged in any more.'
|
||||
);
|
||||
throw new Error(
|
||||
'Cannot finish editing the user as it is not logged in any more.'
|
||||
);
|
||||
}
|
||||
const { username, description } = form;
|
||||
return axios.patch(
|
||||
@@ -236,10 +292,11 @@ export default class Authentication {
|
||||
logout = () => {
|
||||
signOut(this.auth)
|
||||
.then(() => {
|
||||
console.log('Logout successful');
|
||||
console.log('Logout successful.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('An error happened during logout', error);
|
||||
console.error('An error happened during logout.', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -100,6 +100,7 @@ export const fakeIndieAuthenticatedUser: AuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {
|
||||
console.info('This should refresh the user profile');
|
||||
@@ -123,6 +124,7 @@ export const fakeNoSubscriptionAuthenticatedUser: AuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {
|
||||
console.info('This should refresh the user profile');
|
||||
@@ -146,6 +148,7 @@ export const fakeAuthenticatedAndEmailVerifiedUser: AuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {
|
||||
console.info('This should refresh the user profile');
|
||||
@@ -169,6 +172,7 @@ export const fakeAuthenticatedButLoadingAuthenticatedUser: AuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {
|
||||
console.info('This should refresh the user profile');
|
||||
@@ -192,6 +196,7 @@ export const fakeNotAuthenticatedAuthenticatedUser: AuthenticatedUser = {
|
||||
onLogout: () => {},
|
||||
onLogin: () => {},
|
||||
onEdit: () => {},
|
||||
onChangeEmail: () => {},
|
||||
onCreateAccount: () => {},
|
||||
onRefreshUserProfile: () => {
|
||||
console.info('This should refresh the user profile');
|
||||
|
@@ -123,6 +123,7 @@ import UsagesDetails from '../Profile/UsagesDetails';
|
||||
import SubscriptionDialog from '../Profile/SubscriptionDialog';
|
||||
import LoginDialog from '../Profile/LoginDialog';
|
||||
import EditProfileDialog from '../Profile/EditProfileDialog';
|
||||
import ChangeEmailDialog from '../Profile/ChangeEmailDialog';
|
||||
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
|
||||
import { SubscriptionCheckDialog } from '../Profile/SubscriptionChecker';
|
||||
import DebuggerContent from '../Debugger/DebuggerContent';
|
||||
@@ -3942,12 +3943,14 @@ storiesOf('ProfileDetails', module)
|
||||
<ProfileDetails
|
||||
authenticatedUser={fakeIndieAuthenticatedUser}
|
||||
onEditProfile={action('edit profile')}
|
||||
onChangeEmail={action('change email')}
|
||||
/>
|
||||
))
|
||||
.add('loading', () => (
|
||||
<ProfileDetails
|
||||
authenticatedUser={fakeAuthenticatedButLoadingAuthenticatedUser}
|
||||
onEditProfile={action('edit profile')}
|
||||
onChangeEmail={action('change email')}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -4122,6 +4125,73 @@ storiesOf('Profile/EditProfileDialog', module)
|
||||
onEdit={action('on edit')}
|
||||
error={null}
|
||||
/>
|
||||
))
|
||||
.add('errored', () => (
|
||||
<EditProfileDialog
|
||||
profile={{
|
||||
id: 'id',
|
||||
email: 'email',
|
||||
username: 'username',
|
||||
description: 'I am just another video game enthusiast!',
|
||||
}}
|
||||
onClose={action('on close')}
|
||||
editInProgress={false}
|
||||
onEdit={action('on edit')}
|
||||
error={{ code: 'auth/username-used' }}
|
||||
/>
|
||||
))
|
||||
.add('loading', () => (
|
||||
<EditProfileDialog
|
||||
profile={{
|
||||
id: 'id',
|
||||
email: 'email',
|
||||
username: 'username',
|
||||
description: 'I am just another video game enthusiast!',
|
||||
}}
|
||||
onClose={action('on close')}
|
||||
editInProgress
|
||||
onEdit={action('on edit')}
|
||||
error={null}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('Profile/ChangeEmailDialog', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<ChangeEmailDialog
|
||||
firebaseUser={{
|
||||
uid: 'id',
|
||||
email: 'email',
|
||||
}}
|
||||
onClose={action('on close')}
|
||||
changeEmailInProgress={false}
|
||||
onChangeEmail={action('on change email')}
|
||||
error={null}
|
||||
/>
|
||||
))
|
||||
.add('errored', () => (
|
||||
<ChangeEmailDialog
|
||||
firebaseUser={{
|
||||
uid: 'id',
|
||||
email: 'email',
|
||||
}}
|
||||
onClose={action('on close')}
|
||||
changeEmailInProgress={false}
|
||||
onChangeEmail={action('on change email')}
|
||||
error={{ code: 'auth/requires-recent-login' }}
|
||||
/>
|
||||
))
|
||||
.add('loading', () => (
|
||||
<ChangeEmailDialog
|
||||
firebaseUser={{
|
||||
uid: 'id',
|
||||
email: 'email',
|
||||
}}
|
||||
onClose={action('on close')}
|
||||
changeEmailInProgress
|
||||
onChangeEmail={action('on change email')}
|
||||
error={null}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('Profile/CreateAccountDialog', module)
|
||||
|
Reference in New Issue
Block a user