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 {
}>
-
+
@@ -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