From 3dd27fbb3e1cd09421bbf78cc6f0a8487cf6dd67 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 1 Aug 2016 11:34:08 -0300 Subject: [PATCH] Ivan - Update i18n, add recover-password path logic [skip ci] --- client/src/actions/user-actions.js | 5 +- client/src/app/App.js | 7 +- client/src/app/Routes.js | 2 + .../main-home/main-home-page-login-widget.js | 34 +++---- .../src/app/main/main-home/main-home-page.js | 10 +- client/src/app/main/main-layout-header.js | 9 +- .../main-recover-password-page.js | 91 +++++++++++++++++++ .../main-recover-password-page.scss | 19 ++++ client/src/core-components/drop-down.js | 3 +- client/src/data/fixtures/user-fixtures.js | 19 ++++ client/src/data/i18n-data.js | 16 +++- client/src/data/languages/en.js | 12 ++- client/src/data/languages/es.js | 7 -- .../lib-app/validations/email-validator.js | 2 +- .../lib-app/validations/length-validator.js | 16 ++++ .../validations/repeat-password-validator.js | 10 ++ .../validations/validations-factory.js | 10 +- client/src/lib-core/react-dfs.js | 13 ++- client/src/stores/user-store.js | 13 +++ 19 files changed, 245 insertions(+), 53 deletions(-) create mode 100644 client/src/app/main/main-recover-password/main-recover-password-page.js create mode 100644 client/src/app/main/main-recover-password/main-recover-password-page.scss delete mode 100644 client/src/data/languages/es.js create mode 100644 client/src/lib-app/validations/length-validator.js create mode 100644 client/src/lib-app/validations/repeat-password-validator.js diff --git a/client/src/actions/user-actions.js b/client/src/actions/user-actions.js index ef6b8ae9..3fb1714c 100644 --- a/client/src/actions/user-actions.js +++ b/client/src/actions/user-actions.js @@ -1,9 +1,10 @@ import Reflux from 'reflux'; -let UserActions = Reflux.createActions([ +const UserActions = Reflux.createActions([ 'checkLoginStatus', 'login', - 'logout' + 'logout', + 'recoverPassword' ]); export default UserActions; \ No newline at end of file diff --git a/client/src/app/App.js b/client/src/app/App.js index 91b2ce24..2eee2843 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -3,10 +3,11 @@ import Reflux from 'reflux'; import CommonStore from 'stores/common-store'; -let App = React.createClass({ +const App = React.createClass({ contextTypes: { - router: React.PropTypes.object + router: React.PropTypes.object, + location: React.PropTypes.object }, mixins: [Reflux.listenTo(CommonStore, 'onCommonStoreChanged')], @@ -21,7 +22,7 @@ let App = React.createClass({ onCommonStoreChanged(change) { let handle = { - 'i18n': () => {this.forceUpdate()}, + 'i18n': () => {this.context.router.push(this.context.location.pathname)}, 'logged': () => {this.context.router.push('/app/dashboard')}, 'loggedOut': () => {this.context.router.push('/app')} }; diff --git a/client/src/app/Routes.js b/client/src/app/Routes.js index 80a61cb9..71a755fa 100644 --- a/client/src/app/Routes.js +++ b/client/src/app/Routes.js @@ -7,6 +7,7 @@ const DemoPage = require('app/demo/components-demo-page'); const MainLayout = require('app/main/main-layout'); const MainHomePage = require('app/main/main-home/main-home-page'); const MainSignUpPage = require('app/main/main-signup/main-signup-page'); +const MainRecoverPasswordPage = require('app/main/main-recover-password/main-recover-password-page'); const DashboardLayout = require('app/main/dashboard/dashboard-layout'); @@ -25,6 +26,7 @@ export default ( + diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index e6a72c43..37884eb8 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -1,19 +1,19 @@ -const React = require('react'); -const ReactDOM = require('react-dom'); -const Reflux = require('reflux'); -const _ = require('lodash'); -const classNames = require('classnames'); +import React from 'react'; +import ReactDOM from 'react-dom'; +import Reflux from 'reflux'; +import classNames from 'classnames'; -const UserActions = require('actions/user-actions'); -const UserStore = require('stores/user-store'); -const focus = require('lib-core/focus'); +import UserActions from 'actions/user-actions'; +import UserStore from 'stores/user-store'; +import focus from 'lib-core/focus'; +import i18n from 'lib-app/i18n'; -const Button = require('core-components/button'); -const Form = require('core-components/form'); -const Input = require('core-components/input'); -const Checkbox = require('core-components/checkbox'); -const Widget = require('core-components/widget'); -const WidgetTransition = require('core-components/widget-transition'); +import Button from 'core-components/button'; +import Form from 'core-components/form'; +import Input from 'core-components/input'; +import Checkbox from 'core-components/checkbox'; +import Widget from 'core-components/widget'; +import WidgetTransition from 'core-components/widget-transition'; let MainHomePageLoginWidget = React.createClass({ @@ -49,7 +49,7 @@ let MainHomePageLoginWidget = React.createClass({ ); @@ -57,7 +57,7 @@ let MainHomePageLoginWidget = React.createClass({ renderPasswordRecovery() { return ( - +
@@ -103,7 +103,7 @@ let MainHomePageLoginWidget = React.createClass({ if (event === 'LOGIN_FAIL') { this.setState({ loginFormErrors: { - password: 'Password does not match' + password: i18n('ERROR_PASSWORD') } }, function () { this.refs.loginForm.refs.password.focus() diff --git a/client/src/app/main/main-home/main-home-page.js b/client/src/app/main/main-home/main-home-page.js index eb3218cf..57c4bc7b 100644 --- a/client/src/app/main/main-home/main-home-page.js +++ b/client/src/app/main/main-home/main-home-page.js @@ -1,10 +1,10 @@ -const React = require( 'react'); +import React from 'react'; -const MainHomePageLoginWidget = require('app/main/main-home/main-home-page-login-widget'); -const MainHomePagePortal = require('app/main/main-home/main-home-page-portal'); +import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget'; +import MainHomePagePortal from 'app/main/main-home/main-home-page-portal'; -const CommonActions = require('actions/common-actions'); -const UserStore = require('stores/user-store'); +import CommonActions from 'actions/common-actions'; +import UserStore from 'stores/user-store'; const MainHomePage = React.createClass({ diff --git a/client/src/app/main/main-layout-header.js b/client/src/app/main/main-layout-header.js index b1c33fdc..d8a52883 100644 --- a/client/src/app/main/main-layout-header.js +++ b/client/src/app/main/main-layout-header.js @@ -7,14 +7,13 @@ import UserStore from 'stores/user-store'; import Button from 'core-components/button'; import DropDown from 'core-components/drop-down'; -import Icon from 'core-components/icon'; -let languageList = ['English', 'Spanish', 'Portuguese', 'German', 'Turkish', 'Indian']; let codeLanguages = { 'English': 'us', 'Spanish': 'es', - 'Portuguese': 'pt', 'German': 'de', + 'French': 'fr', + 'Chinese': 'cn', 'Turkish': 'tr', 'Indian': 'in' }; @@ -52,7 +51,7 @@ let MainLayoutHeader = React.createClass({ }, getLanguageList() { - return languageList.map((language) => { + return Object.keys(codeLanguages).map((language) => { return { content: language, icon: codeLanguages[language] @@ -61,7 +60,7 @@ let MainLayoutHeader = React.createClass({ }, changeLanguage(event) { - let language = languageList[event.index]; + let language = Object.keys(codeLanguages)[event.index]; CommonActions.changeLanguage(codeLanguages[language]); }, diff --git a/client/src/app/main/main-recover-password/main-recover-password-page.js b/client/src/app/main/main-recover-password/main-recover-password-page.js new file mode 100644 index 00000000..33ce5c7c --- /dev/null +++ b/client/src/app/main/main-recover-password/main-recover-password-page.js @@ -0,0 +1,91 @@ +import React from 'react'; +import Reflux from 'reflux'; +import _ from 'lodash'; + +import CommonActions from 'actions/common-actions'; +import UserActions from 'actions/user-actions'; +import UserStore from 'stores/user-store'; +import i18n from 'lib-app/i18n'; + +import Widget from 'core-components/widget'; +import Form from 'core-components/form'; +import Input from 'core-components/input'; +import Button from 'core-components/button'; + +const MainRecoverPasswordPage = React.createClass({ + + mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')], + + propTypes: { + location: React.PropTypes.object, + router: React.PropTypes.object + }, + + componentWillMount() { + if (UserStore.isLoggedIn()) { + CommonActions.logged(); + } + + if (!this.props.location.query.token || !this.props.location.query.email) { + CommonActions.loggedOut(); + } + }, + + getInitialState() { + return { + recoverStatus: 'waiting' + }; + }, + + render() { + return ( +
+ + +
+ + +
+
+ +
+ {this.renderRecoverStatus()} + +
+
+ ); + }, + + renderRecoverStatus() { + switch (this.state.recoverStatus) { + case 'valid': + return
{i18n('VALID_RECOVER')}
; + case 'invalid': + return
{i18n('INVALID_RECOVER')}
; + case 'waiting': + return null; + } + }, + + handleRecoverPasswordSubmit(formState) { + let recoverData = _.clone(formState); + recoverData.token = this.props.location.query.token; + recoverData.email = this.props.location.query.email; + + UserActions.recoverPassword(formState); + }, + + onUserStoreChanged(event) { + if (event === 'VALID_RECOVER') { + this.setState({ + recoverStatus: 'valid' + }); + } else { + this.setState({ + recoverStatus: 'invalid' + }); + } + } +}); + +export default MainRecoverPasswordPage; \ No newline at end of file diff --git a/client/src/app/main/main-recover-password/main-recover-password-page.scss b/client/src/app/main/main-recover-password/main-recover-password-page.scss new file mode 100644 index 00000000..9b720e83 --- /dev/null +++ b/client/src/app/main/main-recover-password/main-recover-password-page.scss @@ -0,0 +1,19 @@ +.recover-password { + &__inputs { + display: inline-block; + margin: 0 auto 20px; + text-align: left; + } + + &__input { + margin: 10px 0 0 0; + } + + &__text_valid { + color: green; + } + + &__text_invalid { + color: red; + } +} \ No newline at end of file diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index c608b498..be3b1b80 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -63,7 +63,8 @@ const DropDown = React.createClass({ let menuProps = { items: this.props.items, onItemClick: this.handleItemClick, - onMouseDown: this.handleListMouseDown + onMouseDown: this.handleListMouseDown, + selectedIndex: this.state.selectedIndex }; return ( diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index fe8be0c0..f0f8128e 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -46,5 +46,24 @@ module.exports = [ } }; } + }, + { + path: 'user/recover-password', + time: 100, + response: function (data) { + + if (data.password.length > 6) { + return { + status: 'success', + data: {} + }; + } else { + return { + status: 'fail', + message: 'Invalid token', + data: {} + }; + } + } } ]; diff --git a/client/src/data/i18n-data.js b/client/src/data/i18n-data.js index eafa26dd..aa15af21 100644 --- a/client/src/data/i18n-data.js +++ b/client/src/data/i18n-data.js @@ -1,9 +1,19 @@ -const englishLanguage = require('data/languages/en'); -const spanishLanguage = require('data/languages/es'); +import englishLanguage from 'data/languages/en'; +import spanishLanguage from 'data/languages/en'; +import germanLanguage from 'data/languages/en'; +import frenchLanguage from 'data/languages/en'; +import chineseLanguage from 'data/languages/en'; +import turkishLanguage from 'data/languages/en'; +import indianLanguage from 'data/languages/en'; const languages = { 'us': englishLanguage, - 'es': spanishLanguage + 'es': spanishLanguage, + 'de': germanLanguage, + 'fr': frenchLanguage, + 'cn': chineseLanguage, + 'tr': turkishLanguage, + 'in': indianLanguage }; const i18nData = function (key, lang) { diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index c7d9b592..f7cb3111 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -2,6 +2,16 @@ export default { 'SUBMIT': 'Submit', 'LOG_IN': 'Log in', 'SIGN_UP': 'Sign up', + 'FORGOT_PASSWORD': 'Forgot your password?', + 'RECOVER_PASSWORD': 'Recover Password', + 'NEW_PASSWORD': 'New password', + 'REPEAT_NEW_PASSWORD': 'Repeat new password', + 'VALID_RECOVER': 'Password recovered successfully', + 'INVALID_RECOVER': 'Invalid recover data', + + //ERRORS 'ERROR_EMPTY': 'Invalid value', - 'ERROR_EMAIL': 'Invalid email' + 'ERROR_PASSWORD': 'Invalid password', + 'ERROR_EMAIL': 'Invalid email', + 'PASSWORD_NOT_MATCH': 'Password does not match' }; \ No newline at end of file diff --git a/client/src/data/languages/es.js b/client/src/data/languages/es.js deleted file mode 100644 index 8f565699..00000000 --- a/client/src/data/languages/es.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - 'SUBMIT': 'Enviar', - 'LOG_IN': 'Ingresar', - 'SIGN_UP': 'Registrarse', - 'ERROR_EMPTY': 'Valor invalido', - 'ERROR_EMAIL': 'Email invalido' -}; \ No newline at end of file diff --git a/client/src/lib-app/validations/email-validator.js b/client/src/lib-app/validations/email-validator.js index e1b7cb52..ef4b7e45 100644 --- a/client/src/lib-app/validations/email-validator.js +++ b/client/src/lib-app/validations/email-validator.js @@ -1,4 +1,4 @@ -const Validator = require('lib-app/validations/validator'); +import Validator from 'lib-app/validations/validator'; class EmailValidator extends Validator { diff --git a/client/src/lib-app/validations/length-validator.js b/client/src/lib-app/validations/length-validator.js new file mode 100644 index 00000000..2d68dae5 --- /dev/null +++ b/client/src/lib-app/validations/length-validator.js @@ -0,0 +1,16 @@ +import Validator from 'lib-app/validations/validator'; + +class LengthValidator extends Validator { + constructor(length, errorKey = 'INVALID_VALUE') { + super(); + + this.minlength = length; + this.errorKey = errorKey; + } + + validate(value, form) { + if (value.length < this.minlength) return this.getError(this.errorKey); + } +} + +export default LengthValidator; \ No newline at end of file diff --git a/client/src/lib-app/validations/repeat-password-validator.js b/client/src/lib-app/validations/repeat-password-validator.js new file mode 100644 index 00000000..ca88f64c --- /dev/null +++ b/client/src/lib-app/validations/repeat-password-validator.js @@ -0,0 +1,10 @@ +import Validator from 'lib-app/validations/validator'; + +class RepeatPasswordValidator extends Validator { + + validate(value, form) { + if (value !== form.password) return this.getError('PASSWORD_NOT_MATCH'); + } +} + +export default RepeatPasswordValidator; \ No newline at end of file diff --git a/client/src/lib-app/validations/validations-factory.js b/client/src/lib-app/validations/validations-factory.js index 77c98652..355077c9 100644 --- a/client/src/lib-app/validations/validations-factory.js +++ b/client/src/lib-app/validations/validations-factory.js @@ -1,9 +1,13 @@ -const Validator = require('lib-app/validations/validator'); -const EmailValidator = require('lib-app/validations/email-validator'); +import Validator from 'lib-app/validations/validator'; +import EmailValidator from 'lib-app/validations/email-validator'; +import RepeatPasswordValidator from 'lib-app/validations/repeat-password-validator'; +import LengthValidator from 'lib-app/validations/length-validator'; let validators = { 'DEFAULT': new Validator(), - 'EMAIL': new EmailValidator() + 'EMAIL': new EmailValidator(), + 'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'), + 'REPEAT_PASSWORD': new RepeatPasswordValidator() }; class ValidatorFactory { diff --git a/client/src/lib-core/react-dfs.js b/client/src/lib-core/react-dfs.js index 4cfb6c38..bdd4177b 100644 --- a/client/src/lib-core/react-dfs.js +++ b/client/src/lib-core/react-dfs.js @@ -11,11 +11,14 @@ let reactDFS = function (children, visitFunction) { let element = stack.pop(); let tempChilds = []; - if(element.props && element.props.children) { - React.Children.forEach(element.props.children, child => tempChilds.push(child)); + if (element) { + if (element.props && element.props.children) { + React.Children.forEach(element.props.children, child => tempChilds.push(child)); + } + + visitFunction(element); } - visitFunction(element); stack = stack.concat(tempChilds.reverse()); } }; @@ -26,12 +29,12 @@ let renderChildrenWithProps = function(children, mapFunction) { } return React.Children.map(children, function (child) { - let props = mapFunction(child); - if (typeof child !== 'object' || child === null) { return child; } + let props = mapFunction(child); + if (!_.isEmpty(props)) { return React.cloneElement(child, props, child.props && child.props.children); } else { diff --git a/client/src/stores/user-store.js b/client/src/stores/user-store.js index 213123cc..0f14b49b 100644 --- a/client/src/stores/user-store.js +++ b/client/src/stores/user-store.js @@ -13,6 +13,7 @@ const UserStore = Reflux.createStore({ this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus); this.listenTo(UserActions.login, this.loginUser); this.listenTo(UserActions.logout, this.logoutUser); + this.listenTo(UserActions.recoverPassword, this.recoverPassword); }, initSession() { @@ -53,6 +54,18 @@ const UserStore = Reflux.createStore({ }); }, + recoverPassword(recoverData) { + return API.call({ + path: 'user/recover-password', + data: recoverData + }).then(() => { + this.trigger('VALID_RECOVER'); + setTimeout(CommonActions.loggedOut, 2000); + }, () => { + this.trigger('INVALID_RECOVER') + }); + }, + isLoggedIn() { return sessionStore.isLoggedIn(); },