mirror of
				https://github.com/elyby/accounts-frontend.git
				synced 2025-05-31 14:11:58 +05:30 
			
		
		
		
	Merge branch 'master' into profile
This commit is contained in:
		| @@ -18,11 +18,11 @@ | ||||
|     "history": "^1.17.0", | ||||
|     "intl-format-cache": "^2.0.4", | ||||
|     "intl-messageformat": "^1.1.0", | ||||
|     "react": "^0.14.0", | ||||
|     "react-dom": "^0.14.3", | ||||
|     "react": "^15.0.0-rc.1", | ||||
|     "react-dom": "^15.0.0-rc.1", | ||||
|     "react-height": "^2.0.3", | ||||
|     "react-helmet": "^2.3.1", | ||||
|     "react-intl": "=2.0.0-beta-2", | ||||
|     "react-intl": "=v2.0.0-rc-1", | ||||
|     "react-motion": "^0.4.0", | ||||
|     "react-redux": "^4.0.0", | ||||
|     "react-router": "^2.0.0", | ||||
|   | ||||
| @@ -3,30 +3,33 @@ | ||||
|  */ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
|  | ||||
| import AuthError from './AuthError'; | ||||
| import AuthError from 'components/auth/authError/AuthError'; | ||||
| import { userShape } from 'components/user/User'; | ||||
|  | ||||
| export default class BaseAuthBody extends Component { | ||||
|     static propTypes = { | ||||
|     static contextTypes = { | ||||
|         clearErrors: PropTypes.func.isRequired, | ||||
|         resolve: PropTypes.func.isRequired, | ||||
|         reject: PropTypes.func.isRequired, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string | ||||
|         }) | ||||
|             error: PropTypes.string, | ||||
|             scopes: PropTypes.array | ||||
|         }), | ||||
|         user: userShape | ||||
|     }; | ||||
|  | ||||
|     renderErrors() { | ||||
|         return this.props.auth.error | ||||
|             ? <AuthError error={this.props.auth.error} onClose={this.onClearErrors} /> | ||||
|         return this.context.auth.error | ||||
|             ? <AuthError error={this.context.auth.error} onClose={this.onClearErrors} /> | ||||
|             : '' | ||||
|             ; | ||||
|     } | ||||
|  | ||||
|     onFormSubmit() { | ||||
|         this.props.resolve(this.serialize()); | ||||
|         this.context.resolve(this.serialize()); | ||||
|     } | ||||
|  | ||||
|     onClearErrors = this.props.clearErrors; | ||||
|     onClearErrors = this.context.clearErrors; | ||||
|  | ||||
|     form = {}; | ||||
|  | ||||
| @@ -39,6 +42,35 @@ export default class BaseAuthBody extends Component { | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Fixes some issues with scroll, when input beeing focused | ||||
|      * | ||||
|      * When an element is focused, by default browsers will scroll its parents to display | ||||
|      * focused item to user. This behavior may cause unexpected visual effects, when | ||||
|      * you animating apearing of an input (e.g. transform) and auto focusing it. In | ||||
|      * that case the browser will scroll the parent container so that input will be | ||||
|      * visible. | ||||
|      * This method will fix that issue by finding parent with overflow: hidden and | ||||
|      * reseting its scrollLeft value to 0. | ||||
|      * | ||||
|      * Usage: | ||||
|      * <input autoFocus onFocus={this.fixAutoFocus} /> | ||||
|      * | ||||
|      * @param {Object} event | ||||
|      */ | ||||
|     fixAutoFocus = (event) => { | ||||
|         let el = event.target; | ||||
|  | ||||
|         while (el.parentNode) { | ||||
|             el = el.parentNode; | ||||
|  | ||||
|             if (getComputedStyle(el).overflow === 'hidden') { | ||||
|                 el.scrollLeft = 0; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     serialize() { | ||||
|         return Object.keys(this.form).reduce((acc, key) => { | ||||
|             acc[key] = this.form[key].getValue(); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss'; | ||||
| import panelStyles from 'components/ui/panel.scss'; | ||||
| import icons from 'components/ui/icons.scss'; | ||||
| import authFlow from 'services/authFlow'; | ||||
| import { userShape } from 'components/user/User'; | ||||
|  | ||||
| import * as actions from './actions'; | ||||
|  | ||||
| @@ -21,6 +22,7 @@ class PanelTransition extends Component { | ||||
|     static displayName = 'PanelTransition'; | ||||
|  | ||||
|     static propTypes = { | ||||
|         // context props | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
| @@ -28,15 +30,45 @@ class PanelTransition extends Component { | ||||
|                 password: PropTypes.string | ||||
|             }) | ||||
|         }).isRequired, | ||||
|         user: userShape.isRequired, | ||||
|         setError: React.PropTypes.func.isRequired, | ||||
|         clearErrors: React.PropTypes.func.isRequired, | ||||
|         resolve: React.PropTypes.func.isRequired, | ||||
|         reject: React.PropTypes.func.isRequired, | ||||
|  | ||||
|         // local props | ||||
|         path: PropTypes.string.isRequired, | ||||
|         Title: PropTypes.element.isRequired, | ||||
|         Body: PropTypes.element.isRequired, | ||||
|         Footer: PropTypes.element.isRequired, | ||||
|         Links: PropTypes.element.isRequired | ||||
|         Title: PropTypes.element, | ||||
|         Body: PropTypes.element, | ||||
|         Footer: PropTypes.element, | ||||
|         Links: PropTypes.element, | ||||
|         children: PropTypes.element | ||||
|     }; | ||||
|  | ||||
|     static childContextTypes = { | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
|                 login: PropTypes.string, | ||||
|                 password: PropTypes.string | ||||
|             }) | ||||
|         }), | ||||
|         user: userShape, | ||||
|         clearErrors: React.PropTypes.func, | ||||
|         resolve: PropTypes.func, | ||||
|         reject: PropTypes.func | ||||
|     }; | ||||
|  | ||||
|     getChildContext() { | ||||
|         return { | ||||
|             auth: this.props.auth, | ||||
|             user: this.props.user, | ||||
|             clearErrors: this.props.clearErrors, | ||||
|             resolve: this.props.resolve, | ||||
|             reject: this.props.reject | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     state = { | ||||
|         height: {}, | ||||
|         contextHeight: 0 | ||||
| @@ -70,6 +102,12 @@ class PanelTransition extends Component { | ||||
|  | ||||
|         const {path, Title, Body, Footer, Links} = this.props; | ||||
|  | ||||
|         if (this.props.children) { | ||||
|             return this.props.children; | ||||
|         } else if (!Title || !Body || !Footer || !Links) { | ||||
|             throw new Error('Title, Body, Footer and Links are required'); | ||||
|         } | ||||
|  | ||||
|         return ( | ||||
|             <TransitionMotion | ||||
|                 styles={[ | ||||
| @@ -91,7 +129,7 @@ class PanelTransition extends Component { | ||||
|  | ||||
|                     const contentHeight = { | ||||
|                         overflow: 'hidden', | ||||
|                         height: forceHeight ? common.switchContextHeightSpring : 'auto' | ||||
|                         height: forceHeight ? common.style.switchContextHeightSpring : 'auto' | ||||
|                     }; | ||||
|  | ||||
|                     const bodyHeight = { | ||||
| @@ -141,6 +179,7 @@ class PanelTransition extends Component { | ||||
|  | ||||
|     /** | ||||
|      * @param  {Object} config | ||||
|      * @param  {string} config.key | ||||
|      * @param  {Object} [options] | ||||
|      * @param  {Object} [options.isLeave=false] - true, if this is a leave transition | ||||
|      * | ||||
| @@ -155,7 +194,7 @@ class PanelTransition extends Component { | ||||
|             '/password': 1, | ||||
|             '/activation': 1, | ||||
|             '/oauth/permissions': -1, | ||||
|             '/password-change': 1, | ||||
|             '/change-password': 1, | ||||
|             '/forgot-password': 1 | ||||
|         }; | ||||
|         const sign = map[key]; | ||||
| @@ -177,7 +216,7 @@ class PanelTransition extends Component { | ||||
|             '/register': not('/activation') ? 'Y' : 'X', | ||||
|             '/activation': not('/register') ? 'Y' : 'X', | ||||
|             '/oauth/permissions': 'Y', | ||||
|             '/password-change': 'Y', | ||||
|             '/change-password': 'Y', | ||||
|             '/forgot-password': not('/password') && not('/login') ? 'Y' : 'X' | ||||
|         }; | ||||
|  | ||||
| @@ -235,7 +274,7 @@ class PanelTransition extends Component { | ||||
|             <div key={`header${key}`} style={style}> | ||||
|                 {hasBackButton ? backButton : null} | ||||
|                 <div style={scrollStyle}> | ||||
|                     {React.cloneElement(Title, this.props)} | ||||
|                     {Title} | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
| @@ -263,7 +302,6 @@ class PanelTransition extends Component { | ||||
|         return ( | ||||
|             <ReactHeight key={`body${key}`} style={style} onHeightReady={this.onUpdateHeight}> | ||||
|                 {React.cloneElement(Body, { | ||||
|                     ...this.props, | ||||
|                     ref: (body) => { | ||||
|                         this.body = body; | ||||
|                     } | ||||
| @@ -279,7 +317,7 @@ class PanelTransition extends Component { | ||||
|  | ||||
|         return ( | ||||
|             <div key={`footer${key}`} style={style}> | ||||
|                 {React.cloneElement(Footer, this.props)} | ||||
|                 {Footer} | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| @@ -291,15 +329,15 @@ class PanelTransition extends Component { | ||||
|  | ||||
|         return ( | ||||
|             <div key={`links${key}`} style={style}> | ||||
|                 {React.cloneElement(Links, this.props)} | ||||
|                 {Links} | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param  {string} key | ||||
|      * @param  {Object} props | ||||
|      * @param  {number} props.opacitySpring | ||||
|      * @param  {Object} style | ||||
|      * @param  {number} style.opacitySpring | ||||
|      * | ||||
|      * @return {Object} | ||||
|      */ | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { routeActions } from 'react-router-redux'; | ||||
|  | ||||
| import { updateUser, logout as logoutUser, authenticate } from 'components/user/actions'; | ||||
| import { updateUser, logout as logoutUser, changePassword as changeUserPassword, authenticate } from 'components/user/actions'; | ||||
| import request from 'services/request'; | ||||
|  | ||||
| export function login({login = '', password = '', rememberMe = false}) { | ||||
| @@ -32,7 +32,7 @@ export function login({login = '', password = '', rememberMe = false}) { | ||||
|                     username: login, | ||||
|                     email: login | ||||
|                 })); | ||||
|             } else { | ||||
|             } else if (resp.errors) { | ||||
|                 if (resp.errors.login === LOGIN_REQUIRED && password) { | ||||
|                     dispatch(logout()); | ||||
|                 } | ||||
| @@ -46,6 +46,25 @@ export function login({login = '', password = '', rememberMe = false}) { | ||||
|         ; | ||||
| } | ||||
|  | ||||
| export function changePassword({ | ||||
|     password = '', | ||||
|     newPassword = '', | ||||
|     newRePassword = '' | ||||
| }) { | ||||
|     return (dispatch) => | ||||
|         dispatch(changeUserPassword({password, newPassword, newRePassword})) | ||||
|             .catch((resp) => { | ||||
|                 if (resp.errors) { | ||||
|                     const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||
|                     dispatch(setError(errorMessage)); | ||||
|                     throw new Error(errorMessage); | ||||
|                 } | ||||
|  | ||||
|                 // TODO: log unexpected errors | ||||
|             }) | ||||
|             ; | ||||
| } | ||||
|  | ||||
| export function register({ | ||||
|     email = '', | ||||
|     username = '', | ||||
| @@ -67,9 +86,11 @@ export function register({ | ||||
|             dispatch(routeActions.push('/activation')); | ||||
|         }) | ||||
|         .catch((resp) => { | ||||
|             const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||
|             dispatch(setError(errorMessage)); | ||||
|             throw new Error(errorMessage); | ||||
|             if (resp.errors) { | ||||
|                 const errorMessage = resp.errors[Object.keys(resp.errors)[0]]; | ||||
|                 dispatch(setError(errorMessage)); | ||||
|                 throw new Error(errorMessage); | ||||
|             } | ||||
|  | ||||
|             // TODO: log unexpected errors | ||||
|         }) | ||||
| @@ -151,6 +172,7 @@ export function oAuthComplete(params = {}) { | ||||
|             if (resp.statusCode === 401 && resp.error === 'access_denied') { | ||||
|                 // user declined permissions | ||||
|                 return { | ||||
|                     success: false, | ||||
|                     redirectUri: resp.redirectUri | ||||
|                 }; | ||||
|             } | ||||
| @@ -168,6 +190,18 @@ export function oAuthComplete(params = {}) { | ||||
|                 error.acceptRequired = true; | ||||
|                 throw error; | ||||
|             } | ||||
|         }) | ||||
|         .then((resp) => { | ||||
|             if (resp.redirectUri === 'static_page' || resp.redirectUri === 'static_page_with_code') { | ||||
|                 resp.displayCode = resp.redirectUri === 'static_page_with_code'; | ||||
|                 dispatch(setOAuthCode({ | ||||
|                     success: resp.success, | ||||
|                     code: resp.code, | ||||
|                     displayCode: resp.displayCode | ||||
|                 })); | ||||
|             } | ||||
|  | ||||
|             return resp; | ||||
|         }); | ||||
|     }; | ||||
| } | ||||
| @@ -224,6 +258,18 @@ export function setOAuthRequest(oauth) { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export const SET_OAUTH_RESULT = 'set_oauth_result'; | ||||
| export function setOAuthCode(oauth) { | ||||
|     return { | ||||
|         type: SET_OAUTH_RESULT, | ||||
|         payload: { | ||||
|             success: oauth.success, | ||||
|             code: oauth.code, | ||||
|             displayCode: oauth.displayCode | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export const SET_SCOPES = 'set_scopes'; | ||||
| export function setScopes(scopes) { | ||||
|     if (!(scopes instanceof Array)) { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { PropTypes } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| @@ -6,20 +6,12 @@ import Helmet from 'react-helmet'; | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
| import { Input } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import styles from './activation.scss'; | ||||
| import messages from './Activation.messages'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
|                 login: PropTypes.stirng | ||||
|             }) | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'ActivationBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
| @@ -31,7 +23,7 @@ class Body extends BaseAuthBody { | ||||
| 
 | ||||
|                     <div className={styles.descriptionText}> | ||||
|                         <Message {...messages.activationMailWasSent} values={{ | ||||
|                             email: (<b>{this.props.user.email}</b>) | ||||
|                             email: (<b>{this.context.user.email}</b>) | ||||
|                         }} /> | ||||
|                     </div> | ||||
|                 </div> | ||||
| @@ -40,6 +32,7 @@ class Body extends BaseAuthBody { | ||||
|                         color="blue" | ||||
|                         className={styles.activationCodeInput} | ||||
|                         autoFocus | ||||
|                         onFocus={this.fixAutoFocus} | ||||
|                         required | ||||
|                         placeholder={messages.enterTheCode} | ||||
|                     /> | ||||
| @@ -11,8 +11,8 @@ export default class AppInfo extends Component { | ||||
|     static displayName = 'AppInfo'; | ||||
| 
 | ||||
|     static propTypes = { | ||||
|         name: PropTypes.string.isRequired, | ||||
|         description: PropTypes.string.isRequired, | ||||
|         name: PropTypes.string, | ||||
|         description: PropTypes.string, | ||||
|         onGoToAuth: PropTypes.func.isRequired | ||||
|     }; | ||||
| 
 | ||||
| @@ -71,6 +71,10 @@ export default class AuthError extends Component { | ||||
|         '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} /> | ||||
|         'error.key_not_exists': () => <Message {...messages.keyNotExists} />, | ||||
| 
 | ||||
|         'error.newPassword_required': () => <Message {...messages.newPasswordRequired} />, | ||||
|         'error.newRePassword_required': () => <Message {...messages.newRePasswordRequired} />, | ||||
|         'error.newRePassword_does_not_match': () => <Message {...messages.passwordsDoesNotMatch} /> | ||||
|     }; | ||||
| } | ||||
| @@ -31,6 +31,16 @@ export default defineMessages({ | ||||
|         defaultMessage: 'Please enter password' | ||||
|     }, | ||||
| 
 | ||||
|     newPasswordRequired: { | ||||
|         id: 'newPasswordRequired', | ||||
|         defaultMessage: 'Please enter new password' | ||||
|     }, | ||||
| 
 | ||||
|     newRePasswordRequired: { | ||||
|         id: 'newRePasswordRequired', | ||||
|         defaultMessage: 'Please repeat new password' | ||||
|     }, | ||||
| 
 | ||||
|     usernameRequired: { | ||||
|         id: 'usernameRequired', | ||||
|         defaultMessage: 'Username is required' | ||||
| @@ -2,21 +2,17 @@ import React, { PropTypes } from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| import { Link } from 'react-router'; | ||||
| 
 | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
| import { Input } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import passwordChangedMessages from './PasswordChange.messages'; | ||||
| 
 | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import icons from 'components/ui/icons.scss'; | ||||
| import styles from './passwordChange.scss'; | ||||
| 
 | ||||
| import messages from './ChangePassword.messages'; | ||||
| import styles from './changePassword.scss'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes | ||||
|     }; | ||||
|     static displayName = 'ChangePasswordBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
| @@ -28,49 +24,66 @@ class Body extends BaseAuthBody { | ||||
|                 </div> | ||||
| 
 | ||||
|                 <p className={styles.descriptionText}> | ||||
|                     <Message {...passwordChangedMessages.changePasswordMessage} /> | ||||
|                     <Message {...messages.changePasswordMessage} /> | ||||
|                 </p> | ||||
| 
 | ||||
|                 <Input {...this.bindField('password')} | ||||
|                     icon="key" | ||||
|                     color="darkBlue" | ||||
|                     type="password" | ||||
|                     autoFocus | ||||
|                     onFocus={this.fixAutoFocus} | ||||
|                     required | ||||
|                     placeholder={messages.currentPassword} | ||||
|                 /> | ||||
| 
 | ||||
|                 <Input {...this.bindField('newPassword')} | ||||
|                     icon="key" | ||||
|                     color="darkBlue" | ||||
|                     autoFocus | ||||
|                     type="password" | ||||
|                     required | ||||
|                     placeholder={passwordChangedMessages.newPassword} | ||||
|                     placeholder={messages.newPassword} | ||||
|                 /> | ||||
| 
 | ||||
|                 <Input {...this.bindField('newRePassword')} | ||||
|                     icon="key" | ||||
|                     color="darkBlue" | ||||
|                     type="password" | ||||
|                     required | ||||
|                     placeholder={passwordChangedMessages.newRePassword} | ||||
|                     placeholder={messages.newRePassword} | ||||
|                 /> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default function PasswordChange() { | ||||
|     return { | ||||
| export default function ChangePassword() { | ||||
|     const componentsMap = { | ||||
|         Title: () => ( // TODO: separate component for PageTitle | ||||
|             <Message {...passwordChangedMessages.changePasswordTitle}> | ||||
|             <Message {...messages.changePasswordTitle}> | ||||
|                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||
|             </Message> | ||||
|         ), | ||||
|         Body, | ||||
|         Footer: () => ( | ||||
|             <button className={buttons.darkBlue} type="submit"> | ||||
|                 <Message {...passwordChangedMessages.change} /> | ||||
|                 <Message {...messages.change} /> | ||||
|             </button> | ||||
|         ), | ||||
|         Links: (props) => ( | ||||
|         Links: (props, context) => ( | ||||
|             <a href="#" onClick={(event) => { | ||||
|                 event.preventDefault(); | ||||
| 
 | ||||
|                 props.reject(); | ||||
|                 context.reject(); | ||||
|             }}> | ||||
|                 <Message {...passwordChangedMessages.skipThisStep} /> | ||||
|                 <Message {...messages.skipThisStep} /> | ||||
|             </a> | ||||
|         ) | ||||
|     }; | ||||
| 
 | ||||
|     componentsMap.Links.contextTypes = { | ||||
|         reject: PropTypes.func.isRequired | ||||
|     }; | ||||
| 
 | ||||
|     return componentsMap; | ||||
| } | ||||
| @@ -17,6 +17,10 @@ export default defineMessages({ | ||||
|         id: 'change', | ||||
|         defaultMessage: 'Change' | ||||
|     }, | ||||
|     currentPassword: { | ||||
|         id: 'currentPassword', | ||||
|         defaultMessage: 'Enter current password' | ||||
|     }, | ||||
|     newPassword: { | ||||
|         id: 'newPassword', | ||||
|         defaultMessage: 'Enter new password' | ||||
							
								
								
									
										122
									
								
								src/components/auth/finish/Finish.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/components/auth/finish/Finish.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
|  | ||||
| import { connect } from 'react-redux'; | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import classNames from 'classnames'; | ||||
| import Helmet from 'react-helmet'; | ||||
|  | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
|  | ||||
| import messages from './Finish.messages'; | ||||
| import styles from './finish.scss'; | ||||
|  | ||||
| class Finish extends Component { | ||||
|     static displayName = 'Finish'; | ||||
|  | ||||
|     static propTypes = { | ||||
|         appName: PropTypes.string.isRequired, | ||||
|         code: PropTypes.string.isRequired, | ||||
|         displayCode: PropTypes.bool, | ||||
|         success: PropTypes.bool | ||||
|     }; | ||||
|  | ||||
|     state = { | ||||
|         isCopySupported: document.queryCommandSupported && document.queryCommandSupported('copy') | ||||
|     }; | ||||
|  | ||||
|     render() { | ||||
|         const {appName, code, state, displayCode, success} = this.props; | ||||
|         const {isCopySupported} = this.state; | ||||
|         const authData = JSON.stringify({ | ||||
|             auth_code: code, | ||||
|             state: state | ||||
|         }); | ||||
|  | ||||
|         history.pushState(null, null, `#${authData}`); | ||||
|  | ||||
|         return ( | ||||
|             <div className={styles.finishPage}> | ||||
|                 <Helmet title={authData} /> | ||||
|  | ||||
|                 {success ? ( | ||||
|                     <div> | ||||
|                         <div className={styles.successBackground}></div> | ||||
|                         <div className={styles.greenTitle}> | ||||
|                             <Message {...messages.authForAppSuccessful} values={{ | ||||
|                                 appName: (<span className={styles.appName}>{appName}</span>) | ||||
|                             }} /> | ||||
|                         </div> | ||||
|                         {displayCode ? ( | ||||
|                             <div> | ||||
|                                 <div className={styles.description}> | ||||
|                                     <Message {...messages.passCodeToApp} values={{appName}} /> | ||||
|                                 </div> | ||||
|                                 <div className={styles.codeContainer}> | ||||
|                                     <div className={styles.code} ref={this.setCode}>{code}</div> | ||||
|                                 </div> | ||||
|                                 {isCopySupported ? ( | ||||
|                                     <button | ||||
|                                         className={classNames(buttons.smallButton, buttons.green)} | ||||
|                                         onClick={this.handleCopyClick} | ||||
|                                     > | ||||
|                                         <Message {...messages.copy} /> | ||||
|                                     </button> | ||||
|                                 ) : ( | ||||
|                                     '' | ||||
|                                 )} | ||||
|                             </div> | ||||
|                         ) : ( | ||||
|                             <div className={styles.description}> | ||||
|                                 <Message {...messages.waitAppReaction} /> | ||||
|                             </div> | ||||
|                         )} | ||||
|                     </div> | ||||
|                 ) : ( | ||||
|                     <div> | ||||
|                         <div className={styles.failBackground}></div> | ||||
|                         <div className={styles.redTitle}> | ||||
|                             <Message {...messages.authForAppFailed} values={{ | ||||
|                                 appName: (<span className={styles.appName}>{appName}</span>) | ||||
|                             }} /> | ||||
|                         </div> | ||||
|                         <div className={styles.description}> | ||||
|                             <Message {...messages.waitAppReaction} /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 )} | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     handleCopyClick = (event) => { | ||||
|         event.preventDefault(); | ||||
|         // http://stackoverflow.com/a/987376/5184751 | ||||
|  | ||||
|         try { | ||||
|             const selection = window.getSelection(); | ||||
|             const range = document.createRange(); | ||||
|             range.selectNodeContents(this.code); | ||||
|             selection.removeAllRanges(); | ||||
|             selection.addRange(range); | ||||
|  | ||||
|             const successful = document.execCommand('copy'); | ||||
|             selection.removeAllRanges(); | ||||
|  | ||||
|             // TODO: было бы ещё неплохо сделать какую-то анимацию, вроде "Скопировано", | ||||
|             // ибо сейчас после клика как-то неубедительно, скопировалось оно или нет | ||||
|             console.log('Copying text command was ' + (successful ? 'successful' : 'unsuccessful')); | ||||
|         } catch (err) {} | ||||
|     }; | ||||
|  | ||||
|     setCode = (el) => { | ||||
|         this.code = el; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export default connect(({auth}) => ({ | ||||
|     appName: auth.client.name, | ||||
|     code: auth.oauth.code, | ||||
|     displayCode: auth.oauth.displayCode, | ||||
|     state: auth.oauth.state, | ||||
|     success: auth.oauth.success | ||||
| }))(Finish); | ||||
							
								
								
									
										29
									
								
								src/components/auth/finish/Finish.messages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/auth/finish/Finish.messages.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import { defineMessages } from 'react-intl'; | ||||
|  | ||||
| export default defineMessages({ | ||||
|     authForAppSuccessful: { | ||||
|         id: 'authForAppSuccessful', | ||||
|         defaultMessage: 'Authorization for {appName} was successfully completed' | ||||
|         // defaultMessage: 'Авторизация для {appName} успешно выполнена' | ||||
|     }, | ||||
|     authForAppFailed: { | ||||
|         id: 'authForAppFailed', | ||||
|         defaultMessage: 'Authorization for {appName} was failed' | ||||
|         // defaultMessage: 'Авторизация для {appName} не удалась' | ||||
|     }, | ||||
|     waitAppReaction: { | ||||
|         id: 'waitAppReaction', | ||||
|         defaultMessage: 'Please, wait till your application response' | ||||
|         // defaultMessage: 'Пожалуйста, дождитесь реакции вашего приложения' | ||||
|     }, | ||||
|     passCodeToApp: { | ||||
|         id: 'passCodeToApp', | ||||
|         defaultMessage: 'To complete authorization process, please, provide the following code to {appName}' | ||||
|         // defaultMessage: 'Чтобы завершить процесс авторизации, пожалуйста, передай {appName} этот код' | ||||
|     }, | ||||
|     copy: { | ||||
|         id: 'copy', | ||||
|         defaultMessage: 'Copy' | ||||
|         // defaultMessage: 'Скопировать' | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										76
									
								
								src/components/auth/finish/finish.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/components/auth/finish/finish.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| @import '~components/ui/colors.scss'; | ||||
| @import '~components/ui/fonts.scss'; | ||||
|  | ||||
| .finishPage { | ||||
|     font-family: $font-family-title; | ||||
|     position: relative; | ||||
|     max-width: 515px; | ||||
|     padding-top: 40px; | ||||
|     margin: 0 auto; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .iconBackground { | ||||
|     position: absolute; | ||||
|     top: -15px; | ||||
|     transform: translateX(-50%); | ||||
|     font-size: 200px; | ||||
|     color: #e0d9cf; | ||||
|     z-index: -1; | ||||
| } | ||||
|  | ||||
| .successBackground { | ||||
|     composes: checkmark from 'components/ui/icons.scss'; | ||||
|     @extend .iconBackground; | ||||
| } | ||||
|  | ||||
| .failBackground { | ||||
|     composes: close from 'components/ui/icons.scss'; | ||||
|     @extend .iconBackground; | ||||
| } | ||||
|  | ||||
| .title { | ||||
|     font-size: 22px; | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .greenTitle { | ||||
|     composes: title; | ||||
|  | ||||
|     color: $green; | ||||
|  | ||||
|     .appName { | ||||
|         color: darker($green); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .redTitle { | ||||
|     composes: title; | ||||
|  | ||||
|     color: $red; | ||||
|  | ||||
|     .appName { | ||||
|         color: darker($red); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .description { | ||||
|     font-size: 18px; | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .codeContainer { | ||||
|     margin-bottom: 5px; | ||||
|     margin-top: 35px; | ||||
| } | ||||
|  | ||||
| .code { | ||||
|     $border: 5px solid darker($green); | ||||
|  | ||||
|     display: inline-block; | ||||
|     border-right: $border; | ||||
|     border-left: $border; | ||||
|     padding: 5px 10px; | ||||
|     word-break: break-all; | ||||
|     text-align: center; | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { PropTypes } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| @@ -6,22 +6,13 @@ import Helmet from 'react-helmet'; | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
| import { Input } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import messages from './ForgotPassword.messages'; | ||||
| 
 | ||||
| import styles from './forgotPassword.scss'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes, | ||||
|         //login: PropTypes.func.isRequired, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
|                 email: PropTypes.stirng | ||||
|             }) | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'ForgotPasswordBody'; | ||||
| 
 | ||||
|     // Если юзер вводил своё мыло во время попытки авторизации, то почему бы его сюда автоматически не подставить? | ||||
|     render() { | ||||
| @@ -37,6 +28,7 @@ class Body extends BaseAuthBody { | ||||
|                     icon="envelope" | ||||
|                     color="lightViolet" | ||||
|                     autoFocus | ||||
|                     onFocus={this.fixAutoFocus} | ||||
|                     required | ||||
|                     placeholder={messages.accountEmail} | ||||
|                 /> | ||||
| @@ -49,7 +41,6 @@ class Body extends BaseAuthBody { | ||||
| 
 | ||||
|     onFormSubmit() { | ||||
|         // TODO: обработчик отправки письма с инструкцией по смене аккаунта | ||||
|         //this.props.login(this.serialize()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { PropTypes } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| @@ -7,20 +7,12 @@ import { Link } from 'react-router'; | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
| import { Input } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import passwordMessages from 'components/auth/password/Password.messages'; | ||||
| import messages from './Login.messages'; | ||||
| import passwordMessages from './Password.messages'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
|                 login: PropTypes.stirng | ||||
|             }) | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'LoginBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
| @@ -30,6 +22,7 @@ class Body extends BaseAuthBody { | ||||
|                 <Input {...this.bindField('login')} | ||||
|                     icon="envelope" | ||||
|                     autoFocus | ||||
|                     onFocus={this.fixAutoFocus} | ||||
|                     required | ||||
|                     placeholder={messages.emailOrUsername} | ||||
|                 /> | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { PropTypes } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| @@ -8,24 +8,15 @@ import buttons from 'components/ui/buttons.scss'; | ||||
| import icons from 'components/ui/icons.scss'; | ||||
| import { Input, Checkbox } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import styles from './password.scss'; | ||||
| import messages from './Password.messages'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             login: PropTypes.shape({ | ||||
|                 login: PropTypes.stirng, | ||||
|                 password: PropTypes.stirng | ||||
|             }) | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'PasswordBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         const {user} = this.props; | ||||
|         const {user} = this.context; | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
| @@ -46,6 +37,7 @@ class Body extends BaseAuthBody { | ||||
|                     icon="key" | ||||
|                     type="password" | ||||
|                     autoFocus | ||||
|                     onFocus={this.fixAutoFocus} | ||||
|                     required | ||||
|                     placeholder={messages.accountPassword} | ||||
|                 /> | ||||
| @@ -7,22 +7,16 @@ import buttons from 'components/ui/buttons.scss'; | ||||
| import icons from 'components/ui/icons.scss'; | ||||
| import { PanelBodyHeader } from 'components/ui/Panel'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import styles from './permissions.scss'; | ||||
| import messages from './Permissions.messages'; | ||||
| 
 | ||||
| class Body extends BaseAuthBody { | ||||
|     static propTypes = { | ||||
|         ...BaseAuthBody.propTypes, | ||||
|         auth: PropTypes.shape({ | ||||
|             error: PropTypes.string, | ||||
|             scopes: PropTypes.array.isRequired | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'PermissionsBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         const {user} = this.props; | ||||
|         const scopes = this.props.auth.scopes; | ||||
|         const {user} = this.context; | ||||
|         const scopes = this.context.auth.scopes; | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
| @@ -61,7 +55,7 @@ class Body extends BaseAuthBody { | ||||
| } | ||||
| 
 | ||||
| export default function Permissions() { | ||||
|     return { | ||||
|     const componentsMap = { | ||||
|         Title: () => ( // TODO: separate component for PageTitle | ||||
|             <Message {...messages.permissionsTitle}> | ||||
|                 {(msg) => <span>{msg}<Helmet title={msg} /></span>} | ||||
| @@ -73,14 +67,20 @@ export default function Permissions() { | ||||
|                 <Message {...messages.approve} /> | ||||
|             </button> | ||||
|         ), | ||||
|         Links: (props) => ( | ||||
|         Links: (props, context) => ( | ||||
|             <a href="#" onClick={(event) => { | ||||
|                 event.preventDefault(); | ||||
| 
 | ||||
|                 props.reject(); | ||||
|                 context.reject(); | ||||
|             }}> | ||||
|                 <Message {...messages.decline} /> | ||||
|             </a> | ||||
|         ) | ||||
|     }; | ||||
| 
 | ||||
|     componentsMap.Links.contextTypes = { | ||||
|         reject: PropTypes.func.isRequired | ||||
|     }; | ||||
| 
 | ||||
|     return componentsMap; | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { combineReducers } from 'redux'; | ||||
|  | ||||
| import { ERROR, SET_CLIENT, SET_OAUTH, SET_SCOPES } from './actions'; | ||||
| import { ERROR, SET_CLIENT, SET_OAUTH, SET_OAUTH_RESULT, SET_SCOPES } from './actions'; | ||||
|  | ||||
| export default combineReducers({ | ||||
|     error, | ||||
| @@ -56,6 +56,14 @@ function oauth( | ||||
|                 state: payload.state | ||||
|             }; | ||||
|  | ||||
|         case SET_OAUTH_RESULT: | ||||
|             return { | ||||
|                 ...state, | ||||
|                 success: payload.success, | ||||
|                 code: payload.code, | ||||
|                 displayCode: payload.displayCode | ||||
|             }; | ||||
|  | ||||
|         default: | ||||
|             return state; | ||||
|     } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { PropTypes } from 'react'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage as Message } from 'react-intl'; | ||||
| import Helmet from 'react-helmet'; | ||||
| @@ -6,27 +6,14 @@ import Helmet from 'react-helmet'; | ||||
| import buttons from 'components/ui/buttons.scss'; | ||||
| import { Input, Checkbox } from 'components/ui/Form'; | ||||
| 
 | ||||
| import BaseAuthBody from './BaseAuthBody'; | ||||
| import BaseAuthBody from 'components/auth/BaseAuthBody'; | ||||
| import activationMessages from 'components/auth/activation/Activation.messages'; | ||||
| import messages from './Register.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 | ||||
|             }) | ||||
|         }) | ||||
|     }; | ||||
|     static displayName = 'RegisterBody'; | ||||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
| @@ -38,6 +25,7 @@ class Body extends BaseAuthBody { | ||||
|                     color="blue" | ||||
|                     type="text" | ||||
|                     autoFocus | ||||
|                     onFocus={this.fixAutoFocus} | ||||
|                     required | ||||
|                     placeholder={messages.yourNickname} | ||||
|                 /> | ||||
| @@ -14,7 +14,7 @@ export class Input extends Component { | ||||
|             id: PropTypes.string | ||||
|         }), | ||||
|         icon: PropTypes.string, | ||||
|         color: PropTypes.oneOf(['green', 'blue', 'red']) | ||||
|         color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue']) | ||||
|     }; | ||||
|  | ||||
|     static contextTypes = { | ||||
|   | ||||
| @@ -24,7 +24,6 @@ export function logout() { | ||||
|     return setUser({isGuest: true}); | ||||
| } | ||||
|  | ||||
|  | ||||
| export function fetchUserData() { | ||||
|     return (dispatch) => | ||||
|         request.get('/api/accounts/current') | ||||
| @@ -45,6 +44,26 @@ export function fetchUserData() { | ||||
|         }); | ||||
| } | ||||
|  | ||||
| export function changePassword({ | ||||
|     password = '', | ||||
|     newPassword = '', | ||||
|     newRePassword = '' | ||||
| }) { | ||||
|     return (dispatch) => | ||||
|         request.post( | ||||
|             '/api/accounts/change-password', | ||||
|             {password, newPassword, newRePassword} | ||||
|         ) | ||||
|         .then((resp) => { | ||||
|             dispatch(updateUser({ | ||||
|                 shouldChangePassword: false | ||||
|             })); | ||||
|  | ||||
|             return resp; | ||||
|         }) | ||||
|         ; | ||||
| } | ||||
|  | ||||
|  | ||||
| export function authenticate(token) { | ||||
|     if (!token || token.split('.').length !== 3) { | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/index.js
									
									
									
									
									
								
							| @@ -22,19 +22,6 @@ import routesFactory from 'routes'; | ||||
|  | ||||
| import 'index.scss'; | ||||
|  | ||||
| // TODO: временная мера против Intl, который беспощадно спамит консоль | ||||
| if (process.env.NODE_ENV !== 'production') { | ||||
|     const originalConsoleError = console.error; | ||||
|     if (console.error === originalConsoleError) { | ||||
|         console.error = (...args) => { | ||||
|             if (args[0].indexOf('[React Intl] Missing message:') === 0) { | ||||
|                 return; | ||||
|             } | ||||
|             originalConsoleError.call(console, ...args); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| const reducer = combineReducers({ | ||||
|     ...reducers, | ||||
|     routing: routeReducer | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
|  | ||||
| import { connect } from 'react-redux'; | ||||
|  | ||||
| import AppInfo from 'components/auth/AppInfo'; | ||||
| import AppInfo from 'components/auth/appInfo/AppInfo'; | ||||
| import PanelTransition from 'components/auth/PanelTransition'; | ||||
|  | ||||
| import styles from './auth.scss'; | ||||
|   | ||||
| @@ -4,17 +4,9 @@ import { connect } from 'react-redux'; | ||||
|  | ||||
| import ProfilePage from 'pages/profile/ProfilePage'; | ||||
|  | ||||
| import authFlow from 'services/authFlow'; | ||||
|  | ||||
| class IndexPage extends Component { | ||||
|     displayName = 'IndexPage'; | ||||
|  | ||||
|     componentWillMount() { | ||||
|         if (this.props.user.isGuest) { | ||||
|             authFlow.login(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return ( | ||||
|             <div> | ||||
|   | ||||
| @@ -8,14 +8,15 @@ import AuthPage from 'pages/auth/AuthPage'; | ||||
| import { authenticate } from 'components/user/actions'; | ||||
|  | ||||
| import OAuthInit from 'components/auth/OAuthInit'; | ||||
| import Register from 'components/auth/Register'; | ||||
| import Login from 'components/auth/Login'; | ||||
| import Permissions from 'components/auth/Permissions'; | ||||
| import Activation from 'components/auth/Activation'; | ||||
| import Password from 'components/auth/Password'; | ||||
| import Register from 'components/auth/register/Register'; | ||||
| import Login from 'components/auth/login/Login'; | ||||
| import Permissions from 'components/auth/permissions/Permissions'; | ||||
| import Activation from 'components/auth/activation/Activation'; | ||||
| import Password from 'components/auth/password/Password'; | ||||
| import Logout from 'components/auth/Logout'; | ||||
| import PasswordChange from 'components/auth/PasswordChange'; | ||||
| import ForgotPassword from 'components/auth/ForgotPassword'; | ||||
| import ChangePassword from 'components/auth/changePassword/ChangePassword'; | ||||
| import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword'; | ||||
| import Finish from 'components/auth/finish/Finish'; | ||||
|  | ||||
| import authFlow from 'services/authFlow'; | ||||
|  | ||||
| @@ -34,7 +35,7 @@ export default function routesFactory(store) { | ||||
|  | ||||
|     return ( | ||||
|         <Route path="/" component={RootPage}> | ||||
|             <IndexRoute component={IndexPage} /> | ||||
|             <IndexRoute component={IndexPage} {...onEnter} /> | ||||
|  | ||||
|             <Route path="oauth" component={OAuthInit} {...onEnter} /> | ||||
|             <Route path="logout" component={Logout} {...onEnter} /> | ||||
| @@ -45,7 +46,8 @@ export default function routesFactory(store) { | ||||
|                 <Route path="/register" components={new Register()} {...onEnter} /> | ||||
|                 <Route path="/activation" components={new Activation()} {...onEnter} /> | ||||
|                 <Route path="/oauth/permissions" components={new Permissions()} {...onEnter} /> | ||||
|                 <Route path="/password-change" components={new PasswordChange()} {...onEnter} /> | ||||
|                 <Route path="/oauth/finish" component={Finish} {...onEnter} /> | ||||
|                 <Route path="/change-password" components={new ChangePassword()} {...onEnter} /> | ||||
|                 <Route path="/forgot-password" components={new ForgotPassword()} {...onEnter} /> | ||||
|             </Route> | ||||
|         </Route> | ||||
|   | ||||
| @@ -23,7 +23,6 @@ export default class AuthFlow { | ||||
|             const {routing} = this.getState(); | ||||
|  | ||||
|             if (routing.location.pathname !== route) { | ||||
|                 this.ignoreRequest = true; // TODO: remove me | ||||
|                 if (this.replace) { | ||||
|                     this.replace(route); | ||||
|                 } | ||||
| @@ -62,10 +61,10 @@ export default class AuthFlow { | ||||
|             throw new Error('State is required'); | ||||
|         } | ||||
|  | ||||
|         if (this.state instanceof state.constructor) { | ||||
|             // already in this state | ||||
|             return; | ||||
|         } | ||||
|         // if (this.state instanceof state.constructor) { | ||||
|         //     // already in this state | ||||
|         //     return; | ||||
|         // } | ||||
|  | ||||
|         this.state && this.state.leave(this); | ||||
|         this.state = state; | ||||
| @@ -74,9 +73,10 @@ export default class AuthFlow { | ||||
|  | ||||
|     handleRequest(path, replace) { | ||||
|         this.replace = replace; | ||||
|         if (this.ignoreRequest) { | ||||
|             this.ignoreRequest = false; | ||||
|             return; | ||||
|  | ||||
|         if (path === '/') { | ||||
|             // reset oauth data if user tried to navigate to index route | ||||
|             this.run('setOAuthRequest', {}); | ||||
|         } | ||||
|  | ||||
|         switch (path) { | ||||
| @@ -92,11 +92,13 @@ export default class AuthFlow { | ||||
|                 this.setState(new ForgotPasswordState()); | ||||
|                 break; | ||||
|  | ||||
|             case '/': | ||||
|             case '/login': | ||||
|             case '/password': | ||||
|             case '/activation': | ||||
|             case '/password-change': | ||||
|             case '/change-password': | ||||
|             case '/oauth/permissions': | ||||
|             case '/oauth/finish': | ||||
|                 this.setState(new LoginState()); | ||||
|                 break; | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,12 @@ import CompleteState from './CompleteState'; | ||||
|  | ||||
| export default class ChangePasswordState extends AbstractState { | ||||
|     enter(context) { | ||||
|         context.navigate('/password-change'); | ||||
|         context.navigate('/change-password'); | ||||
|     } | ||||
|  | ||||
|     resolve(context, payload) { | ||||
|         context.run('changePassword', payload) | ||||
|             .then(() => context.setState(new CompleteState())); | ||||
|     } | ||||
|  | ||||
|     reject(context) { | ||||
|   | ||||
| @@ -3,8 +3,18 @@ import LoginState from './LoginState'; | ||||
| import PermissionsState from './PermissionsState'; | ||||
| import ActivationState from './ActivationState'; | ||||
| import ChangePasswordState from './ChangePasswordState'; | ||||
| import FinishState from './FinishState'; | ||||
|  | ||||
| export default class CompleteState extends AbstractState { | ||||
|     constructor(options = {}) { | ||||
|         super(options); | ||||
|  | ||||
|         if ('accept' in options) { | ||||
|             this.isPermissionsAccepted = options.accept; | ||||
|             this.isUserReviewedPermissions = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     enter(context) { | ||||
|         const {auth, user} = context.getState(); | ||||
|  | ||||
| @@ -14,17 +24,33 @@ export default class CompleteState extends AbstractState { | ||||
|             context.setState(new ActivationState()); | ||||
|         } else if (user.shouldChangePassword) { | ||||
|             context.setState(new ChangePasswordState()); | ||||
|         } else if (auth.oauth) { | ||||
|             context.run('oAuthComplete').then((resp) => { | ||||
|                 location.href = resp.redirectUri; | ||||
|             }, (resp) => { | ||||
|                 // TODO | ||||
|                 if (resp.unauthorized) { | ||||
|                     context.setState(new LoginState()); | ||||
|                 } else if (resp.acceptRequired) { | ||||
|                     context.setState(new PermissionsState()); | ||||
|         } else if (auth.oauth && auth.oauth.clientId) { | ||||
|             if (auth.oauth.code) { | ||||
|                 context.setState(new FinishState()); | ||||
|             } else { | ||||
|                 let data = {}; | ||||
|                 if (this.isUserReviewedPermissions) { | ||||
|                     data.accept = this.isPermissionsAccepted; | ||||
|                 } | ||||
|             }); | ||||
|                 context.run('oAuthComplete', data).then((resp) => { | ||||
|                     switch (resp.redirectUri) { | ||||
|                         case 'static_page': | ||||
|                         case 'static_page_with_code': | ||||
|                             context.setState(new FinishState()); | ||||
|                             break; | ||||
|                         default: | ||||
|                             location.href = resp.redirectUri; | ||||
|                             break; | ||||
|                     } | ||||
|                 }, (resp) => { | ||||
|                     // TODO | ||||
|                     if (resp.unauthorized) { | ||||
|                         context.setState(new LoginState()); | ||||
|                     } else if (resp.acceptRequired) { | ||||
|                         context.setState(new PermissionsState()); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } else { | ||||
|             context.navigate('/'); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/services/authFlow/FinishState.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/services/authFlow/FinishState.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import AbstractState from './AbstractState'; | ||||
|  | ||||
| export default class CompleteState extends AbstractState { | ||||
|     enter(context) { | ||||
|         context.navigate('/oauth/finish'); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| import AbstractState from './AbstractState'; | ||||
| import CompleteState from './CompleteState'; | ||||
|  | ||||
| export default class PermissionsState extends AbstractState { | ||||
|     enter(context) { | ||||
| @@ -14,8 +15,8 @@ export default class PermissionsState extends AbstractState { | ||||
|     } | ||||
|  | ||||
|     process(context, accept) { | ||||
|         context.run('oAuthComplete', { | ||||
|         context.setState(new CompleteState({ | ||||
|             accept | ||||
|         }).then((resp) => location.href = resp.redirectUri); | ||||
|         })); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user