#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 MeasureHeight from 'components/MeasureHeight';
import changeEmail from './changeEmail.scss'; import changeEmail from './changeEmail.scss';
import messages from './ChangeEmail.messages'; import messages from './ChangeEmail.intl.json';
const STEPS_TOTAL = 3; const STEPS_TOTAL = 3;
// TODO: disable code field, if the code was passed through url
export default class ChangeEmail extends Component { export default class ChangeEmail extends Component {
static displayName = 'ChangeEmail'; static displayName = 'ChangeEmail';
static propTypes = { static propTypes = {
onChangeStep: PropTypes.func, onChangeStep: PropTypes.func,
email: PropTypes.string.isRequired, 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, onSubmit: PropTypes.func.isRequired,
step: PropTypes.oneOf([0, 1, 2]), step: PropTypes.oneOf([0, 1, 2]),
code: PropTypes.string code: PropTypes.string
@ -32,7 +41,11 @@ export default class ChangeEmail extends Component {
static get defaultProps() { static get defaultProps() {
return { return {
form: new FormModel(), stepForms: [
new FormModel(),
new FormModel(),
new FormModel()
],
onChangeStep() {}, onChangeStep() {},
step: 0 step: 0
}; };
@ -45,18 +58,19 @@ export default class ChangeEmail extends Component {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this.setState({ this.setState({
activeStep: nextProps.step || this.state.activeStep, activeStep: typeof nextProps.step === 'number' ? nextProps.step : this.state.activeStep,
code: nextProps.code || '' code: nextProps.code || ''
}); });
} }
render() { render() {
const {form} = this.props;
const {activeStep} = this.state; const {activeStep} = this.state;
const form = this.props.stepForms[activeStep];
return ( return (
<Form onSubmit={this.onFormSubmit} <Form form={form}
form={form} onSubmit={this.onFormSubmit}
onInvalid={() => this.forceUpdate()}
> >
<div className={styles.contentWithBackButton}> <div className={styles.contentWithBackButton}>
<Link className={styles.backButton} to="/" /> <Link className={styles.backButton} to="/" />
@ -95,7 +109,7 @@ export default class ChangeEmail extends Component {
color="violet" color="violet"
block block
label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton} label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton}
onClick={this.onSwitchStep} onClick={this.onSubmit}
/> />
</div> </div>
@ -112,9 +126,9 @@ export default class ChangeEmail extends Component {
} }
renderStepForms() { renderStepForms() {
const {form, email} = this.props; const {email} = this.props;
const {activeStep, code} = this.state; const {activeStep, code} = this.state;
const isCodeEntered = !!this.props.code; const isCodeSpecified = !!this.props.code;
const activeStepHeight = this.state[`step${activeStep}Height`] || 0; const activeStepHeight = this.state[`step${activeStep}Height`] || 0;
@ -138,90 +152,25 @@ export default class ChangeEmail extends Component {
WebkitTransform: `translateX(-${interpolatingStyle.transform}%)`, WebkitTransform: `translateX(-${interpolatingStyle.transform}%)`,
transform: `translateX(-${interpolatingStyle.transform}%)` transform: `translateX(-${interpolatingStyle.transform}%)`
}}> }}>
<MeasureHeight className={changeEmail.stepForm} onMeasure={this.onStepMeasure(0)}> {(new Array(STEPS_TOTAL)).fill(0).map((_, step) => {
<div className={styles.formBody}> const form = this.props.stepForms[step];
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.currentAccountEmail} />
</p>
</div>
<div className={styles.formRow}> return (
<h2 className={changeEmail.currentAccountEmail}> <MeasureHeight
{email} className={changeEmail.stepForm}
</h2> onMeasure={this.onStepMeasure(step)}
</div> state={`${step}.${form.hasErrors()}`}
key={step}
<div className={styles.formRow}> >
<p className={styles.description}> {this[`renderStep${step}`]({
<Message {...messages.pressButtonToStart} /> email,
</p> code,
</div> form,
</div> isActiveStep: step === activeStep
</MeasureHeight> })}
</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>
</div> </div>
</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) { onStepMeasure(step) {
return (height) => this.setState({ return (height) => this.setState({
[`step${step}Height`]: height [`step${step}Height`]: height
}); });
} }
onSwitchStep = (event) => { nextStep() {
event.preventDefault();
const {activeStep} = this.state; const {activeStep} = this.state;
const nextStep = activeStep + 1; const nextStep = activeStep + 1;
@ -248,6 +296,16 @@ export default class ChangeEmail extends Component {
this.props.onChangeStep(nextStep); this.props.onChangeStep(nextStep);
} }
}
isLastStep() {
return this.state.activeStep + 1 === STEPS_TOTAL;
}
onSwitchStep = (event) => {
event.preventDefault();
this.nextStep();
}; };
onCodeInput = (event) => { onCodeInput = (event) => {
@ -258,11 +316,14 @@ export default class ChangeEmail extends Component {
}); });
}; };
isLastStep() {
return this.state.activeStep + 1 === STEPS_TOTAL;
}
onFormSubmit = () => { 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 { Input, Button, Form, FormModel } from 'components/ui/form';
import styles from 'components/profile/profileForm.scss'; import styles from 'components/profile/profileForm.scss';
import messages from './ChangeUsername.messages'; import messages from './ChangeUsername.intl.json';
export default class ChangeUsername extends Component { export default class ChangeUsername extends Component {
static displayName = 'ChangeUsername'; 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 { 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 { export default class PasswordRequestForm extends Component {
static displayName = 'PasswordRequestForm'; 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; return this.errors[fieldId] || null;
} }
hasErrors() {
return Object.keys(this.errors).length > 0;
}
serialize() { serialize() {
return Object.keys(this.fields).reduce((acc, fieldId) => { return Object.keys(this.fields).reduce((acc, fieldId) => {
acc[fieldId] = this.fields[fieldId].getValue(); acc[fieldId] = this.fields[fieldId].getValue();

View File

@ -140,6 +140,17 @@
color: $red; color: $red;
font-size: 12px; font-size: 12px;
margin: 3px 0; 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); @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.accountActivationTitle": "Account activation",
"components.auth.activation.activationMailWasSent": "Please check {email} for the message with the last registration step", "components.auth.activation.activationMailWasSent": "Please check {email} for the message with the last registration step",
"components.auth.activation.confirmEmail": "Confirm E-mail", "components.auth.activation.confirmEmail": "Confirm E-mail",
@ -69,6 +60,19 @@
"components.langMenu.siteLanguage": "Site language", "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.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.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.achievementLossWarning": "Are you cherish your game achievements, right?",
"components.profile.changePassword.changePasswordButton": "Change password", "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.", "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.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.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.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.changedAt": "Changed {at}",
"components.profile.disabled": "Disabled", "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.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.nickname": "Nickname",
"components.profile.oldHashingAlgoWarning": "Your was hashed with an old hashing algorithm.<br />Please, change password.", "components.profile.oldHashingAlgoWarning": "Your was hashed with an old hashing algorithm.<br />Please, change password.",
"components.profile.password": "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.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.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", "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", "logout": "Logout",
"newEmailPlaceholder": "Enter new E-mail",
"pages.root.siteName": "Ely.by", "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", "register": "Join",
"sendEmailButton": "Send E-mail",
"services.accountNotActivated": "The account is not activated", "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.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", "services.emailInvalid": "Email is invalid",
@ -112,6 +114,7 @@
"services.loginRequired": "Please enter email or username", "services.loginRequired": "Please enter email or username",
"services.newPasswordRequired": "Please enter new password", "services.newPasswordRequired": "Please enter new password",
"services.newRePasswordRequired": "Please repeat 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.passwordRequired": "Please enter password",
"services.passwordTooShort": "Your password should be at least 8 characters length", "services.passwordTooShort": "Your password should be at least 8 characters length",
"services.passwordsDoesNotMatch": "The passwords does not match", "services.passwordsDoesNotMatch": "The passwords does not match",
@ -122,6 +125,5 @@
"services.usernameRequired": "Username is required", "services.usernameRequired": "Username is required",
"services.usernameTooLong": "Username is too long", "services.usernameTooLong": "Username is too long",
"services.usernameTooShort": "Username is too short", "services.usernameTooShort": "Username is too short",
"services.usernameUnavailable": "This username is already taken", "services.usernameUnavailable": "This username is already taken"
"title": "Confirm your action"
} }

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.accountActivationTitle": "Активация аккаунта",
"components.auth.activation.activationMailWasSent": "На {email} отправлено письмо с инструкциями по завершению регистрации", "components.auth.activation.activationMailWasSent": "На {email} отправлено письмо с инструкциями по завершению регистрации",
"components.auth.activation.confirmEmail": "Подтверждение E-mail", "components.auth.activation.confirmEmail": "Подтверждение E-mail",
@ -69,6 +60,19 @@
"components.langMenu.siteLanguage": "Язык сайта", "components.langMenu.siteLanguage": "Язык сайта",
"components.profile.accountDescription": "Благодаря аккаунту Ely.by вы можете получить доступ ко многим ресурсам, связанным с Minecraft. Берегите свой аккаунт, используйте надёжный пароль и регулярно его меняйте.", "components.profile.accountDescription": "Благодаря аккаунту Ely.by вы можете получить доступ ко многим ресурсам, связанным с Minecraft. Берегите свой аккаунт, используйте надёжный пароль и регулярно его меняйте.",
"components.profile.accountPreferencesTitle": "Настройки аккаунта Ely.by", "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.achievementLossWarning": "Вы ведь дорожите своими игровыми достижениями?",
"components.profile.changePassword.changePasswordButton": "Сменить пароль", "components.profile.changePassword.changePasswordButton": "Сменить пароль",
"components.profile.changePassword.changePasswordDescription": "Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.", "components.profile.changePassword.changePasswordDescription": "Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.",
@ -77,26 +81,24 @@
"components.profile.changePassword.newPasswordLabel": "Новый пароль:", "components.profile.changePassword.newPasswordLabel": "Новый пароль:",
"components.profile.changePassword.passwordRequirements": "Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!", "components.profile.changePassword.passwordRequirements": "Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!",
"components.profile.changePassword.repeatNewPasswordLabel": "Повторите указанный пароль:", "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.changedAt": "Изменен {at}",
"components.profile.disabled": "Не включена", "components.profile.disabled": "Не включена",
"components.profile.mojangPriorityWarning": "Найден аккаунт Mojang с таким же ником и, по правилам проекта, его владелец вправе потребовать восстановления контроля над ником.", "components.profile.mojangPriorityWarning": "Найден аккаунт Mojang с таким же ником и, по правилам проекта, его владелец вправе потребовать восстановления контроля над ником.",
"components.profile.nickname": "Ник", "components.profile.nickname": "Ник",
"components.profile.oldHashingAlgoWarning": "Для пароля применяется старый алгоритм хэширования<br />Пожалуйста, смените пароль.", "components.profile.oldHashingAlgoWarning": "Для пароля применяется старый алгоритм хэширования<br />Пожалуйста, смените пароль.",
"components.profile.password": "Пароль", "components.profile.password": "Пароль",
"components.profile.passwordRequestForm.pleaseEnterPassword": "Пожалуйста, введите пароль от аккаунта",
"components.profile.passwordRequestForm.title": "Confirm your action",
"components.profile.personalData": "Персональные данные", "components.profile.personalData": "Персональные данные",
"components.profile.preferencesDescription": "Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.", "components.profile.preferencesDescription": "Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.",
"components.profile.twoFactorAuth": "Двухфакторная аутентификация", "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": "Выход", "logout": "Выход",
"newEmailPlaceholder": "Новый E-mail",
"pages.root.siteName": "Ely.by", "pages.root.siteName": "Ely.by",
"pleaseEnterPassword": "Пожалуйста, введите пароль от аккаунта",
"pressButtonToStart": "Нажмите на кнопку ниже, чтобы отправить письмо с кодом для инициализации процесса смены E-mail адреса.",
"register": "Регистрация", "register": "Регистрация",
"sendEmailButton": "Отправить E-mail",
"services.accountNotActivated": "Аккаунт не активирован", "services.accountNotActivated": "Аккаунт не активирован",
"services.emailFrequency": "Пожалуйста, успокойтесь, вы запрашиваете E-mail слишком часто. Новый ключ можно будет заказать через 30 минут от предыдущего запроса.", "services.emailFrequency": "Пожалуйста, успокойтесь, вы запрашиваете E-mail слишком часто. Новый ключ можно будет заказать через 30 минут от предыдущего запроса.",
"services.emailInvalid": "Указан неправильный E-mail", "services.emailInvalid": "Указан неправильный E-mail",
@ -112,6 +114,7 @@
"services.loginRequired": "Пожалуйста, укажите E-mail или ник", "services.loginRequired": "Пожалуйста, укажите E-mail или ник",
"services.newPasswordRequired": "Пожалуйста, заполните поле пароля", "services.newPasswordRequired": "Пожалуйста, заполните поле пароля",
"services.newRePasswordRequired": "Пожалуйста, введите повтор пароля", "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.passwordRequired": "Пожалуйста, введите пароль",
"services.passwordTooShort": "Пароль должен быть как минимум 8 символов в длинну", "services.passwordTooShort": "Пароль должен быть как минимум 8 символов в длинну",
"services.passwordsDoesNotMatch": "Пароли не совпадают", "services.passwordsDoesNotMatch": "Пароли не совпадают",
@ -122,6 +125,5 @@
"services.usernameRequired": "Поле ника обязательно к заполнению", "services.usernameRequired": "Поле ника обязательно к заполнению",
"services.usernameTooLong": "Слишком длинный ник", "services.usernameTooLong": "Слишком длинный ник",
"services.usernameTooShort": "Ник слишком короткий", "services.usernameTooShort": "Ник слишком короткий",
"services.usernameUnavailable": "Этот ник уже занят", "services.usernameUnavailable": "Этот ник уже занят"
"title": "Confirm your action"
} }

View File

@ -8,10 +8,12 @@ class ChangePasswordPage extends Component {
static displayName = 'ChangePasswordPage'; static displayName = 'ChangePasswordPage';
static propTypes = { static propTypes = {
updateUser: PropTypes.func.isRequired
}; };
static contextTypes = { static contextTypes = {
onSubmit: PropTypes.func.isRequired onSubmit: PropTypes.func.isRequired,
goToProfile: PropTypes.func.isRequired
}; };
form = new FormModel(); form = new FormModel();
@ -32,6 +34,7 @@ class ChangePasswordPage extends Component {
passwordChangedAt: Date.now() / 1000, passwordChangedAt: Date.now() / 1000,
shouldChangePassword: false shouldChangePassword: false
}); });
this.context.goToProfile();
}); });
}; };
} }

