diff --git a/src/actions/helpers.js b/src/actions/helpers.js index d6a1938db..3531a8729 100644 --- a/src/actions/helpers.js +++ b/src/actions/helpers.js @@ -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)); diff --git a/src/actions/logout.js b/src/actions/logout.js new file mode 100644 index 000000000..265d9674a --- /dev/null +++ b/src/actions/logout.js @@ -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 + ); +} diff --git a/src/client/client.js b/src/client/client.js index a60fdc652..ad55c9391 100644 --- a/src/client/client.js +++ b/src/client/client.js @@ -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`, diff --git a/src/components/logout.js b/src/components/logout.js new file mode 100644 index 000000000..3a5459fa5 --- /dev/null +++ b/src/components/logout.js @@ -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 ( + + {'logout'} + + ); + } +} + +function mapStateToProps(state) { + return { + logout: state.views.logout + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(logoutActions, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(Logout); diff --git a/src/constants/index.js b/src/constants/index.js index efc5f12f9..0a5c947bc 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -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 diff --git a/src/constants/logout.js b/src/constants/logout.js new file mode 100644 index 000000000..69939a85f --- /dev/null +++ b/src/constants/logout.js @@ -0,0 +1,3 @@ +export const LOGOUT_REQUEST = 'LOGOUT_REQUEST'; +export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; +export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; diff --git a/src/reducers/channels.js b/src/reducers/channels.js index fb6bd45ed..7b0886b05 100644 --- a/src/reducers/channels.js +++ b/src/reducers/channels.js @@ -37,6 +37,8 @@ export default function reduceChannels(state = initState, action) { error: action.error }; + case types.LOGOUT_SUCCESS: + return initState; default: return state; } diff --git a/src/reducers/index.js b/src/reducers/index.js index c5bc47b83..1f00e943e 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -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({ diff --git a/src/reducers/login.js b/src/reducers/login.js index ff11db8cf..59040d0a1 100644 --- a/src/reducers/login.js +++ b/src/reducers/login.js @@ -26,6 +26,8 @@ export default function reduceLogin(state = initState, action) { error: action.error }; + case types.LOGOUT_SUCCESS: + return initState; default: return state; } diff --git a/src/reducers/logout.js b/src/reducers/logout.js new file mode 100644 index 000000000..24070d878 --- /dev/null +++ b/src/reducers/logout.js @@ -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; + } +} diff --git a/src/reducers/posts.js b/src/reducers/posts.js index 5f9b3a80d..c79858705 100644 --- a/src/reducers/posts.js +++ b/src/reducers/posts.js @@ -25,6 +25,8 @@ export default function reducePosts(state = initState, action) { error: action.error }; + case types.LOGOUT_SUCCESS: + return initState; default: return state; } diff --git a/src/reducers/teams.js b/src/reducers/teams.js index f6e6cd8a1..c4cd7d17f 100644 --- a/src/reducers/teams.js +++ b/src/reducers/teams.js @@ -31,6 +31,8 @@ export default function reduceTeams(state = initState, action) { error: action.error }; + case types.LOGOUT_SUCCESS: + return initState; default: return state; } diff --git a/src/routes.js b/src/routes.js index 5e0b7e9c6..dee447023 100644 --- a/src/routes.js +++ b/src/routes.js @@ -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={() => + + } /> + + } /> + + } /> diff --git a/test/general.test.js b/test/reducers/general.test.js similarity index 100% rename from test/general.test.js rename to test/reducers/general.test.js diff --git a/test/reducers/logout.test.js b/test/reducers/logout.test.js new file mode 100644 index 000000000..47810b67b --- /dev/null +++ b/test/reducers/logout.test.js @@ -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); + }); + }); +});