This commit is contained in:
2019-01-03 19:33:16 +03:00
parent a7a36d2199
commit ca773d5b2d
11 changed files with 243 additions and 36 deletions

View File

@@ -1,8 +1,32 @@
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() {
interface IHomeProps {
user: IUserJSON | null;
logout: () => void;
}
export class HomeComponent extends React.PureComponent<IHomeProps> {
constructor(props: IHomeProps) {
super(props);
}
public render() {
return (
<>
{this.props.user && (
<>
<Navbar>
<Navbar.Group align={Alignment.LEFT}>
@@ -19,7 +43,41 @@ export function Home() {
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);

View File

@@ -0,0 +1,5 @@
import { fetchJSON, fetchJSONAuth } from "../utils";
export async function fetchUser() {
return fetchJSONAuth("/users/user", "GET");
}

View File

@@ -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<string, string>,
) {
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) {

View File

@@ -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 {

View File

@@ -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<IAuthState, AuthAction> = (
state: IAuthState = defaultAuthState,
action: AuthAction,
action: AuthAction | UserAction,
) => {
switch (action.type) {
case AuthTypes.AUTH_START:
@@ -26,16 +28,25 @@ export const authReducer: Reducer<IAuthState, AuthAction> = (
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;

View File

@@ -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));
}

View File

@@ -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,
});

View File

@@ -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);

View 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;

View 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;
}
};

View 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)]);
}