#41: integrate change email with backend

This commit is contained in:
SleepWalker 2016-05-22 10:53:40 +03:00
parent fa5ba292cb
commit dffc73cc6d
21 changed files with 338 additions and 284 deletions

View File

@ -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"
}

View File

@ -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 (
<Form onSubmit={this.onFormSubmit}
form={form}
<Form form={form}
onSubmit={this.onFormSubmit}
onInvalid={() => this.forceUpdate()}
>
<div className={styles.contentWithBackButton}>
<Link className={styles.backButton} to="/" />
@ -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}
/>
</div>
@ -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}%)`
}}>
<MeasureHeight className={changeEmail.stepForm} onMeasure={this.onStepMeasure(0)}>
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.currentAccountEmail} />
</p>
</div>
{(new Array(STEPS_TOTAL)).fill(0).map((_, step) => {
const form = this.props.stepForms[step];
<div className={styles.formRow}>
<h2 className={changeEmail.currentAccountEmail}>
{email}
</h2>
</div>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.pressButtonToStart} />
</p>
</div>
</div>
</MeasureHeight>
<MeasureHeight className={changeEmail.stepForm} onMeasure={this.onStepMeasure(1)}>
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.enterInitializationCode} values={{
email: (<b>{email}</b>)
}} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('initializationCode')}
required
disabled={isCodeEntered}
value={code}
onChange={this.onCodeInput}
skin="light"
color="violet"
placeholder={messages.codePlaceholder}
/>
</div>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.enterNewEmail} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('newEmail')}
required
skin="light"
color="violet"
placeholder={messages.newEmailPlaceholder}
/>
</div>
</div>
</MeasureHeight>
<MeasureHeight className={changeEmail.stepForm} onMeasure={this.onStepMeasure(2)}>
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.enterFinalizationCode} values={{
email: (<b>{form.value('newEmail')}</b>)
}} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('finalizationCode')}
required
disabled={isCodeEntered}
value={code}
onChange={this.onCodeInput}
skin="light"
color="violet"
placeholder={messages.codePlaceholder}
/>
</div>
</div>
</MeasureHeight>
return (
<MeasureHeight
className={changeEmail.stepForm}
onMeasure={this.onStepMeasure(step)}
state={`${step}.${form.hasErrors()}`}
key={step}
>
{this[`renderStep${step}`]({
email,
code,
form,
isActiveStep: step === activeStep
})}
</MeasureHeight>
);
})}
</div>
</div>
)}
@ -229,15 +178,114 @@ export default class ChangeEmail extends Component {
);
}
renderStep0({email}) {
return (
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.currentAccountEmail} />
</p>
</div>
<div className={styles.formRow}>
<h2 className={changeEmail.currentAccountEmail}>
{email}
</h2>
</div>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.pressButtonToStart} />
</p>
</div>
</div>
);
}
renderStep1({email, form, code, isCodeSpecified, isActiveStep}) {
return (
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.enterInitializationCode} values={{
email: (<b>{email}</b>)
}} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('key')}
required={isActiveStep}
disabled={isCodeSpecified}
value={code}
onChange={this.onCodeInput}
autoComplete="off"
skin="light"
color="violet"
placeholder={messages.codePlaceholder}
/>
</div>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.enterNewEmail} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('email')}
required={isActiveStep}
skin="light"
color="violet"
placeholder={messages.newEmailPlaceholder}
/>
</div>
</div>
);
}
renderStep2({form, code, isCodeSpecified, isActiveStep}) {
const newEmail = this.props.stepForms[1].value('email');
return (
<div className={styles.formBody}>
<div className={styles.formRow}>
<p className={styles.description}>
{newEmail ? (
<span>
<Message {...messages.finalizationCodeWasSentToEmail} values={{
email: (<b>{newEmail}</b>)
}} />
{' '}
</span>
) : null}
<Message {...messages.enterFinalizationCode} />
</p>
</div>
<div className={styles.formRow}>
<Input {...form.bindField('key')}
required={isActiveStep}
disabled={isCodeSpecified}
value={code}
onChange={this.onCodeInput}
autoComplete="off"
skin="light"
color="violet"
placeholder={messages.codePlaceholder}
/>
</div>
</div>
);
}
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());
};
}

View File

@ -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: 'Я получил код'
}
});

View File

@ -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"
}

View File

@ -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';

View File

@ -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: 'Сменить никнейм'
}
});

View File

@ -0,0 +1,4 @@
{
"pleaseEnterPassword": "Please, enter your current password",
"title": "Confirm your action"
}

View File

@ -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';

View File

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

View File

@ -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();

View File

@ -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);

View File

@ -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.<br />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"
}

View File

@ -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": "Для пароля применяется старый алгоритм хэширования<br />Пожалуйста, смените пароль.",
"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": "Этот ник уже занят"
}

View File

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

View File

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

View File

@ -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 (
<ChangeEmail form={this.form}
<ChangeEmail
onSubmit={this.onSubmit}
email={this.props.email}
step={step ? step.slice(-1) * 1 - 1 : step}
step={step.slice(-1) * 1 - 1}
onChangeStep={this.onChangeStep}
code={code}
/>
@ -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();
});
};
}

View File

@ -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);

View File

@ -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}
);
}
};

View File

@ -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."
}

View File

@ -16,6 +16,7 @@ const errorsMap = {
'error.password_required': () => <Message {...messages.passwordRequired} />,
'error.password_invalid': () => <Message {...messages.invalidPassword} />,
'error.old_hash_strategy': () => <Message {...messages.oldHashStrategy} />,
'error.password_incorrect': () => (
<span>
<Message {...messages.invalidPassword} />

View File

@ -14,7 +14,7 @@ function convertQueryValue(value) {
return value;
}
function buildQuery(data) {
function buildQuery(data = {}) {
return Object.keys(data)
.map(
(keyName) =>