From dffc73cc6d6d798f04a8b7f3b4ba22d6dcfa1ac5 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sun, 22 May 2016 10:53:40 +0300 Subject: [PATCH] #41: integrate change email with backend --- .../profile/changeEmail/ChangeEmail.intl.json | 17 ++ .../profile/changeEmail/ChangeEmail.jsx | 267 +++++++++++------- .../changeEmail/ChangeEmail.messages.js | 66 ----- .../changeUsername/ChangeUsername.intl.json | 6 + .../profile/changeUsername/ChangeUsername.jsx | 2 +- .../changeUsername/ChangeUsername.messages.js | 24 -- .../PasswordRequestForm.intl.json | 4 + .../PasswordRequestForm.jsx | 2 +- .../PasswordRequestForm.messages.js | 12 - src/components/ui/form/FormModel.js | 4 + src/components/ui/form/form.scss | 11 + src/i18n/en.json | 40 +-- src/i18n/ru.json | 40 +-- src/pages/profile/ChangePasswordPage.jsx | 5 +- src/pages/profile/ChangeUsernamePage.jsx | 5 +- src/pages/profile/ProfileChangeEmailPage.jsx | 36 ++- src/pages/profile/ProfilePage.jsx | 52 ++-- src/services/api/accounts.js | 23 ++ src/services/errorsDict.intl.json | 3 +- src/services/errorsDict.js | 1 + src/services/request.js | 2 +- 21 files changed, 338 insertions(+), 284 deletions(-) create mode 100644 src/components/profile/changeEmail/ChangeEmail.intl.json delete mode 100644 src/components/profile/changeEmail/ChangeEmail.messages.js create mode 100644 src/components/profile/changeUsername/ChangeUsername.intl.json delete mode 100644 src/components/profile/changeUsername/ChangeUsername.messages.js create mode 100644 src/components/profile/passwordRequestForm/PasswordRequestForm.intl.json delete mode 100644 src/components/profile/passwordRequestForm/PasswordRequestForm.messages.js diff --git a/src/components/profile/changeEmail/ChangeEmail.intl.json b/src/components/profile/changeEmail/ChangeEmail.intl.json new file mode 100644 index 0000000..5a8f433 --- /dev/null +++ b/src/components/profile/changeEmail/ChangeEmail.intl.json @@ -0,0 +1,17 @@ +{ + "changeEmailTitle": "Change E-mail", + "changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.", + "currentAccountEmail": "Current account E-mail address:", + "pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.", + "enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:", + + "enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.", + "finalizationCodeWasSentToEmail": "The E-mail change confirmation code was sent to {email}.", + "enterFinalizationCode": "In order to confirm your new E-mail, please enter the code received into the field below:", + + "newEmailPlaceholder": "Enter new E-mail", + "codePlaceholder": "Paste the code here", + "sendEmailButton": "Send E-mail", + "changeEmailButton": "Change E-mail", + "alreadyReceivedCode": "Already received code" +} diff --git a/src/components/profile/changeEmail/ChangeEmail.jsx b/src/components/profile/changeEmail/ChangeEmail.jsx index 3a4c3e9..264e500 100644 --- a/src/components/profile/changeEmail/ChangeEmail.jsx +++ b/src/components/profile/changeEmail/ChangeEmail.jsx @@ -12,19 +12,28 @@ import helpLinks from 'components/auth/helpLinks.scss'; import MeasureHeight from 'components/MeasureHeight'; import changeEmail from './changeEmail.scss'; -import messages from './ChangeEmail.messages'; +import messages from './ChangeEmail.intl.json'; const STEPS_TOTAL = 3; -// TODO: disable code field, if the code was passed through url - export default class ChangeEmail extends Component { static displayName = 'ChangeEmail'; static propTypes = { onChangeStep: PropTypes.func, email: PropTypes.string.isRequired, - form: PropTypes.instanceOf(FormModel), + stepForms: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => { + if (propValue.length !== 3) { + return new Error(`\`${propFullName}\` must be an array of 3 FormModel instances. Validation failed.`); + } + + if (!(propValue[key] instanceof FormModel)) { + return new Error( + `Invalid prop \`${propFullName}\` supplied to \ + \`${componentName}\`. Validation failed.` + ); + } + }), onSubmit: PropTypes.func.isRequired, step: PropTypes.oneOf([0, 1, 2]), code: PropTypes.string @@ -32,7 +41,11 @@ export default class ChangeEmail extends Component { static get defaultProps() { return { - form: new FormModel(), + stepForms: [ + new FormModel(), + new FormModel(), + new FormModel() + ], onChangeStep() {}, step: 0 }; @@ -45,18 +58,19 @@ export default class ChangeEmail extends Component { componentWillReceiveProps(nextProps) { this.setState({ - activeStep: nextProps.step || this.state.activeStep, + activeStep: typeof nextProps.step === 'number' ? nextProps.step : this.state.activeStep, code: nextProps.code || '' }); } render() { - const {form} = this.props; const {activeStep} = this.state; + const form = this.props.stepForms[activeStep]; return ( -
this.forceUpdate()} >
@@ -95,7 +109,7 @@ export default class ChangeEmail extends Component { color="violet" block label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton} - onClick={this.onSwitchStep} + onClick={this.onSubmit} />
@@ -112,9 +126,9 @@ export default class ChangeEmail extends Component { } renderStepForms() { - const {form, email} = this.props; + const {email} = this.props; const {activeStep, code} = this.state; - const isCodeEntered = !!this.props.code; + const isCodeSpecified = !!this.props.code; const activeStepHeight = this.state[`step${activeStep}Height`] || 0; @@ -138,90 +152,25 @@ export default class ChangeEmail extends Component { WebkitTransform: `translateX(-${interpolatingStyle.transform}%)`, transform: `translateX(-${interpolatingStyle.transform}%)` }}> - -
-
-

