mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-01-15 16:22:07 +05:30
Merge branch 'master' into profile
This commit is contained in:
commit
0c61b18b5e
@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user