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 (
+
+ );
+ }
+
+ 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'