- -

-
+ {(new Array(STEPS_TOTAL)).fill(0).map((_, step) => { + const form = this.props.stepForms[step]; -
-

- {email} -

-
- -
-

- -

-
-
-
- - -
-
-

- {email}) - }} /> -

-
- -
- -
- -
-

- -

-
- -
- -
-
-
- - -
-
-

- {form.value('newEmail')}) - }} /> -

-
- -
- -
-
-
+ return ( + + {this[`renderStep${step}`]({ + email, + code, + form, + isActiveStep: step === activeStep + })} + + ); + })} )} @@ -229,15 +178,114 @@ export default class ChangeEmail extends Component { ); } + renderStep0({email}) { + return ( +
+
+

+ +

+
+ +
+

+ {email} +

+
+ +
+

+ +

+
+
+ ); + } + + renderStep1({email, form, code, isCodeSpecified, isActiveStep}) { + return ( +
+
+

+ {email}) + }} /> +

+
+ +
+ +
+ +
+

+ +

+
+ +
+ +
+
+ ); + } + + renderStep2({form, code, isCodeSpecified, isActiveStep}) { + const newEmail = this.props.stepForms[1].value('email'); + + return ( +
+
+

+ {newEmail ? ( + + {newEmail}) + }} /> + {' '} + + ) : null} + +

