mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
implemented logout and rename login module to auth
This commit is contained in:
@@ -1,15 +1,22 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
||||||
|
import PrimaryNavigationAction from "./PrimaryNavigationAction";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {
|
||||||
|
onLogout: () => void
|
||||||
|
};
|
||||||
|
|
||||||
class PrimaryNavigation extends React.Component<Props> {
|
class PrimaryNavigation extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<nav className="tabs is-boxed">
|
<nav className="tabs is-boxed">
|
||||||
<ul>
|
<ul>
|
||||||
<PrimaryNavigationLink to="/users">Users</PrimaryNavigationLink>
|
<PrimaryNavigationLink to="/users" label="Users" />
|
||||||
|
<PrimaryNavigationAction
|
||||||
|
onClick={this.props.onLogout}
|
||||||
|
label="Logout"
|
||||||
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
20
scm-ui/src/components/PrimaryNavigationAction.js
Normal file
20
scm-ui/src/components/PrimaryNavigationAction.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//@flow
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string,
|
||||||
|
onClick: () => void
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrimaryNavigationAction extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { label, onClick } = this.props;
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<a onClick={onClick}>{label}</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrimaryNavigationAction;
|
||||||
@@ -4,16 +4,16 @@ import { Route, Link } from "react-router-dom";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string,
|
to: string,
|
||||||
activeOnlyWhenExact?: boolean,
|
label: string,
|
||||||
children?: React.Node
|
activeOnlyWhenExact?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrimaryNavigationLink extends React.Component<Props> {
|
class PrimaryNavigationLink extends React.Component<Props> {
|
||||||
renderLink = (route: any) => {
|
renderLink = (route: any) => {
|
||||||
const { to, children } = this.props;
|
const { to, label } = this.props;
|
||||||
return (
|
return (
|
||||||
<li className={route.match ? "is-active" : ""}>
|
<li className={route.match ? "is-active" : ""}>
|
||||||
<Link to={to}>{children}</Link>
|
<Link to={to}>{label}</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,25 +5,31 @@ import Login from "./Login";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
import { fetchMe } from "../modules/me";
|
import { fetchMe } from "../modules/me";
|
||||||
|
import { logout } from "../modules/auth";
|
||||||
|
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Header from "../components/Header";
|
import Header from "../components/Header";
|
||||||
import PrimaryNavigation from "../components/PrimaryNavigation";
|
import PrimaryNavigation from "../components/PrimaryNavigation";
|
||||||
import Loading from "../components/Loading";
|
import Loading from "../components/Loading";
|
||||||
import Footer from "../components/Footer";
|
|
||||||
import ErrorNotification from "../components/ErrorNotification";
|
import ErrorNotification from "../components/ErrorNotification";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
me: any,
|
me: any,
|
||||||
error: Error,
|
error: Error,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
fetchMe: () => void
|
fetchMe: () => void,
|
||||||
|
logout: () => void
|
||||||
};
|
};
|
||||||
|
|
||||||
class App extends Component<Props> {
|
class App extends Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchMe();
|
this.props.fetchMe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout = () => {
|
||||||
|
this.props.logout();
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { me, loading, error } = this.props;
|
const { me, loading, error } = this.props;
|
||||||
|
|
||||||
@@ -39,7 +45,7 @@ class App extends Component<Props> {
|
|||||||
content = <Login />;
|
content = <Login />;
|
||||||
} else {
|
} else {
|
||||||
content = <Main me={me} />;
|
content = <Main me={me} />;
|
||||||
navigation = <PrimaryNavigation />;
|
navigation = <PrimaryNavigation onLogout={this.logout} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,7 +59,8 @@ class App extends Component<Props> {
|
|||||||
|
|
||||||
const mapDispatchToProps = (dispatch: ThunkDispatch) => {
|
const mapDispatchToProps = (dispatch: ThunkDispatch) => {
|
||||||
return {
|
return {
|
||||||
fetchMe: () => dispatch(fetchMe())
|
fetchMe: () => dispatch(fetchMe()),
|
||||||
|
logout: () => dispatch(logout())
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import { login } from "../modules/login";
|
import { login } from "../modules/auth";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import InputField from "../components/InputField";
|
import InputField from "../components/InputField";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux";
|
|||||||
|
|
||||||
import repositories from "./repositories/modules/repositories";
|
import repositories from "./repositories/modules/repositories";
|
||||||
import users from "./users/modules/users";
|
import users from "./users/modules/users";
|
||||||
import login from "./modules/login";
|
import auth from "./modules/auth";
|
||||||
import me from "./modules/me";
|
import me from "./modules/me";
|
||||||
|
|
||||||
import type { BrowserHistory } from "history/createBrowserHistory";
|
import type { BrowserHistory } from "history/createBrowserHistory";
|
||||||
@@ -19,7 +19,7 @@ function createReduxStore(history: BrowserHistory) {
|
|||||||
router: routerReducer,
|
router: routerReducer,
|
||||||
repositories,
|
repositories,
|
||||||
users,
|
users,
|
||||||
login,
|
auth,
|
||||||
me
|
me
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
|
|
||||||
import { apiClient, NOT_AUTHENTICATED_ERROR } from "../apiclient";
|
import { apiClient } from "../apiclient";
|
||||||
import { fetchMe } from "./me";
|
import { fetchMe } from "./me";
|
||||||
|
|
||||||
const LOGIN_URL = "/auth/access_token";
|
const LOGIN_URL = "/auth/access_token";
|
||||||
@@ -9,6 +9,10 @@ export const LOGIN_REQUEST = "scm/auth/login_request";
|
|||||||
export const LOGIN_SUCCESSFUL = "scm/auth/login_successful";
|
export const LOGIN_SUCCESSFUL = "scm/auth/login_successful";
|
||||||
export const LOGIN_FAILED = "scm/auth/login_failed";
|
export const LOGIN_FAILED = "scm/auth/login_failed";
|
||||||
|
|
||||||
|
export const LOGOUT_REQUEST = "scm/auth/logout_request";
|
||||||
|
export const LOGOUT_SUCCESSFUL = "scm/auth/logout_successful";
|
||||||
|
export const LOGOUT_FAILED = "scm/auth/logout_failed";
|
||||||
|
|
||||||
export function login(username: string, password: string) {
|
export function login(username: string, password: string) {
|
||||||
const login_data = {
|
const login_data = {
|
||||||
cookie: true,
|
cookie: true,
|
||||||
@@ -50,6 +54,41 @@ export function loginFailed(error: Error) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(logoutRequest());
|
||||||
|
return apiClient
|
||||||
|
.delete(LOGIN_URL)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(logoutSuccess());
|
||||||
|
// not the best way or?
|
||||||
|
dispatch(fetchMe());
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(logoutFailed(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutRequest() {
|
||||||
|
return {
|
||||||
|
type: LOGOUT_REQUEST
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutSuccess() {
|
||||||
|
return {
|
||||||
|
type: LOGOUT_SUCCESSFUL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutFailed(error: Error) {
|
||||||
|
return {
|
||||||
|
type: LOGOUT_FAILED,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function reducer(state: any = {}, action: any = {}) {
|
export default function reducer(state: any = {}, action: any = {}) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case LOGIN_REQUEST:
|
case LOGIN_REQUEST:
|
||||||
@@ -74,6 +113,26 @@ export default function reducer(state: any = {}, action: any = {}) {
|
|||||||
error: action.payload
|
error: action.payload
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case LOGOUT_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
case LOGOUT_SUCCESSFUL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
login: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
case LOGOUT_FAILED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: action.payload
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import reducer, {
|
import reducer, {
|
||||||
login,
|
login,
|
||||||
|
logout,
|
||||||
LOGIN_REQUEST,
|
LOGIN_REQUEST,
|
||||||
LOGIN_FAILED,
|
LOGIN_FAILED,
|
||||||
LOGIN_SUCCESSFUL
|
LOGIN_SUCCESSFUL,
|
||||||
} from "./login";
|
LOGOUT_REQUEST,
|
||||||
|
LOGOUT_SUCCESSFUL,
|
||||||
|
LOGOUT_FAILED
|
||||||
|
} from "./auth";
|
||||||
|
|
||||||
import { ME_AUTHENTICATED_REQUEST, ME_AUTHENTICATED_SUCCESS } from "./me";
|
import { ME_AUTHENTICATED_REQUEST, ME_AUTHENTICATED_SUCCESS } from "./me";
|
||||||
|
|
||||||
@@ -66,6 +70,44 @@ describe("action tests", () => {
|
|||||||
expect(actions[1].payload).toBeDefined();
|
expect(actions[1].payload).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("logout success", () => {
|
||||||
|
fetchMock.deleteOnce("/scm/api/rest/v2/auth/access_token", {
|
||||||
|
status: 204
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock.getOnce("/scm/api/rest/v2/me", {
|
||||||
|
status: 401
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: LOGOUT_REQUEST },
|
||||||
|
{ type: LOGOUT_SUCCESSFUL },
|
||||||
|
{ type: ME_AUTHENTICATED_REQUEST }
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
return store.dispatch(logout()).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logout failed", () => {
|
||||||
|
fetchMock.deleteOnce("/scm/api/rest/v2/auth/access_token", {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedActions = [{ type: LOGOUT_REQUEST }, { type: LOGOUT_FAILED }];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(logout()).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toEqual(LOGOUT_REQUEST);
|
||||||
|
expect(actions[1].type).toEqual(LOGOUT_FAILED);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("reducer tests", () => {
|
describe("reducer tests", () => {
|
||||||
@@ -90,4 +132,29 @@ describe("reducer tests", () => {
|
|||||||
expect(newState.login).toBeFalsy();
|
expect(newState.login).toBeFalsy();
|
||||||
expect(newState.error).toBe(err);
|
expect(newState.error).toBe(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("logout request", () => {
|
||||||
|
var newState = reducer({ login: true }, { type: LOGOUT_REQUEST });
|
||||||
|
expect(newState.loading).toBeTruthy();
|
||||||
|
expect(newState.login).toBeTruthy();
|
||||||
|
expect(newState.error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logout successful", () => {
|
||||||
|
var newState = reducer({ login: true }, { type: LOGOUT_SUCCESSFUL });
|
||||||
|
expect(newState.loading).toBeFalsy();
|
||||||
|
expect(newState.login).toBeFalsy();
|
||||||
|
expect(newState.error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logout failed", () => {
|
||||||
|
const err = new Error("error!");
|
||||||
|
var newState = reducer(
|
||||||
|
{ login: true },
|
||||||
|
{ type: LOGOUT_FAILED, payload: err }
|
||||||
|
);
|
||||||
|
expect(newState.loading).toBeFalsy();
|
||||||
|
expect(newState.login).toBeTruthy();
|
||||||
|
expect(newState.error).toBe(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user