mirror of
				https://github.com/elyby/accounts-frontend.git
				synced 2025-05-31 14:11:58 +05:30 
			
		
		
		
	В первом приближении заинтегрировался с беком
This commit is contained in:
		| @@ -1,48 +1,69 @@ | |||||||
| import React, { Component } from 'react'; | import React, { PropTypes } from 'react'; | ||||||
|  |  | ||||||
| import { FormattedMessage as Message } from 'react-intl'; | import { FormattedMessage as Message } from 'react-intl'; | ||||||
| import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||||
|  |  | ||||||
| import buttons from 'components/ui/buttons.scss'; | import buttons from 'components/ui/buttons.scss'; | ||||||
| import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel'; |  | ||||||
| import { Input } from 'components/ui/Form'; | import { Input } from 'components/ui/Form'; | ||||||
|  |  | ||||||
|  | import BaseAuthBody from './BaseAuthBody'; | ||||||
| import styles from './activation.scss'; | import styles from './activation.scss'; | ||||||
| import {helpLinks as helpLinksStyles} from './helpLinks.scss'; |  | ||||||
| import messages from './Activation.messages'; | import messages from './Activation.messages'; | ||||||
|  |  | ||||||
| export default function Activation() { | class Body extends BaseAuthBody { | ||||||
|     var Title = () => ( // TODO: separate component for PageTitle |     static propTypes = { | ||||||
|         <Message {...messages.accountActivationTitle}> |         ...BaseAuthBody.propTypes, | ||||||
|             {(msg) => <span>{msg}<Helmet title={msg} /></span>} |         activate: PropTypes.func.isRequired, | ||||||
|         </Message> |         auth: PropTypes.shape({ | ||||||
|     ); |             error: PropTypes.string, | ||||||
|     Title.goBack = '/register'; |             login: PropTypes.shape({ | ||||||
|  |                 login: PropTypes.stirng | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     return { |     render() { | ||||||
|         Title, |         return ( | ||||||
|         Body: () => ( |  | ||||||
|             <div> |             <div> | ||||||
|  |                 {this.renderErrors()} | ||||||
|  |  | ||||||
|                 <div className={styles.description}> |                 <div className={styles.description}> | ||||||
|                     <div className={styles.descriptionImage} /> |                     <div className={styles.descriptionImage} /> | ||||||
|  |  | ||||||
|                     <div className={styles.descriptionText}> |                     <div className={styles.descriptionText}> | ||||||
|                         <Message {...messages.activationMailWasSent} values={{ |                         <Message {...messages.activationMailWasSent} values={{ | ||||||
|                             email: (<b>erickskrauch@yandex.ru</b>) |                             email: (<b>{this.props.user.email}</b>) | ||||||
|                         }} /> |                         }} /> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div className={styles.formRow}> |                 <div className={styles.formRow}> | ||||||
|                     <Input color="blue" className={styles.activationCodeInput} placeholder={messages.enterTheCode} /> |                     <Input {...this.bindField('key')} | ||||||
|  |                         color="blue" | ||||||
|  |                         className={styles.activationCodeInput} | ||||||
|  |                         autoFocus | ||||||
|  |                         required | ||||||
|  |                         placeholder={messages.enterTheCode} | ||||||
|  |                     /> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         ), |         ); | ||||||
|         Footer: (props) => ( |     } | ||||||
|             <button className={buttons.blue} onClick={(event) => { |  | ||||||
|                 event.preventDefault(); |  | ||||||
|  |  | ||||||
|                 props.history.push('/oauth/permissions'); |     onFormSubmit() { | ||||||
|             }}> |         this.props.activate(this.serialize()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function Activation() { | ||||||
|  |     return { | ||||||
|  |         Title: () => ( // TODO: separate component for PageTitle | ||||||
|  |             <Message {...messages.accountActivationTitle}> | ||||||
|  |                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||||
|  |             </Message> | ||||||
|  |         ), | ||||||
|  |         Body, | ||||||
|  |         Footer: () => ( | ||||||
|  |             <button className={buttons.blue}> | ||||||
|                 <Message {...messages.confirmEmail} /> |                 <Message {...messages.confirmEmail} /> | ||||||
|             </button> |             </button> | ||||||
|         ), |         ), | ||||||
| @@ -53,45 +74,3 @@ export default function Activation() { | |||||||
|         ) |         ) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| export class _Activation extends Component { |  | ||||||
|     displayName = 'Activation'; |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return ( |  | ||||||
|             <div> |  | ||||||
|                 <Message {...messages.accountActivationTitle}> |  | ||||||
|                     {(msg) => <Helmet title={msg} />} |  | ||||||
|                 </Message> |  | ||||||
|  |  | ||||||
|                 <Panel icon="arrowLeft" title={<Message {...messages.accountActivationTitle} />}> |  | ||||||
|                     <PanelBody> |  | ||||||
|                         <div className={styles.description}> |  | ||||||
|                             <div className={styles.descriptionImage} /> |  | ||||||
|  |  | ||||||
|                             <div className={styles.descriptionText}> |  | ||||||
|                                 <Message {...messages.activationMailWasSent} values={{ |  | ||||||
|                                     email: (<b>erickskrauch@yandex.ru</b>) |  | ||||||
|                                 }} /> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div className={styles.formRow}> |  | ||||||
|                             <Input color="blue" className={styles.activationCodeInput} placeholder={messages.enterTheCode} /> |  | ||||||
|                         </div> |  | ||||||
|                     </PanelBody> |  | ||||||
|                     <PanelFooter> |  | ||||||
|                         <button className={buttons.blue}> |  | ||||||
|                             <Message {...messages.confirmEmail} /> |  | ||||||
|                         </button> |  | ||||||
|                     </PanelFooter> |  | ||||||
|                 </Panel> |  | ||||||
|                 <div className={helpLinksStyles}> |  | ||||||
|                     <a href="#"> |  | ||||||
|                         <Message {...messages.didNotReceivedEmail} /> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								src/components/auth/AuthError.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/components/auth/AuthError.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import { FormattedMessage as Message } from 'react-intl'; | ||||||
|  | import { PanelBodyHeader } from 'components/ui/Panel'; | ||||||
|  |  | ||||||
|  | import messages from './AuthError.messages'; | ||||||
|  |  | ||||||
|  | export default class AuthError extends Component { | ||||||
|  |     static displayName = 'AuthError'; | ||||||
|  |  | ||||||
|  |     static propTypes = { | ||||||
|  |         error: PropTypes.string.isRequired, | ||||||
|  |         onClose: PropTypes.func | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         let { error } = this.props; | ||||||
|  |  | ||||||
|  |         if (this.errorsMap[error]) { | ||||||
|  |             error = this.errorsMap[error](); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |             <PanelBodyHeader type="error" onClose={this.props.onClose}> | ||||||
|  |                 {error} | ||||||
|  |             </PanelBodyHeader> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     errorsMap = { | ||||||
|  |         'error.login_required': () => <Message {...messages.loginRequired} />, | ||||||
|  |         'error.login_not_exist': () => <Message {...messages.loginNotExist} />, | ||||||
|  |         'error.password_required': () => <Message {...messages.passwordRequired} />, | ||||||
|  |  | ||||||
|  |         'error.password_incorrect': () => ( | ||||||
|  |             <span> | ||||||
|  |                 <Message {...messages.invalidPassword} /> | ||||||
|  |                 <br/> | ||||||
|  |                 <Message {...messages.suggestResetPassword} values={{ | ||||||
|  |                     link: ( | ||||||
|  |                         <a href="#"> | ||||||
|  |                             <Message {...messages.forgotYourPassword} /> | ||||||
|  |                         </a> | ||||||
|  |                     ) | ||||||
|  |                 }} /> | ||||||
|  |             </span> | ||||||
|  |         ), | ||||||
|  |  | ||||||
|  |         'error.username_required': () => <Message {...messages.usernameRequired} />, | ||||||
|  |         'error.email_required': () => <Message {...messages.emailRequired} />, | ||||||
|  |         'error.email_invalid': () => <Message {...messages.emailInvalid} />, | ||||||
|  |  | ||||||
|  |         'error.email_not_available': () => ( | ||||||
|  |             <span> | ||||||
|  |                 <Message {...messages.emailNotAvailable} /> | ||||||
|  |                 <br/> | ||||||
|  |                 <Message {...messages.suggestResetPassword} values={{ | ||||||
|  |                     link: ( | ||||||
|  |                         <a href="#"> | ||||||
|  |                             <Message {...messages.forgotYourPassword} /> | ||||||
|  |                         </a> | ||||||
|  |                     ) | ||||||
|  |                 }} /> | ||||||
|  |             </span> | ||||||
|  |         ), | ||||||
|  |  | ||||||
|  |         'error.rePassword_required': () => <Message {...messages.rePasswordRequired} />, | ||||||
|  |         'error.password_too_short': () => <Message {...messages.passwordTooShort} />, | ||||||
|  |         'error.rePassword_does_not_match': () => <Message {...messages.passwordsDoesNotMatch} />, | ||||||
|  |         'error.rulesAgreement_required': () => <Message {...messages.rulesAgreementRequired} />, | ||||||
|  |         'error.you_must_accept_rules': () => this.errorsMap['error.rulesAgreement_required'](), | ||||||
|  |         'error.key_required': () => <Message {...messages.keyRequired} />, | ||||||
|  |         'error.key_is_required': () => this.errorsMap['error.key_required'](), | ||||||
|  |         'error.key_not_exists': () => <Message {...messages.keyNotExists} /> | ||||||
|  |     }; | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								src/components/auth/AuthError.messages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/components/auth/AuthError.messages.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | import { defineMessages } from 'react-intl'; | ||||||
|  |  | ||||||
|  | export default defineMessages({ | ||||||
|  |     invalidPassword: { | ||||||
|  |         id: 'invalidPassword', | ||||||
|  |         defaultMessage: 'You 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' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     usernameRequired: { | ||||||
|  |         id: 'usernameRequired', | ||||||
|  |         defaultMessage: 'Username is required' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     emailRequired: { | ||||||
|  |         id: 'emailRequired', | ||||||
|  |         defaultMessage: 'Email is required' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     emailInvalid: { | ||||||
|  |         id: 'emailInvalid', | ||||||
|  |         defaultMessage: 'Email is invalid' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     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' | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										43
									
								
								src/components/auth/BaseAuthBody.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/components/auth/BaseAuthBody.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | /** | ||||||
|  |  * Helps with form fields binding, form serialization and errors rendering | ||||||
|  |  */ | ||||||
|  | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import AuthError from './AuthError'; | ||||||
|  |  | ||||||
|  | export default class BaseAuthBody extends Component { | ||||||
|  |     static propTypes = { | ||||||
|  |         clearErrors: PropTypes.func.isRequired, | ||||||
|  |         auth: PropTypes.shape({ | ||||||
|  |             error: PropTypes.string | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     renderErrors() { | ||||||
|  |         return this.props.auth.error | ||||||
|  |             ? <AuthError error={this.props.auth.error} onClose={this.onClearErrors} /> | ||||||
|  |             : '' | ||||||
|  |             ; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onClearErrors = this.props.clearErrors; | ||||||
|  |  | ||||||
|  |     form = {}; | ||||||
|  |  | ||||||
|  |     bindField(name) { | ||||||
|  |         return { | ||||||
|  |             name, | ||||||
|  |             ref: (el) => { | ||||||
|  |                 this.form[name] = el; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     serialize() { | ||||||
|  |         return Object.keys(this.form).reduce((acc, key) => { | ||||||
|  |             acc[key] = this.form[key].getValue(); | ||||||
|  |  | ||||||
|  |             return acc; | ||||||
|  |         }, {}); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,40 +1,57 @@ | |||||||
| import React, { Component } from 'react'; | import React, { PropTypes } from 'react'; | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| import { routeActions } from 'react-router-redux'; |  | ||||||
|  |  | ||||||
| import { FormattedMessage as Message } from 'react-intl'; | import { FormattedMessage as Message } from 'react-intl'; | ||||||
| import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||||
|  |  | ||||||
| import buttons from 'components/ui/buttons.scss'; | import buttons from 'components/ui/buttons.scss'; | ||||||
| import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel'; |  | ||||||
| import { Input } from 'components/ui/Form'; | import { Input } from 'components/ui/Form'; | ||||||
|  |  | ||||||
|  | import BaseAuthBody from './BaseAuthBody'; | ||||||
| import messages from './Login.messages'; | import messages from './Login.messages'; | ||||||
| import {helpLinks as helpLinksStyles} from './helpLinks.scss'; |  | ||||||
| import passwordMessages from './Password.messages'; | import passwordMessages from './Password.messages'; | ||||||
|  |  | ||||||
| export default function Login() { | class Body extends BaseAuthBody { | ||||||
|     var context = { |     static propTypes = { | ||||||
|         onSubmit(event) { |         ...BaseAuthBody.propTypes, | ||||||
|             event.preventDefault(); |         login: PropTypes.func.isRequired, | ||||||
|  |         auth: PropTypes.shape({ | ||||||
|             this.props.push('/password'); |             error: PropTypes.string, | ||||||
|         } |             login: PropTypes.shape({ | ||||||
|  |                 login: PropTypes.stirng | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         return ( | ||||||
|  |             <div> | ||||||
|  |                 {this.renderErrors()} | ||||||
|  |  | ||||||
|  |                 <Input {...this.bindField('login')} | ||||||
|  |                     icon="envelope" | ||||||
|  |                     autoFocus | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.emailOrUsername} | ||||||
|  |                 /> | ||||||
|  |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFormSubmit() { | ||||||
|  |         this.props.login(this.serialize()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function Login() { | ||||||
|     return { |     return { | ||||||
|         Title: () => ( // TODO: separate component for PageTitle |         Title: () => ( // TODO: separate component for PageTitle | ||||||
|             <Message {...messages.loginTitle}> |             <Message {...messages.loginTitle}> | ||||||
|                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} |                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||||
|             </Message> |             </Message> | ||||||
|         ), |         ), | ||||||
|         Body: () => <Input icon="envelope" type="email" placeholder={messages.emailOrUsername} />, |         Body, | ||||||
|         Footer: (props) => ( |         Footer: () => ( | ||||||
|             <button className={buttons.green} onClick={(event) => { |             <button className={buttons.green} type="submit"> | ||||||
|                 event.preventDefault(); |  | ||||||
|  |  | ||||||
|                 props.history.push('/password'); |  | ||||||
|             }}> |  | ||||||
|                 <Message {...messages.next} /> |                 <Message {...messages.next} /> | ||||||
|             </button> |             </button> | ||||||
|         ), |         ), | ||||||
| @@ -45,44 +62,3 @@ export default function Login() { | |||||||
|         ) |         ) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| class _Login extends Component { |  | ||||||
|     displayName = 'Login'; |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return ( |  | ||||||
|             <div> |  | ||||||
|                 <Message {...messages.loginTitle}> |  | ||||||
|                     {(msg) => <Helmet title={msg} />} |  | ||||||
|                 </Message> |  | ||||||
|  |  | ||||||
|                 <Panel title={<Message {...messages.loginTitle} />}> |  | ||||||
|                     <PanelBody> |  | ||||||
|                         <Input icon="envelope" type="email" placeholder={messages.emailOrUsername} /> |  | ||||||
|                     </PanelBody> |  | ||||||
|                     <PanelFooter> |  | ||||||
|                         <button className={buttons.green} onClick={this.onSubmit}> |  | ||||||
|                             <Message {...messages.next} /> |  | ||||||
|                         </button> |  | ||||||
|                     </PanelFooter> |  | ||||||
|                 </Panel> |  | ||||||
|                 <div className={helpLinksStyles}> |  | ||||||
|                     <a href="#"> |  | ||||||
|                         <Message {...passwordMessages.forgotPassword} /> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     onSubmit = (event) => { |  | ||||||
|         event.preventDefault(); |  | ||||||
|  |  | ||||||
|         this.props.push('/password'); |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // export connect(null, { |  | ||||||
| //     push: routeActions.push |  | ||||||
| // })(Login); |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								src/components/auth/Logout.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/components/auth/Logout.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  |  | ||||||
|  | import { logout } from 'components/auth/actions'; | ||||||
|  |  | ||||||
|  | class Logout extends Component { | ||||||
|  |     static displayName = 'Logout'; | ||||||
|  |  | ||||||
|  |     static propTypes = { | ||||||
|  |         logout: PropTypes.func.isRequired | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     componentWillMount() { | ||||||
|  |         this.props.logout(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         return <span />; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default connect(null, { | ||||||
|  |     logout | ||||||
|  | })(Logout); | ||||||
| @@ -1,48 +1,73 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { routeActions } from 'react-router-redux'; | ||||||
| import { TransitionMotion, spring } from 'react-motion'; | import { TransitionMotion, spring } from 'react-motion'; | ||||||
| import ReactHeight from 'react-height'; | import ReactHeight from 'react-height'; | ||||||
|  |  | ||||||
| import { Panel, PanelBody, PanelFooter, PanelHeader } from 'components/ui/Panel'; | import { Panel, PanelBody, PanelFooter, PanelHeader } from 'components/ui/Panel'; | ||||||
|  | import { Form } from 'components/ui/Form'; | ||||||
| import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss'; | import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss'; | ||||||
| import panelStyles from 'components/ui/panel.scss'; | import panelStyles from 'components/ui/panel.scss'; | ||||||
| import icons from 'components/ui/icons.scss'; | import icons from 'components/ui/icons.scss'; | ||||||
|  |  | ||||||
|  | import * as actions from './actions'; | ||||||
|  |  | ||||||
| const opacitySpringConfig = [300, 20]; | const opacitySpringConfig = [300, 20]; | ||||||
| const transformSpringConfig = [500, 50]; | const transformSpringConfig = [500, 50]; | ||||||
| const changeContextSpringConfig = [500, 20]; | const changeContextSpringConfig = [500, 20]; | ||||||
|  |  | ||||||
| export default class PanelTransition extends Component { | class PanelTransition extends Component { | ||||||
|  |     static displayName = 'PanelTransition'; | ||||||
|  |  | ||||||
|  |     static propTypes = { | ||||||
|  |         auth: PropTypes.shape({ | ||||||
|  |             error: PropTypes.string, | ||||||
|  |             login: PropTypes.shape({ | ||||||
|  |                 login: PropTypes.string, | ||||||
|  |                 password: PropTypes.string | ||||||
|  |             }) | ||||||
|  |         }).isRequired, | ||||||
|  |         goBack: React.PropTypes.func.isRequired, | ||||||
|  |         setError: React.PropTypes.func.isRequired, | ||||||
|  |         clearErrors: React.PropTypes.func.isRequired, | ||||||
|  |         path: PropTypes.string.isRequired, | ||||||
|  |         Title: PropTypes.element.isRequired, | ||||||
|  |         Body: PropTypes.element.isRequired, | ||||||
|  |         Footer: PropTypes.element.isRequired, | ||||||
|  |         Links: PropTypes.element.isRequired | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     state = { |     state = { | ||||||
|         height: {}, |         height: {}, | ||||||
|         contextHeight: 0 |         contextHeight: 0 | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     componentWillReceiveProps(nextProps) { |     componentWillReceiveProps(nextProps) { | ||||||
|         var previousRoute = this.props.location; |         var nextPath = nextProps.path; | ||||||
|  |         var previousPath = this.props.path; | ||||||
|  |  | ||||||
|         var next = nextProps.path; |         if (nextPath !== previousPath) { | ||||||
|         var prev = previousRoute && previousRoute.pathname; |             var direction = this.getDirection(nextPath, previousPath); | ||||||
|  |             var forceHeight = direction === 'Y' && nextPath !== previousPath ? 1 : 0; | ||||||
|  |  | ||||||
|         var direction = this.getDirection(next, next, prev); |             this.props.clearErrors(); | ||||||
|         var forceHeight = direction === 'Y' && next !== prev ? 1 : 0; |             this.setState({ | ||||||
|  |                 direction, | ||||||
|  |                 forceHeight, | ||||||
|  |                 previousPath | ||||||
|  |             }); | ||||||
|  |  | ||||||
|         this.setState({ |             if (forceHeight) { | ||||||
|             direction, |                 setTimeout(() => { | ||||||
|             forceHeight, |                     this.setState({forceHeight: 0}); | ||||||
|             previousRoute |                 }, 100); | ||||||
|         }); |             } | ||||||
|  |  | ||||||
|         if (forceHeight) { |  | ||||||
|             setTimeout(() => { |  | ||||||
|                 this.setState({forceHeight: 0}); |  | ||||||
|             }, 100); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|         var {previousRoute, height, contextHeight, forceHeight} = this.state; |         const {height, canAnimateHeight, contextHeight, forceHeight} = this.state; | ||||||
|  |  | ||||||
|         const {path, Title, Body, Footer, Links} = this.props; |         const {path, Title, Body, Footer, Links} = this.props; | ||||||
|  |  | ||||||
| @@ -54,7 +79,7 @@ export default class PanelTransition extends Component { | |||||||
|                         Body, |                         Body, | ||||||
|                         Footer, |                         Footer, | ||||||
|                         Links, |                         Links, | ||||||
|                         hasBackButton: previousRoute && previousRoute.pathname === Title.type.goBack, |                         hasBackButton: Title.type.goBack, | ||||||
|                         transformSpring: spring(0, transformSpringConfig), |                         transformSpring: spring(0, transformSpringConfig), | ||||||
|                         opacitySpring: spring(1, opacitySpringConfig) |                         opacitySpring: spring(1, opacitySpringConfig) | ||||||
|                     }, |                     }, | ||||||
| @@ -67,24 +92,28 @@ export default class PanelTransition extends Component { | |||||||
|                 willLeave={this.willLeave} |                 willLeave={this.willLeave} | ||||||
|             > |             > | ||||||
|                 {(items) => { |                 {(items) => { | ||||||
|                     var keys = Object.keys(items).filter((key) => key !== 'common'); |                     const keys = Object.keys(items).filter((key) => key !== 'common'); | ||||||
|  |  | ||||||
|  |                     const contentHeight = { | ||||||
|  |                         overflow: 'hidden', | ||||||
|  |                         height: forceHeight ? items.common.switchContextHeightSpring : 'auto' | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     const bodyHeight = { | ||||||
|  |                         position: 'relative', | ||||||
|  |                         height: `${canAnimateHeight ? items.common.heightSpring : height[path]}px` | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|                     return ( |                     return ( | ||||||
|                         <div> |                         <Form id={path} onSubmit={this.onFormSubmit} onInvalid={this.onFormInvalid}> | ||||||
|                             <Panel> |                             <Panel> | ||||||
|                                 <PanelHeader> |                                 <PanelHeader> | ||||||
|                                     {keys.map((key) => this.getHeader(key, items[key]))} |                                     {keys.map((key) => this.getHeader(key, items[key]))} | ||||||
|                                 </PanelHeader> |                                 </PanelHeader> | ||||||
|                                 <div style={{ |                                 <div style={contentHeight}> | ||||||
|                                     overflow: 'hidden', |                                     <ReactHeight onHeightReady={this.onUpdateContextHeight}> | ||||||
|                                     height: forceHeight ? items.common.switchContextHeightSpring : 'auto' |  | ||||||
|                                 }}> |  | ||||||
|                                     <ReactHeight onHeightReady={this.updateContextHeight}> |  | ||||||
|                                         <PanelBody> |                                         <PanelBody> | ||||||
|                                             <div style={{ |                                             <div style={bodyHeight}> | ||||||
|                                                 position: 'relative', |  | ||||||
|                                                 height: `${previousRoute ? items.common.heightSpring : height[path]}px` |  | ||||||
|                                             }}> |  | ||||||
|                                                 {keys.map((key) => this.getBody(key, items[key]))} |                                                 {keys.map((key) => this.getBody(key, items[key]))} | ||||||
|                                             </div> |                                             </div> | ||||||
|                                         </PanelBody> |                                         </PanelBody> | ||||||
| @@ -97,21 +126,24 @@ export default class PanelTransition extends Component { | |||||||
|                             <div className={helpLinksStyles}> |                             <div className={helpLinksStyles}> | ||||||
|                                 {keys.map((key) => this.getLinks(key, items[key]))} |                                 {keys.map((key) => this.getLinks(key, items[key]))} | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </Form> | ||||||
|                     ); |                     ); | ||||||
|                 }} |                 }} | ||||||
|             </TransitionMotion> |             </TransitionMotion> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     willEnter = (key, styles) => { |     onFormSubmit = () => { | ||||||
|         return this.getTransitionStyles(key, styles); |         this.body.onFormSubmit(); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     willLeave = (key, styles) => { |     onFormInvalid = (errorMessage) => { | ||||||
|         return this.getTransitionStyles(key, styles, {isLeave: true}); |         this.props.setError(errorMessage); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     willEnter = (key, styles) => this.getTransitionStyles(key, styles); | ||||||
|  |     willLeave = (key, styles) => this.getTransitionStyles(key, styles, {isLeave: true}); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param  {string} key |      * @param  {string} key | ||||||
|      * @param  {Object} styles |      * @param  {Object} styles | ||||||
| @@ -140,8 +172,11 @@ export default class PanelTransition extends Component { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateHeight = (height) => { |     onUpdateHeight = (height) => { | ||||||
|  |         const canAnimateHeight = Object.keys(this.state.height).length > 1 || this.state.height[[this.props.path]]; | ||||||
|  |  | ||||||
|         this.setState({ |         this.setState({ | ||||||
|  |             canAnimateHeight, | ||||||
|             height: { |             height: { | ||||||
|                 ...this.state.height, |                 ...this.state.height, | ||||||
|                 [this.props.path]: height |                 [this.props.path]: height | ||||||
| @@ -149,7 +184,7 @@ export default class PanelTransition extends Component { | |||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     updateContextHeight = (height) => { |     onUpdateContextHeight = (height) => { | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             contextHeight: height |             contextHeight: height | ||||||
|         }); |         }); | ||||||
| @@ -158,10 +193,11 @@ export default class PanelTransition extends Component { | |||||||
|     onGoBack = (event) => { |     onGoBack = (event) => { | ||||||
|         event.preventDefault(); |         event.preventDefault(); | ||||||
|  |  | ||||||
|         this.props.history.goBack(); |         this.body.onGoBack && this.body.onGoBack(); | ||||||
|  |         this.props.goBack(); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     getDirection(key, next, prev) { |     getDirection(next, prev) { | ||||||
|         var not = (path) => prev !== path && next !== path; |         var not = (path) => prev !== path && next !== path; | ||||||
|  |  | ||||||
|         var map = { |         var map = { | ||||||
| @@ -172,7 +208,7 @@ export default class PanelTransition extends Component { | |||||||
|             '/oauth/permissions': 'Y' |             '/oauth/permissions': 'Y' | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         return map[key]; |         return map[next]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getHeader(key, props) { |     getHeader(key, props) { | ||||||
| @@ -192,7 +228,7 @@ export default class PanelTransition extends Component { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         var backButton = ( |         var backButton = ( | ||||||
|             <button style={sideScrollStyle} onClick={this.onGoBack} className={panelStyles.headerControl}> |             <button style={sideScrollStyle} type="button" onClick={this.onGoBack} className={panelStyles.headerControl}> | ||||||
|                 <span className={icons.arrowLeft} /> |                 <span className={icons.arrowLeft} /> | ||||||
|             </button> |             </button> | ||||||
|         ); |         ); | ||||||
| @@ -201,7 +237,7 @@ export default class PanelTransition extends Component { | |||||||
|             <div key={`header${key}`} style={style}> |             <div key={`header${key}`} style={style}> | ||||||
|                 {hasBackButton ? backButton : null} |                 {hasBackButton ? backButton : null} | ||||||
|                 <div style={scrollStyle}> |                 <div style={scrollStyle}> | ||||||
|                     {Title} |                     {React.cloneElement(Title, this.props)} | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         ); |         ); | ||||||
| @@ -228,8 +264,13 @@ export default class PanelTransition extends Component { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|             <ReactHeight key={`body${key}`} style={style} onHeightReady={this.updateHeight}> |             <ReactHeight key={`body${key}`} style={style} onHeightReady={this.onUpdateHeight}> | ||||||
|                 {Body} |                 {React.cloneElement(Body, { | ||||||
|  |                     ...this.props, | ||||||
|  |                     ref: (body) => { | ||||||
|  |                         this.body = body; | ||||||
|  |                     } | ||||||
|  |                 })} | ||||||
|             </ReactHeight> |             </ReactHeight> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -241,7 +282,7 @@ export default class PanelTransition extends Component { | |||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|             <div key={`footer${key}`} style={style}> |             <div key={`footer${key}`} style={style}> | ||||||
|                 {Footer} |                 {React.cloneElement(Footer, this.props)} | ||||||
|             </div> |             </div> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -253,7 +294,7 @@ export default class PanelTransition extends Component { | |||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|             <div key={`links${key}`} style={style}> |             <div key={`links${key}`} style={style}> | ||||||
|                 {Links} |                 {React.cloneElement(Links, this.props)} | ||||||
|             </div> |             </div> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -293,3 +334,16 @@ export default class PanelTransition extends Component { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export default connect((state) => ({ | ||||||
|  |     user: state.user, | ||||||
|  |     auth: state.auth, | ||||||
|  |     path: state.routing.location.pathname | ||||||
|  | }), { | ||||||
|  |     goBack: routeActions.goBack, | ||||||
|  |     login: actions.login, | ||||||
|  |     logout: actions.logout, | ||||||
|  |     register: actions.register, | ||||||
|  |     activate: actions.activate, | ||||||
|  |     clearErrors: actions.clearErrors, | ||||||
|  |     setError: actions.setError | ||||||
|  | })(PanelTransition); | ||||||
|   | |||||||
| @@ -1,61 +1,86 @@ | |||||||
| import React, { Component } from 'react'; | import React, { PropTypes } from 'react'; | ||||||
|  |  | ||||||
| import { FormattedMessage as Message } from 'react-intl'; | import { FormattedMessage as Message } from 'react-intl'; | ||||||
| import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||||
|  |  | ||||||
| import buttons from 'components/ui/buttons.scss'; | import buttons from 'components/ui/buttons.scss'; | ||||||
| import icons from 'components/ui/icons.scss'; | import icons from 'components/ui/icons.scss'; | ||||||
| import { Panel, PanelBody, PanelFooter, PanelBodyHeader } from 'components/ui/Panel'; |  | ||||||
| import { Input, Checkbox } from 'components/ui/Form'; | import { Input, Checkbox } from 'components/ui/Form'; | ||||||
|  |  | ||||||
|  | import BaseAuthBody from './BaseAuthBody'; | ||||||
| import styles from './password.scss'; | import styles from './password.scss'; | ||||||
| import {helpLinks as helpLinksStyles} from './helpLinks.scss'; |  | ||||||
| import messages from './Password.messages'; | import messages from './Password.messages'; | ||||||
|  |  | ||||||
|  | class Body extends BaseAuthBody { | ||||||
|  |     static propTypes = { | ||||||
|  |         ...BaseAuthBody.propTypes, | ||||||
|  |         login: PropTypes.func.isRequired, | ||||||
|  |         logout: PropTypes.func.isRequired, | ||||||
|  |         auth: PropTypes.shape({ | ||||||
|  |             error: PropTypes.string, | ||||||
|  |             login: PropTypes.shape({ | ||||||
|  |                 login: PropTypes.stirng, | ||||||
|  |                 password: PropTypes.stirng | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         const {user} = this.props; | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |             <div> | ||||||
|  |                 {this.renderErrors()} | ||||||
|  |  | ||||||
|  |                 <div className={styles.miniProfile}> | ||||||
|  |                     <div className={styles.avatar}> | ||||||
|  |                         {user.avatar | ||||||
|  |                             ? <img src={user.avatar} /> | ||||||
|  |                             : <span className={icons.user} /> | ||||||
|  |                         } | ||||||
|  |                     </div> | ||||||
|  |                     <div className={styles.email}> | ||||||
|  |                         {user.email || user.username} | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <Input {...this.bindField('password')} | ||||||
|  |                     icon="key" | ||||||
|  |                     type="password" | ||||||
|  |                     autoFocus | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.accountPassword} | ||||||
|  |                 /> | ||||||
|  |  | ||||||
|  |                 <Checkbox {...this.bindField('rememberMe')} label={<Message {...messages.rememberMe} />} /> | ||||||
|  |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFormSubmit() { | ||||||
|  |         this.props.login({ | ||||||
|  |             ...this.serialize(), | ||||||
|  |             login: this.props.user.email || this.props.user.username | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onGoBack() { | ||||||
|  |         this.props.logout(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| export default function Password() { | export default function Password() { | ||||||
|     var Title = () => ( // TODO: separate component for PageTitle |     var Title = () => ( // TODO: separate component for PageTitle | ||||||
|         <Message {...messages.passwordTitle}> |         <Message {...messages.passwordTitle}> | ||||||
|             {(msg) => <span>{msg}<Helmet title={msg} /></span>} |             {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||||
|         </Message> |         </Message> | ||||||
|     ); |     ); | ||||||
|     Title.goBack = '/login'; |     Title.goBack = true; | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         Title, |         Title, | ||||||
|         Body: () => ( |         Body, | ||||||
|             <div> |         Footer: () => ( | ||||||
|                 <PanelBodyHeader type="error"> |             <button className={buttons.green} type="submit"> | ||||||
|                     <Message {...messages.invalidPassword} /> |  | ||||||
|                     <br/> |  | ||||||
|                     <Message {...messages.suggestResetPassword} values={{ |  | ||||||
|                         link: ( |  | ||||||
|                             <a href="#"> |  | ||||||
|                                 <Message {...messages.forgotYourPassword} /> |  | ||||||
|                             </a> |  | ||||||
|                         ) |  | ||||||
|                     }} /> |  | ||||||
|                 </PanelBodyHeader> |  | ||||||
|                 <div className={styles.miniProfile}> |  | ||||||
|                     <div className={styles.avatar}> |  | ||||||
|                         {/*<img src="//lorempixel.com/g/90/90" />*/} |  | ||||||
|                         <span className={icons.user} /> |  | ||||||
|                     </div> |  | ||||||
|                     <div className={styles.email}> |  | ||||||
|                         {/* На деле тут может быть и ник, в зависимости от того, что введут в 1 вьюху */} |  | ||||||
|                         erickskrauch@yandex.ru |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <Input icon="key" type="password" placeholder={messages.accountPassword} /> |  | ||||||
|  |  | ||||||
|                 <Checkbox label={<Message {...messages.rememberMe} />} /> |  | ||||||
|             </div> |  | ||||||
|         ), |  | ||||||
|         Footer: (props) => ( |  | ||||||
|             <button className={buttons.green} onClick={(event) => { |  | ||||||
|                 event.preventDefault(); |  | ||||||
|  |  | ||||||
|                 props.history.push('/oauth/permissions'); |  | ||||||
|             }}> |  | ||||||
|                 <Message {...messages.signInButton} /> |                 <Message {...messages.signInButton} /> | ||||||
|             </button> |             </button> | ||||||
|         ), |         ), | ||||||
| @@ -66,56 +91,3 @@ export default function Password() { | |||||||
|         ) |         ) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class _Password extends Component { |  | ||||||
|     displayName = 'Password'; |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return ( |  | ||||||
|             <div> |  | ||||||
|                 <Message {...messages.passwordTitle}> |  | ||||||
|                     {(msg) => <Helmet title={msg} />} |  | ||||||
|                 </Message> |  | ||||||
|  |  | ||||||
|                 <Panel icon="arrowLeft" title={<Message {...messages.passwordTitle} />}> |  | ||||||
|                     <PanelBody> |  | ||||||
|                         <PanelBodyHeader type="error"> |  | ||||||
|                             <Message {...messages.invalidPassword} /> |  | ||||||
|                             <br/> |  | ||||||
|                             <Message {...messages.suggestResetPassword} values={{ |  | ||||||
|                                 link: ( |  | ||||||
|                                     <a href="#"> |  | ||||||
|                                         <Message {...messages.forgotYourPassword} /> |  | ||||||
|                                     </a> |  | ||||||
|                                 ) |  | ||||||
|                             }} /> |  | ||||||
|                         </PanelBodyHeader> |  | ||||||
|                         <div className={styles.miniProfile}> |  | ||||||
|                             <div className={styles.avatar}> |  | ||||||
|                                 {/*<img src="//lorempixel.com/g/90/90" />*/} |  | ||||||
|                                 <span className={icons.user} /> |  | ||||||
|                             </div> |  | ||||||
|                             <div className={styles.email}> |  | ||||||
|                                 {/* На деле тут может быть и ник, в зависимости от того, что введут в 1 вьюху */} |  | ||||||
|                                 erickskrauch@yandex.ru |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <Input icon="key" type="password" placeholder={messages.accountPassword} /> |  | ||||||
|  |  | ||||||
|                         <Checkbox label={<Message {...messages.rememberMe} />} /> |  | ||||||
|                     </PanelBody> |  | ||||||
|                     <PanelFooter> |  | ||||||
|                         <button className={buttons.green}> |  | ||||||
|                             <Message {...messages.signInButton} /> |  | ||||||
|                         </button> |  | ||||||
|                     </PanelFooter> |  | ||||||
|                 </Panel> |  | ||||||
|                 <div className={helpLinksStyles}> |  | ||||||
|                     <a href="#"> |  | ||||||
|                         <Message {...messages.forgotPassword} /> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,25 +1,33 @@ | |||||||
| import React, { Component } from 'react'; | import React, { PropTypes } from 'react'; | ||||||
|  |  | ||||||
| import { FormattedMessage as Message } from 'react-intl'; | import { FormattedMessage as Message } from 'react-intl'; | ||||||
| import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||||
|  |  | ||||||
| import buttons from 'components/ui/buttons.scss'; | import buttons from 'components/ui/buttons.scss'; | ||||||
| import icons from 'components/ui/icons.scss'; | import icons from 'components/ui/icons.scss'; | ||||||
| import { Panel, PanelBody, PanelFooter, PanelBodyHeader } from 'components/ui/Panel'; | import { PanelBodyHeader } from 'components/ui/Panel'; | ||||||
|  |  | ||||||
|  | import BaseAuthBody from './BaseAuthBody'; | ||||||
| import styles from './permissions.scss'; | import styles from './permissions.scss'; | ||||||
| import {helpLinks as helpLinksStyles} from './helpLinks.scss'; |  | ||||||
| import messages from './Permissions.messages'; | import messages from './Permissions.messages'; | ||||||
|  |  | ||||||
| export default function Permissions() { | class Body extends BaseAuthBody { | ||||||
|     return { |     static propTypes = { | ||||||
|         Title: () => ( // TODO: separate component for PageTitle |         ...BaseAuthBody.propTypes, | ||||||
|             <Message {...messages.permissionsTitle}> |         login: PropTypes.func.isRequired, | ||||||
|                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} |         auth: PropTypes.shape({ | ||||||
|             </Message> |             error: PropTypes.string, | ||||||
|         ), |             login: PropTypes.shape({ | ||||||
|         Body: () => ( |                 login: PropTypes.stirng | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         return ( | ||||||
|             <div> |             <div> | ||||||
|  |                 {this.renderErrors()} | ||||||
|  |  | ||||||
|                 <PanelBodyHeader> |                 <PanelBodyHeader> | ||||||
|                     <div className={styles.authInfo}> |                     <div className={styles.authInfo}> | ||||||
|                         <div className={styles.authInfoAvatar}> |                         <div className={styles.authInfoAvatar}> | ||||||
| @@ -30,7 +38,7 @@ export default function Permissions() { | |||||||
|                             <Message {...messages.youAuthorizedAs} /> |                             <Message {...messages.youAuthorizedAs} /> | ||||||
|                         </div> |                         </div> | ||||||
|                         <div className={styles.authInfoEmail}> |                         <div className={styles.authInfoEmail}> | ||||||
|                             erickskrauch@yandex.ru |                             {'erickskrauch@yandex.ru'} | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </PanelBodyHeader> |                 </PanelBodyHeader> | ||||||
| @@ -47,9 +55,24 @@ export default function Permissions() { | |||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFormSubmit() { | ||||||
|  |         // TODO | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function Permissions() { | ||||||
|  |     return { | ||||||
|  |         Title: () => ( // TODO: separate component for PageTitle | ||||||
|  |             <Message {...messages.permissionsTitle}> | ||||||
|  |                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||||
|  |             </Message> | ||||||
|         ), |         ), | ||||||
|  |         Body, | ||||||
|         Footer: () => ( |         Footer: () => ( | ||||||
|             <button className={buttons.green}> |             <button className={buttons.orange} autoFocus> | ||||||
|                 <Message {...messages.approve} /> |                 <Message {...messages.approve} /> | ||||||
|             </button> |             </button> | ||||||
|         ), |         ), | ||||||
| @@ -60,57 +83,3 @@ export default function Permissions() { | |||||||
|         ) |         ) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class _Permissions extends Component { |  | ||||||
|     displayName = 'Permissions'; |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return ( |  | ||||||
|             <div> |  | ||||||
|                 <Message {...messages.permissionsTitle}> |  | ||||||
|                     {(msg) => <Helmet title={msg} />} |  | ||||||
|                 </Message> |  | ||||||
|  |  | ||||||
|                 <Panel title={<Message {...messages.permissionsTitle} />}> |  | ||||||
|                     <PanelBody> |  | ||||||
|                         <PanelBodyHeader> |  | ||||||
|                             <div className={styles.authInfo}> |  | ||||||
|                                 <div className={styles.authInfoAvatar}> |  | ||||||
|                                     {/*<img src="//lorempixel.com/g/90/90" />*/} |  | ||||||
|                                     <span className={icons.user} /> |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className={styles.authInfoTitle}> |  | ||||||
|                                     <Message {...messages.youAuthorizedAs} /> |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className={styles.authInfoEmail}> |  | ||||||
|                                     erickskrauch@yandex.ru |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                         </PanelBodyHeader> |  | ||||||
|                         <div className={styles.permissionsContainer}> |  | ||||||
|                             <div className={styles.permissionsTitle}> |  | ||||||
|                                 <Message {...messages.theAppNeedsAccess} /> |  | ||||||
|                             </div> |  | ||||||
|                             <ul className={styles.permissionsList}> |  | ||||||
|                                 <li>Authorization for Minecraft servers</li> |  | ||||||
|                                 <li>Manage your skins directory and additional rows for multiline</li> |  | ||||||
|                                 <li>Change the active skin</li> |  | ||||||
|                                 <li>View your E-mail address</li> |  | ||||||
|                             </ul> |  | ||||||
|                         </div> |  | ||||||
|                     </PanelBody> |  | ||||||
|                     <PanelFooter> |  | ||||||
|                         <button className={buttons.green}> |  | ||||||
|                             <Message {...messages.approve} /> |  | ||||||
|                         </button> |  | ||||||
|                     </PanelFooter> |  | ||||||
|                 </Panel> |  | ||||||
|                 <div className={helpLinksStyles}> |  | ||||||
|                     <a href="#"> |  | ||||||
|                         <Message {...messages.decline} /> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,16 +1,93 @@ | |||||||
| import React, { Component } from 'react'; | import React, { PropTypes } from 'react'; | ||||||
|  |  | ||||||
| import { FormattedMessage as Message } from 'react-intl'; | import { FormattedMessage as Message } from 'react-intl'; | ||||||
| import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||||
|  |  | ||||||
| import buttons from 'components/ui/buttons.scss'; | import buttons from 'components/ui/buttons.scss'; | ||||||
| import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel'; |  | ||||||
| import { Input, Checkbox } from 'components/ui/Form'; | import { Input, Checkbox } from 'components/ui/Form'; | ||||||
|  |  | ||||||
| import {helpLinks as helpLinksStyles} from './helpLinks.scss'; | import BaseAuthBody from './BaseAuthBody'; | ||||||
| import messages from './Register.messages'; | import messages from './Register.messages'; | ||||||
| import activationMessages from './Activation.messages'; | import activationMessages from './Activation.messages'; | ||||||
|  |  | ||||||
|  | // TODO: password and username can be validate for length and sameness | ||||||
|  |  | ||||||
|  | class Body extends BaseAuthBody { | ||||||
|  |     static propTypes = { | ||||||
|  |         ...BaseAuthBody.propTypes, | ||||||
|  |         register: PropTypes.func.isRequired, | ||||||
|  |         auth: PropTypes.shape({ | ||||||
|  |             error: PropTypes.string, | ||||||
|  |             register: PropTypes.shape({ | ||||||
|  |                 email: PropTypes.string, | ||||||
|  |                 username: PropTypes.stirng, | ||||||
|  |                 password: PropTypes.stirng, | ||||||
|  |                 rePassword: PropTypes.stirng, | ||||||
|  |                 rulesAgreement: PropTypes.boolean | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         return ( | ||||||
|  |             <div> | ||||||
|  |                 {this.renderErrors()} | ||||||
|  |  | ||||||
|  |                 <Input {...this.bindField('username')} | ||||||
|  |                     icon="user" | ||||||
|  |                     color="blue" | ||||||
|  |                     type="text" | ||||||
|  |                     autoFocus | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.yourNickname} | ||||||
|  |                 /> | ||||||
|  |  | ||||||
|  |                 <Input {...this.bindField('email')} | ||||||
|  |                     icon="envelope" | ||||||
|  |                     color="blue" | ||||||
|  |                     type="email" | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.yourEmail} | ||||||
|  |                 /> | ||||||
|  |  | ||||||
|  |                 <Input {...this.bindField('password')} | ||||||
|  |                     icon="key" | ||||||
|  |                     color="blue" | ||||||
|  |                     type="password" | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.accountPassword} | ||||||
|  |                 /> | ||||||
|  |  | ||||||
|  |                 <Input {...this.bindField('rePassword')} | ||||||
|  |                     icon="key" | ||||||
|  |                     color="blue" | ||||||
|  |                     type="password" | ||||||
|  |                     required | ||||||
|  |                     placeholder={messages.repeatPassword} | ||||||
|  |                 /> | ||||||
|  |  | ||||||
|  |                 <Checkbox {...this.bindField('rulesAgreement')} | ||||||
|  |                     color="blue" | ||||||
|  |                     required | ||||||
|  |                     label={ | ||||||
|  |                         <Message {...messages.acceptRules} values={{ | ||||||
|  |                             link: ( | ||||||
|  |                                 <a href="#"> | ||||||
|  |                                     <Message {...messages.termsOfService} /> | ||||||
|  |                                 </a> | ||||||
|  |                             ) | ||||||
|  |                         }} /> | ||||||
|  |                     } | ||||||
|  |                 /> | ||||||
|  |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFormSubmit() { | ||||||
|  |         this.props.register(this.serialize()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| export default function Register() { | export default function Register() { | ||||||
|     return { |     return { | ||||||
|         Title: () => ( // TODO: separate component for PageTitle |         Title: () => ( // TODO: separate component for PageTitle | ||||||
| @@ -18,30 +95,9 @@ export default function Register() { | |||||||
|                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} |                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||||
|             </Message> |             </Message> | ||||||
|         ), |         ), | ||||||
|         Body: () => ( |         Body, | ||||||
|             <div> |         Footer: () => ( | ||||||
|                 <Input icon="user" color="blue" type="text" placeholder={messages.yourNickname} /> |             <button className={buttons.blue} type="submit"> | ||||||
|                 <Input icon="envelope" color="blue" type="email" placeholder={messages.yourEmail} /> |  | ||||||
|                 <Input icon="key" color="blue" type="password" placeholder={messages.accountPassword} /> |  | ||||||
|                 <Input icon="key" color="blue" type="password" placeholder={messages.repeatPassword} /> |  | ||||||
|  |  | ||||||
|                 <Checkbox color="blue" label={ |  | ||||||
|                     <Message {...messages.acceptRules} values={{ |  | ||||||
|                         link: ( |  | ||||||
|                             <a href="#"> |  | ||||||
|                                 <Message {...messages.termsOfService} /> |  | ||||||
|                             </a> |  | ||||||
|                         ) |  | ||||||
|                     }} /> |  | ||||||
|                 } /> |  | ||||||
|             </div> |  | ||||||
|         ), |  | ||||||
|         Footer: (props) => ( |  | ||||||
|             <button className={buttons.blue} onClick={(event) => { |  | ||||||
|                 event.preventDefault(); |  | ||||||
|  |  | ||||||
|                 props.history.push('/activation'); |  | ||||||
|             }}> |  | ||||||
|                 <Message {...messages.signUpButton} /> |                 <Message {...messages.signUpButton} /> | ||||||
|             </button> |             </button> | ||||||
|         ), |         ), | ||||||
| @@ -52,46 +108,3 @@ export default function Register() { | |||||||
|         ) |         ) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class _Register extends Component { |  | ||||||
|     displayName = 'Register'; |  | ||||||
|  |  | ||||||
|     render() { |  | ||||||
|         return ( |  | ||||||
|             <div> |  | ||||||
|                 <Message {...messages.registerTitle}> |  | ||||||
|                     {(msg) => <Helmet title={msg} />} |  | ||||||
|                 </Message> |  | ||||||
|  |  | ||||||
|                 <Panel title={<Message {...messages.registerTitle} />}> |  | ||||||
|                     <PanelBody> |  | ||||||
|                         <Input icon="user" color="blue" type="text" placeholder={messages.yourNickname} /> |  | ||||||
|                         <Input icon="envelope" color="blue" type="email" placeholder={messages.yourEmail} /> |  | ||||||
|                         <Input icon="key" color="blue" type="password" placeholder={messages.accountPassword} /> |  | ||||||
|                         <Input icon="key" color="blue" type="password" placeholder={messages.repeatPassword} /> |  | ||||||
|  |  | ||||||
|                         <Checkbox color="blue" label={ |  | ||||||
|                             <Message {...messages.acceptRules} values={{ |  | ||||||
|                                 link: ( |  | ||||||
|                                     <a href="#"> |  | ||||||
|                                         <Message {...messages.termsOfService} /> |  | ||||||
|                                     </a> |  | ||||||
|                                 ) |  | ||||||
|                             }} /> |  | ||||||
|                         } /> |  | ||||||
|                     </PanelBody> |  | ||||||
|                     <PanelFooter> |  | ||||||
|                         <button className={buttons.blue}> |  | ||||||
|                             <Message {...messages.signUpButton} /> |  | ||||||
|                         </button> |  | ||||||
|                     </PanelFooter> |  | ||||||
|                 </Panel> |  | ||||||
|                 <div className={helpLinksStyles}> |  | ||||||
|                     <a href="#"> |  | ||||||
|                         <Message {...activationMessages.didNotReceivedEmail} /> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										104
									
								
								src/components/auth/actions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/components/auth/actions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | import { routeActions } from 'react-router-redux'; | ||||||
|  |  | ||||||
|  | import { updateUser, logout as logoutUser } from 'components/user/actions'; | ||||||
|  | import request from 'services/request'; | ||||||
|  |  | ||||||
|  | export function login({login = '', password = '', rememberMe = false}) { | ||||||
|  |     const PASSWORD_REQUIRED = 'error.password_required'; | ||||||
|  |     const LOGIN_REQUIRED = 'error.login_required'; | ||||||
|  |  | ||||||
|  |     return (dispatch) => | ||||||
|  |         request.post( | ||||||
|  |             '/api/authentication/login', | ||||||
|  |             {login, password, rememberMe} | ||||||
|  |         ) | ||||||
|  |         .then(() => { | ||||||
|  |             dispatch(updateUser({ | ||||||
|  |                 isGuest: false | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |             dispatch(redirectToGoal()); | ||||||
|  |         }) | ||||||
|  |         .catch((resp) => { | ||||||
|  |             if (resp.errors.password === PASSWORD_REQUIRED) { | ||||||
|  |                 dispatch(updateUser({ | ||||||
|  |                     username: login, | ||||||
|  |                     email: login | ||||||
|  |                 })); | ||||||
|  |                 dispatch(routeActions.push('/password')); | ||||||
|  |             } else { | ||||||
|  |                 if (resp.errors.login === LOGIN_REQUIRED && password) { | ||||||
|  |                     dispatch(logout()); | ||||||
|  |                 } | ||||||
|  |                 const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||||
|  |                 dispatch(setError(errorMessage)); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function register({ | ||||||
|  |     email = '', | ||||||
|  |     username = '', | ||||||
|  |     password = '', | ||||||
|  |     rePassword = '', | ||||||
|  |     rulesAgreement = false | ||||||
|  | }) { | ||||||
|  |     return (dispatch) => | ||||||
|  |         request.post( | ||||||
|  |             '/api/signup/register', | ||||||
|  |             {email, username, password, rePassword, rulesAgreement} | ||||||
|  |         ) | ||||||
|  |         .then(() => { | ||||||
|  |             dispatch(routeActions.push('/activation')); | ||||||
|  |         }) | ||||||
|  |         .catch((resp) => { | ||||||
|  |             const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||||
|  |             dispatch(setError(errorMessage)); | ||||||
|  |         }) | ||||||
|  |         ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function activate({key = ''}) { | ||||||
|  |     return (dispatch) => | ||||||
|  |         request.post( | ||||||
|  |             '/api/signup/confirm', | ||||||
|  |             {key} | ||||||
|  |         ) | ||||||
|  |         .then(() => { | ||||||
|  |             dispatch(updateUser({ | ||||||
|  |                 isActive: true | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |             dispatch(redirectToGoal()); | ||||||
|  |         }) | ||||||
|  |         .catch((resp) => { | ||||||
|  |             const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||||
|  |             dispatch(setError(errorMessage)); | ||||||
|  |         }) | ||||||
|  |         ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function redirectToGoal() { | ||||||
|  |     return routeActions.push('/oauth/permissions'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const ERROR = 'error'; | ||||||
|  | export function setError(error) { | ||||||
|  |     return { | ||||||
|  |         type: ERROR, | ||||||
|  |         payload: error, | ||||||
|  |         error: true | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function clearErrors() { | ||||||
|  |     return setError(null); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function logout() { | ||||||
|  |     return (dispatch) => { | ||||||
|  |         dispatch(logoutUser()); | ||||||
|  |         dispatch(routeActions.push('/login')); | ||||||
|  |     }; | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/components/auth/reducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/components/auth/reducer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import { combineReducers } from 'redux'; | ||||||
|  |  | ||||||
|  | import { ERROR } from './actions'; | ||||||
|  |  | ||||||
|  | export default combineReducers({ | ||||||
|  |     error | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | function error( | ||||||
|  |     state = null, | ||||||
|  |     {type, payload = null, error = false} | ||||||
|  | ) { | ||||||
|  |     switch (type) { | ||||||
|  |         case ERROR: | ||||||
|  |             if (!error) { | ||||||
|  |                 throw new Error('Expected payload with error'); | ||||||
|  |             } | ||||||
|  |             return payload; | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |             return state; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,60 +1,166 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import {injectIntl, intlShape} from 'react-intl'; | import { intlShape } from 'react-intl'; | ||||||
|  |  | ||||||
| import icons from './icons.scss'; | import icons from './icons.scss'; | ||||||
| import styles from './form.scss'; | import styles from './form.scss'; | ||||||
|  |  | ||||||
| function Input(props) { | export class Input extends Component { | ||||||
|     var { icon, color = 'green' } = props; |     static displayName = 'Input'; | ||||||
|  |  | ||||||
|     props = { |     static propTypes = { | ||||||
|         type: 'text', |         placeholder: PropTypes.shape({ | ||||||
|         ...props |             id: PropTypes.string | ||||||
|  |         }), | ||||||
|  |         icon: PropTypes.string, | ||||||
|  |         color: PropTypes.oneOf(['green', 'blue', 'red']) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (props.placeholder && props.placeholder.id) { |     static contextTypes = { | ||||||
|         props.placeholder = props.intl.formatMessage(props.placeholder); |         intl: intlShape.isRequired | ||||||
|     } |     }; | ||||||
|  |  | ||||||
|     var baseClass = styles.formRow; |     render() { | ||||||
|     if (icon) { |         let { icon, color = 'green' } = this.props; | ||||||
|         baseClass = styles.formIconRow; |  | ||||||
|         icon = ( |         const props = { | ||||||
|             <div className={classNames(styles.formFieldIcon, icons[icon])} /> |             type: 'text', | ||||||
|  |             ...this.props | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if (props.placeholder && props.placeholder.id) { | ||||||
|  |             props.placeholder = this.context.intl.formatMessage(props.placeholder); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let baseClass = styles.formRow; | ||||||
|  |         if (icon) { | ||||||
|  |             baseClass = styles.formIconRow; | ||||||
|  |             icon = ( | ||||||
|  |                 <div className={classNames(styles.formFieldIcon, icons[icon])} /> | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |             <div className={baseClass}> | ||||||
|  |                 <input ref={this.setEl} className={styles[`${color}TextField`]} {...props} /> | ||||||
|  |                 {icon} | ||||||
|  |             </div> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return ( |     setEl = (el) => { | ||||||
|         <div className={baseClass}> |         this.el = el; | ||||||
|             <input className={styles[`${color}TextField`]} {...props} /> |     }; | ||||||
|             {icon} |  | ||||||
|         </div> |     getValue() { | ||||||
|     ); |         return this.el.value; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| Input.displayName = 'Input'; | export class Checkbox extends Component { | ||||||
| Input.propTypes = { |     static displayName = 'Checkbox'; | ||||||
|     intl: intlShape.isRequired |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const IntlInput = injectIntl(Input); |     static propTypes = { | ||||||
|  |         color: PropTypes.oneOf(['green', 'blue', 'red']) | ||||||
|  |     }; | ||||||
|  |  | ||||||
| export {IntlInput as Input}; |     render() { | ||||||
|  |         const { label, color = 'green' } = this.props; | ||||||
|  |  | ||||||
| export function Checkbox(props) { |         return ( | ||||||
|     var { label, color = 'green' } = props; |             <div className={styles[`${color}CheckboxRow`]}> | ||||||
|  |                 <label className={styles.checkboxContainer}> | ||||||
|  |                     <input ref={this.setEl} className={styles.checkboxInput} type="checkbox" {...this.props} /> | ||||||
|  |                     <div className={styles.checkbox} /> | ||||||
|  |                     {label} | ||||||
|  |                 </label> | ||||||
|  |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return ( |     setEl = (el) => { | ||||||
|         <div className={styles[`${color}CheckboxRow`]}> |         this.el = el; | ||||||
|             <label className={styles.checkboxContainer}> |     }; | ||||||
|                 <input className={styles.checkboxInput} type="checkbox" /> |  | ||||||
|                 <div className={styles.checkbox} /> |     getValue() { | ||||||
|                 {label} |         return this.el.checked ? 1 : 0; | ||||||
|             </label> |     } | ||||||
|         </div> |  | ||||||
|     ); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Checkbox.displayName = 'Checkbox'; | export class Form extends Component { | ||||||
|  |     static displayName = 'Form'; | ||||||
|  |  | ||||||
|  |     static propTypes = { | ||||||
|  |         id: PropTypes.string, // and id, that uniquely identifies form contents | ||||||
|  |         onSubmit: PropTypes.func, | ||||||
|  |         onInvalid: PropTypes.func, | ||||||
|  |         children: PropTypes.oneOfType([ | ||||||
|  |             PropTypes.arrayOf(PropTypes.node), | ||||||
|  |             PropTypes.node | ||||||
|  |         ]) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     static defaultProps = { | ||||||
|  |         id: 'default', | ||||||
|  |         onSubmit() {}, | ||||||
|  |         onInvalid() {} | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     state = { | ||||||
|  |         isTouched: false | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     componentWillReceiveProps(nextProps) { | ||||||
|  |         if (nextProps.id !== this.props.id) { | ||||||
|  |             this.setState({ | ||||||
|  |                 isTouched: false | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         return ( | ||||||
|  |             <form | ||||||
|  |                 className={classNames( | ||||||
|  |                     styles.form, | ||||||
|  |                     { | ||||||
|  |                         [styles.formTouched]: this.state.isTouched | ||||||
|  |                     } | ||||||
|  |                 )} | ||||||
|  |                 onSubmit={this.onFormSubmit} | ||||||
|  |                 noValidate | ||||||
|  |             > | ||||||
|  |                 {this.props.children} | ||||||
|  |             </form> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFormSubmit = (event) => { | ||||||
|  |         event.preventDefault(); | ||||||
|  |  | ||||||
|  |         if (!this.state.isTouched) { | ||||||
|  |             this.setState({ | ||||||
|  |                 isTouched: true | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const form = event.currentTarget; | ||||||
|  |  | ||||||
|  |         if (form.checkValidity()) { | ||||||
|  |             this.props.onSubmit(); | ||||||
|  |         } else { | ||||||
|  |             const firstError = form.querySelectorAll(':invalid')[0]; | ||||||
|  |             firstError.focus(); | ||||||
|  |  | ||||||
|  |             let errorMessage = firstError.validationMessage; | ||||||
|  |             if (firstError.validity.valueMissing) { | ||||||
|  |                 errorMessage = `error.${firstError.name}_required`; | ||||||
|  |             } else if (firstError.validity.typeMismatch) { | ||||||
|  |                 errorMessage = `error.${firstError.name}_invalid`; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.props.onInvalid(errorMessage); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| import React from 'react'; | import React, { Component, PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import classNames from 'classnames'; | ||||||
|  |  | ||||||
| import styles from './panel.scss'; | import styles from './panel.scss'; | ||||||
| import icons from './icons.scss'; | import icons from './icons.scss'; | ||||||
| @@ -56,21 +58,41 @@ export function PanelFooter(props) { | |||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function PanelBodyHeader(props) { | export class PanelBodyHeader extends Component { | ||||||
|     var { type = 'default' } = props; |     static displayName = 'PanelBodyHeader'; | ||||||
|  |  | ||||||
|     var close; |     static propTypes = { | ||||||
|  |         type: PropTypes.oneOf(['default', 'error']), | ||||||
|  |         onClose: PropTypes.func | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     if (type === 'error') { |     render() { | ||||||
|         close = ( |         const {type = 'default', children} = this.props; | ||||||
|             <span className={styles.close} /> |  | ||||||
|  |         let close; | ||||||
|  |         if (type === 'error') { | ||||||
|  |             close = ( | ||||||
|  |                 <span className={styles.close} onClick={this.onClose} /> | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const className = classNames(styles[`${type}BodyHeader`], { | ||||||
|  |             [styles.isClosed]: this.state && this.state.isClosed | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |             <div className={className} {...this.props}> | ||||||
|  |                 {close} | ||||||
|  |                 {children} | ||||||
|  |             </div> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return ( |     onClose = (event) => { | ||||||
|         <div className={styles[`${type}BodyHeader`]} {...props}> |         event.preventDefault(); | ||||||
|             {close} |  | ||||||
|             {props.children} |         this.setState({isClosed: true}); | ||||||
|         </div> |  | ||||||
|     ); |         this.props.onClose(); | ||||||
|  |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -58,3 +58,4 @@ | |||||||
|  |  | ||||||
| @include button-theme('blue', $blue); | @include button-theme('blue', $blue); | ||||||
| @include button-theme('green', $green); | @include button-theme('green', $green); | ||||||
|  | @include button-theme('orange', $orange); | ||||||
|   | |||||||
| @@ -66,8 +66,7 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     &:hover { |     &:hover { | ||||||
|         border-color: #aaa; |         &, | ||||||
|  |  | ||||||
|         ~ .formFieldIcon { |         ~ .formFieldIcon { | ||||||
|             border-color: #aaa; |             border-color: #aaa; | ||||||
|         } |         } | ||||||
| @@ -95,12 +94,14 @@ | |||||||
|     text-align: center; |     text-align: center; | ||||||
|     border: 2px solid lighter($black); |     border: 2px solid lighter($black); | ||||||
|     color: #444; |     color: #444; | ||||||
|  |     cursor: default; | ||||||
|  |  | ||||||
|     @include form-transition(); |     @include form-transition(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @include input-theme('green', $green); | @include input-theme('green', $green); | ||||||
| @include input-theme('blue', $blue); | @include input-theme('blue', $blue); | ||||||
|  | @include input-theme('red', $red); | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -190,3 +191,44 @@ | |||||||
|  |  | ||||||
| @include checkbox-theme('green', $green); | @include checkbox-theme('green', $green); | ||||||
| @include checkbox-theme('blue', $blue); | @include checkbox-theme('blue', $blue); | ||||||
|  | @include checkbox-theme('red', $red); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Form validation | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | .formTouched .textField:invalid { | ||||||
|  |     box-shadow: none; | ||||||
|  |  | ||||||
|  |     &, | ||||||
|  |     ~ .formFieldIcon { | ||||||
|  |         border-color: #3e2727; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ~ .formFieldIcon { | ||||||
|  |         color: #3e2727; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     &:hover { | ||||||
|  |         &, | ||||||
|  |         ~ .formFieldIcon { | ||||||
|  |             border-color: $red; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     &:focus { | ||||||
|  |         border-color: $red; | ||||||
|  |  | ||||||
|  |         ~ .formFieldIcon { | ||||||
|  |             background: $red; | ||||||
|  |             border-color: $red; | ||||||
|  |             color: #fff; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .formTouched .checkboxInput:invalid { | ||||||
|  |     ~ .checkbox { | ||||||
|  |         border-color: $red; | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -74,9 +74,20 @@ $bodyTopBottomPadding: 15px; | |||||||
|  |  | ||||||
| .bodyHeader { | .bodyHeader { | ||||||
|     position: relative; |     position: relative; | ||||||
|  |     overflow: hidden; | ||||||
|     padding: 10px; |     padding: 10px; | ||||||
|     margin: (-$bodyTopBottomPadding) (-$bodyLeftRightPadding); |     margin: (-$bodyTopBottomPadding) (-$bodyLeftRightPadding); | ||||||
|     margin-bottom: 15px; |     margin-bottom: 15px; | ||||||
|  |     max-height: 200px; | ||||||
|  |  | ||||||
|  |     transition: 0.4s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .isClosed { | ||||||
|  |     max-height: 0; | ||||||
|  |     opacity: 0; | ||||||
|  |     padding: 0; | ||||||
|  |     margin: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .errorBodyHeader { | .errorBodyHeader { | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								src/components/user/User.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/components/user/User.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | import { PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | const KEY_USER = 'user'; | ||||||
|  |  | ||||||
|  | export default class User { | ||||||
|  |     /** | ||||||
|  |      * @param  {Object|string|undefined} data plain object or jwt token or empty to load from storage | ||||||
|  |      * | ||||||
|  |      * @return {User} | ||||||
|  |      */ | ||||||
|  |     constructor(data) { | ||||||
|  |         if (!data) { | ||||||
|  |             return this.load(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // TODO: strict value types validation | ||||||
|  |  | ||||||
|  |         const defaults = { | ||||||
|  |             id: null, | ||||||
|  |             token: '', | ||||||
|  |             username: '', | ||||||
|  |             email: '', | ||||||
|  |             avatar: '', | ||||||
|  |             isGuest: true, | ||||||
|  |             isActive: false | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         const user = Object.keys(defaults).reduce((user, key) => { | ||||||
|  |             if (data.hasOwnProperty(key)) { | ||||||
|  |                 user[key] = data[key]; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return user; | ||||||
|  |         }, defaults); | ||||||
|  |  | ||||||
|  |         localStorage.setItem(KEY_USER, JSON.stringify(user)); | ||||||
|  |  | ||||||
|  |         return user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     load() { | ||||||
|  |         try { | ||||||
|  |             return new User(JSON.parse(localStorage.getItem(KEY_USER))); | ||||||
|  |         } catch (error) { | ||||||
|  |             return new User({isGuest: true}); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const userShape = PropTypes.shape({ | ||||||
|  |     id: PropTypes.number, | ||||||
|  |     token: PropTypes.string, | ||||||
|  |     username: PropTypes.string, | ||||||
|  |     email: PropTypes.string, | ||||||
|  |     avatar: PropTypes.string, | ||||||
|  |     isGuest: PropTypes.bool.isRequired, | ||||||
|  |     isActive: PropTypes.bool.isRequired | ||||||
|  | }); | ||||||
							
								
								
									
										23
									
								
								src/components/user/actions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/components/user/actions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | export const UPDATE = 'USER_UPDATE'; | ||||||
|  | /** | ||||||
|  |  * @param  {string|Object} payload jwt token or user object | ||||||
|  |  * @return {Object} action definition | ||||||
|  |  */ | ||||||
|  | export function updateUser(payload) { | ||||||
|  |     return { | ||||||
|  |         type: UPDATE, | ||||||
|  |         payload | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const SET = 'USER_SET'; | ||||||
|  | export function setUser(payload) { | ||||||
|  |     return { | ||||||
|  |         type: SET, | ||||||
|  |         payload | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function logout() { | ||||||
|  |     return setUser({isGuest: true}); | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/components/user/reducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/components/user/reducer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import { UPDATE, SET } from './actions'; | ||||||
|  |  | ||||||
|  | import User from './User'; | ||||||
|  |  | ||||||
|  | export default function user( | ||||||
|  |     state = new User(), | ||||||
|  |     {type, payload = null} | ||||||
|  | ) { | ||||||
|  |     switch (type) { | ||||||
|  |         case UPDATE: | ||||||
|  |             if (!payload) { | ||||||
|  |                 throw new Error('payload is required for user reducer'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new User({ | ||||||
|  |                 ...state, | ||||||
|  |                 ...payload | ||||||
|  |             }); | ||||||
|  |         case SET: | ||||||
|  |             return new User(payload || {}); | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |             return state; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,16 +8,31 @@ import buttons from 'components/ui/buttons.scss'; | |||||||
| import messages from './Userbar.messages.js'; | import messages from './Userbar.messages.js'; | ||||||
| import styles from './userbar.scss'; | import styles from './userbar.scss'; | ||||||
|  |  | ||||||
|  | import { userShape } from 'components/user/User'; | ||||||
|  |  | ||||||
| export default class Userbar extends Component { | export default class Userbar extends Component { | ||||||
|  |     static displayName = 'Userbar'; | ||||||
|  |     static propTypes = { | ||||||
|  |         user: userShape | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|  |         const { user } = this.props; | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|             <div className={styles.userbar}> |             <div className={styles.userbar}> | ||||||
|                 <Link to="/register" className={buttons.blue}> |                 {user.isGuest | ||||||
|                     <Message {...messages.register} /> |                     ? ( | ||||||
|                 </Link> |                         <Link to="/register" className={buttons.blue}> | ||||||
|                 <Link to="/oauth/permissions" className={buttons.blue}> |                             <Message {...messages.register} /> | ||||||
|                     Test oAuth |                         </Link> | ||||||
|                 </Link> |                     ) | ||||||
|  |                     : ( | ||||||
|  |                         <Link to="/logout" className={buttons.blue}> | ||||||
|  |                             <Message {...messages.logout} /> | ||||||
|  |                         </Link> | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|             </div> |             </div> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,5 +4,10 @@ export default defineMessages({ | |||||||
|     register: { |     register: { | ||||||
|         id: 'register', |         id: 'register', | ||||||
|         defaultMessage: 'Join' |         defaultMessage: 'Join' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     logout: { | ||||||
|  |         id: 'logout', | ||||||
|  |         defaultMessage: 'Logout' | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ import { syncHistory, routeReducer } from 'react-router-redux'; | |||||||
| import { IntlProvider } from 'react-intl'; | import { IntlProvider } from 'react-intl'; | ||||||
|  |  | ||||||
| import reducers from 'reducers'; | import reducers from 'reducers'; | ||||||
| import routes from 'routes'; | import routesFactory from 'routes'; | ||||||
|  |  | ||||||
| import 'index.scss'; | import 'index.scss'; | ||||||
|  |  | ||||||
| @@ -51,7 +51,7 @@ ReactDOM.render( | |||||||
|     <IntlProvider locale="en" messages={{}}> |     <IntlProvider locale="en" messages={{}}> | ||||||
|         <ReduxProvider store={store}> |         <ReduxProvider store={store}> | ||||||
|             <Router history={browserHistory}> |             <Router history={browserHistory}> | ||||||
|                 {routes} |                 {routesFactory(store)} | ||||||
|             </Router> |             </Router> | ||||||
|         </ReduxProvider> |         </ReduxProvider> | ||||||
|     </IntlProvider>, |     </IntlProvider>, | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import { connect } from 'react-redux'; |  | ||||||
|  |  | ||||||
| import AppInfo from 'components/auth/AppInfo'; | import AppInfo from 'components/auth/AppInfo'; | ||||||
| import PanelTransition from 'components/auth/PanelTransition'; | import PanelTransition from 'components/auth/PanelTransition'; | ||||||
|  |  | ||||||
| import styles from './auth.scss'; | import styles from './auth.scss'; | ||||||
|  |  | ||||||
| class AuthPage extends Component { | export default class AuthPage extends Component { | ||||||
|     static displayName = 'AuthPage'; |     static displayName = 'AuthPage'; | ||||||
|  |  | ||||||
|     state = { |     state = { | ||||||
| @@ -39,7 +38,3 @@ class AuthPage extends Component { | |||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default connect((state) => ({ |  | ||||||
|     path: state.routing.location.pathname |  | ||||||
| }))(AuthPage); |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ $sidebar-width: 320px; | |||||||
|     right: 0; |     right: 0; | ||||||
|     left: 0; |     left: 0; | ||||||
|     top: 50px; |     top: 50px; | ||||||
|     z-index: 1; |     z-index: 10; | ||||||
|  |  | ||||||
|     background: $black; |     background: $black; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| import React from 'react'; | import React, { PropTypes } from 'react'; | ||||||
|  |  | ||||||
|  | import { connect } from 'react-redux'; | ||||||
| import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||||
|  |  | ||||||
| import Userbar from 'components/userbar/Userbar'; | import Userbar from 'components/userbar/Userbar'; | ||||||
|  |  | ||||||
| import styles from './root.scss'; | import styles from './root.scss'; | ||||||
|  |  | ||||||
| export default function RootPage(props) { | function RootPage(props) { | ||||||
|     return ( |     return ( | ||||||
|         <div className={styles.root}> |         <div className={styles.root}> | ||||||
|             <div className={styles.header}> |             <div className={styles.header}> | ||||||
| @@ -15,7 +16,7 @@ export default function RootPage(props) { | |||||||
|                         Ely.by |                         Ely.by | ||||||
|                     </Link> |                     </Link> | ||||||
|                     <div className={styles.userbar}> |                     <div className={styles.userbar}> | ||||||
|                         <Userbar /> |                         <Userbar {...props} /> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
| @@ -25,3 +26,12 @@ export default function RootPage(props) { | |||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | RootPage.displayName = 'RootPage'; | ||||||
|  | RootPage.propTypes = { | ||||||
|  |     children: PropTypes.element | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default connect((state) => ({ | ||||||
|  |     user: state.user | ||||||
|  | }))(RootPage); | ||||||
|   | |||||||
| @@ -1,2 +1,7 @@ | |||||||
|  | import auth from 'components/auth/reducer'; | ||||||
|  | import user from 'components/user/reducer'; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|  |     auth, | ||||||
|  |     user | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -10,29 +10,46 @@ import Login from 'components/auth/Login'; | |||||||
| import Permissions from 'components/auth/Permissions'; | import Permissions from 'components/auth/Permissions'; | ||||||
| import Activation from 'components/auth/Activation'; | import Activation from 'components/auth/Activation'; | ||||||
| import Password from 'components/auth/Password'; | import Password from 'components/auth/Password'; | ||||||
|  | import Logout from 'components/auth/Logout'; | ||||||
|  |  | ||||||
| function requireAuth(nextState, replace) { | export default function routesFactory(store) { | ||||||
|     // if (!auth.loggedIn()) { |     function checkAuth(nextState, replace) { | ||||||
|         replace({ |         const state = store.getState(); | ||||||
|             pathname: '/login', |  | ||||||
|             state: { |         let forcePath; | ||||||
|                 nextPathname: nextState.location.pathname |         if (!state.user.isGuest) { | ||||||
|  |             if (!state.user.isActive) { | ||||||
|  |                 forcePath = '/activation'; | ||||||
|  |             } else { | ||||||
|  |                 forcePath = '/oauth/permissions'; | ||||||
|             } |             } | ||||||
|         }); |         } else { | ||||||
|     // } |             if (state.user.email || state.user.username) { | ||||||
|  |                 forcePath = '/password'; | ||||||
|  |             } else { | ||||||
|  |                 forcePath = '/login'; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (forcePath && state.routing.location.pathname !== forcePath) { | ||||||
|  |             replace({pathname: forcePath}); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return ( | ||||||
|  |         <Route path="/" component={RootPage}> | ||||||
|  |             <IndexRoute component={IndexPage} onEnter={checkAuth} /> | ||||||
|  |  | ||||||
|  |             <Route path="auth" component={AuthPage}> | ||||||
|  |                 <Route path="/login" components={new Login()} onEnter={checkAuth} /> | ||||||
|  |                 <Route path="/password" components={new Password()} onEnter={checkAuth} /> | ||||||
|  |                 <Route path="/register" components={new Register()} /> | ||||||
|  |                 <Route path="/activation" components={new Activation()} /> | ||||||
|  |                 <Route path="/oauth/permissions" components={new Permissions()} onEnter={checkAuth} /> | ||||||
|  |                 <Route path="/oauth/:id" component={Permissions} /> | ||||||
|  |             </Route> | ||||||
|  |  | ||||||
|  |             <Route path="logout" component={Logout} /> | ||||||
|  |         </Route> | ||||||
|  |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default ( |  | ||||||
|   <Route path="/" component={RootPage}> |  | ||||||
|     <IndexRoute component={IndexPage} onEnter={requireAuth} /> |  | ||||||
|  |  | ||||||
|     <Route path="auth" component={AuthPage}> |  | ||||||
|         <Route path="/login" components={new Login()} /> |  | ||||||
|         <Route path="/password" components={new Password()} /> |  | ||||||
|         <Route path="/register" components={new Register()} /> |  | ||||||
|         <Route path="/activation" components={new Activation()} /> |  | ||||||
|         <Route path="/oauth/permissions" components={new Permissions()} /> |  | ||||||
|         <Route path="/oauth/:id" component={Permissions} /> |  | ||||||
|     </Route> |  | ||||||
|   </Route> |  | ||||||
| ); |  | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								src/services/request.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/services/request.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | function serialize(data) { | ||||||
|  |     return Object.keys(data) | ||||||
|  |         .map( | ||||||
|  |             (keyName) => | ||||||
|  |                 [keyName, data[keyName]] | ||||||
|  |                     .map(encodeURIComponent) | ||||||
|  |                     .join('=') | ||||||
|  |         ) | ||||||
|  |         .join('&') | ||||||
|  |         ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     post(url, data) { | ||||||
|  |         return fetch(url, { | ||||||
|  |             method: 'POST', | ||||||
|  |             headers: { | ||||||
|  |                 Accept: 'application/json', | ||||||
|  |                 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' | ||||||
|  |             }, | ||||||
|  |             body: serialize(data) | ||||||
|  |         }) | ||||||
|  |         .then((resp) => resp.json()) | ||||||
|  |         .then((resp) => Promise[resp.success ? 'resolve' : 'reject'](resp)) | ||||||
|  |         ; | ||||||
|  |     } | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user