Add logout to the topnav (#45)
* WIP * Remove session token after logout. * Add logout reducer test * Pop stack to login * Use newProps and doFetchWithResponse * Setup the reducers to clear the store
This commit is contained in:
parent
bfec2fedc9
commit
25f284a068
15 changed files with 208 additions and 2 deletions
|
|
@ -36,7 +36,6 @@ export function bindClientFunc(clientFunc, request, success, failure, ...args) {
|
|||
|
||||
try {
|
||||
const data = await clientFunc(...args);
|
||||
|
||||
dispatch(requestSuccess(success, data), getState);
|
||||
} catch (err) {
|
||||
dispatch(requestFailure(failure, err));
|
||||
|
|
|
|||
15
src/actions/logout.js
Normal file
15
src/actions/logout.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {LogoutTypes as types} from 'constants';
|
||||
import Client from 'client/client_instance';
|
||||
import {bindClientFunc} from 'actions/helpers.js';
|
||||
|
||||
export function logout() {
|
||||
return bindClientFunc(
|
||||
Client.logout,
|
||||
types.LOGOUT_REQUEST,
|
||||
types.LOGOUT_SUCCESS,
|
||||
types.LOGOUT_FAILURE
|
||||
);
|
||||
}
|
||||
|
|
@ -182,6 +182,17 @@ export default class Client {
|
|||
return data;
|
||||
}
|
||||
|
||||
logout = async () => {
|
||||
const response = await this.doFetchWithResponse(
|
||||
`${this.getUsersRoute()}/logout`,
|
||||
{method: 'post'}
|
||||
);
|
||||
if (response.ok) {
|
||||
this.token = '';
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
getInitialLoad = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/initial_load`,
|
||||
|
|
|
|||
49
src/components/logout.js
Normal file
49
src/components/logout.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {TouchableHighlight, Text} from 'react-native';
|
||||
import * as logoutActions from 'actions/logout';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {Actions as Routes} from 'react-native-router-flux';
|
||||
|
||||
const propTypes = {
|
||||
logout: PropTypes.object.isRequired,
|
||||
actions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class Logout extends Component {
|
||||
static propTypes = propTypes;
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (this.props.logout.status === 'fetching' &&
|
||||
newProps.logout.status === 'fetched') {
|
||||
Routes.popTo('goToLogin');
|
||||
}
|
||||
}
|
||||
|
||||
logout = () => this.props.actions.logout();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight onPress={this.logout}>
|
||||
<Text>{'logout'}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
logout: state.views.logout
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(logoutActions, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Logout);
|
||||
|
|
@ -2,6 +2,7 @@ import * as ChannelsTypes from './channels';
|
|||
import * as DeviceTypes from './device';
|
||||
import * as GeneralTypes from './general';
|
||||
import * as LoginTypes from './login';
|
||||
import * as LogoutTypes from './logout';
|
||||
import * as TeamsTypes from './teams';
|
||||
import * as PostsTypes from './posts';
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ export {
|
|||
DeviceTypes,
|
||||
GeneralTypes,
|
||||
LoginTypes,
|
||||
LogoutTypes,
|
||||
TeamsTypes,
|
||||
ChannelsTypes,
|
||||
PostsTypes
|
||||
|
|
|
|||
3
src/constants/logout.js
Normal file
3
src/constants/logout.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
|
||||
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
|
||||
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
|
||||
|
|
@ -37,6 +37,8 @@ export default function reduceChannels(state = initState, action) {
|
|||
error: action.error
|
||||
};
|
||||
|
||||
case types.LOGOUT_SUCCESS:
|
||||
return initState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import channels from './channels';
|
|||
import device from './device.js';
|
||||
import general from './general.js';
|
||||
import login from './login';
|
||||
import logout from './logout';
|
||||
import teams from './teams';
|
||||
import posts from './posts';
|
||||
|
||||
|
|
@ -19,7 +20,8 @@ const entities = combineReducers({
|
|||
|
||||
const views = combineReducers({
|
||||
device,
|
||||
login
|
||||
login,
|
||||
logout
|
||||
});
|
||||
|
||||
export default combineReducers({
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export default function reduceLogin(state = initState, action) {
|
|||
error: action.error
|
||||
};
|
||||
|
||||
case types.LOGOUT_SUCCESS:
|
||||
return initState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
31
src/reducers/logout.js
Normal file
31
src/reducers/logout.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {LogoutTypes as types} from 'constants';
|
||||
|
||||
export const initState = {
|
||||
status: 'not fetched',
|
||||
error: null
|
||||
};
|
||||
|
||||
export default function reduceLogout(state = initState, action) {
|
||||
switch (action.type) {
|
||||
case types.LOGOUT_REQUEST:
|
||||
return {...state,
|
||||
status: 'fetching',
|
||||
error: null
|
||||
};
|
||||
case types.LOGOUT_SUCCESS:
|
||||
return {...state,
|
||||
status: 'fetched'
|
||||
};
|
||||
case types.LOGOUT_FAILURE:
|
||||
return {...state,
|
||||
status: 'failed',
|
||||
error: action.error
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ export default function reducePosts(state = initState, action) {
|
|||
error: action.error
|
||||
};
|
||||
|
||||
case types.LOGOUT_SUCCESS:
|
||||
return initState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export default function reduceTeams(state = initState, action) {
|
|||
error: action.error
|
||||
};
|
||||
|
||||
case types.LOGOUT_SUCCESS:
|
||||
return initState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import LoginContainer from 'containers/login_container';
|
|||
import SelectServerContainer from 'containers/select_server_container';
|
||||
import SelectTeamContainer from 'containers/select_team_container';
|
||||
import PostsListContainer from 'containers/posts_list_container';
|
||||
import Logout from 'components/logout';
|
||||
import * as logout from 'actions/logout';
|
||||
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
|
|
@ -39,16 +41,25 @@ class Routes extends Component {
|
|||
key='goToChannelsList'
|
||||
component={ChannelsListContainer}
|
||||
title={formatMessage({id: 'routes.channels', defaultMessage: 'Channels'})}
|
||||
renderRightButton={() =>
|
||||
<Logout actions={logout}/>
|
||||
}
|
||||
/>
|
||||
<Scene
|
||||
key='goToSelectTeam'
|
||||
component={SelectTeamContainer}
|
||||
title={formatMessage({id: 'routes.selectTeam', defaultMessage: 'Select Team'})}
|
||||
renderRightButton={() =>
|
||||
<Logout actions={logout}/>
|
||||
}
|
||||
/>
|
||||
<Scene
|
||||
key='goToPostsList'
|
||||
component={PostsListContainer}
|
||||
title={formatMessage({id: 'routes.postsList', defaultMessage: 'Posts List'})}
|
||||
renderRightButton={() =>
|
||||
<Logout actions={logout}/>
|
||||
}
|
||||
/>
|
||||
</Scene>
|
||||
</Router>
|
||||
|
|
|
|||
75
test/reducers/logout.test.js
Normal file
75
test/reducers/logout.test.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import assert from 'assert';
|
||||
import reduceLogout, {initState} from 'reducers/logout';
|
||||
import {LogoutTypes as types} from 'constants';
|
||||
|
||||
describe('logout reducer', () => {
|
||||
describe('Init', () => {
|
||||
let store;
|
||||
let expectedStore;
|
||||
before(() => {
|
||||
store = reduceLogout(store, {type: ''});
|
||||
expectedStore = {...initState};
|
||||
});
|
||||
it('should be initial state', () => {
|
||||
assert.equal(typeof store, 'object');
|
||||
});
|
||||
it('have a specifc initial state', () => {
|
||||
assert.deepEqual(store, expectedStore);
|
||||
});
|
||||
});
|
||||
describe(`when ${types.LOGOUT_REQUEST}`, () => {
|
||||
let store;
|
||||
let expectedStore;
|
||||
before(() => {
|
||||
store = reduceLogout(store, {
|
||||
type: types.LOGOUT_REQUEST
|
||||
});
|
||||
expectedStore = {
|
||||
...initState,
|
||||
status: 'fetching'
|
||||
};
|
||||
});
|
||||
it('should set status to fetching', () => {
|
||||
assert.deepEqual(store, expectedStore);
|
||||
});
|
||||
});
|
||||
describe(`when ${types.LOGOUT_SUCCESS}`, () => {
|
||||
let store;
|
||||
let expectedStore;
|
||||
before(() => {
|
||||
store = reduceLogout(store, {
|
||||
type: types.LOGOUT_SUCCESS
|
||||
});
|
||||
expectedStore = {
|
||||
...initState,
|
||||
status: 'fetched'
|
||||
};
|
||||
});
|
||||
it('should set status to fetched and data', () => {
|
||||
assert.deepEqual(store, expectedStore);
|
||||
});
|
||||
});
|
||||
describe(`when ${types.LOGOUT_FAILURE}`, () => {
|
||||
let store;
|
||||
let error;
|
||||
let expectedStore;
|
||||
before(() => {
|
||||
error = {id: 'the.error.id', message: 'Something went wrong'};
|
||||
store = reduceLogout(store, {
|
||||
type: types.LOGOUT_FAILURE,
|
||||
error
|
||||
});
|
||||
expectedStore = {
|
||||
...initState,
|
||||
status: 'failed',
|
||||
error
|
||||
};
|
||||
});
|
||||
it('should set status to failed and error', () => {
|
||||
assert.deepEqual(store, expectedStore);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue