Интеграция appInfo с API

This commit is contained in:
SleepWalker
2016-02-23 07:57:16 +02:00
parent aa422cb4f2
commit 404684b8d9
6 changed files with 146 additions and 15 deletions

View File

@@ -0,0 +1,42 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { oAuthValidate } from 'components/auth/actions';
class OAuthInit extends Component {
static displayName = 'OAuthInit';
static propTypes = {
query: PropTypes.shape({
client_id: PropTypes.string.isRequired,
redirect_uri: PropTypes.string.isRequired,
response_type: PropTypes.string.isRequired,
scope: PropTypes.string.isRequired,
state: PropTypes.string
}),
validate: PropTypes.func.isRequired
};
componentWillMount() {
const {query} = this.props;
this.props.validate({
clientId: query.client_id,
redirectUrl: query.redirect_uri,
responseType: query.response_type,
scope: query.scope,
state: query.state
});
}
render() {
return <span />;
}
}
export default connect((state) => ({
query: state.routing.location.query
}), {
validate: oAuthValidate
})(OAuthInit);

View File

@@ -107,3 +107,45 @@ export function logout() {
dispatch(routeActions.push('/login')); dispatch(routeActions.push('/login'));
}; };
} }
// TODO: move to oAuth actions?
// test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session
export function oAuthValidate({clientId, redirectUrl, responseType, scope, state}) {
return (dispatch) =>
request.get(
'/api/oauth/validate',
{
client_id: clientId,
redirect_uri: redirectUrl,
response_type: responseType,
scope,
state
}
)
.then((resp) => {
dispatch(setClient(resp.client));
dispatch(routeActions.push('/oauth/permissions'));
})
.catch((resp = {}) => { // TODO
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
alert(`Invalid request (${resp.parameter} required).`);
}
if (resp.statusCode === 401 && resp.error === 'invalid_client') {
alert('Can not find application you are trying to authorize.');
}
if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
alert(`Invalid response type '${resp.parameter}'.`);
}
if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
alert(`Invalid scope '${resp.parameter}'.`);
}
});
}
export const SET_CLIENT = 'set_client';
export function setClient({id, name, description}) {
return {
type: SET_CLIENT,
payload: {id, name, description}
};
}

View File

@@ -1,9 +1,10 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { ERROR } from './actions'; import { ERROR, SET_CLIENT } from './actions';
export default combineReducers({ export default combineReducers({
error error,
client
}); });
function error( function error(
@@ -21,3 +22,20 @@ function error(
return state; return state;
} }
} }
function client(
state = null,
{type, payload = {}}
) {
switch (type) {
case SET_CLIENT:
return {
id: payload.id,
name: payload.name,
description: payload.description
};
default:
return state;
}
}

View File

@@ -1,29 +1,34 @@
import React, { Component } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import AppInfo from 'components/auth/AppInfo'; import AppInfo from 'components/auth/AppInfo';
import PanelTransition from 'components/auth/PanelTransition'; import PanelTransition from 'components/auth/PanelTransition';
import styles from './auth.scss'; import styles from './auth.scss';
export default class AuthPage extends Component { class AuthPage extends Component {
static displayName = 'AuthPage'; static displayName = 'AuthPage';
static propTypes = {
client: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired
})
};
state = { state = {
isSidebarHidden: false isSidebarHidden: false
}; };
render() { render() {
var {isSidebarHidden} = this.state; const {isSidebarHidden} = this.state;
const {client} = this.props;
var appInfo = {
name: 'TLauncher',
description: `Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.`
};
return ( return (
<div> <div>
<div className={isSidebarHidden ? styles.hiddenSidebar : styles.sidebar}> <div className={isSidebarHidden ? styles.hiddenSidebar : styles.sidebar}>
<AppInfo {...appInfo} onGoToAuth={this.onGoToAuth} /> <AppInfo {...client} onGoToAuth={this.onGoToAuth} />
</div> </div>
<div className={styles.content}> <div className={styles.content}>
<PanelTransition {...this.props} /> <PanelTransition {...this.props} />
@@ -38,3 +43,8 @@ export default class AuthPage extends Component {
}); });
}; };
} }
export default connect((state) => ({
client: state.auth.client
}))(AuthPage);

View File

@@ -5,6 +5,7 @@ import RootPage from 'pages/root/RootPage';
import IndexPage from 'pages/index/IndexPage'; import IndexPage from 'pages/index/IndexPage';
import AuthPage from 'pages/auth/AuthPage'; import AuthPage from 'pages/auth/AuthPage';
import OAuthInit from 'components/auth/OAuthInit';
import Register from 'components/auth/Register'; import Register from 'components/auth/Register';
import Login from 'components/auth/Login'; import Login from 'components/auth/Login';
import Permissions from 'components/auth/Permissions'; import Permissions from 'components/auth/Permissions';
@@ -47,10 +48,10 @@ export default function routesFactory(store) {
<Route path="/register" components={new Register()} /> <Route path="/register" components={new Register()} />
<Route path="/activation" components={new Activation()} /> <Route path="/activation" components={new Activation()} />
<Route path="/oauth/permissions" components={new Permissions()} onEnter={checkAuth} /> <Route path="/oauth/permissions" components={new Permissions()} onEnter={checkAuth} />
<Route path="/oauth/:id" component={Permissions} />
<Route path="/password-change" components={new PasswordChange()} /> <Route path="/password-change" components={new PasswordChange()} />
</Route> </Route>
<Route path="oauth" component={OAuthInit} />
<Route path="logout" component={Logout} /> <Route path="logout" component={Logout} />
</Route> </Route>
); );

View File

@@ -2,7 +2,7 @@ function serialize(data) {
return Object.keys(data) return Object.keys(data)
.map( .map(
(keyName) => (keyName) =>
[keyName, data[keyName]] [keyName, typeof data[keyName] === 'undefined' ? '' : data[keyName]]
.map(encodeURIComponent) .map(encodeURIComponent)
.join('=') .join('=')
) )
@@ -10,6 +10,9 @@ function serialize(data) {
; ;
} }
const toJSON = (resp) => resp.json();
const handleResponse = (resp) => Promise[resp.success ? 'resolve' : 'reject'](resp);
export default { export default {
post(url, data) { post(url, data) {
return fetch(url, { return fetch(url, {
@@ -20,8 +23,23 @@ export default {
}, },
body: serialize(data) body: serialize(data)
}) })
.then((resp) => resp.json()) .then(toJSON)
.then((resp) => Promise[resp.success ? 'resolve' : 'reject'](resp)) .then(handleResponse)
;
},
get(url, data) {
if (typeof data === 'object') {
const separator = url.indexOf('?') === -1 ? '?' : '&';
url += separator + serialize(data);
}
return fetch(url, {
headers: {
Accept: 'application/json'
}
})
.then(toJSON)
.then(handleResponse)
; ;
} }
}; };