diff --git a/src/components/profile/Profile.jsx b/src/components/profile/Profile.jsx index 38dd408..9c36f4d 100644 --- a/src/components/profile/Profile.jsx +++ b/src/components/profile/Profile.jsx @@ -49,6 +49,7 @@ export default class Profile extends Component { } value={user.username} warningMessage={user.hasMojangUsernameCollision ? ( @@ -62,8 +63,8 @@ export default class Profile extends Component { /> } link="/profile/change-password" + label={} value={) }} />} diff --git a/src/components/profile/changeUsername/ChangeUsername.jsx b/src/components/profile/changeUsername/ChangeUsername.jsx new file mode 100644 index 0000000..f935b45 --- /dev/null +++ b/src/components/profile/changeUsername/ChangeUsername.jsx @@ -0,0 +1,86 @@ +import React, { Component, PropTypes } from 'react'; + +import { FormattedMessage as Message } from 'react-intl'; +import { Link } from 'react-router'; +import Helmet from 'react-helmet'; + +import { Input, Button, Form, FormModel } from 'components/ui/form'; + +import styles from 'components/profile/profileForm.scss'; +import messages from './ChangeUsername.messages'; + +export default class ChangeUsername extends Component { + static displayName = 'ChangeUsername'; + + static propTypes = { + username: PropTypes.string.isRequired, + form: PropTypes.instanceOf(FormModel), + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired + }; + + static get defaultProps() { + return { + form: new FormModel() + }; + } + + + render() { + const {form, username} = this.props; + + return ( +
+
+ + +
+
+ + {(pageTitle) => ( +

+ + {pageTitle} +

+ )} +
+ +
+

+ +

+
+ +
+ +
+ +
+

+ +

+
+
+ +
+
+
+ ); + } + + onUsernameChange = (event) => { + this.props.onChange(event.target.value); + }; + + onFormSubmit = () => { + this.props.onSubmit(this.props.form); + }; +} diff --git a/src/components/profile/changeUsername/ChangeUsername.messages.js b/src/components/profile/changeUsername/ChangeUsername.messages.js new file mode 100644 index 0000000..4ea3681 --- /dev/null +++ b/src/components/profile/changeUsername/ChangeUsername.messages.js @@ -0,0 +1,24 @@ +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + changeUsernameTitle: { + id: 'changeUsernameTitle', + defaultMessage: 'Change nickname' + // defaultMessage: 'Смена никнейма' + }, + changeUsernameDescription: { + id: 'changeUsernameDescription', + defaultMessage: 'You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.' + // defaultMessage: 'Вы можете сменить свой никнейм на любое допустимое значение. Помните о том, что не рекомендуется занимать никнеймы пользователей Mojang.' + }, + changeUsernameWarning: { + id: 'changeUsernameWarning', + defaultMessage: 'Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.' + // defaultMessage: 'Будьте внимательны: если вы играли на сервере с привязкой по нику, то после смены ника вы можете утратить весь свой прогресс.' + }, + changeUsernameButton: { + id: 'changeUsernameButton', + defaultMessage: 'Change nickname' + // defaultMessage: 'Сменить никнейм' + } +}); diff --git a/src/pages/profile/ChangeUsernamePage.jsx b/src/pages/profile/ChangeUsernamePage.jsx new file mode 100644 index 0000000..01a0b25 --- /dev/null +++ b/src/pages/profile/ChangeUsernamePage.jsx @@ -0,0 +1,115 @@ +import React, { Component, PropTypes } from 'react'; + +import accounts from 'services/api/accounts'; +import { FormModel } from 'components/ui/form'; +import ChangeUsername from 'components/profile/changeUsername/ChangeUsername'; +import PasswordRequestForm from 'components/profile/passwordRequestForm/PasswordRequestForm'; + +class ChangeUsernamePage extends Component { + static displayName = 'ChangeUsernamePage'; + + static propTypes = { + username: PropTypes.string.isRequired, + updateUsername: PropTypes.func.isRequired, // updates username in state + changeUsername: PropTypes.func.isRequired // saves username to backend + }; + + form = new FormModel(); + + componentWillMount() { + this.setState({ + actualUsername: this.props.username + }); + } + + componentWillUnmount() { + this.props.updateUsername(this.state.actualUsername); + } + + render() { + return ( + + ); + } + + onUsernameChange = (username) => { + this.props.updateUsername(username); + }; + + onSubmit = () => { + this.props.changeUsername(this.form).then(() => { + console.log('update to', this.props.username) + this.setState({ + actualUsername: this.props.username + }); + }); + }; +} + +import { connect } from 'react-redux'; +import { routeActions } from 'react-router-redux'; +import { register as registerPopup, create as createPopup } from 'components/ui/popup/actions'; +import { updateUser } from 'components/user/actions'; + +function goToProfile() { + return routeActions.push('/'); +} + +export default connect((state) => ({ + username: state.user.username +}), { + updateUsername: (username) => { + return updateUser({username}); + }, + changeUsername: (form) => { + return (dispatch) => accounts.changeUsername(form.serialize()) + .catch((resp) => { + // prevalidate user input, because requestPassword popup will block the + // entire form from input, so it must be valid + if (resp.errors) { + Reflect.deleteProperty(resp.errors, 'password'); + + if (Object.keys(resp.errors).length) { + form.setErrors(resp.errors); + return Promise.reject(resp); + } + } + + return Promise.resolve(); + }) + .then(() => { + return new Promise((resolve) => { + // TODO: судя по всему registerPopup было явно лишним. Надо еще раз + // обдумать API и переписать + dispatch(registerPopup('requestPassword', PasswordRequestForm)); + dispatch(createPopup('requestPassword', (props) => ({ + form, + onSubmit: () => { + // TODO: hide this logic in action + accounts.changeUsername(form.serialize()) + .catch((resp) => { + if (resp.errors) { + form.setErrors(resp.errors); + } + + return Promise.reject(resp); + }) + .then(() => { + dispatch(updateUser({ + username: form.value('username') + })); + }) + .then(resolve) + .then(props.onClose) + .then(() => dispatch(goToProfile())); + } + }))); + }); + }) + ; + } +})(ChangeUsernamePage); diff --git a/src/routes.js b/src/routes.js index d44e59a..5973916 100644 --- a/src/routes.js +++ b/src/routes.js @@ -7,6 +7,7 @@ import AuthPage from 'pages/auth/AuthPage'; import ProfilePage from 'pages/profile/ProfilePage'; import ProfileChangePasswordPage from 'pages/profile/ChangePasswordPage'; +import ProfileChangeUsernamePage from 'pages/profile/ChangeUsernamePage'; import { authenticate } from 'components/user/actions'; @@ -55,6 +56,7 @@ export default function routesFactory(store) { + ); diff --git a/src/services/api/accounts.js b/src/services/api/accounts.js index 67eb43d..3319939 100644 --- a/src/services/api/accounts.js +++ b/src/services/api/accounts.js @@ -15,5 +15,15 @@ export default { '/api/accounts/change-password', {password, newPassword, newRePassword, logoutAll} ); + }, + + changeUsername({ + username = '', + password = '' + }) { + return request.post( + '/api/accounts/change-username', + {username, password} + ); } }; diff --git a/src/services/errorsDict.js b/src/services/errorsDict.js index 3c289fc..f02a5ee 100644 --- a/src/services/errorsDict.js +++ b/src/services/errorsDict.js @@ -31,6 +31,7 @@ const errorsMap = { ), 'error.username_required': () => , + 'error.username_not_available': () => , 'error.email_required': () => , 'error.email_invalid': () => , 'error.email_is_tempmail': () => , diff --git a/src/services/errorsDict.messages.js b/src/services/errorsDict.messages.js index eae6b2e..0bea008 100644 --- a/src/services/errorsDict.messages.js +++ b/src/services/errorsDict.messages.js @@ -46,6 +46,11 @@ export default defineMessages({ defaultMessage: 'Username is required' }, + usernameUnavailable: { + id: 'usernameUnavailable', + defaultMessage: 'This username is already taken' + }, + emailRequired: { id: 'emailRequired', defaultMessage: 'Email is required'