From 3472c7b29f6c2b2088abaee827f49e73b61b6307 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sat, 14 May 2016 23:53:58 +0300 Subject: [PATCH] #26: forgot/restore password frontend logic --- package.json | 2 +- src/components/auth/PanelTransition.jsx | 6 +- src/components/auth/actions.js | 89 +++++++---- .../auth/activation/ActivationBody.jsx | 2 +- .../auth/activation/activation.scss | 7 - .../forgotPassword/ForgotPassword.intl.json | 8 +- .../auth/forgotPassword/ForgotPassword.jsx | 6 +- .../forgotPassword/ForgotPasswordBody.jsx | 24 +-- src/components/auth/password/PasswordBody.jsx | 2 - .../recoverPassword/RecoverPassword.intl.json | 8 + .../auth/recoverPassword/RecoverPassword.jsx | 18 +++ .../recoverPassword/RecoverPasswordBody.jsx | 78 ++++++++++ .../auth/recoverPassword/recoverPassword.scss | 8 + src/components/ui/form/FormModel.js | 4 +- src/components/user/User.js | 3 + src/i18n/en.json | 66 +++++---- src/i18n/ru.json | 66 +++++---- src/routes.js | 2 + src/services/authFlow/AuthFlow.js | 7 +- src/services/authFlow/ForgotPasswordState.js | 27 +++- src/services/authFlow/PasswordState.js | 6 +- src/services/authFlow/RecoverPasswordState.js | 31 ++++ src/services/errorsDict.intl.json | 28 ++++ src/services/errorsDict.js | 8 +- src/services/errorsDict.messages.js | 123 ---------------- .../authFlow/ForgotPasswordState.test.js | 139 ++++++++++++++++++ tests/services/authFlow/PasswordState.test.js | 4 +- .../authFlow/RecoverPasswordState.test.js | 104 +++++++++++++ 28 files changed, 621 insertions(+), 255 deletions(-) create mode 100644 src/components/auth/recoverPassword/RecoverPassword.intl.json create mode 100644 src/components/auth/recoverPassword/RecoverPassword.jsx create mode 100644 src/components/auth/recoverPassword/RecoverPasswordBody.jsx create mode 100644 src/components/auth/recoverPassword/recoverPassword.scss create mode 100644 src/services/authFlow/RecoverPasswordState.js create mode 100644 src/services/errorsDict.intl.json delete mode 100644 src/services/errorsDict.messages.js create mode 100644 tests/services/authFlow/ForgotPasswordState.test.js create mode 100644 tests/services/authFlow/RecoverPasswordState.test.js 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: () =>