Merge pull request #811 from ZmagoD/zd_SCI_1567

fixes sign out action on settings page, clean the redux store, handle…
This commit is contained in:
Zmago Devetak 2017-10-09 09:24:57 +02:00 committed by GitHub
commit f2103dca6d
14 changed files with 97 additions and 19 deletions

View file

@ -17,6 +17,7 @@
"plugins": [
"transform-object-rest-spread",
"syntax-dynamic-import",
"transform-react-jsx-source",
[
"transform-class-properties",
{

View file

@ -2,6 +2,16 @@ module ClientApi
module Users
class UsersController < ApplicationController
def sign_out_user
respond_to do |format|
if sign_out current_user
format.json { render json: {}, status: :ok }
else
format.json { render json: {}, status: :unauthorized }
end
end
end
def preferences_info
settings = current_user.settings
respond_to do |format|

View file

@ -1,12 +1,13 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { func, shape, string, number } from "prop-types";
import { NavDropdown, MenuItem, Image } from "react-bootstrap";
import styled from "styled-components";
import { FormattedMessage } from "react-intl";
import { SIGN_IN_PATH } from "../../../config/routes";
import { getCurrentUser } from "../../../services/api/users_api";
import { addCurrentUser } from "../../actions/UsersActions";
import { addCurrentUser, destroyState } from "../../actions/UsersActions";
import { signOutUser, getCurrentUser } from "../../../services/api/users_api";
const StyledNavDropdown = styled(NavDropdown)`
& #user-account-dropdown {
@ -25,6 +26,15 @@ class UserAccountDropdown extends Component {
getCurrentUser().then(data => {
this.props.addCurrentUser(data);
});
this.signOut = this.signOut.bind(this);
}
signOut() {
document.querySelector('meta[name="csrf-token"]').remove();
signOutUser().then(() => {
this.props.destroyState();
window.location = SIGN_IN_PATH;
});
}
render() {
@ -52,7 +62,7 @@ class UserAccountDropdown extends Component {
<FormattedMessage id="user_account_dropdown.settings" />
</MenuItem>
<MenuItem divider />
<MenuItem href="/users/sign_out">
<MenuItem onClick={this.signOut}>
<FormattedMessage id="user_account_dropdown.log_out" />
</MenuItem>
</StyledNavDropdown>
@ -61,17 +71,18 @@ class UserAccountDropdown extends Component {
}
UserAccountDropdown.propTypes = {
addCurrentUser: PropTypes.func.isRequired,
current_user: PropTypes.shape({
id: PropTypes.number.isRequired,
fullName: PropTypes.string.isRequired,
avatarThumb: PropTypes.string.isRequired
addCurrentUser: func.isRequired,
destroyState: func.isRequired,
current_user: shape({
id: number.isRequired,
fullName: string.isRequired,
avatarThumb: string.isRequired
}).isRequired
};
// Map the states from store to component
const mapStateToProps = ({ current_user }) => ({ current_user });
export default connect(mapStateToProps, { addCurrentUser })(
export default connect(mapStateToProps, { destroyState, addCurrentUser })(
UserAccountDropdown
);

View file

@ -1,8 +1,8 @@
import axios from "../../config/axios";
import { USER_LOGOUT, SET_CURRENT_USER } from "../../config/action_types";
import {
SET_CURRENT_USER,
} from "../../config/action_types";
export function destroyState() {
return { type: USER_LOGOUT };
}
export function addCurrentUser(data) {
return {

View file

@ -8,6 +8,7 @@ export const GLOBAL_ACTIVITIES_DATA = "GLOBAL_ACTIVITIES_DATA";
export const DESTROY_GLOBAL_ACTIVITIES_DATA = "DESTROY_GLOBAL_ACTIVITIES_DATA";
// users
export const USER_LOGOUT = "USER_LOGOUT";
export const SET_CURRENT_USER = "SET_CURRENT_USER";
// user teams
@ -24,4 +25,4 @@ export const SPINNER_OFF = "SPINNER_OFF";
// alerts
export const ADD_ALERT = "ADD_ALERT";
export const CLEAR_ALERT = "CLEAR_ALERT";
export const CLEAR_ALL_ALERTS = "CLEAR_ALL_ALERTS";
export const CLEAR_ALL_ALERTS = "CLEAR_ALL_ALERTS";

View file

@ -1,7 +1,19 @@
// @TODO remove this file ASAP the preferences/profile refactoring is merged
import axios from "axios";
import store from "./store";
import { SIGN_IN_PATH } from "./routes";
import { destroyState } from "../components/actions/UsersActions";
export default axios.create({
withCredentials: true,
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content
},
validateStatus(status) {
if (status === 401) {
store.dispatch(destroyState);
window.location = SIGN_IN_PATH;
}
return status >= 200 && status < 300;
}
});

View file

@ -1,14 +1,15 @@
import { combineReducers } from "redux";
import { USER_LOGOUT } from "./action_types";
import {
setCurrentTeam,
getListOfTeams,
showLeaveTeamModal,
showLeaveTeamModal
} from "../components/reducers/TeamReducers";
import { globalActivities } from "../components/reducers/ActivitiesReducers";
import { currentUser } from "../components/reducers/UsersReducer";
import { alerts } from "../components/reducers/AlertsReducers";
export default combineReducers({
const appReducer = combineReducers({
current_team: setCurrentTeam,
all_teams: getListOfTeams,
global_activities: globalActivities,
@ -16,3 +17,12 @@ export default combineReducers({
showLeaveTeamModal,
alerts
});
const rootReducer = (state, action) => {
if (action.type === USER_LOGOUT) {
state = undefined;
}
return appReducer(state, action);
};
export default rootReducer;

View file

@ -1,8 +1,9 @@
export const ROOT_PATH = "/";
export const SIGN_IN_PATH = "/users/sign_in";
// Settings page
export const SETTINGS_TEAMS_ROUTE = "/settings/teams";
export const SETTINGS_TEAM_ROUTE = "/settings/teams/:id";
export const SETTINGS_NEW_TEAM_ROUTE = "/settings/teams/new";
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";

View file

@ -1,8 +1,20 @@
import axios from "axios";
import store from "../../config/store";
import { SIGN_IN_PATH } from "../../config/routes";
import { destroyState } from "../../components/actions/UsersActions";
export const axiosInstance = axios.create({
withCredentials: true,
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content
},
validateStatus(status) {
if (status === 401) {
setTimeout(() => {
store.dispatch(destroyState)
window.location = SIGN_IN_PATH;
}, 500);
}
return status >= 200 && status < 300;
}
});

View file

@ -22,6 +22,7 @@ export const USER_PROFILE_INFO = "/client_api/users/profile_info";
export const UPDATE_USER_PATH = "/client_api/users/update";
export const PREFERENCES_INFO_PATH = "/client_api/users/preferences_info"
export const STATISTICS_INFO_PATH = "/client_api/users/statistics_info"
export const SIGN_OUT_PATH = "/client_api/users/sign_out_user"
// info dropdown_title
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";

View file

@ -4,7 +4,8 @@ import {
UPDATE_USER_PATH,
CURRENT_USER_PATH,
PREFERENCES_INFO_PATH,
STATISTICS_INFO_PATH
STATISTICS_INFO_PATH,
SIGN_OUT_PATH
} from "./endpoints";
export const getUserProfileInfo = () =>
@ -29,3 +30,5 @@ export const getCurrentUser = () =>
export const getStatisticsInfo = () =>
axiosInstance.get(STATISTICS_INFO_PATH).then(({ data }) => data.user);
export const signOutUser = () => axiosInstance.get(SIGN_OUT_PATH);

View file

@ -32,6 +32,7 @@ Rails.application.routes.draw do
get '/current_user_info', to: 'users/users#current_user_info'
namespace :users do
get '/sign_out_user', to: 'users#sign_out_user'
delete '/remove_user', to: 'user_teams#remove_user'
delete '/leave_team', to: 'user_teams#leave_team'
put '/update_role', to: 'user_teams#update_role'

View file

@ -21,6 +21,7 @@
},
"devDependencies": {
"babel-eslint": "^8.0.1",
"babel-plugin-transform-react-jsx-source": "^6.22.0",
"eslint": "^4.7.2",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-google": "^0.9.1",

View file

@ -8,6 +8,20 @@ describe ClientApi::Users::UsersController, type: :controller do
@user = User.first
end
describe '#sign_out_user' do
it 'returns unauthorized response' do
sign_out @user
get :sign_out_user, format: :json
expect(response).to have_http_status(:unauthorized)
end
it 'responds successfully if the user is signed out' do
get :sign_out_user, format: :json
expect(response).to have_http_status(:ok)
expect(subject.current_user).to eq(nil)
end
end
describe 'GET current_user_info' do
it 'responds successfully' do
get :current_user_info, format: :json