diff --git a/package.json b/package.json
index 23ffa7b..75700ca 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"react-motion": "^0.4.0",
"react-redux": "^4.0.0",
"react-router": "^2.0.0",
- "react-router-redux": "^2.1.0",
+ "react-router-redux": "^3.0.0",
"redux": "^3.0.4",
"redux-thunk": "^1.0.0",
"whatwg-fetch": "^0.11.0"
diff --git a/src/components/auth/PanelTransition.jsx b/src/components/auth/PanelTransition.jsx
index c29c9b8..320bbdb 100644
--- a/src/components/auth/PanelTransition.jsx
+++ b/src/components/auth/PanelTransition.jsx
@@ -219,7 +219,8 @@ class PanelTransition extends Component {
activation: fromRight,
permissions: fromLeft,
changePassword: fromRight,
- forgotPassword: fromRight
+ forgotPassword: [panelId, prevPanelId].includes('recoverPassword') ? fromLeft : fromRight,
+ recoverPassword: fromRight
};
const sign = map[key];
const transform = sign * 100;
@@ -240,7 +241,8 @@ class PanelTransition extends Component {
activation: not('register') ? 'Y' : 'X',
permissions: 'Y',
changePassword: 'Y',
- forgotPassword: not('password') && not('login') ? 'Y' : 'X'
+ forgotPassword: not('password') && not('login') ? 'Y' : 'X',
+ recoverPassword: not('password') && not('login') && not('forgotPassword') ? 'Y' : 'X'
};
return map[next];
diff --git a/src/components/auth/actions.js b/src/components/auth/actions.js
index a247ae5..708fa3b 100644
--- a/src/components/auth/actions.js
+++ b/src/components/auth/actions.js
@@ -33,12 +33,9 @@ export function login({login = '', password = '', rememberMe = false}) {
if (resp.errors.login === LOGIN_REQUIRED && password) {
dispatch(logout());
}
- const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
- dispatch(setError(errorMessage));
- return Promise.reject(errorMessage);
}
- // TODO: log unexpected errors
+ return validationErrorsHandler(dispatch)(resp);
})
);
}
@@ -50,15 +47,44 @@ export function changePassword({
}) {
return wrapInLoader((dispatch) =>
dispatch(changeUserPassword({password, newPassword, newRePassword, logoutAll : false}))
- .catch((resp) => {
- if (resp.errors) {
- const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
- dispatch(setError(errorMessage));
- return Promise.reject(errorMessage);
- }
+ .catch(validationErrorsHandler(dispatch))
+ );
+}
- // TODO: log unexpected errors
- })
+export function forgotPassword({
+ login = ''
+}) {
+ return wrapInLoader((dispatch, getState) =>
+ request.post(
+ '/api/authentication/forgot-password',
+ {login}
+ )
+ .then(({data = {}}) => dispatch(updateUser({
+ maskedEmail: data.emailMask || getState().user.email
+ })))
+ .catch(validationErrorsHandler(dispatch))
+ );
+}
+
+export function recoverPassword({
+ key = '',
+ newPassword = '',
+ newRePassword = ''
+}) {
+ return wrapInLoader((dispatch) =>
+ request.post(
+ '/api/authentication/recover-password',
+ {key, newPassword, newRePassword}
+ )
+ .then((resp) => {
+ dispatch(updateUser({
+ isGuest: false,
+ isActive: true
+ }));
+
+ return dispatch(authenticate(resp.jwt));
+ })
+ .catch(validationErrorsHandler(dispatch))
);
}
@@ -82,18 +108,7 @@ export function register({
dispatch(needActivation());
dispatch(routeActions.push('/activation'));
})
- .catch((resp) => {
- if (resp.errors) {
- const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
- dispatch(setError(errorMessage));
- return Promise.reject(errorMessage);
- }
-
- // TODO: log unexpected errors
- // We can get here something like:
- // code: 500
- // {"name":"Invalid Configuration","message":"","code":0,"type":"yii\\base\\InvalidConfigException","file":"/home/sleepwalker/www/account/api/components/ReCaptcha/Component.php","line":12,"stack-trace":["#0 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Component->init()","#1 [internal function]: yii\\base\\Object->__construct(Array)","#2 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(368): ReflectionClass->newInstanceArgs(Array)","#3 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#4 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#5 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(133): yii\\BaseYii::createObject(Array)","#6 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(71): yii\\di\\ServiceLocator->get('reCaptcha')","#7 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(20): yii\\di\\ServiceLocator->__get('reCaptcha')","#8 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(25): api\\components\\ReCaptcha\\Validator->getComponent()","#9 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Validator->init()","#10 [internal function]: yii\\base\\Object->__construct(Array)","#11 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(374): ReflectionClass->newInstanceArgs(Array)","#12 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#13 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#14 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/validators/Validator.php(209): yii\\BaseYii::createObject(Array)","#15 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(445): yii\\validators\\Validator::createValidator('api\\\\components\\\\...', Object(api\\models\\RegistrationForm), Array, Array)","#16 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(409): yii\\base\\Model->createValidators()","#17 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(185): yii\\base\\Model->getValidators()","#18 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(751): yii\\base\\Model->scenarios()","#19 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(695): yii\\base\\Model->safeAttributes()","#20 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(823): yii\\base\\Model->setAttributes(Array)","#21 /home/sleepwalker/www/account/api/controllers/SignupController.php(41): yii\\base\\Model->load(Array)","#22 [internal function]: api\\controllers\\SignupController->actionIndex()","#23 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/InlineAction.php(55): call_user_func_array(Array, Array)","#24 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Controller.php(154): yii\\base\\InlineAction->runWithParams(Array)","#25 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Module.php(454): yii\\base\\Controller->runAction('', Array)","#26 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/web/Application.php(84): yii\\base\\Module->runAction('signup', Array)","#27 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Application.php(375): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))","#28 /home/sleepwalker/www/account/api/web/index.php(18): yii\\base\\Application->run()","#29 {main}"]}
- })
+ .catch(validationErrorsHandler(dispatch))
);
}
@@ -111,13 +126,7 @@ export function activate({key = ''}) {
return dispatch(authenticate(resp.jwt));
})
- .catch((resp) => {
- const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
- dispatch(setError(errorMessage));
- return Promise.reject(errorMessage);
-
- // TODO: log unexpected errors
- })
+ .catch(validationErrorsHandler(dispatch))
);
}
@@ -322,3 +331,21 @@ function needActivation() {
isGuest: false
});
}
+
+function validationErrorsHandler(dispatch) {
+ return (resp) => {
+ if (resp.errors) {
+ const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
+ dispatch(setError(errorMessage));
+ return Promise.reject(errorMessage);
+ }
+
+ return Promise.reject(resp);
+
+ // TODO: log unexpected errors
+ // We can get here something like:
+ // code: 500
+ // {"name":"Invalid Configuration","message":"","code":0,"type":"yii\\base\\InvalidConfigException","file":"/home/sleepwalker/www/account/api/components/ReCaptcha/Component.php","line":12,"stack-trace":["#0 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Component->init()","#1 [internal function]: yii\\base\\Object->__construct(Array)","#2 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(368): ReflectionClass->newInstanceArgs(Array)","#3 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#4 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#5 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(133): yii\\BaseYii::createObject(Array)","#6 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(71): yii\\di\\ServiceLocator->get('reCaptcha')","#7 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(20): yii\\di\\ServiceLocator->__get('reCaptcha')","#8 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(25): api\\components\\ReCaptcha\\Validator->getComponent()","#9 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Validator->init()","#10 [internal function]: yii\\base\\Object->__construct(Array)","#11 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(374): ReflectionClass->newInstanceArgs(Array)","#12 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#13 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#14 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/validators/Validator.php(209): yii\\BaseYii::createObject(Array)","#15 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(445): yii\\validators\\Validator::createValidator('api\\\\components\\\\...', Object(api\\models\\RegistrationForm), Array, Array)","#16 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(409): yii\\base\\Model->createValidators()","#17 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(185): yii\\base\\Model->getValidators()","#18 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(751): yii\\base\\Model->scenarios()","#19 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(695): yii\\base\\Model->safeAttributes()","#20 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(823): yii\\base\\Model->setAttributes(Array)","#21 /home/sleepwalker/www/account/api/controllers/SignupController.php(41): yii\\base\\Model->load(Array)","#22 [internal function]: api\\controllers\\SignupController->actionIndex()","#23 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/InlineAction.php(55): call_user_func_array(Array, Array)","#24 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Controller.php(154): yii\\base\\InlineAction->runWithParams(Array)","#25 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Module.php(454): yii\\base\\Controller->runAction('', Array)","#26 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/web/Application.php(84): yii\\base\\Module->runAction('signup', Array)","#27 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Application.php(375): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))","#28 /home/sleepwalker/www/account/api/web/index.php(18): yii\\base\\Application->run()","#29 {main}"]}
+ // We need here status code. Probably `request` module should add _request field en each resp
+ };
+}
diff --git a/src/components/auth/activation/ActivationBody.jsx b/src/components/auth/activation/ActivationBody.jsx
index cb54c4f..e119987 100644
--- a/src/components/auth/activation/ActivationBody.jsx
+++ b/src/components/auth/activation/ActivationBody.jsx
@@ -31,7 +31,7 @@ export default class ActivationBody extends BaseAuthBody {
diff --git a/src/components/auth/activation/activation.scss b/src/components/auth/activation/activation.scss
index 655c25f..0a4ce86 100644
--- a/src/components/auth/activation/activation.scss
+++ b/src/components/auth/activation/activation.scss
@@ -17,10 +17,3 @@
line-height: 1.4;
font-size: 16px;
}
-
-.activationCodeInput {
- composes: darkTextField from 'components/ui/form/form.scss';
- composes: blueTextField from 'components/ui/form/form.scss';
-
- text-align: center;
-}
diff --git a/src/components/auth/forgotPassword/ForgotPassword.intl.json b/src/components/auth/forgotPassword/ForgotPassword.intl.json
index d41a486..31b8232 100644
--- a/src/components/auth/forgotPassword/ForgotPassword.intl.json
+++ b/src/components/auth/forgotPassword/ForgotPassword.intl.json
@@ -1,7 +1,7 @@
{
- "forgotPasswordTitle": "Forgot password",
- "contactSupport": "Contact support",
+ "title": "Forgot password",
"sendMail": "Send mail",
- "forgotPasswordMessage": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
- "accountEmail": "Enter account E-mail"
+ "specifyEmail": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
+ "pleasePressButton": "Please press the button bellow to get an email with password recovery code.",
+ "alreadyHaveCode": "Already have a code"
}
diff --git a/src/components/auth/forgotPassword/ForgotPassword.jsx b/src/components/auth/forgotPassword/ForgotPassword.jsx
index 0d3c215..61e69f4 100644
--- a/src/components/auth/forgotPassword/ForgotPassword.jsx
+++ b/src/components/auth/forgotPassword/ForgotPassword.jsx
@@ -9,9 +9,9 @@ import Body from './ForgotPasswordBody';
export default function ForgotPassword() {
return {
- Title: () =>
,
+ Title: () =>
,
Body,
- Footer: () =>
,
- Links: () =>
+ Footer: () =>
,
+ Links: () =>
};
}
diff --git a/src/components/auth/forgotPassword/ForgotPasswordBody.jsx b/src/components/auth/forgotPassword/ForgotPasswordBody.jsx
index 6de8d10..ef621ed 100644
--- a/src/components/auth/forgotPassword/ForgotPasswordBody.jsx
+++ b/src/components/auth/forgotPassword/ForgotPasswordBody.jsx
@@ -13,27 +13,29 @@ export default class ForgotPasswordBody extends BaseAuthBody {
static panelId = 'forgotPassword';
static hasGoBack = true;
- autoFocusField = 'email';
-
- // Если юзер вводил своё мыло во время попытки авторизации, то почему бы его сюда автоматически не подставить?
render() {
const {user} = this.context;
+ const hasIdentity = user.email || user.username;
+ const message = hasIdentity ? messages.pleasePressButton : messages.specifyEmail;
return (
);
}
diff --git a/src/components/auth/password/PasswordBody.jsx b/src/components/auth/password/PasswordBody.jsx
index c3c3b57..ea9d80a 100644
--- a/src/components/auth/password/PasswordBody.jsx
+++ b/src/components/auth/password/PasswordBody.jsx
@@ -1,7 +1,5 @@
import React from 'react';
-import { FormattedMessage as Message } from 'react-intl';
-
import icons from 'components/ui/icons.scss';
import { Input, Checkbox } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
diff --git a/src/components/auth/recoverPassword/RecoverPassword.intl.json b/src/components/auth/recoverPassword/RecoverPassword.intl.json
new file mode 100644
index 0000000..4743dc1
--- /dev/null
+++ b/src/components/auth/recoverPassword/RecoverPassword.intl.json
@@ -0,0 +1,8 @@
+{
+ "title": "Restore password",
+ "contactSupport": "Contact support",
+ "messageWasSentTo": "The recovery code was sent to your email {email}.",
+ "enterCodeBelow": "Please enter the code received into the field below:",
+ "enterNewPasswordBelow": "Enter and repeat new password below:",
+ "enterTheCode": "Enter confirmation code"
+}
diff --git a/src/components/auth/recoverPassword/RecoverPassword.jsx b/src/components/auth/recoverPassword/RecoverPassword.jsx
new file mode 100644
index 0000000..9e57588
--- /dev/null
+++ b/src/components/auth/recoverPassword/RecoverPassword.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+import { Button } from 'components/ui/form';
+import RejectionLink from 'components/auth/RejectionLink';
+import AuthTitle from 'components/auth/AuthTitle';
+import changePassword from 'components/auth/changePassword/ChangePassword.intl.json';
+
+import messages from './RecoverPassword.intl.json';
+import Body from './RecoverPasswordBody';
+
+export default function RecoverPassword() {
+ return {
+ Title: () =>
,
+ Body,
+ Footer: () =>
,
+ Links: () =>
+ };
+}
diff --git a/src/components/auth/recoverPassword/RecoverPasswordBody.jsx b/src/components/auth/recoverPassword/RecoverPasswordBody.jsx
new file mode 100644
index 0000000..96a45d7
--- /dev/null
+++ b/src/components/auth/recoverPassword/RecoverPasswordBody.jsx
@@ -0,0 +1,78 @@
+import React, { PropTypes } from 'react';
+
+import { FormattedMessage as Message } from 'react-intl';
+
+import { Input } from 'components/ui/form';
+import BaseAuthBody from 'components/auth/BaseAuthBody';
+import changePassword from 'components/auth/changePassword/ChangePassword.intl.json';
+
+import styles from './recoverPassword.scss';
+import messages from './RecoverPassword.intl.json';
+
+// TODO: activation code field may be decoupled into common component and reused here and in activation panel
+// TODO: new password fields may be decoupled into common component and reused here and in changePassword panel
+
+export default class RecoverPasswordBody extends BaseAuthBody {
+ static displayName = 'RecoverPasswordBody';
+ static panelId = 'recoverPassword';
+ static hasGoBack = true;
+
+ static propTypes = {
+ params: PropTypes.shape({
+ key: PropTypes.string
+ })
+ };
+
+ autoFocusField = this.props.params && this.props.params.key ? 'newPassword' : 'key';
+
+ // Если юзер вводил своё мыло во время попытки авторизации, то почему бы его сюда автоматически не подставить?
+ render() {
+ const {user} = this.context;
+ const {key} = this.props.params;
+
+ return (
+
+ );
+ }
+}
diff --git a/src/components/auth/recoverPassword/recoverPassword.scss b/src/components/auth/recoverPassword/recoverPassword.scss
new file mode 100644
index 0000000..fcf5777
--- /dev/null
+++ b/src/components/auth/recoverPassword/recoverPassword.scss
@@ -0,0 +1,8 @@
+@import '~components/ui/colors.scss';
+
+.descriptionText {
+ font-size: 15px;
+ line-height: 1.4;
+ padding-bottom: 8px;
+ color: #aaa;
+}
diff --git a/src/components/ui/form/FormModel.js b/src/components/ui/form/FormModel.js
index f2b46f4..18810e7 100644
--- a/src/components/ui/form/FormModel.js
+++ b/src/components/ui/form/FormModel.js
@@ -37,7 +37,7 @@ export default class FormModel {
focus(fieldId) {
if (!this.fields[fieldId]) {
- throw new Error(`The field with an id ${fieldId} does not exists`);
+ throw new Error(`Can not focus. The field with an id ${fieldId} does not exists`);
}
this.fields[fieldId].focus();
@@ -47,7 +47,7 @@ export default class FormModel {
const field = this.fields[fieldId];
if (!field) {
- throw new Error(`The field with an id ${fieldId} does not exists`);
+ throw new Error(`Can not get value. The field with an id ${fieldId} does not exists`);
}
if (!field.getValue) {
diff --git a/src/components/user/User.js b/src/components/user/User.js
index 4d32790..8dc4a08 100644
--- a/src/components/user/User.js
+++ b/src/components/user/User.js
@@ -21,6 +21,9 @@ export default class User {
token: '',
username: '',
email: '',
+ // will contain user's email or masked email
+ // (e.g. ex**ple@em*il.c**) depending on what information user have already provided
+ maskedEmail: '',
avatar: '',
lang: '',
goal: null, // the goal with wich user entered site
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ad101a8..f8e65fa 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -26,11 +26,11 @@
"components.auth.finish.copy": "Copy",
"components.auth.finish.passCodeToApp": "To complete authorization process, please, provide the following code to {appName}",
"components.auth.finish.waitAppReaction": "Please, wait till your application response",
- "components.auth.forgotPassword.accountEmail": "Enter account E-mail",
- "components.auth.forgotPassword.contactSupport": "Contact support",
- "components.auth.forgotPassword.forgotPasswordMessage": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
- "components.auth.forgotPassword.forgotPasswordTitle": "Forgot password",
+ "components.auth.forgotPassword.alreadyHaveCode": "Already have a code",
+ "components.auth.forgotPassword.pleasePressButton": "Please press the button bellow to get an email with password recovery code.",
"components.auth.forgotPassword.sendMail": "Send mail",
+ "components.auth.forgotPassword.specifyEmail": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
+ "components.auth.forgotPassword.title": "Forgot password",
"components.auth.login.emailOrUsername": "E-mail or username",
"components.auth.login.loginTitle": "Sign in",
"components.auth.login.next": "Next",
@@ -50,6 +50,12 @@
"components.auth.permissions.theAppNeedsAccess1": "This application needs access",
"components.auth.permissions.theAppNeedsAccess2": "to your data",
"components.auth.permissions.youAuthorizedAs": "You authorized as:",
+ "components.auth.recoverPassword.contactSupport": "Contact support",
+ "components.auth.recoverPassword.enterCodeBelow": "Please enter the code received into the field below:",
+ "components.auth.recoverPassword.enterNewPasswordBelow": "Enter and repeat new password below:",
+ "components.auth.recoverPassword.enterTheCode": "Enter confirmation code",
+ "components.auth.recoverPassword.messageWasSentTo": "The recovery code was sent to your email {email}.",
+ "components.auth.recoverPassword.title": "Restore password",
"components.auth.register.acceptRules": "I agree with {link}",
"components.auth.register.accountPassword": "Account password",
"components.auth.register.registerTitle": "Sign Up",
@@ -78,38 +84,40 @@
"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:",
- "emailInvalid": "Email is invalid",
- "emailIsTempmail": "Tempmail E-mail addresses is not allowed",
- "emailNotAvailable": "This email is already registered.",
- "emailRequired": "Email is required",
- "emailToLong": "Email is too long",
"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.",
- "forgotYourPassword": "forgot your password",
- "invalidPassword": "You have entered wrong account password.",
- "keyNotExists": "The key is incorrect",
- "keyRequired": "Please, enter an activation key",
- "loginNotExist": "Sorry, Ely doesn't recognise your login.",
- "loginRequired": "Please enter email or username",
"logout": "Logout",
"newEmailPlaceholder": "Enter new E-mail",
- "newPasswordRequired": "Please enter new password",
- "newRePasswordRequired": "Please repeat new password",
- "passwordRequired": "Please enter password",
- "passwordTooShort": "Your password is too short",
- "passwordsDoesNotMatch": "The passwords does not match",
"pleaseEnterPassword": "Please, enter your current password",
"pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.",
- "rePasswordRequired": "Please retype your password",
"register": "Join",
- "rulesAgreementRequired": "You must accept rules in order to create an account",
"sendEmailButton": "Send E-mail",
- "suggestResetPassword": "Are you have {link}?",
- "title": "Confirm your action",
- "usernameInvalid": "Username is invalid",
- "usernameRequired": "Username is required",
- "usernameTooLong": "Username is too long",
- "usernameTooShort": "Username is too short",
- "usernameUnavailable": "This username is already taken"
+ "services.accountNotActivated": "The account is not activated",
+ "services.emailFrequency": "Please cool down, you are requesting emails too often",
+ "services.emailInvalid": "Email is invalid",
+ "services.emailIsTempmail": "Tempmail E-mail addresses is not allowed",
+ "services.emailNotAvailable": "This email is already registered.",
+ "services.emailRequired": "Email is required",
+ "services.emailToLong": "Email is too long",
+ "services.forgotYourPassword": "forgot your password",
+ "services.invalidPassword": "You have entered wrong account password.",
+ "services.keyNotExists": "The key is incorrect",
+ "services.keyRequired": "Please, enter an activation key",
+ "services.loginNotExist": "Sorry, Ely doesn't recognise your login.",
+ "services.loginRequired": "Please enter email or username",
+ "services.newPasswordRequired": "Please enter new password",
+ "services.newRePasswordRequired": "Please repeat new password",
+ "services.passwordRequired": "Please enter password",
+ "services.passwordTooShort": "Your password is too short",
+ "services.passwordsDoesNotMatch": "The passwords does not match",
+ "services.rePasswordRequired": "Please retype your password",
+ "services.rulesAgreementRequired": "You must accept rules in order to create an account",
+ "services.suggestResetPassword": "Are you have {link}?",
+ "services.usernameInvalid": "Username is invalid",
+ "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"
}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 17d7806..a515644 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -26,11 +26,11 @@
"components.auth.finish.copy": "Скопировать",
"components.auth.finish.passCodeToApp": "Чтобы завершить процесс авторизации, пожалуйста, передай {appName} этот код",
"components.auth.finish.waitAppReaction": "Пожалуйста, дождитесь реакции вашего приложения",
- "components.auth.forgotPassword.accountEmail": "Enter account E-mail",
- "components.auth.forgotPassword.contactSupport": "Contact support",
- "components.auth.forgotPassword.forgotPasswordMessage": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
- "components.auth.forgotPassword.forgotPasswordTitle": "Forgot password",
+ "components.auth.forgotPassword.alreadyHaveCode": "Already have a code",
+ "components.auth.forgotPassword.pleasePressButton": "Please press the button bellow to get an email with password recovery code.",
"components.auth.forgotPassword.sendMail": "Send mail",
+ "components.auth.forgotPassword.specifyEmail": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
+ "components.auth.forgotPassword.title": "Forgot password",
"components.auth.login.emailOrUsername": "E-mail or username",
"components.auth.login.loginTitle": "Sign in",
"components.auth.login.next": "Next",
@@ -50,6 +50,12 @@
"components.auth.permissions.theAppNeedsAccess1": "This application needs access",
"components.auth.permissions.theAppNeedsAccess2": "to your data",
"components.auth.permissions.youAuthorizedAs": "You authorized as:",
+ "components.auth.recoverPassword.contactSupport": "Contact support",
+ "components.auth.recoverPassword.enterCodeBelow": "Please enter the code received into the field below:",
+ "components.auth.recoverPassword.enterNewPasswordBelow": "Enter and repeat new password below:",
+ "components.auth.recoverPassword.enterTheCode": "Enter confirmation code",
+ "components.auth.recoverPassword.messageWasSentTo": "The recovery code was sent to your email {email}.",
+ "components.auth.recoverPassword.title": "Restore password",
"components.auth.register.acceptRules": "I agree with {link}",
"components.auth.register.accountPassword": "Account password",
"components.auth.register.registerTitle": "Sign Up",
@@ -78,38 +84,40 @@
"components.profile.preferencesDescription": "Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.",
"components.profile.twoFactorAuth": "Двухфакторная аутентификация",
"currentAccountEmail": "Current account E-mail address:",
- "emailInvalid": "Email is invalid",
- "emailIsTempmail": "Tempmail E-mail addresses is not allowed",
- "emailNotAvailable": "This email is already registered.",
- "emailRequired": "Email is required",
- "emailToLong": "Email is too long",
"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.",
- "forgotYourPassword": "forgot your password",
- "invalidPassword": "You have entered wrong account password.",
- "keyNotExists": "The key is incorrect",
- "keyRequired": "Please, enter an activation key",
- "loginNotExist": "Sorry, Ely doesn't recognise your login.",
- "loginRequired": "Please enter email or username",
"logout": "Logout",
"newEmailPlaceholder": "Enter new E-mail",
- "newPasswordRequired": "Please enter new password",
- "newRePasswordRequired": "Please repeat new password",
- "passwordRequired": "Please enter password",
- "passwordTooShort": "Your password is too short",
- "passwordsDoesNotMatch": "The passwords does not match",
"pleaseEnterPassword": "Please, enter your current password",
"pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.",
- "rePasswordRequired": "Please retype your password",
"register": "Join",
- "rulesAgreementRequired": "You must accept rules in order to create an account",
"sendEmailButton": "Send E-mail",
- "suggestResetPassword": "Are you have {link}?",
- "title": "Confirm your action",
- "usernameInvalid": "Username is invalid",
- "usernameRequired": "Username is required",
- "usernameTooLong": "Username is too long",
- "usernameTooShort": "Username is too short",
- "usernameUnavailable": "This username is already taken"
+ "services.accountNotActivated": "The account is not activated",
+ "services.emailFrequency": "Please cool down, you are requesting emails too often",
+ "services.emailInvalid": "Email is invalid",
+ "services.emailIsTempmail": "Tempmail E-mail addresses is not allowed",
+ "services.emailNotAvailable": "This email is already registered.",
+ "services.emailRequired": "Email is required",
+ "services.emailToLong": "Email is too long",
+ "services.forgotYourPassword": "forgot your password",
+ "services.invalidPassword": "You have entered wrong account password.",
+ "services.keyNotExists": "The key is incorrect",
+ "services.keyRequired": "Please, enter an activation key",
+ "services.loginNotExist": "Sorry, Ely doesn't recognise your login.",
+ "services.loginRequired": "Please enter email or username",
+ "services.newPasswordRequired": "Please enter new password",
+ "services.newRePasswordRequired": "Please repeat new password",
+ "services.passwordRequired": "Please enter password",
+ "services.passwordTooShort": "Your password is too short",
+ "services.passwordsDoesNotMatch": "The passwords does not match",
+ "services.rePasswordRequired": "Please retype your password",
+ "services.rulesAgreementRequired": "You must accept rules in order to create an account",
+ "services.suggestResetPassword": "Are you have {link}?",
+ "services.usernameInvalid": "Username is invalid",
+ "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"
}
diff --git a/src/routes.js b/src/routes.js
index cf3b33a..7ea9791 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -18,6 +18,7 @@ import Activation from 'components/auth/activation/Activation';
import Password from 'components/auth/password/Password';
import ChangePassword from 'components/auth/changePassword/ChangePassword';
import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
+import RecoverPassword from 'components/auth/recoverPassword/RecoverPassword';
import Finish from 'components/auth/finish/Finish';
import authFlow from 'services/authFlow';
@@ -54,6 +55,7 @@ export default function routesFactory(store) {
+
diff --git a/src/services/authFlow/AuthFlow.js b/src/services/authFlow/AuthFlow.js
index 6b6f72b..239d2fd 100644
--- a/src/services/authFlow/AuthFlow.js
+++ b/src/services/authFlow/AuthFlow.js
@@ -4,6 +4,7 @@ import RegisterState from './RegisterState';
import LoginState from './LoginState';
import OAuthState from './OAuthState';
import ForgotPasswordState from './ForgotPasswordState';
+import RecoverPasswordState from './RecoverPasswordState';
// TODO: a way to unload service (when we are on account page)
@@ -81,7 +82,7 @@ export default class AuthFlow {
this.run('setOAuthRequest', {});
}
- switch (path) {
+ switch (path.replace(/(.)\/.+/, '$1')) { // use only first part of an url
case '/oauth':
this.setState(new OAuthState());
break;
@@ -90,6 +91,10 @@ export default class AuthFlow {
this.setState(new RegisterState());
break;
+ case '/recover-password':
+ this.setState(new RecoverPasswordState());
+ break;
+
case '/forgot-password':
this.setState(new ForgotPasswordState());
break;
diff --git a/src/services/authFlow/ForgotPasswordState.js b/src/services/authFlow/ForgotPasswordState.js
index 89d6131..5155ddc 100644
--- a/src/services/authFlow/ForgotPasswordState.js
+++ b/src/services/authFlow/ForgotPasswordState.js
@@ -1,9 +1,26 @@
import AbstractState from './AbstractState';
import LoginState from './LoginState';
+import CompleteState from './CompleteState';
+import RecoverPasswordState from './RecoverPasswordState';
export default class ForgotPasswordState extends AbstractState {
enter(context) {
- context.navigate('/forgot-password');
+ const {user} = context.getState();
+
+ if (user.isGuest) {
+ if (this.getLogin(context)) {
+ context.navigate('/forgot-password');
+ } else {
+ context.setState(new LoginState());
+ }
+ } else {
+ context.setState(new CompleteState());
+ }
+ }
+
+ resolve(context) {
+ context.run('forgotPassword', {login: this.getLogin(context)})
+ .then(() => context.setState(new RecoverPasswordState()));
}
goBack(context) {
@@ -11,6 +28,12 @@ export default class ForgotPasswordState extends AbstractState {
}
reject(context) {
- context.navigate('/send-message');
+ context.setState(new RecoverPasswordState());
+ }
+
+ getLogin(context) {
+ const {user} = context.getState();
+
+ return user.email || user.username;
}
}
diff --git a/src/services/authFlow/PasswordState.js b/src/services/authFlow/PasswordState.js
index 8c3635a..2bdd112 100644
--- a/src/services/authFlow/PasswordState.js
+++ b/src/services/authFlow/PasswordState.js
@@ -7,10 +7,10 @@ export default class PasswordState extends AbstractState {
enter(context) {
const {user} = context.getState();
- if (!user.isGuest) {
- context.setState(new CompleteState());
- } else {
+ if (user.isGuest) {
context.navigate('/password');
+ } else {
+ context.setState(new CompleteState());
}
}
diff --git a/src/services/authFlow/RecoverPasswordState.js b/src/services/authFlow/RecoverPasswordState.js
new file mode 100644
index 0000000..d15a6b7
--- /dev/null
+++ b/src/services/authFlow/RecoverPasswordState.js
@@ -0,0 +1,31 @@
+import AbstractState from './AbstractState';
+import LoginState from './LoginState';
+import CompleteState from './CompleteState';
+
+export default class RecoverPasswordState extends AbstractState {
+ enter(context) {
+ const {user, routing} = context.getState();
+
+ if (user.isGuest) {
+ const url = routing.location.pathname.indexOf('/recover-password') === 0
+ ? routing.location.pathname
+ : '/recover-password';
+ context.navigate(url);
+ } else {
+ context.setState(new CompleteState());
+ }
+ }
+
+ resolve(context, payload) {
+ context.run('recoverPassword', payload)
+ .then(() => context.setState(new CompleteState()));
+ }
+
+ goBack(context) {
+ context.setState(new LoginState());
+ }
+
+ reject(context) {
+ context.navigate('/send-message');
+ }
+}
diff --git a/src/services/errorsDict.intl.json b/src/services/errorsDict.intl.json
new file mode 100644
index 0000000..c101548
--- /dev/null
+++ b/src/services/errorsDict.intl.json
@@ -0,0 +1,28 @@
+{
+ "invalidPassword": "You have entered wrong account password.",
+ "suggestResetPassword": "Are you have {link}?",
+ "forgotYourPassword": "forgot your password",
+ "loginRequired": "Please enter email or username",
+ "loginNotExist": "Sorry, Ely doesn't recognise your login.",
+ "passwordRequired": "Please enter password",
+ "newPasswordRequired": "Please enter new password",
+ "newRePasswordRequired": "Please repeat new password",
+ "usernameRequired": "Username is required",
+ "usernameInvalid": "Username is invalid",
+ "usernameTooShort": "Username is too short",
+ "usernameTooLong": "Username is too long",
+ "usernameUnavailable": "This username is already taken",
+ "emailRequired": "Email is required",
+ "emailInvalid": "Email is invalid",
+ "emailToLong": "Email is too long",
+ "emailIsTempmail": "Tempmail E-mail addresses is not allowed",
+ "emailNotAvailable": "This email is already registered.",
+ "rePasswordRequired": "Please retype your password",
+ "passwordTooShort": "Your password is too short",
+ "passwordsDoesNotMatch": "The passwords does not match",
+ "rulesAgreementRequired": "You must accept rules in order to create an account",
+ "keyRequired": "Please, enter an activation key",
+ "keyNotExists": "The key is incorrect",
+ "emailFrequency": "Please cool down, you are requesting emails too often",
+ "accountNotActivated": "The account is not activated"
+}
diff --git a/src/services/errorsDict.js b/src/services/errorsDict.js
index 620a9cd..cbb8499 100644
--- a/src/services/errorsDict.js
+++ b/src/services/errorsDict.js
@@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage as Message } from 'react-intl';
-import messages from './errorsDict.messages';
+import messages from './errorsDict.intl.json';
export default {
resolve(error) {
@@ -65,5 +65,9 @@ const errorsMap = {
'error.newPassword_required': () => ,
'error.newRePassword_required': () => ,
- 'error.newRePassword_does_not_match': () =>
+ 'error.newRePassword_does_not_match': () => ,
+
+ 'error.account_not_activated': () => ,
+
+ 'error.email_frequency': () =>
};
diff --git a/src/services/errorsDict.messages.js b/src/services/errorsDict.messages.js
deleted file mode 100644
index c4afaeb..0000000
--- a/src/services/errorsDict.messages.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { defineMessages } from 'react-intl';
-
-export default defineMessages({
- invalidPassword: {
- id: 'invalidPassword',
- defaultMessage: 'You have entered wrong account password.'
- },
-
- suggestResetPassword: {
- id: 'suggestResetPassword',
- defaultMessage: 'Are you have {link}?'
- },
-
- forgotYourPassword: {
- id: 'forgotYourPassword',
- defaultMessage: 'forgot your password'
- },
-
- loginRequired: {
- id: 'loginRequired',
- defaultMessage: 'Please enter email or username'
- },
-
- loginNotExist: {
- id: 'loginNotExist',
- defaultMessage: 'Sorry, Ely doesn\'t recognise your login.'
- },
-
- passwordRequired: {
- id: 'passwordRequired',
- defaultMessage: 'Please enter password'
- },
-
- newPasswordRequired: {
- id: 'newPasswordRequired',
- defaultMessage: 'Please enter new password'
- },
-
- newRePasswordRequired: {
- id: 'newRePasswordRequired',
- defaultMessage: 'Please repeat new password'
- },
-
- usernameRequired: {
- id: 'usernameRequired',
- defaultMessage: 'Username is required'
- },
-
- usernameInvalid: {
- id: 'usernameInvalid',
- defaultMessage: 'Username is invalid'
- },
-
- usernameTooShort: {
- id: 'usernameTooShort',
- defaultMessage: 'Username is too short'
- },
-
- usernameTooLong: {
- id: 'usernameTooLong',
- defaultMessage: 'Username is too long'
- },
-
- usernameUnavailable: {
- id: 'usernameUnavailable',
- defaultMessage: 'This username is already taken'
- },
-
- emailRequired: {
- id: 'emailRequired',
- defaultMessage: 'Email is required'
- },
-
- emailInvalid: {
- id: 'emailInvalid',
- defaultMessage: 'Email is invalid'
- },
-
- emailToLong: {
- id: 'emailToLong',
- defaultMessage: 'Email is too long'
- },
-
- emailIsTempmail: {
- id: 'emailIsTempmail',
- defaultMessage: 'Tempmail E-mail addresses is not allowed'
- },
-
- emailNotAvailable: {
- id: 'emailNotAvailable',
- defaultMessage: 'This email is already registered.'
- },
-
- rePasswordRequired: {
- id: 'rePasswordRequired',
- defaultMessage: 'Please retype your password'
- },
-
- passwordTooShort: {
- id: 'passwordTooShort',
- defaultMessage: 'Your password is too short'
- },
-
- passwordsDoesNotMatch: {
- id: 'passwordsDoesNotMatch',
- defaultMessage: 'The passwords does not match'
- },
-
- rulesAgreementRequired: {
- id: 'rulesAgreementRequired',
- defaultMessage: 'You must accept rules in order to create an account'
- },
-
- keyRequired: {
- id: 'keyRequired',
- defaultMessage: 'Please, enter an activation key'
- },
-
- keyNotExists: {
- id: 'keyNotExists',
- defaultMessage: 'The key is incorrect'
- }
-});
diff --git a/tests/services/authFlow/ForgotPasswordState.test.js b/tests/services/authFlow/ForgotPasswordState.test.js
new file mode 100644
index 0000000..bde9651
--- /dev/null
+++ b/tests/services/authFlow/ForgotPasswordState.test.js
@@ -0,0 +1,139 @@
+import ForgotPasswordState from 'services/authFlow/ForgotPasswordState';
+import RecoverPasswordState from 'services/authFlow/RecoverPasswordState';
+import CompleteState from 'services/authFlow/CompleteState';
+import LoginState from 'services/authFlow/LoginState';
+
+import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
+
+describe('ForgotPasswordState', () => {
+ let state;
+ let context;
+ let mock;
+
+ beforeEach(() => {
+ state = new ForgotPasswordState();
+
+ const data = bootstrap();
+ context = data.context;
+ mock = data.mock;
+ });
+
+ afterEach(() => {
+ mock.verify();
+ });
+
+ describe('#enter', () => {
+ it('should navigate to /forgot-password if email set', () => {
+ context.getState.returns({
+ user: {isGuest: true, email: 'foo@bar.com'}
+ });
+
+ expectNavigate(mock, '/forgot-password');
+
+ state.enter(context);
+ });
+
+ it('should navigate to /forgot-password if username set', () => {
+ context.getState.returns({
+ user: {isGuest: true, username: 'foobar'}
+ });
+
+ expectNavigate(mock, '/forgot-password');
+
+ state.enter(context);
+ });
+
+ it('should transition to complete if not guest', () => {
+ context.getState.returns({
+ user: {isGuest: false}
+ });
+
+ expectState(mock, CompleteState);
+
+ state.enter(context);
+ });
+
+ it('should transition to login if no identity', () => {
+ context.getState.returns({
+ user: {isGuest: true}
+ });
+
+ expectState(mock, LoginState);
+
+ state.enter(context);
+ });
+ });
+
+ describe('#resolve', () => {
+ it('should call forgotPassword with email', () => {
+ const expectedLogin = 'foo@bar.com';
+ context.getState.returns({
+ user: {
+ email: expectedLogin
+ }
+ });
+
+ expectRun(
+ mock,
+ 'forgotPassword',
+ sinon.match({
+ login: expectedLogin
+ })
+ ).returns({then() {}});
+
+ state.resolve(context, {});
+ });
+
+ it('should call forgotPassword with username', () => {
+ const expectedLogin = 'foobar';
+ context.getState.returns({
+ user: {
+ username: expectedLogin
+ }
+ });
+
+ expectRun(
+ mock,
+ 'forgotPassword',
+ sinon.match({
+ login: expectedLogin
+ })
+ ).returns({then() {}});
+
+ state.resolve(context, {});
+ });
+
+ it('should transition to recoverPassword state on success', () => {
+ const promise = Promise.resolve();
+ const expectedLogin = 'foo@bar.com';
+ context.getState.returns({
+ user: {
+ email: expectedLogin
+ }
+ });
+
+ mock.expects('run').returns(promise);
+ expectState(mock, RecoverPasswordState);
+
+ state.resolve(context, {});
+
+ return promise;
+ });
+ });
+
+ describe('#reject', () => {
+ it('should navigate to /send-message', () => {
+ expectState(mock, RecoverPasswordState);
+
+ state.reject(context);
+ });
+ });
+
+ describe('#goBack', () => {
+ it('should transition to login state', () => {
+ expectState(mock, LoginState);
+
+ state.goBack(context);
+ });
+ });
+});
diff --git a/tests/services/authFlow/PasswordState.test.js b/tests/services/authFlow/PasswordState.test.js
index e55d52d..2606012 100644
--- a/tests/services/authFlow/PasswordState.test.js
+++ b/tests/services/authFlow/PasswordState.test.js
@@ -45,7 +45,7 @@ describe('PasswordState', () => {
});
describe('#resolve', () => {
- (() => {
+ (function() {
const expectedLogin = 'login';
const expectedPassword = 'password';
@@ -78,7 +78,7 @@ describe('PasswordState', () => {
email: expectedLogin,
username: expectedLogin
});
- });
+ }());
it('should transition to complete state on successfull login', () => {
const promise = Promise.resolve();
diff --git a/tests/services/authFlow/RecoverPasswordState.test.js b/tests/services/authFlow/RecoverPasswordState.test.js
new file mode 100644
index 0000000..da6da92
--- /dev/null
+++ b/tests/services/authFlow/RecoverPasswordState.test.js
@@ -0,0 +1,104 @@
+import RecoverPasswordState from 'services/authFlow/RecoverPasswordState';
+import CompleteState from 'services/authFlow/CompleteState';
+import LoginState from 'services/authFlow/LoginState';
+
+import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
+
+describe('RecoverPasswordState', () => {
+ let state;
+ let context;
+ let mock;
+
+ beforeEach(() => {
+ state = new RecoverPasswordState();
+
+ const data = bootstrap();
+ context = data.context;
+ mock = data.mock;
+ });
+
+ afterEach(() => {
+ mock.verify();
+ });
+
+ describe('#enter', () => {
+ it('should navigate to /recover-password', () => {
+ const expectedPath = '/recover-password';
+ context.getState.returns({
+ user: {isGuest: true},
+ routing: {
+ location: {pathname: expectedPath}
+ }
+ });
+
+ expectNavigate(mock, expectedPath);
+
+ state.enter(context);
+ });
+
+ it('should navigate to /recover-password/key', () => {
+ const expectedPath = '/recover-password/sasx5AS4d61';
+ context.getState.returns({
+ user: {isGuest: true},
+ routing: {
+ location: {pathname: expectedPath}
+ }
+ });
+
+ expectNavigate(mock, expectedPath);
+
+ state.enter(context);
+ });
+
+ it('should transition to complete if not guest', () => {
+ context.getState.returns({
+ user: {isGuest: false}
+ });
+
+ expectState(mock, CompleteState);
+
+ state.enter(context);
+ });
+ });
+
+ describe('#resolve', () => {
+ it('should call recoverPassword with provided payload', () => {
+ const expectedPayload = {key: 123, newPassword: '123', newRePassword: '123'};
+
+ expectRun(
+ mock,
+ 'recoverPassword',
+ sinon.match(expectedPayload)
+ ).returns({then() {}});
+
+ state.resolve(context, expectedPayload);
+ });
+
+ it('should transition to complete state on success', () => {
+ const promise = Promise.resolve();
+
+ mock.expects('run').returns(promise);
+ expectState(mock, CompleteState);
+
+ state.resolve(context, {});
+
+ return promise;
+ });
+ });
+
+ describe('#reject', () => {
+ it('should navigate to /send-message', () => {
+ expectNavigate(mock, '/send-message');
+
+ state.reject(context);
+ });
+ });
+
+ describe('#goBack', () => {
+ it('should transition to login state', () => {
+ expectState(mock, LoginState);
+
+ state.goBack(context);
+ });
+ });
+});