+
+ +
+ +
+
+ ); + } + onStepMeasure(step) { return (height) => this.setState({ [`step${step}Height`]: height }); } - onSwitchStep = (event) => { - event.preventDefault(); - + nextStep() { const {activeStep} = this.state; const nextStep = activeStep + 1; @@ -248,6 +296,16 @@ export default class ChangeEmail extends Component { this.props.onChangeStep(nextStep); } + } + + isLastStep() { + return this.state.activeStep + 1 === STEPS_TOTAL; + } + + onSwitchStep = (event) => { + event.preventDefault(); + + this.nextStep(); }; onCodeInput = (event) => { @@ -258,11 +316,14 @@ export default class ChangeEmail extends Component { }); }; - isLastStep() { - return this.state.activeStep + 1 === STEPS_TOTAL; - } - onFormSubmit = () => { - this.props.onSubmit(this.props.form); + const {activeStep} = this.state; + const promise = this.props.onSubmit(activeStep, this.props.stepForms[activeStep]); + + if (!promise || !promise.then) { + throw new Error('Expecting promise from onSubmit'); + } + + promise.then(() => this.nextStep(), () => this.forceUpdate()); }; } diff --git a/src/components/profile/changeEmail/ChangeEmail.messages.js b/src/components/profile/changeEmail/ChangeEmail.messages.js deleted file mode 100644 index bbcb7f7..0000000 --- a/src/components/profile/changeEmail/ChangeEmail.messages.js +++ /dev/null @@ -1,66 +0,0 @@ -import { defineMessages } from 'react-intl'; - -export default defineMessages({ - changeEmailTitle: { - id: 'changeEmailTitle', - defaultMessage: 'Change E-mail' - // defaultMessage: 'Смена E-mail' - }, - changeEmailDescription: { - id: 'changeEmailDescription', - defaultMessage: 'To change current account E-mail you must first verify that you own the current address and then confirm the new one.' - // defaultMessage: 'Для смены E-mail адреса аккаунта сперва необходимо подтвердить владение текущим адресом, а за тем привязать новый.' - }, - currentAccountEmail: { - id: 'currentAccountEmail', - defaultMessage: 'Current account E-mail address:' - // defaultMessage: 'Текущий E-mail адрес, привязанный к аккаунту:' - }, - pressButtonToStart: { - id: 'pressButtonToStart', - defaultMessage: 'Press the button below to send a message with the code for E-mail change initialization.' - // defaultMessage: 'Нажмите кнопку ниже, что бы отправить письмо с кодом для инциализации процесса смены E-mail адреса.' - }, - enterInitializationCode: { - id: 'enterInitializationCode', - defaultMessage: 'The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:' - // defaultMessage: 'На E-mail {email} было отправлено письмо с кодом для инициализации смены E-mail адреса. Введите его в поле ниже:' - }, -// - enterNewEmail: { - id: 'enterNewEmail', - defaultMessage: 'Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.' - // defaultMessage: 'За тем укажите новый E-mail адрес, к котором хотите привязать свой аккаунт. На него будет выслан код с подтверждением.' - }, - enterFinalizationCode: { - id: 'enterFinalizationCode', - defaultMessage: 'The E-mail change confirmation code was sent to {email}. Please enter the code received into the field below:' - // defaultMessage: 'На указанный E-mail {email} было выслано письмо с кодом для завершщения смены E-mail адреса. Введите полученный код в поле ниже:' - }, -// - newEmailPlaceholder: { - id: 'newEmailPlaceholder', - defaultMessage: 'Enter new E-mail' - // defaultMessage: 'Введите новый E-mail' - }, - codePlaceholder: { - id: 'codePlaceholder', - defaultMessage: 'Paste the code here' - // defaultMessage: 'Вставьте код сюда' - }, - sendEmailButton: { - id: 'sendEmailButton', - defaultMessage: 'Send E-mail' - // defaultMessage: 'Отправить E-mail' - }, - changeEmailButton: { - id: 'changeEmailButton', - defaultMessage: 'Change E-mail' - // defaultMessage: 'Сменить E-mail' - }, - alreadyReceivedCode: { - id: 'alreadyReceivedCode', - defaultMessage: 'Already received code' - // defaultMessage: 'Я получил код' - } -}); diff --git a/src/components/profile/changeUsername/ChangeUsername.intl.json b/src/components/profile/changeUsername/ChangeUsername.intl.json new file mode 100644 index 0000000..877100e --- /dev/null +++ b/src/components/profile/changeUsername/ChangeUsername.intl.json @@ -0,0 +1,6 @@ +{ + "changeUsernameTitle": "Change nickname", + "changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.", + "changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.", + "changeUsernameButton": "Change nickname" +} diff --git a/src/components/profile/changeUsername/ChangeUsername.jsx b/src/components/profile/changeUsername/ChangeUsername.jsx index f6bc3ab..bf7d11d 100644 --- a/src/components/profile/changeUsername/ChangeUsername.jsx +++ b/src/components/profile/changeUsername/ChangeUsername.jsx @@ -7,7 +7,7 @@ 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'; +import messages from './ChangeUsername.intl.json'; export default class ChangeUsername extends Component { static displayName = 'ChangeUsername'; diff --git a/src/components/profile/changeUsername/ChangeUsername.messages.js b/src/components/profile/changeUsername/ChangeUsername.messages.js deleted file mode 100644 index 4ea3681..0000000 --- a/src/components/profile/changeUsername/ChangeUsername.messages.js +++ /dev/null @@ -1,24 +0,0 @@ -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/components/profile/passwordRequestForm/PasswordRequestForm.intl.json b/src/components/profile/passwordRequestForm/PasswordRequestForm.intl.json new file mode 100644 index 0000000..02362fa --- /dev/null +++ b/src/components/profile/passwordRequestForm/PasswordRequestForm.intl.json @@ -0,0 +1,4 @@ +{ + "pleaseEnterPassword": "Please, enter your current password", + "title": "Confirm your action" +} diff --git a/src/components/profile/passwordRequestForm/PasswordRequestForm.jsx b/src/components/profile/passwordRequestForm/PasswordRequestForm.jsx index fbcf355..edd4e85 100644 --- a/src/components/profile/passwordRequestForm/PasswordRequestForm.jsx +++ b/src/components/profile/passwordRequestForm/PasswordRequestForm.jsx @@ -4,7 +4,7 @@ import { FormattedMessage as Message } from 'react-intl'; import { Form, Button, Input, FormModel } from 'components/ui/form'; -import messages from './PasswordRequestForm.messages'; +import messages from './PasswordRequestForm.intl.json'; export default class PasswordRequestForm extends Component { static displayName = 'PasswordRequestForm'; diff --git a/src/components/profile/passwordRequestForm/PasswordRequestForm.messages.js b/src/components/profile/passwordRequestForm/PasswordRequestForm.messages.js deleted file mode 100644 index b522c08..0000000 --- a/src/components/profile/passwordRequestForm/PasswordRequestForm.messages.js +++ /dev/null @@ -1,12 +0,0 @@ -import { defineMessages } from 'react-intl'; - -export default defineMessages({ - pleaseEnterPassword: { - id: 'pleaseEnterPassword', - defaultMessage: 'Please, enter your current password' - }, - title: { - id: 'title', - defaultMessage: 'Confirm your action' - } -}); diff --git a/src/components/ui/form/FormModel.js b/src/components/ui/form/FormModel.js index 18810e7..418e0f3 100644 --- a/src/components/ui/form/FormModel.js +++ b/src/components/ui/form/FormModel.js @@ -72,6 +72,10 @@ export default class FormModel { return this.errors[fieldId] || null; } + hasErrors() { + return Object.keys(this.errors).length > 0; + } + serialize() { return Object.keys(this.fields).reduce((acc, fieldId) => { acc[fieldId] = this.fields[fieldId].getValue(); diff --git a/src/components/ui/form/form.scss b/src/components/ui/form/form.scss index a22bcd0..5396bd6 100644 --- a/src/components/ui/form/form.scss +++ b/src/components/ui/form/form.scss @@ -140,6 +140,17 @@ color: $red; font-size: 12px; margin: 3px 0; + + a { + border-bottom: 1px dotted rgba($red, 0.75); + text-decoration: none; + transition: .25s; + color: $red; + + &:hover { + border-bottom-color: transparent; + } + } } @include input-theme('green', $green); diff --git a/src/i18n/en.json b/src/i18n/en.json index 48cbc64..9a0d547 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,13 +1,4 @@ { - "alreadyReceivedCode": "Already received code", - "changeEmailButton": "Change E-mail", - "changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.", - "changeEmailTitle": "Change E-mail", - "changeUsernameButton": "Change nickname", - "changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.", - "changeUsernameTitle": "Change nickname", - "changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.", - "codePlaceholder": "Paste the code here", "components.auth.activation.accountActivationTitle": "Account activation", "components.auth.activation.activationMailWasSent": "Please check {email} for the message with the last registration step", "components.auth.activation.confirmEmail": "Confirm E-mail", @@ -69,6 +60,19 @@ "components.langMenu.siteLanguage": "Site language", "components.profile.accountDescription": "Ely.by account allows you to get access to many Minecraft resources. Please, take care of your account safety. Use secure password and change it regularly.", "components.profile.accountPreferencesTitle": "Ely.by account preferences", + "components.profile.changeEmail.alreadyReceivedCode": "Already received code", + "components.profile.changeEmail.changeEmailButton": "Change E-mail", + "components.profile.changeEmail.changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.", + "components.profile.changeEmail.changeEmailTitle": "Change E-mail", + "components.profile.changeEmail.codePlaceholder": "Paste the code here", + "components.profile.changeEmail.currentAccountEmail": "Current account E-mail address:", + "components.profile.changeEmail.enterFinalizationCode": "In order to confirm your new E-mail, please enter the code received into the field below:", + "components.profile.changeEmail.enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:", + "components.profile.changeEmail.enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.", + "components.profile.changeEmail.finalizationCodeWasSentToEmail": "The E-mail change confirmation code was sent to {email}.", + "components.profile.changeEmail.newEmailPlaceholder": "Enter new E-mail", + "components.profile.changeEmail.pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.", + "components.profile.changeEmail.sendEmailButton": "Send E-mail", "components.profile.changePassword.achievementLossWarning": "Are you cherish your game achievements, right?", "components.profile.changePassword.changePasswordButton": "Change password", "components.profile.changePassword.changePasswordDescription": "Please take a password, that will be different from your passwords on the other sites and will not be the same you are using to enter Minecraft game servers you are playing.", @@ -77,26 +81,24 @@ "components.profile.changePassword.newPasswordLabel": "New password:", "components.profile.changePassword.passwordRequirements": "Password must contain at least 8 characters. It can be any symbols — do not limit yourself, create an unpredictable password!", "components.profile.changePassword.repeatNewPasswordLabel": "Repeat the password:", + "components.profile.changeUsername.changeUsernameButton": "Change nickname", + "components.profile.changeUsername.changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.", + "components.profile.changeUsername.changeUsernameTitle": "Change nickname", + "components.profile.changeUsername.changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.", "components.profile.changedAt": "Changed {at}", "components.profile.disabled": "Disabled", "components.profile.mojangPriorityWarning": "A Mojang account with the same nickname was found. According to project rules, account owner has the right to demand the restoration of control over nickname.", "components.profile.nickname": "Nickname", "components.profile.oldHashingAlgoWarning": "Your was hashed with an old hashing algorithm.
Please, change password.", "components.profile.password": "Password", + "components.profile.passwordRequestForm.pleaseEnterPassword": "Please, enter your current password", + "components.profile.passwordRequestForm.title": "Confirm your action", "components.profile.personalData": "Personal data", "components.profile.preferencesDescription": "Here you can change the key preferences of your account. Please note that all actions must be confirmed by entering a password.", "components.profile.twoFactorAuth": "Two factor auth", - "currentAccountEmail": "Current account E-mail address:", - "enterFinalizationCode": "The E-mail change confirmation code was sent to {email}. Please enter the code received into the field below:", - "enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:", - "enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.", "logout": "Logout", - "newEmailPlaceholder": "Enter new E-mail", "pages.root.siteName": "Ely.by", - "pleaseEnterPassword": "Please, enter your current password", - "pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.", "register": "Join", - "sendEmailButton": "Send E-mail", "services.accountNotActivated": "The account is not activated", "services.emailFrequency": "Please cool down, you are requesting emails too often. New key can be retrieved after 30 minutes from the previous request.", "services.emailInvalid": "Email is invalid", @@ -112,6 +114,7 @@ "services.loginRequired": "Please enter email or username", "services.newPasswordRequired": "Please enter new password", "services.newRePasswordRequired": "Please repeat new password", + "services.oldHashStrategy": "Sorry, but your account's password is too old. Please change your password in order to perform this action.", "services.passwordRequired": "Please enter password", "services.passwordTooShort": "Your password should be at least 8 characters length", "services.passwordsDoesNotMatch": "The passwords does not match", @@ -122,6 +125,5 @@ "services.usernameRequired": "Username is required", "services.usernameTooLong": "Username is too long", "services.usernameTooShort": "Username is too short", - "services.usernameUnavailable": "This username is already taken", - "title": "Confirm your action" + "services.usernameUnavailable": "This username is already taken" } diff --git a/src/i18n/ru.json b/src/i18n/ru.json index f2a6f03..d12ac9e 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -1,13 +1,4 @@ { - "alreadyReceivedCode": "Already received code", - "changeEmailButton": "Change E-mail", - "changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.", - "changeEmailTitle": "Change E-mail", - "changeUsernameButton": "Change nickname", - "changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.", - "changeUsernameTitle": "Change nickname", - "changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.", - "codePlaceholder": "Paste the code here", "components.auth.activation.accountActivationTitle": "Активация аккаунта", "components.auth.activation.activationMailWasSent": "На {email} отправлено письмо с инструкциями по завершению регистрации", "components.auth.activation.confirmEmail": "Подтверждение E-mail", @@ -69,6 +60,19 @@ "components.langMenu.siteLanguage": "Язык сайта", "components.profile.accountDescription": "Благодаря аккаунту Ely.by вы можете получить доступ ко многим ресурсам, связанным с Minecraft. Берегите свой аккаунт, используйте надёжный пароль и регулярно его меняйте.", "components.profile.accountPreferencesTitle": "Настройки аккаунта Ely.by", + "components.profile.changeEmail.alreadyReceivedCode": "Я уже получил код", + "components.profile.changeEmail.changeEmailButton": "Сменить E-mail", + "components.profile.changeEmail.changeEmailDescription": "Для смены E-mail адреса аккаунта сперва необходимо подтвердить владение текущим адресом, а за тем привязать новый.", + "components.profile.changeEmail.changeEmailTitle": "Смена E-mail", + "components.profile.changeEmail.codePlaceholder": "Вставьте код сюда", + "components.profile.changeEmail.currentAccountEmail": "Текущий E-mail адрес, привязанный к аккаунту:", + "components.profile.changeEmail.enterFinalizationCode": "Что бы подтвердить ваш новый E-mail, пожалуйста аведите полученный код в поле ниже:", + "components.profile.changeEmail.enterInitializationCode": "На E-mail {email} было отправлено письмо с кодом для инициализации смены E-mail адреса. Введите его в поле ниже:", + "components.profile.changeEmail.enterNewEmail": "За тем укажите новый E-mail адрес, к котором хотите привязать свой аккаунт. На него будет выслан код с подтверждением.", + "components.profile.changeEmail.finalizationCodeWasSentToEmail": "На указанный E-mail {email} было выслано письмо с кодом для завершщения смены E-mail адреса.", + "components.profile.changeEmail.newEmailPlaceholder": "Введите новый E-mail", + "components.profile.changeEmail.pressButtonToStart": "Нажмите на кнопку ниже, чтобы отправить письмо с кодом для инициализации процесса смены E-mail адреса.", + "components.profile.changeEmail.sendEmailButton": "Отправить E-mail", "components.profile.changePassword.achievementLossWarning": "Вы ведь дорожите своими игровыми достижениями?", "components.profile.changePassword.changePasswordButton": "Сменить пароль", "components.profile.changePassword.changePasswordDescription": "Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.", @@ -77,26 +81,24 @@ "components.profile.changePassword.newPasswordLabel": "Новый пароль:", "components.profile.changePassword.passwordRequirements": "Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!", "components.profile.changePassword.repeatNewPasswordLabel": "Повторите указанный пароль:", + "components.profile.changeUsername.changeUsernameButton": "Сменить никнейм", + "components.profile.changeUsername.changeUsernameDescription": "Вы можете сменить свой никнейм на любое допустимое значение. Помните о том, что не рекомендуется занимать никнеймы пользователей Mojang.", + "components.profile.changeUsername.changeUsernameTitle": "Смена никнейма", + "components.profile.changeUsername.changeUsernameWarning": "Будьте внимательны: если вы играли на сервере с привязкой по нику, то после смены ника вы можете утратить весь свой прогресс.", "components.profile.changedAt": "Изменен {at}", "components.profile.disabled": "Не включена", "components.profile.mojangPriorityWarning": "Найден аккаунт Mojang с таким же ником и, по правилам проекта, его владелец вправе потребовать восстановления контроля над ником.", "components.profile.nickname": "Ник", "components.profile.oldHashingAlgoWarning": "Для пароля применяется старый алгоритм хэширования
Пожалуйста, смените пароль.", "components.profile.password": "Пароль", + "components.profile.passwordRequestForm.pleaseEnterPassword": "Пожалуйста, введите пароль от аккаунта", + "components.profile.passwordRequestForm.title": "Confirm your action", "components.profile.personalData": "Персональные данные", "components.profile.preferencesDescription": "Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.", "components.profile.twoFactorAuth": "Двухфакторная аутентификация", - "currentAccountEmail": "Current account E-mail address:", - "enterFinalizationCode": "The E-mail change confirmation code was sent to {email}. Please enter the code received into the field below:", - "enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:", - "enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.", "logout": "Выход", - "newEmailPlaceholder": "Новый E-mail", "pages.root.siteName": "Ely.by", - "pleaseEnterPassword": "Пожалуйста, введите пароль от аккаунта", - "pressButtonToStart": "Нажмите на кнопку ниже, чтобы отправить письмо с кодом для инициализации процесса смены E-mail адреса.", "register": "Регистрация", - "sendEmailButton": "Отправить E-mail", "services.accountNotActivated": "Аккаунт не активирован", "services.emailFrequency": "Пожалуйста, успокойтесь, вы запрашиваете E-mail слишком часто. Новый ключ можно будет заказать через 30 минут от предыдущего запроса.", "services.emailInvalid": "Указан неправильный E-mail", @@ -112,6 +114,7 @@ "services.loginRequired": "Пожалуйста, укажите E-mail или ник", "services.newPasswordRequired": "Пожалуйста, заполните поле пароля", "services.newRePasswordRequired": "Пожалуйста, введите повтор пароля", + "services.oldHashStrategy": "Sorry, but your account's password is too old. Please change your password in order to perform this action.", "services.passwordRequired": "Пожалуйста, введите пароль", "services.passwordTooShort": "Пароль должен быть как минимум 8 символов в длинну", "services.passwordsDoesNotMatch": "Пароли не совпадают", @@ -122,6 +125,5 @@ "services.usernameRequired": "Поле ника обязательно к заполнению", "services.usernameTooLong": "Слишком длинный ник", "services.usernameTooShort": "Ник слишком короткий", - "services.usernameUnavailable": "Этот ник уже занят", - "title": "Confirm your action" + "services.usernameUnavailable": "Этот ник уже занят" } diff --git a/src/pages/profile/ChangePasswordPage.jsx b/src/pages/profile/ChangePasswordPage.jsx index 7ce6c4c..ead9a6f 100644 --- a/src/pages/profile/ChangePasswordPage.jsx +++ b/src/pages/profile/ChangePasswordPage.jsx @@ -8,10 +8,12 @@ class ChangePasswordPage extends Component { static displayName = 'ChangePasswordPage'; static propTypes = { + updateUser: PropTypes.func.isRequired }; static contextTypes = { - onSubmit: PropTypes.func.isRequired + onSubmit: PropTypes.func.isRequired, + goToProfile: PropTypes.func.isRequired }; form = new FormModel(); @@ -32,6 +34,7 @@ class ChangePasswordPage extends Component { passwordChangedAt: Date.now() / 1000, shouldChangePassword: false }); + this.context.goToProfile(); }); }; } diff --git a/src/pages/profile/ChangeUsernamePage.jsx b/src/pages/profile/ChangeUsernamePage.jsx index d062db9..4e40c66 100644 --- a/src/pages/profile/ChangeUsernamePage.jsx +++ b/src/pages/profile/ChangeUsernamePage.jsx @@ -55,10 +55,7 @@ class ChangeUsernamePage extends Component { sendData: () => accounts.changeUsername(form.serialize()) }).then(() => { this.props.updateUsername(form.value('username')); - - this.setState({ - actualUsername: this.props.username - }); + this.context.goToProfile(); }); }; } diff --git a/src/pages/profile/ProfileChangeEmailPage.jsx b/src/pages/profile/ProfileChangeEmailPage.jsx index f9150a8..c25e8f9 100644 --- a/src/pages/profile/ProfileChangeEmailPage.jsx +++ b/src/pages/profile/ProfileChangeEmailPage.jsx @@ -1,8 +1,9 @@ import React, { Component, PropTypes } from 'react'; -import { FormModel } from 'components/ui/form'; import ChangeEmail from 'components/profile/changeEmail/ChangeEmail'; +import accounts from 'services/api/accounts'; + class ProfileChangeEmailPage extends Component { static displayName = 'ProfileChangeEmailPage'; @@ -17,11 +18,11 @@ class ProfileChangeEmailPage extends Component { static contextTypes = { router: PropTypes.shape({ push: PropTypes.func - }).isRequired + }).isRequired, + onSubmit: PropTypes.func.isRequired, + goToProfile: PropTypes.func.isRequired }; - form = new FormModel(); - componentWillMount() { const step = this.props.params.step; @@ -33,13 +34,13 @@ class ProfileChangeEmailPage extends Component { } render() { - const {params: {step, code}} = this.props; + const {params: {step = 'step1', code}} = this.props; return ( - @@ -50,7 +51,26 @@ class ProfileChangeEmailPage extends Component { this.context.router.push(`/profile/change-email/step${++step}`); }; - onSubmit = () => { + onSubmit = (step, form) => { + return this.context.onSubmit({ + form, + sendData: () => { + const data = form.serialize(); + + switch (step) { + case 0: + return accounts.requestEmailChange(); + case 1: + return accounts.setNewEmail(data); + case 2: + return accounts.confirmNewEmail(data); + default: + throw new Error(`Unsupported step ${step}`); + } + } + }).then(() => { + step > 1 && this.context.goToProfile(); + }); }; } diff --git a/src/pages/profile/ProfilePage.jsx b/src/pages/profile/ProfilePage.jsx index e5ef75b..817de01 100644 --- a/src/pages/profile/ProfilePage.jsx +++ b/src/pages/profile/ProfilePage.jsx @@ -7,7 +7,8 @@ class ProfilePage extends Component { static propTypes = { onSubmit: PropTypes.func.isRequired, - goToProfile: PropTypes.func.isRequired + goToProfile: PropTypes.func.isRequired, + children: PropTypes.element }; static childContextTypes = { @@ -36,15 +37,15 @@ import { routeActions } from 'react-router-redux'; import { create as createPopup } from 'components/ui/popup/actions'; import PasswordRequestForm from 'components/profile/passwordRequestForm/PasswordRequestForm'; -function goToProfile() { - return routeActions.push('/'); -} - export default connect(null, { - goToProfile, + goToProfile() { + return routeActions.push('/'); + }, onSubmit: ({form, sendData}) => (dispatch) => sendData() .catch((resp) => { + const requirePassword = resp.errors && !!resp.errors.password; + // prevalidate user input, because requestPassword popup will block the // entire form from input, so it must be valid if (resp.errors) { @@ -54,26 +55,29 @@ export default connect(null, { form.setErrors(resp.errors); return Promise.reject(resp); } + + return Promise.resolve({requirePassword}); } - - return Promise.resolve(); }) - .then(() => new Promise((resolve) => { - dispatch(createPopup(PasswordRequestForm, (props) => ({ - form, - onSubmit: () => { - sendData() - .catch((resp) => { - if (resp.errors) { - form.setErrors(resp.errors); - } + .then((resp) => new Promise((resolve) => { + if (resp.requirePassword) { + dispatch(createPopup(PasswordRequestForm, (props) => ({ + form, + onSubmit: () => { + sendData() + .catch((resp) => { + if (resp.errors) { + form.setErrors(resp.errors); + } - return Promise.reject(resp); - }) - .then(resolve) - .then(props.onClose) - .then(() => dispatch(goToProfile())); - } - }))); + return Promise.reject(resp); + }) + .then(resolve) + .then(props.onClose); + } + }))); + } else { + resolve(); + } })) })(ProfilePage); diff --git a/src/services/api/accounts.js b/src/services/api/accounts.js index d47a6be..467e9cd 100644 --- a/src/services/api/accounts.js +++ b/src/services/api/accounts.js @@ -32,5 +32,28 @@ export default { '/api/accounts/change-lang', {lang} ); + }, + + requestEmailChange() { + return request.post( + '/api/accounts/change-email/initialize' + ); + }, + + setNewEmail({ + email = '', + key = '' + }) { + return request.post( + '/api/accounts/change-email/submit-new-email', + {email, key} + ); + }, + + confirmNewEmail({key}) { + return request.post( + '/api/accounts/change-email/confirm-new-email', + {key} + ); } }; diff --git a/src/services/errorsDict.intl.json b/src/services/errorsDict.intl.json index 96ff7aa..fd0750d 100644 --- a/src/services/errorsDict.intl.json +++ b/src/services/errorsDict.intl.json @@ -24,5 +24,6 @@ "keyRequired": "Please, enter an activation key", "keyNotExists": "The key is incorrect", "emailFrequency": "Please cool down, you are requesting emails too often. New key can be retrieved after 30 minutes from the previous request.", - "accountNotActivated": "The account is not activated" + "accountNotActivated": "The account is not activated", + "oldHashStrategy": "Sorry, but your account's password is too old. Please change your password in order to perform this action." } diff --git a/src/services/errorsDict.js b/src/services/errorsDict.js index cbb8499..a0f7438 100644 --- a/src/services/errorsDict.js +++ b/src/services/errorsDict.js @@ -16,6 +16,7 @@ const errorsMap = { 'error.password_required': () => , 'error.password_invalid': () => , + 'error.old_hash_strategy': () => , 'error.password_incorrect': () => ( diff --git a/src/services/request.js b/src/services/request.js index e2c0ce8..e882afa 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -14,7 +14,7 @@ function convertQueryValue(value) { return value; } -function buildQuery(data) { +function buildQuery(data = {}) { return Object.keys(data) .map( (keyName) =>