From ca773d5b2d4e854817f3215cc3c43f5969266bb4 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Thu, 3 Jan 2019 19:33:16 +0300 Subject: [PATCH] logout --- frontend/src/Home/Home.tsx | 102 +++++++++++++++++++++------ frontend/src/redux/api/user/index.ts | 5 ++ frontend/src/redux/api/utils.ts | 4 +- frontend/src/redux/auth/actions.ts | 10 +-- frontend/src/redux/auth/reducer.ts | 13 +++- frontend/src/redux/auth/sagas.ts | 9 +-- frontend/src/redux/reducers.ts | 3 + frontend/src/redux/store.ts | 4 ++ frontend/src/redux/user/actions.ts | 55 +++++++++++++++ frontend/src/redux/user/reducer.ts | 36 ++++++++++ frontend/src/redux/user/sagas.ts | 38 ++++++++++ 11 files changed, 243 insertions(+), 36 deletions(-) create mode 100644 frontend/src/redux/api/user/index.ts create mode 100644 frontend/src/redux/user/actions.ts create mode 100644 frontend/src/redux/user/reducer.ts create mode 100644 frontend/src/redux/user/sagas.ts diff --git a/frontend/src/Home/Home.tsx b/frontend/src/Home/Home.tsx index 52e22b4..d9b47fb 100644 --- a/frontend/src/Home/Home.tsx +++ b/frontend/src/Home/Home.tsx @@ -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 { 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() { - return ( - <> - - - Writer - - + } + content={ + + + + } + /> + + + + )} + + ); + } +} + +function mapStateToProps(state: IAppState) { + return { user: state.user.user }; +} + +function mapDispatchToProps(dispatch: Dispatch) { + return { logout: () => dispatch(logoutUser()) }; +} + +export const Home = connect( + mapStateToProps, + mapDispatchToProps, +)(HomeComponent); diff --git a/frontend/src/redux/api/user/index.ts b/frontend/src/redux/api/user/index.ts new file mode 100644 index 0000000..7efd863 --- /dev/null +++ b/frontend/src/redux/api/user/index.ts @@ -0,0 +1,5 @@ +import { fetchJSON, fetchJSONAuth } from "../utils"; + +export async function fetchUser() { + return fetchJSONAuth("/users/user", "GET"); +} diff --git a/frontend/src/redux/api/utils.ts b/frontend/src/redux/api/utils.ts index 546551c..3b4a1ff 100644 --- a/frontend/src/redux/api/utils.ts +++ b/frontend/src/redux/api/utils.ts @@ -17,7 +17,7 @@ const root = "http://localhost:3000"; export async function fetchJSON( path: string, method: string, - body: string | object, + body?: string | object, headers?: Record, ) { if (typeof body === "object") { @@ -38,7 +38,7 @@ export async function fetchJSON( export async function fetchJSONAuth( path: string, method: string, - body: string | object, + body?: string | object, headers?: object, ) { if (token) { diff --git a/frontend/src/redux/auth/actions.ts b/frontend/src/redux/auth/actions.ts index e98a754..01c06fa 100644 --- a/frontend/src/redux/auth/actions.ts +++ b/frontend/src/redux/auth/actions.ts @@ -1,5 +1,7 @@ import { Action } from "redux"; +import { IUserAuthJSON } from "~../../src/entity/User"; + export enum AuthTypes { AUTH_START = "AUTH_START", SIGNUP_START = "SIGNUP_START", @@ -27,9 +29,7 @@ export interface ISignupStartActionAction extends Action { export interface IAuthSuccessActionAction extends Action { type: AuthTypes.AUTH_SUCCESS; - payload: { - jwt: string; - }; + payload: IUserAuthJSON; } export interface IAuthFailureActionAction extends Action { @@ -65,8 +65,8 @@ export function signupStart( }; } -export function authSuccess(jwt: string): IAuthSuccessActionAction { - return { type: AuthTypes.AUTH_SUCCESS, payload: { jwt } }; +export function authSuccess(user: IUserAuthJSON): IAuthSuccessActionAction { + return { type: AuthTypes.AUTH_SUCCESS, payload: user }; } export function authFail(error: string): IAuthFailureActionAction { diff --git a/frontend/src/redux/auth/reducer.ts b/frontend/src/redux/auth/reducer.ts index 86a486e..caf8e9e 100644 --- a/frontend/src/redux/auth/reducer.ts +++ b/frontend/src/redux/auth/reducer.ts @@ -1,5 +1,7 @@ import { Reducer } from "react"; +import { setToken } from "~redux/api/utils"; +import { UserAction, UserTypes } from "~redux/user/actions"; import { AuthAction, AuthTypes } from "./actions"; export interface IAuthState { @@ -18,7 +20,7 @@ const defaultAuthState: IAuthState = { export const authReducer: Reducer = ( state: IAuthState = defaultAuthState, - action: AuthAction, + action: AuthAction | UserAction, ) => { switch (action.type) { case AuthTypes.AUTH_START: @@ -26,16 +28,25 @@ export const authReducer: Reducer = ( return { ...defaultAuthState, inProgress: true }; break; case AuthTypes.AUTH_SUCCESS: + case UserTypes.USER_GET_SUCCESS: + setToken(action.payload.jwt); return { ...defaultAuthState, jwt: action.payload.jwt, }; break; + case UserTypes.USER_GET_FAIL: + if (action.payload.logout) { + return defaultAuthState; + } case AuthTypes.AUTH_FAIL: return { ...defaultAuthState, formError: action.payload.error }; break; case AuthTypes.AUTH_START_FORM_SPINNER: return { ...state, formSpinner: true }; + case UserTypes.USER_LOGOUT: + return defaultAuthState; + break; default: return state; break; diff --git a/frontend/src/redux/auth/sagas.ts b/frontend/src/redux/auth/sagas.ts index 5c02149..20847b7 100644 --- a/frontend/src/redux/auth/sagas.ts +++ b/frontend/src/redux/auth/sagas.ts @@ -9,7 +9,6 @@ import { takeLatest, } from "redux-saga/effects"; import { login, signup } from "~redux/api/auth"; -import { setToken } from "~redux/api/utils"; import { authFail, @@ -43,13 +42,12 @@ function* authStart(action: IAuthStartActionAction) { } if (response.data) { const user = response.data; - yield call(setToken, user.jwt); - yield put(authSuccess(user.jwt)); + yield put(authSuccess(user)); } else { yield put(authFail(response.error)); } } catch (e) { - yield put(authFail(e.toString())); + yield put(authFail("Internal error")); } } @@ -71,8 +69,7 @@ function* signupStart(action: ISignupStartActionAction) { } if (response.data) { const user = response.data; - yield call(setToken, user.jwt); - yield put(authSuccess(user.jwt)); + yield put(authSuccess(user)); } else { yield put(authFail(response.error)); } diff --git a/frontend/src/redux/reducers.ts b/frontend/src/redux/reducers.ts index abcbb7f..cce7a86 100644 --- a/frontend/src/redux/reducers.ts +++ b/frontend/src/redux/reducers.ts @@ -2,9 +2,11 @@ import { combineReducers } from "redux"; import { persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import { authReducer, IAuthState } from "~redux/auth/reducer"; +import { IUserState, userReducer } from "./user/reducer"; export interface IAppState { auth: IAuthState; + user: IUserState; } const authPersistConfig = { @@ -14,4 +16,5 @@ const authPersistConfig = { export const rootReducer = combineReducers({ auth: persistReducer(authPersistConfig, authReducer), + user: userReducer, }); diff --git a/frontend/src/redux/store.ts b/frontend/src/redux/store.ts index e50417a..6dc7290 100644 --- a/frontend/src/redux/store.ts +++ b/frontend/src/redux/store.ts @@ -5,6 +5,8 @@ import { rootReducer } from "~redux/reducers"; import { setToken } from "./api/utils"; import { authSaga } from "./auth/sagas"; +import { getUser } from "./user/actions"; +import { userSaga } from "./user/sagas"; const sagaMiddleware = createSagaMiddlware(); @@ -14,7 +16,9 @@ export const persistor = persistStore(store, null, () => { const state = store.getState(); if (state.auth.jwt) { setToken(state.auth.jwt); + store.dispatch(getUser()); } }); sagaMiddleware.run(authSaga); +sagaMiddleware.run(userSaga); diff --git a/frontend/src/redux/user/actions.ts b/frontend/src/redux/user/actions.ts new file mode 100644 index 0000000..106735c --- /dev/null +++ b/frontend/src/redux/user/actions.ts @@ -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; diff --git a/frontend/src/redux/user/reducer.ts b/frontend/src/redux/user/reducer.ts new file mode 100644 index 0000000..8633572 --- /dev/null +++ b/frontend/src/redux/user/reducer.ts @@ -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 = ( + 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; + } +}; diff --git a/frontend/src/redux/user/sagas.ts b/frontend/src/redux/user/sagas.ts new file mode 100644 index 0000000..cb3f406 --- /dev/null +++ b/frontend/src/redux/user/sagas.ts @@ -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)]); +}