mirror of
https://github.com/usatiuk/writer.git
synced 2025-10-29 08:27:49 +01:00
logout
This commit is contained in:
@@ -1,25 +1,83 @@
|
|||||||
import { Alignment, Button, Classes, Navbar } from "@blueprintjs/core";
|
import {
|
||||||
|
Alignment,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
Menu,
|
||||||
|
Navbar,
|
||||||
|
Popover,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { IUserJSON } from "~../../src/entity/User";
|
||||||
|
import { IAppState } from "~redux/reducers";
|
||||||
|
import { logoutUser } from "~redux/user/actions";
|
||||||
|
|
||||||
export function Home() {
|
interface IHomeProps {
|
||||||
return (
|
user: IUserJSON | null;
|
||||||
<>
|
logout: () => void;
|
||||||
<Navbar>
|
|
||||||
<Navbar.Group align={Alignment.LEFT}>
|
|
||||||
<Navbar.Heading>Writer</Navbar.Heading>
|
|
||||||
<Navbar.Divider />
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon="home"
|
|
||||||
text="Home"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon="document"
|
|
||||||
text="Files"
|
|
||||||
/>
|
|
||||||
</Navbar.Group>
|
|
||||||
</Navbar>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HomeComponent extends React.PureComponent<IHomeProps> {
|
||||||
|
constructor(props: IHomeProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.props.user && (
|
||||||
|
<>
|
||||||
|
<Navbar>
|
||||||
|
<Navbar.Group align={Alignment.LEFT}>
|
||||||
|
<Navbar.Heading>Writer</Navbar.Heading>
|
||||||
|
<Navbar.Divider />
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon="home"
|
||||||
|
text="Home"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon="document"
|
||||||
|
text="Files"
|
||||||
|
/>
|
||||||
|
</Navbar.Group>
|
||||||
|
<Navbar.Group align={Alignment.RIGHT}>
|
||||||
|
<Popover
|
||||||
|
target={
|
||||||
|
<Button id="userButton">
|
||||||
|
{this.props.user.username}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
icon="log-out"
|
||||||
|
text="Logout"
|
||||||
|
onClick={this.props.logout}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Navbar.Group>
|
||||||
|
</Navbar>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: IAppState) {
|
||||||
|
return { user: state.user.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: Dispatch) {
|
||||||
|
return { logout: () => dispatch(logoutUser()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Home = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(HomeComponent);
|
||||||
|
|||||||
5
frontend/src/redux/api/user/index.ts
Normal file
5
frontend/src/redux/api/user/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { fetchJSON, fetchJSONAuth } from "../utils";
|
||||||
|
|
||||||
|
export async function fetchUser() {
|
||||||
|
return fetchJSONAuth("/users/user", "GET");
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ const root = "http://localhost:3000";
|
|||||||
export async function fetchJSON(
|
export async function fetchJSON(
|
||||||
path: string,
|
path: string,
|
||||||
method: string,
|
method: string,
|
||||||
body: string | object,
|
body?: string | object,
|
||||||
headers?: Record<string, string>,
|
headers?: Record<string, string>,
|
||||||
) {
|
) {
|
||||||
if (typeof body === "object") {
|
if (typeof body === "object") {
|
||||||
@@ -38,7 +38,7 @@ export async function fetchJSON(
|
|||||||
export async function fetchJSONAuth(
|
export async function fetchJSONAuth(
|
||||||
path: string,
|
path: string,
|
||||||
method: string,
|
method: string,
|
||||||
body: string | object,
|
body?: string | object,
|
||||||
headers?: object,
|
headers?: object,
|
||||||
) {
|
) {
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
|
|
||||||
|
import { IUserAuthJSON } from "~../../src/entity/User";
|
||||||
|
|
||||||
export enum AuthTypes {
|
export enum AuthTypes {
|
||||||
AUTH_START = "AUTH_START",
|
AUTH_START = "AUTH_START",
|
||||||
SIGNUP_START = "SIGNUP_START",
|
SIGNUP_START = "SIGNUP_START",
|
||||||
@@ -27,9 +29,7 @@ export interface ISignupStartActionAction extends Action {
|
|||||||
|
|
||||||
export interface IAuthSuccessActionAction extends Action {
|
export interface IAuthSuccessActionAction extends Action {
|
||||||
type: AuthTypes.AUTH_SUCCESS;
|
type: AuthTypes.AUTH_SUCCESS;
|
||||||
payload: {
|
payload: IUserAuthJSON;
|
||||||
jwt: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthFailureActionAction extends Action {
|
export interface IAuthFailureActionAction extends Action {
|
||||||
@@ -65,8 +65,8 @@ export function signupStart(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authSuccess(jwt: string): IAuthSuccessActionAction {
|
export function authSuccess(user: IUserAuthJSON): IAuthSuccessActionAction {
|
||||||
return { type: AuthTypes.AUTH_SUCCESS, payload: { jwt } };
|
return { type: AuthTypes.AUTH_SUCCESS, payload: user };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authFail(error: string): IAuthFailureActionAction {
|
export function authFail(error: string): IAuthFailureActionAction {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Reducer } from "react";
|
import { Reducer } from "react";
|
||||||
|
|
||||||
|
import { setToken } from "~redux/api/utils";
|
||||||
|
import { UserAction, UserTypes } from "~redux/user/actions";
|
||||||
import { AuthAction, AuthTypes } from "./actions";
|
import { AuthAction, AuthTypes } from "./actions";
|
||||||
|
|
||||||
export interface IAuthState {
|
export interface IAuthState {
|
||||||
@@ -18,7 +20,7 @@ const defaultAuthState: IAuthState = {
|
|||||||
|
|
||||||
export const authReducer: Reducer<IAuthState, AuthAction> = (
|
export const authReducer: Reducer<IAuthState, AuthAction> = (
|
||||||
state: IAuthState = defaultAuthState,
|
state: IAuthState = defaultAuthState,
|
||||||
action: AuthAction,
|
action: AuthAction | UserAction,
|
||||||
) => {
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case AuthTypes.AUTH_START:
|
case AuthTypes.AUTH_START:
|
||||||
@@ -26,16 +28,25 @@ export const authReducer: Reducer<IAuthState, AuthAction> = (
|
|||||||
return { ...defaultAuthState, inProgress: true };
|
return { ...defaultAuthState, inProgress: true };
|
||||||
break;
|
break;
|
||||||
case AuthTypes.AUTH_SUCCESS:
|
case AuthTypes.AUTH_SUCCESS:
|
||||||
|
case UserTypes.USER_GET_SUCCESS:
|
||||||
|
setToken(action.payload.jwt);
|
||||||
return {
|
return {
|
||||||
...defaultAuthState,
|
...defaultAuthState,
|
||||||
jwt: action.payload.jwt,
|
jwt: action.payload.jwt,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case UserTypes.USER_GET_FAIL:
|
||||||
|
if (action.payload.logout) {
|
||||||
|
return defaultAuthState;
|
||||||
|
}
|
||||||
case AuthTypes.AUTH_FAIL:
|
case AuthTypes.AUTH_FAIL:
|
||||||
return { ...defaultAuthState, formError: action.payload.error };
|
return { ...defaultAuthState, formError: action.payload.error };
|
||||||
break;
|
break;
|
||||||
case AuthTypes.AUTH_START_FORM_SPINNER:
|
case AuthTypes.AUTH_START_FORM_SPINNER:
|
||||||
return { ...state, formSpinner: true };
|
return { ...state, formSpinner: true };
|
||||||
|
case UserTypes.USER_LOGOUT:
|
||||||
|
return defaultAuthState;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
takeLatest,
|
takeLatest,
|
||||||
} from "redux-saga/effects";
|
} from "redux-saga/effects";
|
||||||
import { login, signup } from "~redux/api/auth";
|
import { login, signup } from "~redux/api/auth";
|
||||||
import { setToken } from "~redux/api/utils";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authFail,
|
authFail,
|
||||||
@@ -43,13 +42,12 @@ function* authStart(action: IAuthStartActionAction) {
|
|||||||
}
|
}
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const user = response.data;
|
const user = response.data;
|
||||||
yield call(setToken, user.jwt);
|
yield put(authSuccess(user));
|
||||||
yield put(authSuccess(user.jwt));
|
|
||||||
} else {
|
} else {
|
||||||
yield put(authFail(response.error));
|
yield put(authFail(response.error));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(authFail(e.toString()));
|
yield put(authFail("Internal error"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +69,7 @@ function* signupStart(action: ISignupStartActionAction) {
|
|||||||
}
|
}
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const user = response.data;
|
const user = response.data;
|
||||||
yield call(setToken, user.jwt);
|
yield put(authSuccess(user));
|
||||||
yield put(authSuccess(user.jwt));
|
|
||||||
} else {
|
} else {
|
||||||
yield put(authFail(response.error));
|
yield put(authFail(response.error));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { combineReducers } from "redux";
|
|||||||
import { persistReducer } from "redux-persist";
|
import { persistReducer } from "redux-persist";
|
||||||
import storage from "redux-persist/lib/storage";
|
import storage from "redux-persist/lib/storage";
|
||||||
import { authReducer, IAuthState } from "~redux/auth/reducer";
|
import { authReducer, IAuthState } from "~redux/auth/reducer";
|
||||||
|
import { IUserState, userReducer } from "./user/reducer";
|
||||||
|
|
||||||
export interface IAppState {
|
export interface IAppState {
|
||||||
auth: IAuthState;
|
auth: IAuthState;
|
||||||
|
user: IUserState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authPersistConfig = {
|
const authPersistConfig = {
|
||||||
@@ -14,4 +16,5 @@ const authPersistConfig = {
|
|||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
auth: persistReducer(authPersistConfig, authReducer),
|
auth: persistReducer(authPersistConfig, authReducer),
|
||||||
|
user: userReducer,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { rootReducer } from "~redux/reducers";
|
|||||||
|
|
||||||
import { setToken } from "./api/utils";
|
import { setToken } from "./api/utils";
|
||||||
import { authSaga } from "./auth/sagas";
|
import { authSaga } from "./auth/sagas";
|
||||||
|
import { getUser } from "./user/actions";
|
||||||
|
import { userSaga } from "./user/sagas";
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddlware();
|
const sagaMiddleware = createSagaMiddlware();
|
||||||
|
|
||||||
@@ -14,7 +16,9 @@ export const persistor = persistStore(store, null, () => {
|
|||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
if (state.auth.jwt) {
|
if (state.auth.jwt) {
|
||||||
setToken(state.auth.jwt);
|
setToken(state.auth.jwt);
|
||||||
|
store.dispatch(getUser());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sagaMiddleware.run(authSaga);
|
sagaMiddleware.run(authSaga);
|
||||||
|
sagaMiddleware.run(userSaga);
|
||||||
|
|||||||
55
frontend/src/redux/user/actions.ts
Normal file
55
frontend/src/redux/user/actions.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { IUserAuthJSON, IUserJSON } from "~../../src/entity/User";
|
||||||
|
|
||||||
|
export enum UserTypes {
|
||||||
|
USER_GET = "USER_GET",
|
||||||
|
USER_GET_SUCCESS = "USER_GET_SUCCESS",
|
||||||
|
USER_GET_FAIL = "USER_GET_FAIL",
|
||||||
|
USER_LOGOUT = "USER_LOGOUT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserGetAction extends Action {
|
||||||
|
type: UserTypes.USER_GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserLogoutAction extends Action {
|
||||||
|
type: UserTypes.USER_LOGOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserGetSuccessAction extends Action {
|
||||||
|
type: UserTypes.USER_GET_SUCCESS;
|
||||||
|
payload: IUserAuthJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserGetFailAction extends Action {
|
||||||
|
type: UserTypes.USER_GET_FAIL;
|
||||||
|
payload: {
|
||||||
|
error: string;
|
||||||
|
logout: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser(): IUserGetAction {
|
||||||
|
return { type: UserTypes.USER_GET };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutUser(): IUserLogoutAction {
|
||||||
|
return { type: UserTypes.USER_LOGOUT };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserSuccess(user: IUserAuthJSON): IUserGetSuccessAction {
|
||||||
|
return { type: UserTypes.USER_GET_SUCCESS, payload: user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserFail(
|
||||||
|
error: string,
|
||||||
|
logout: boolean,
|
||||||
|
): IUserGetFailAction {
|
||||||
|
return { type: UserTypes.USER_GET_FAIL, payload: { error, logout } };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserAction =
|
||||||
|
| IUserGetAction
|
||||||
|
| IUserGetSuccessAction
|
||||||
|
| IUserGetFailAction
|
||||||
|
| IUserLogoutAction;
|
||||||
36
frontend/src/redux/user/reducer.ts
Normal file
36
frontend/src/redux/user/reducer.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Reducer } from "react";
|
||||||
|
import { IUserJSON } from "~../../src/entity/User";
|
||||||
|
import { AuthAction, AuthTypes } from "~redux/auth/actions";
|
||||||
|
import { UserAction, UserTypes } from "./actions";
|
||||||
|
|
||||||
|
export interface IUserState {
|
||||||
|
user: IUserJSON | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUserState: IUserState = {
|
||||||
|
user: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userReducer: Reducer<IUserState, AuthAction> = (
|
||||||
|
state: IUserState = defaultUserState,
|
||||||
|
action: AuthAction | UserAction,
|
||||||
|
) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case AuthTypes.AUTH_SUCCESS:
|
||||||
|
case UserTypes.USER_GET_SUCCESS:
|
||||||
|
return {
|
||||||
|
...defaultUserState,
|
||||||
|
user: action.payload,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case UserTypes.USER_GET_FAIL:
|
||||||
|
return defaultUserState;
|
||||||
|
break;
|
||||||
|
case UserTypes.USER_LOGOUT:
|
||||||
|
return defaultUserState;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
38
frontend/src/redux/user/sagas.ts
Normal file
38
frontend/src/redux/user/sagas.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { delay } from "redux-saga";
|
||||||
|
import {
|
||||||
|
all,
|
||||||
|
call,
|
||||||
|
cancel,
|
||||||
|
fork,
|
||||||
|
put,
|
||||||
|
race,
|
||||||
|
takeLatest,
|
||||||
|
} from "redux-saga/effects";
|
||||||
|
import { fetchUser } from "~redux/api/user";
|
||||||
|
import { getUserFail, getUserSuccess, UserTypes } from "./actions";
|
||||||
|
|
||||||
|
function* getUser() {
|
||||||
|
try {
|
||||||
|
const { response, timeout } = yield race({
|
||||||
|
response: call(fetchUser),
|
||||||
|
timeout: call(delay, 10000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
yield put(getUserFail("Timeout", false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.data) {
|
||||||
|
const user = response.data;
|
||||||
|
yield put(getUserSuccess(user));
|
||||||
|
} else {
|
||||||
|
yield put(getUserFail(response.error, true));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
yield put(getUserFail("Internal error", false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* userSaga() {
|
||||||
|
yield all([takeLatest(UserTypes.USER_GET, getUser)]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user