View File

@ -55,10 +55,7 @@ class ChangeUsernamePage extends Component {
sendData: () => accounts.changeUsername(form.serialize()) sendData: () => accounts.changeUsername(form.serialize())
}).then(() => { }).then(() => {
this.props.updateUsername(form.value('username')); this.props.updateUsername(form.value('username'));
this.context.goToProfile();
this.setState({
actualUsername: this.props.username
});
}); });
}; };
} }

View File

@ -1,8 +1,9 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormModel } from 'components/ui/form';
import ChangeEmail from 'components/profile/changeEmail/ChangeEmail'; import ChangeEmail from 'components/profile/changeEmail/ChangeEmail';
import accounts from 'services/api/accounts';
class ProfileChangeEmailPage extends Component { class ProfileChangeEmailPage extends Component {
static displayName = 'ProfileChangeEmailPage'; static displayName = 'ProfileChangeEmailPage';
@ -17,11 +18,11 @@ class ProfileChangeEmailPage extends Component {
static contextTypes = { static contextTypes = {
router: PropTypes.shape({ router: PropTypes.shape({
push: PropTypes.func push: PropTypes.func
}).isRequired }).isRequired,
onSubmit: PropTypes.func.isRequired,
goToProfile: PropTypes.func.isRequired
}; };
form = new FormModel();
componentWillMount() { componentWillMount() {
const step = this.props.params.step; const step = this.props.params.step;
@ -33,13 +34,13 @@ class ProfileChangeEmailPage extends Component {
} }
render() { render() {
const {params: {step, code}} = this.props; const {params: {step = 'step1', code}} = this.props;
return ( return (
<ChangeEmail form={this.form} <ChangeEmail
onSubmit={this.onSubmit} onSubmit={this.onSubmit}
email={this.props.email} email={this.props.email}
step={step ? step.slice(-1) * 1 - 1 : step} step={step.slice(-1) * 1 - 1}
onChangeStep={this.onChangeStep} onChangeStep={this.onChangeStep}
code={code} code={code}
/> />
@ -50,7 +51,26 @@ class ProfileChangeEmailPage extends Component {
this.context.router.push(`/profile/change-email/step${++step}`); 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 = { static propTypes = {
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
goToProfile: PropTypes.func.isRequired goToProfile: PropTypes.func.isRequired,
children: PropTypes.element
}; };
static childContextTypes = { static childContextTypes = {
@ -36,15 +37,15 @@ import { routeActions } from 'react-router-redux';
import { create as createPopup } from 'components/ui/popup/actions'; import { create as createPopup } from 'components/ui/popup/actions';
import PasswordRequestForm from 'components/profile/passwordRequestForm/PasswordRequestForm'; import PasswordRequestForm from 'components/profile/passwordRequestForm/PasswordRequestForm';
function goToProfile() {
return routeActions.push('/');
}
export default connect(null, { export default connect(null, {
goToProfile, goToProfile() {
return routeActions.push('/');
},
onSubmit: ({form, sendData}) => (dispatch) => onSubmit: ({form, sendData}) => (dispatch) =>
sendData() sendData()
.catch((resp) => { .catch((resp) => {
const requirePassword = resp.errors && !!resp.errors.password;
// prevalidate user input, because requestPassword popup will block the // prevalidate user input, because requestPassword popup will block the
// entire form from input, so it must be valid // entire form from input, so it must be valid
if (resp.errors) { if (resp.errors) {
@ -54,26 +55,29 @@ export default connect(null, {
form.setErrors(resp.errors); form.setErrors(resp.errors);
return Promise.reject(resp); return Promise.reject(resp);
} }
return Promise.resolve({requirePassword});
} }
return Promise.resolve();
}) })
.then(() => new Promise((resolve) => { .then((resp) => new Promise((resolve) => {
dispatch(createPopup(PasswordRequestForm, (props) => ({ if (resp.requirePassword) {
form, dispatch(createPopup(PasswordRequestForm, (props) => ({
onSubmit: () => { form,
sendData() onSubmit: () => {
.catch((resp) => { sendData()
if (resp.errors) { .catch((resp) => {
form.setErrors(resp.errors); if (resp.errors) {
} form.setErrors(resp.errors);
}
return Promise.reject(resp); return Promise.reject(resp);
}) })
.then(resolve) .then(resolve)
.then(props.onClose) .then(props.onClose);
.then(() => dispatch(goToProfile())); }
} })));
}))); } else {
resolve();
}
})) }))
})(ProfilePage); })(ProfilePage);

View File

@ -32,5 +32,28 @@ export default {
'/api/accounts/change-lang', '/api/accounts/change-lang',
{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", "keyRequired": "Please, enter an activation key",
"keyNotExists": "The key is incorrect", "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.", "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_required': () => <Message {...messages.passwordRequired} />,
'error.password_invalid': () => <Message {...messages.invalidPassword} />, 'error.password_invalid': () => <Message {...messages.invalidPassword} />,
'error.old_hash_strategy': () => <Message {...messages.oldHashStrategy} />,
'error.password_incorrect': () => ( 'error.password_incorrect': () => (
<span> <span>
<Message {...messages.invalidPassword} /> <Message {...messages.invalidPassword} />

View File

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