diff --git a/src/actions/SlackAuthAction.js b/src/actions/SlackAuthAction.js deleted file mode 100644 index 1f6df76d..00000000 --- a/src/actions/SlackAuthAction.js +++ /dev/null @@ -1,30 +0,0 @@ -import makeActionCreator from '../utils/actions'; -import { replace } from 'react-router-redux'; -import { client } from 'utils/ApiClient'; -import auth from 'utils/auth'; - -export const SLACK_AUTH_PENDING = 'SLACK_AUTH_PENDING'; -export const SLACK_AUTH_SUCCESS = 'SLACK_AUTH_SUCCESS'; -export const SLACK_AUTH_FAILURE = 'SLACK_AUTH_FAILURE'; - -const slackAuthPending = makeActionCreator(SLACK_AUTH_PENDING); -const slackAuthSuccess = makeActionCreator(SLACK_AUTH_SUCCESS, 'user', 'team'); -const slackAuthFailure = makeActionCreator(SLACK_AUTH_FAILURE, 'error'); - -export function slackAuthAsync(code) { - return (dispatch) => { - dispatch(slackAuthPending()); - - client.post('/auth.slack', { - code: code, - }) - .then(data => { - auth.setToken(data.data.accessToken); - dispatch(slackAuthSuccess(data.data.user, data.data.team)); - dispatch(replace('/dashboard')); - }) - .catch((err) => { - dispatch(push('/error')); - }) - }; -}; \ No newline at end of file diff --git a/src/actions/UserActions.js b/src/actions/UserActions.js deleted file mode 100644 index 3f7ae360..00000000 --- a/src/actions/UserActions.js +++ /dev/null @@ -1,15 +0,0 @@ -import { push } from 'react-router-redux'; -import auth from 'utils/auth'; - -import makeActionCreator from '../utils/actions'; - -export const UPDATE_USER = 'UPDATE_USER'; - -export const updateUser = makeActionCreator(UPDATE_USER, 'user'); - -export function logoutUser() { - return (dispatch) => { - auth.logout(); - dispatch(push('/')); - }; -}; diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index 5a5ed621..f8715d63 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,8 +1,8 @@ import React from 'react'; -import { connect } from 'react-redux'; import Link from 'react-router/lib/Link'; -import { bindActionCreators } from 'redux'; -import { logoutUser } from 'actions/UserActions'; +import { observe } from 'mobx'; + +import store from 'stores/UserStore'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import Flex from 'components/Flex'; @@ -21,10 +21,6 @@ class Layout extends React.Component { loading: React.PropTypes.bool, } - onLogout = () => { - this.props.logoutUser(); - } - render() { return (
@@ -33,7 +29,7 @@ class Layout extends React.Component { ) : null }
- { this.props.teamName } + { store.team.name }
{ this.props.title } @@ -47,10 +43,10 @@ class Layout extends React.Component { }> - Logout + Logout
@@ -63,20 +59,4 @@ class Layout extends React.Component { } } -const mapStateToProps = (state) => { - return { - teamName: state.team ? state.team.name : null, - avatarUrl: state.user ? state.user.avatarUrl : null, - } -}; - -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ - logoutUser, - }, dispatch) -} - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Layout); \ No newline at end of file +export default Layout; \ No newline at end of file diff --git a/src/components/SlackAuthLink/SlackAuthLink.js b/src/components/SlackAuthLink/SlackAuthLink.js index a61c4cf9..14d36b1d 100644 --- a/src/components/SlackAuthLink/SlackAuthLink.js +++ b/src/components/SlackAuthLink/SlackAuthLink.js @@ -1,16 +1,14 @@ import React from 'react'; +import { observe } from 'mobx' +import store from 'stores/UserStore'; import styles from './SlackAuthLink.scss'; -export default class SlackAuthLink extends React.Component { +class SlackAuthLink extends React.Component { static propTypes = { scopes: React.PropTypes.arrayOf(React.PropTypes.string), } - state = { - oauthState: Math.random().toString(36).substring(7), - } - static defaultProps = { scopes: [ 'identity.email', @@ -20,10 +18,6 @@ export default class SlackAuthLink extends React.Component { ] } - componentDidMount = () => { - localStorage.oauthState = this.state.oauthState; - } - slackUrl = () => { const baseUrl = 'https://slack.com/oauth/authorize'; const params = { @@ -32,7 +26,7 @@ export default class SlackAuthLink extends React.Component { redirect_uri: __DEV__ ? 'http://localhost:3000/auth/slack/' : 'https://www.beautifulatlas.com/auth/slack/', - state: this.state.oauthState, + state: store.getOauthState(), }; const urlParams = Object.keys(params).map(function(key) { @@ -48,3 +42,5 @@ export default class SlackAuthLink extends React.Component { ) } } + +export default SlackAuthLink; diff --git a/src/index.js b/src/index.js index 83e5d3fa..e7250722 100644 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,6 @@ import Route from 'react-router/lib/Route'; import IndexRoute from 'react-router/lib/IndexRoute'; import { createStore, applyMiddleware } from 'redux'; import { routerMiddleware } from 'react-router-redux'; -import { persistStore, autoRehydrate } from 'redux-persist'; import thunkMiddleware from 'redux-thunk'; import createLogger from 'redux-logger'; import History from 'utils/History'; @@ -41,42 +40,34 @@ if (__DEV__) { thunkMiddleware, routerMiddlewareWithHistory, loggerMiddleware, - ), autoRehydrate()); + )); } else { store = createStore(reducers, applyMiddleware( thunkMiddleware, routerMiddlewareWithHistory, - ), autoRehydrate()); + )); } +render(( +
+ + + + -persistStore(store, { - whitelist: [ - 'user', - 'team', - ] -}, () => { - render(( -
- - - - + + + + + - - - - - - - - - - - { __DEV__ ? : null } -
- ), document.getElementById('root')); -}); + + +
+
+ { __DEV__ ? : null } +
+), document.getElementById('root')); function requireAuth(nextState, replace) { if (!auth.loggedIn()) { diff --git a/src/reducers/index.js b/src/reducers/index.js index 0fb0024b..4320ed5a 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,12 +2,8 @@ import { combineReducers } from 'redux'; import atlases from './atlases'; import document from './document'; -import team from './team'; -import user from './user'; export default combineReducers({ atlases, document, - team, - user, }); diff --git a/src/reducers/team.js b/src/reducers/team.js deleted file mode 100644 index e86e78c3..00000000 --- a/src/reducers/team.js +++ /dev/null @@ -1,15 +0,0 @@ -import { SLACK_AUTH_SUCCESS } from 'actions/SlackAuthAction'; - -const team = (state = null, action) => { - switch (action.type) { - case SLACK_AUTH_SUCCESS: { - return { - ...action.team, - }; - } - default: - return state; - } -}; - -export default team; \ No newline at end of file diff --git a/src/reducers/user.js b/src/reducers/user.js deleted file mode 100644 index 406d656b..00000000 --- a/src/reducers/user.js +++ /dev/null @@ -1,15 +0,0 @@ -import { SLACK_AUTH_SUCCESS } from 'actions/SlackAuthAction'; - -const user = (state = null, action) => { - switch (action.type) { - case SLACK_AUTH_SUCCESS: { - return { - ...action.user, - }; - } - default: - return state; - } -}; - -export default user; \ No newline at end of file diff --git a/src/scenes/DocumentEdit/DocumentEditState.js b/src/scenes/DocumentEdit/DocumentEditState.js index 351872c4..55300d29 100644 --- a/src/scenes/DocumentEdit/DocumentEditState.js +++ b/src/scenes/DocumentEdit/DocumentEditState.js @@ -88,7 +88,7 @@ const documentEditState = new class DocumentEditState { } constructor() { - // Rehydrate + // Rehydrate syncronously localforage.getItem(DOCUMENT_EDIT_SETTINGS) .then(data => { this.preview = data.preview; diff --git a/src/scenes/Home/Home.js b/src/scenes/Home/Home.js index 016113f8..7b2c649c 100644 --- a/src/scenes/Home/Home.js +++ b/src/scenes/Home/Home.js @@ -1,22 +1,15 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { replace } from 'react-router-redux'; - -import auth from '../../utils/auth'; +import store from 'stores/UserStore'; +import { browserHistory } from 'react-router' import SlackAuthLink from '../../components/SlackAuthLink'; import styles from './Home.scss'; -class Home extends React.Component { - static propTypes = { - replace: React.PropTypes.func.isRequired, - } - +export default class Home extends React.Component { componentDidMount = () => { - if (auth.loggedIn()) { - this.props.replace('/dashboard'); + if (store.authenticated) { + browserHistory.replace('/dashboard'); } } @@ -53,11 +46,3 @@ class Home extends React.Component { ); } } - -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ replace }, dispatch) -} - -export default connect( - null, mapDispatchToProps -)(Home); \ No newline at end of file diff --git a/src/scenes/SlackAuth/SlackAuth.js b/src/scenes/SlackAuth/SlackAuth.js index 410ef847..ffb6dbf4 100644 --- a/src/scenes/SlackAuth/SlackAuth.js +++ b/src/scenes/SlackAuth/SlackAuth.js @@ -1,21 +1,10 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; +import store from 'stores/UserStore'; -import { slackAuthAsync } from '../../actions/SlackAuthAction'; - -import { client } from '../../utils/ApiClient'; - -class SlackAuth extends React.Component { +export default class SlackAuth extends React.Component { componentDidMount = () => { - const { query } = this.props.location - - // Validate OAuth2 state param - if (localStorage.oauthState != query.state) { - return; - } - - this.props.slackAuthAsync(query.code); + const { code, state } = this.props.location.query; + store.authWithSlack(code, state); } render() { @@ -24,12 +13,3 @@ class SlackAuth extends React.Component { ); } } - -const mapDispactcToProps = (dispatch) => { - return bindActionCreators({ slackAuthAsync }, dispatch); -}; - -export default connect( - null, - mapDispactcToProps -)(SlackAuth); \ No newline at end of file diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js new file mode 100644 index 00000000..fd73bdb5 --- /dev/null +++ b/src/stores/UserStore.js @@ -0,0 +1,82 @@ +import { observable, action, computed, autorun } from 'mobx'; +import { browserHistory } from 'react-router'; +import { client } from 'utils/ApiClient'; +import localforage from 'localforage'; + +const USER_STORE = 'USER_STORE'; + +const store = new class UserStore { + @observable user; + @observable team; + + @observable token; + @observable oauthState; + + @observable isLoading; + + /* Computed */ + + @computed get authenticated() { + return !!this.token; + } + + @computed get asJson() { + return JSON.stringify({ + user: this.user, + team: this.team, + token: this.token, + oauthState: this.oauthState, + }); + } + + /* Actions */ + + @action logout = () => { + this.user = null; + this.token = null; + browserHistory.push('/'); + }; + + @action getOauthState = () => { + const state = Math.random().toString(36).substring(7); + this.oauthState = state; + return this.oauthState; + } + + @action authWithSlack = async (code, state) => { + if (state !== this.oauthState) { + browserHistory.push('/auth-error'); + return; + } + + let res; + try { + res = await client.post('/auth.slack', { code: code }); + } catch (e) { + browserHistory.push('/auth-error'); + return; + } + + this.user = res.data.user; + this.team = res.data.team; + this.token = res.data.accessToken; + browserHistory.replace('/dashboard'); + } + + constructor() { + // Rehydrate + const data = JSON.parse(localStorage.getItem(USER_STORE)) || {}; + this.user = data.user; + this.team = data.team; + this.token = data.token; + this.oauthState = data.oauthState; + } +}(); + +// Persist store to localStorage +autorun(() => { + localStorage.setItem(USER_STORE, store.asJson); +}); + + +export default store; diff --git a/src/utils/ApiClient.js b/src/utils/ApiClient.js index c412e32c..dcfcaec8 100644 --- a/src/utils/ApiClient.js +++ b/src/utils/ApiClient.js @@ -1,6 +1,6 @@ import _map from 'lodash/map'; +import store from 'stores/UserStore'; -import auth from './auth'; import constants from '../constants'; class ApiClient { @@ -25,8 +25,8 @@ class ApiClient { 'Content-Type': 'application/json', 'User-Agent': this.userAgent, }); - if (auth.getToken()) { - headers.set('Authorization', `Bearer ${auth.getToken()}`); + if (store.authenticated) { + headers.set('Authorization', `Bearer ${store.token}`); } // Construct request @@ -48,7 +48,7 @@ class ApiClient { // Handle 401, log out user if (response.status === 401) { - auth.logout(); // replace with dispatch+action + store.logout(); } // Handle failed responses